summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java3
-rw-r--r--apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java11
-rw-r--r--cmds/statsd/src/StatsLogProcessor.h8
-rw-r--r--cmds/statsd/src/atoms.proto102
-rw-r--r--cmds/statsd/src/metrics/CountMetricProducer.cpp6
-rw-r--r--cmds/statsd/src/metrics/CountMetricProducer.h4
-rw-r--r--cmds/statsd/src/metrics/DurationMetricProducer.cpp11
-rw-r--r--cmds/statsd/src/metrics/DurationMetricProducer.h4
-rw-r--r--cmds/statsd/src/metrics/MetricProducer.h6
-rw-r--r--cmds/statsd/src/metrics/MetricsManager.h8
-rw-r--r--cmds/statsd/src/metrics/ValueMetricProducer.cpp17
-rw-r--r--cmds/statsd/src/metrics/ValueMetricProducer.h2
-rw-r--r--cmds/statsd/src/state/StateListener.h4
-rw-r--r--cmds/statsd/src/state/StateTracker.cpp54
-rw-r--r--cmds/statsd/src/state/StateTracker.h6
-rw-r--r--cmds/statsd/src/statsd_config.proto2
-rw-r--r--cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp98
-rw-r--r--cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp135
-rw-r--r--cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp40
-rw-r--r--cmds/statsd/tests/state/StateTracker_test.cpp5
-rw-r--r--cmds/statsd/tests/statsd_test_util.cpp46
-rw-r--r--cmds/statsd/tests/statsd_test_util.h13
-rw-r--r--core/java/android/app/ActivityThread.java48
-rw-r--r--core/java/android/app/IActivityTaskManager.aidl11
-rw-r--r--core/java/android/app/IApplicationThread.aidl1
-rw-r--r--core/java/android/app/PropertyInvalidatedCache.java111
-rw-r--r--core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java11
-rw-r--r--core/java/android/app/servertransaction/MoveToDisplayItem.java17
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java5
-rw-r--r--core/java/android/content/pm/PackageManager.java3
-rw-r--r--core/java/android/content/pm/PackageParser.java6
-rw-r--r--core/java/android/database/DatabaseUtils.java52
-rw-r--r--core/java/android/database/sqlite/SQLiteQueryBuilder.java6
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java86
-rw-r--r--core/java/android/util/AtomicFile.java164
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureSession.java7
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSession.java2
-rw-r--r--core/java/android/webkit/WebSettings.java2
-rw-r--r--core/java/com/android/internal/app/ChooserListAdapter.java12
-rw-r--r--core/java/com/android/internal/app/ResolverListAdapter.java6
-rw-r--r--core/jni/AndroidRuntime.cpp11
-rw-r--r--core/proto/android/view/imefocuscontroller.proto30
-rw-r--r--core/proto/android/view/imeinsetssourceconsumer.proto31
-rw-r--r--core/proto/android/view/inputmethod/editorinfo.proto33
-rw-r--r--core/proto/android/view/inputmethod/inputmethodeditortrace.proto69
-rw-r--r--core/proto/android/view/inputmethod/inputmethodmanager.proto32
-rw-r--r--core/proto/android/view/insetsanimationcontrolimpl.proto35
-rw-r--r--core/proto/android/view/insetscontroller.proto32
-rw-r--r--core/proto/android/view/insetssource.proto33
-rw-r--r--core/proto/android/view/insetssourceconsumer.proto36
-rw-r--r--core/proto/android/view/insetssourcecontrol.proto33
-rw-r--r--core/proto/android/view/insetsstate.proto32
-rw-r--r--core/proto/android/view/viewrootimpl.proto48
-rw-r--r--core/res/AndroidManifest.xml3
-rw-r--r--core/res/res/drawable/chooser_group_background.xml2
-rw-r--r--core/res/res/drawable/chooser_pinned_background.xml25
-rw-r--r--core/res/res/drawable/ic_chooser_pin.xml26
-rw-r--r--core/res/res/layout/resolve_grid_item.xml2
-rw-r--r--core/res/res/values/config.xml15
-rw-r--r--core/res/res/values/symbols.xml4
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java106
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java5
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java5
-rw-r--r--core/tests/utiltests/src/android/util/AtomicFileTest.java245
-rw-r--r--data/etc/com.android.systemui.xml1
-rw-r--r--graphics/java/android/graphics/PorterDuff.java2
-rw-r--r--media/java/android/media/AudioManagerInternal.java6
-rw-r--r--media/java/android/media/MediaMuxer.java35
-rw-r--r--media/java/android/media/tv/tuner/Tuner.java48
-rw-r--r--media/jni/android_media_MediaCodec.cpp72
-rw-r--r--media/jni/android_media_MediaCodec.h3
-rw-r--r--media/jni/android_media_MediaCodecLinearBlock.h12
-rw-r--r--media/jni/android_media_MediaMuxer.cpp34
-rw-r--r--packages/SettingsLib/res/values/strings.xml3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java4
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java8
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java4
-rw-r--r--packages/Shell/AndroidManifest.xml16
-rw-r--r--packages/Shell/res/layout/null_home_finishing_boot.xml44
-rw-r--r--packages/Shell/src/com/android/shell/NullHome.java43
-rw-r--r--packages/SystemUI/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/res/anim/media_button_state_list_animator.xml50
-rw-r--r--packages/SystemUI/res/drawable/stat_sys_media.xml31
-rw-r--r--packages/SystemUI/res/layout/global_actions_grid_item_v2.xml8
-rw-r--r--packages/SystemUI/res/layout/global_actions_grid_v2.xml2
-rw-r--r--packages/SystemUI/res/layout/global_screenshot_action_chip.xml38
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml3
-rw-r--r--packages/SystemUI/res/values/styles.xml1
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl6
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsItem.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java195
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaData.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt117
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHost.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java313
-rw-r--r--packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java368
-rw-r--r--packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java100
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt210
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java161
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt195
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt62
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.java168
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java91
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java20
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java174
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java73
-rwxr-xr-xservices/core/java/com/android/server/audio/AudioService.java178
-rw-r--r--services/core/java/com/android/server/compat/TEST_MAPPING2
-rw-r--r--services/core/java/com/android/server/display/BrightnessMappingStrategy.java9
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java10
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java11
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java5
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java14
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java3
-rw-r--r--services/core/java/com/android/server/pm/AppsFilter.java6
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java8
-rw-r--r--services/core/java/com/android/server/pm/Settings.java46
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java55
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionsState.java57
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java5
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java69
-rw-r--r--services/core/java/com/android/server/uri/UriGrantsManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityStack.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java5
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java9
-rw-r--r--services/core/java/com/android/server/wm/DisplayArea.java5
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java20
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java2
-rw-r--r--services/core/java/com/android/server/wm/FixedRotationAnimationController.java197
-rw-r--r--services/core/java/com/android/server/wm/RemoteAnimationController.java4
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java2
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimator.java9
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java11
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotPersister.java33
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java13
-rw-r--r--services/core/java/com/android/server/wm/WindowToken.java3
-rw-r--r--services/incremental/IncrementalService.cpp4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java249
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java35
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java2
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java21
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java13
-rw-r--r--telephony/common/com/android/internal/telephony/SmsApplication.java27
-rw-r--r--telephony/java/android/telephony/SubscriptionInfo.java16
-rw-r--r--telephony/java/android/telephony/ims/ImsRcsManager.java13
-rw-r--r--telephony/java/android/telephony/ims/RcsUceAdapter.java5
198 files changed, 5213 insertions, 1753 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 2834ab14f28d..94e5d0b2591b 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1098,8 +1098,7 @@ public class AppStandbyController implements AppStandbyInternal {
if (mAppWidgetManager != null
&& mInjector.isBoundWidgetPackage(mAppWidgetManager, packageName, userId)) {
- // TODO: consider lowering to ACTIVE
- return STANDBY_BUCKET_EXEMPTED;
+ return STANDBY_BUCKET_ACTIVE;
}
if (isDeviceProvisioningPackage(packageName)) {
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index 5cf5e0b1d182..cbc8ed636ff2 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -662,14 +662,19 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
return;
}
+ // Cleann up from previous statsd - cancel any alarms that had been set. Do this here
+ // instead of in binder death because statsd can come back and set different alarms, or not
+ // want to set an alarm when it had been set. This guarantees that when we get a new statsd,
+ // we cancel any alarms before it is able to set them.
+ cancelAnomalyAlarm();
+ cancelPullingAlarm();
+ cancelAlarmForSubscriberTriggering();
+
if (DEBUG) Log.d(TAG, "Saying hi to statsd");
mStatsManagerService.statsdReady(statsd);
try {
statsd.statsCompanionReady();
- cancelAnomalyAlarm();
- cancelPullingAlarm();
-
BroadcastReceiver appUpdateReceiver = new AppUpdateReceiver();
BroadcastReceiver userUpdateReceiver = new UserUpdateReceiver();
BroadcastReceiver shutdownEventReceiver = new ShutdownEventReceiver();
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index ffd83ba978f4..7090bd46635d 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -307,9 +307,6 @@ private:
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation);
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition);
FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents);
- FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
- FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
- FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket);
FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets);
@@ -328,6 +325,7 @@ private:
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation);
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
+ FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges);
FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
@@ -345,6 +343,10 @@ private:
FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
+ FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index f00a35d10819..8ba52ef06c44 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -440,6 +440,7 @@ message Atom {
273 [(module) = "permissioncontroller"];
EvsUsageStatsReported evs_usage_stats_reported = 274 [(module) = "evs"];
AudioPowerUsageDataReported audio_power_usage_data_reported = 275;
+ TvTunerStateChanged tv_tuner_state_changed = 276 [(module) = "framework"];
SdkExtensionStatus sdk_extension_status = 354;
// StatsdStats tracks platform atoms with ids upto 500.
@@ -447,7 +448,7 @@ message Atom {
}
// Pulled events will start at field 10000.
- // Next: 10082
+ // Next: 10084
oneof pulled {
WifiBytesTransfer wifi_bytes_transfer = 10000 [(module) = "framework"];
WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001 [(module) = "framework"];
@@ -539,6 +540,9 @@ message Atom {
SupportedRadioAccessFamily supported_radio_access_family = 10079 [(module) = "telephony"];
SettingSnapshot setting_snapshot = 10080 [(module) = "framework"];
DisplayWakeReason display_wake_reason = 10081 [(module) = "framework"];
+ DataUsageBytesTransfer data_usage_bytes_transfer = 10082 [(module) = "framework"];
+ BytesTransferByTagAndMetered bytes_transfer_by_tag_and_metered =
+ 10083 [(module) = "framework"];
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -4940,6 +4944,52 @@ message MobileBytesTransferByFgBg {
}
/**
+ * Used for pull network statistics via mobile|wifi networks, and sliced by interesting dimensions.
+ * Note that data is expected to be sliced into more dimensions in future. In other words,
+ * the caller must not assume the data is unique when filtering with a set of matching conditions.
+ * Thus, as the dimension grows, the caller will not be affected.
+ *
+ * Pulled from:
+ * StatsPullAtomService (using NetworkStatsService to get NetworkStats)
+ */
+message DataUsageBytesTransfer {
+ // State of this record. Should be NetworkStats#SET_DEFAULT or NetworkStats#SET_FOREGROUND to
+ // indicate the foreground state, or NetworkStats#SET_ALL to indicate the record is for all
+ // states combined, not including debug states. See NetworkStats#SET_*.
+ optional int32 state = 1;
+
+ optional int64 rx_bytes = 2;
+
+ optional int64 rx_packets = 3;
+
+ optional int64 tx_bytes = 4;
+
+ optional int64 tx_packets = 5;
+
+ // Radio Access Technology (RAT) type of this record, should be one of
+ // TelephonyManager#NETWORK_TYPE_* constants, or NetworkTemplate#NETWORK_TYPE_ALL to indicate
+ // the record is for all rat types combined.
+ optional int32 rat_type = 6;
+
+ // Mcc/Mnc read from sim if the record is for a specific subscription, null indicates the
+ // record is combined regardless of subscription.
+ optional string sim_mcc = 7;
+ optional string sim_mnc = 8;
+
+ // Enumeration of opportunistic states with an additional ALL state indicates the record is
+ // combined regardless of the boolean value in its field.
+ enum DataSubscriptionState {
+ ALL = 1;
+ OPPORTUNISTIC = 2;
+ NOT_OPPORTUNISTIC = 3;
+ }
+ // Mark whether the subscription is an opportunistic data subscription, and ALL indicates the
+ // record is combined regardless of opportunistic data subscription.
+ // See {@link SubscriptionManager#setOpportunistic}.
+ optional DataSubscriptionState opportunistic_data_sub = 9;
+}
+
+/**
* Pulls bytes transferred via bluetooth. It is pulled from Bluetooth controller.
*
* Pulled from:
@@ -5982,6 +6032,12 @@ message PackageNotificationChannelPreferences {
optional int32 user_locked_fields = 6;
// Indicates if the channel was deleted by the app.
optional bool is_deleted = 7;
+ // Indicates if the channel was marked as a conversation by the app.
+ optional bool is_conversation = 8;
+ // Indicates if the channel is a conversation that was demoted by the user.
+ optional bool is_demoted_conversation = 9;
+ // Indicates if the channel is a conversation that was marked as important by the user.
+ optional bool is_important_conversation = 10;
}
/**
@@ -9121,6 +9177,28 @@ message SdkExtensionStatus {
}
/**
+ * Logs when a tune occurs through device's Frontend.
+ * This is atom ID 276.
+ *
+ * Logged from:
+ * frameworks/base/media/java/android/media/tv/tuner/Tuner.java
+ */
+message TvTunerStateChanged {
+ enum State {
+ UNKNOWN = 0;
+ TUNING = 1; // Signal is tuned
+ LOCKED = 2; // the signal is locked
+ NOT_LOCKED = 3; // the signal isn’t locked.
+ SIGNAL_LOST = 4; // the signal was locked, but is lost now.
+ SCANNING = 5; // the signal is scanned
+ SCAN_STOPPED = 6; // the scan is stopped.
+ }
+ // The uid of the application that sent this custom atom.
+ optional int32 uid = 1 [(is_uid) = true];
+ // new state
+ optional State state = 2;
+}
+/**
* Logs when an app is frozen or unfrozen.
*
* Logged from:
@@ -9756,3 +9834,25 @@ message AudioPowerUsageDataReported {
}
optional AudioType type = 4;
}
+
+/**
+ * Pulls bytes transferred over WiFi and mobile networks sliced by uid, is_metered, and tag.
+ *
+ * Pulled from:
+ * StatsPullAtomService, which uses NetworkStatsService to query NetworkStats.
+ */
+message BytesTransferByTagAndMetered {
+ optional int32 uid = 1 [(is_uid) = true];
+
+ optional bool is_metered = 2;
+
+ optional int32 tag = 3;
+
+ optional int64 rx_bytes = 4;
+
+ optional int64 rx_packets = 5;
+
+ optional int64 tx_bytes = 6;
+
+ optional int64 tx_packets = 7;
+}
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 21ffff32f539..d865c2176c1e 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -122,11 +122,11 @@ CountMetricProducer::~CountMetricProducer() {
}
void CountMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
- const HashableDimensionKey& primaryKey, int oldState,
- int newState) {
+ const HashableDimensionKey& primaryKey,
+ const FieldValue& oldState, const FieldValue& newState) {
VLOG("CountMetric %lld onStateChanged time %lld, State%d, key %s, %d -> %d",
(long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(),
- oldState, newState);
+ oldState.mValue.int_value, newState.mValue.int_value);
}
void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index f9a8842efc3d..26b3d3cc6722 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -53,8 +53,8 @@ public:
virtual ~CountMetricProducer();
void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
- const HashableDimensionKey& primaryKey, int oldState,
- int newState) override;
+ const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+ const FieldValue& newState) override;
protected:
void onMatchedLogEventInternalLocked(
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 0de92f3d9f47..663365924829 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -161,13 +161,12 @@ sp<AnomalyTracker> DurationMetricProducer::addAnomalyTracker(
void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
const HashableDimensionKey& primaryKey,
- const int32_t oldState, const int32_t newState) {
- // Create a FieldValue object to hold the new state.
- FieldValue value;
- value.mValue.setInt(newState);
+ const FieldValue& oldState,
+ const FieldValue& newState) {
// Check if this metric has a StateMap. If so, map the new state value to
// the correct state group id.
- mapStateValue(atomId, &value);
+ FieldValue newStateCopy = newState;
+ mapStateValue(atomId, &newStateCopy);
flushIfNeededLocked(eventTimeNs);
@@ -185,7 +184,7 @@ void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int
if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) {
continue;
}
- whatIt.second->onStateChanged(eventTimeNs, atomId, value);
+ whatIt.second->onStateChanged(eventTimeNs, atomId, newStateCopy);
}
}
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 6f84076ee6b5..53f0f28c3386 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -55,8 +55,8 @@ public:
const sp<AlarmMonitor>& anomalyAlarmMonitor) override;
void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
- const HashableDimensionKey& primaryKey, const int32_t oldState,
- const int32_t newState) override;
+ const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+ const FieldValue& newState) override;
protected:
void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 28563ad4b0f5..e86fdf06e836 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -182,8 +182,8 @@ public:
};
void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
- const HashableDimensionKey& primaryKey, const int32_t oldState,
- const int32_t newState){};
+ const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+ const FieldValue& newState){};
// Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp.
// This method clears all the past buckets.
@@ -459,6 +459,7 @@ protected:
FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields);
+ FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges);
FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
@@ -488,6 +489,7 @@ protected:
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
+ FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index c30532a39244..ad30a88c5d19 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -292,9 +292,6 @@ private:
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation);
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsNoCondition);
FRIEND_TEST(GaugeMetricE2eTest, TestConditionChangeToTrueSamplePulledEvents);
- FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
- FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
- FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket);
FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets);
@@ -322,6 +319,7 @@ private:
TestActivationOnBootMultipleActivationsDifferentActivationTypes);
FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
+ FRIEND_TEST(CountMetricE2eTest, TestInitialConditionChanges);
FRIEND_TEST(CountMetricE2eTest, TestSlicedState);
FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
@@ -339,6 +337,10 @@ private:
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset);
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
+ FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
+ FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index bf636a4f048d..f03ce4550bc4 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -182,15 +182,26 @@ ValueMetricProducer::~ValueMetricProducer() {
}
void ValueMetricProducer::onStateChanged(int64_t eventTimeNs, int32_t atomId,
- const HashableDimensionKey& primaryKey, int oldState,
- int newState) {
+ const HashableDimensionKey& primaryKey,
+ const FieldValue& oldState, const FieldValue& newState) {
VLOG("ValueMetric %lld onStateChanged time %lld, State %d, key %s, %d -> %d",
(long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(),
- oldState, newState);
+ oldState.mValue.int_value, newState.mValue.int_value);
// If condition is not true, we do not need to pull for this state change.
if (mCondition != ConditionState::kTrue) {
return;
}
+
+ // If old and new states are in the same StateGroup, then we do not need to
+ // pull for this state change.
+ FieldValue oldStateCopy = oldState;
+ FieldValue newStateCopy = newState;
+ mapStateValue(atomId, &oldStateCopy);
+ mapStateValue(atomId, &newStateCopy);
+ if (oldStateCopy == newStateCopy) {
+ return;
+ }
+
bool isEventLate = eventTimeNs < mCurrentBucketStartTimeNs;
if (isEventLate) {
VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index c8dc8cc290c4..751fef2bf2b1 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -90,7 +90,7 @@ public:
};
void onStateChanged(int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey,
- int oldState, int newState) override;
+ const FieldValue& oldState, const FieldValue& newState) override;
protected:
void onMatchedLogEventInternalLocked(
diff --git a/cmds/statsd/src/state/StateListener.h b/cmds/statsd/src/state/StateListener.h
index d1af1968ac38..63880017ca18 100644
--- a/cmds/statsd/src/state/StateListener.h
+++ b/cmds/statsd/src/state/StateListener.h
@@ -45,8 +45,8 @@ public:
* [newState]: Current state value after state change
*/
virtual void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
- const HashableDimensionKey& primaryKey, int oldState,
- int newState) = 0;
+ const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+ const FieldValue& newState) = 0;
};
} // namespace statsd
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
index b63713b64c5d..41e525c343ba 100644
--- a/cmds/statsd/src/state/StateTracker.cpp
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -35,31 +35,30 @@ void StateTracker::onLogEvent(const LogEvent& event) {
HashableDimensionKey primaryKey;
filterPrimaryKey(event.getValues(), &primaryKey);
- FieldValue stateValue;
- if (!getStateFieldValueFromLogEvent(event, &stateValue)) {
+ FieldValue newState;
+ if (!getStateFieldValueFromLogEvent(event, &newState)) {
ALOGE("StateTracker error extracting state from log event. Missing exclusive state field.");
clearStateForPrimaryKey(eventTimeNs, primaryKey);
return;
}
- mField.setField(stateValue.mField.getField());
+ mField.setField(newState.mField.getField());
- if (stateValue.mValue.getType() != INT) {
+ if (newState.mValue.getType() != INT) {
ALOGE("StateTracker error extracting state from log event. Type: %d",
- stateValue.mValue.getType());
+ newState.mValue.getType());
clearStateForPrimaryKey(eventTimeNs, primaryKey);
return;
}
- const int32_t resetState = event.getResetState();
- if (resetState != -1) {
+ if (int resetState = event.getResetState(); resetState != -1) {
VLOG("StateTracker new reset state: %d", resetState);
- handleReset(eventTimeNs, resetState);
+ const FieldValue resetStateFieldValue(mField, Value(resetState));
+ handleReset(eventTimeNs, resetStateFieldValue);
return;
}
- const int32_t newState = stateValue.mValue.int_value;
- const bool nested = stateValue.mAnnotations.isNested();
+ const bool nested = newState.mAnnotations.isNested();
StateValueInfo* stateValueInfo = &mStateMap[primaryKey];
updateStateForPrimaryKey(eventTimeNs, primaryKey, newState, nested, stateValueInfo);
}
@@ -85,7 +84,7 @@ bool StateTracker::getStateValue(const HashableDimensionKey& queryKey, FieldValu
return false;
}
-void StateTracker::handleReset(const int64_t eventTimeNs, const int32_t newState) {
+void StateTracker::handleReset(const int64_t eventTimeNs, const FieldValue& newState) {
VLOG("StateTracker handle reset");
for (auto& [primaryKey, stateValueInfo] : mStateMap) {
updateStateForPrimaryKey(eventTimeNs, primaryKey, newState,
@@ -102,8 +101,9 @@ void StateTracker::clearStateForPrimaryKey(const int64_t eventTimeNs,
// If there is no entry for the primaryKey in mStateMap, then the state is already
// kStateUnknown.
+ const FieldValue state(mField, Value(kStateUnknown));
if (it != mStateMap.end()) {
- updateStateForPrimaryKey(eventTimeNs, primaryKey, kStateUnknown,
+ updateStateForPrimaryKey(eventTimeNs, primaryKey, state,
false /* nested; treat this state change as not nested */,
&it->second);
}
@@ -111,22 +111,26 @@ void StateTracker::clearStateForPrimaryKey(const int64_t eventTimeNs,
void StateTracker::updateStateForPrimaryKey(const int64_t eventTimeNs,
const HashableDimensionKey& primaryKey,
- const int32_t newState, const bool nested,
+ const FieldValue& newState, const bool nested,
StateValueInfo* stateValueInfo) {
- const int32_t oldState = stateValueInfo->state;
+ FieldValue oldState;
+ oldState.mField = mField;
+ oldState.mValue.setInt(stateValueInfo->state);
+ const int32_t oldStateValue = stateValueInfo->state;
+ const int32_t newStateValue = newState.mValue.int_value;
- if (kStateUnknown == newState) {
+ if (kStateUnknown == newStateValue) {
mStateMap.erase(primaryKey);
}
// Update state map for non-nested counting case.
// Every state event triggers a state overwrite.
if (!nested) {
- stateValueInfo->state = newState;
+ stateValueInfo->state = newStateValue;
stateValueInfo->count = 1;
// Notify listeners if state has changed.
- if (oldState != newState) {
+ if (oldStateValue != newStateValue) {
notifyListeners(eventTimeNs, primaryKey, oldState, newState);
}
return;
@@ -142,26 +146,26 @@ void StateTracker::updateStateForPrimaryKey(const int64_t eventTimeNs,
// In atoms.proto, a state atom with nested counting enabled
// must only have 2 states. There is no enforcemnt here of this requirement.
// The atom must be logged correctly.
- if (kStateUnknown == newState) {
- if (kStateUnknown != oldState) {
+ if (kStateUnknown == newStateValue) {
+ if (kStateUnknown != oldStateValue) {
notifyListeners(eventTimeNs, primaryKey, oldState, newState);
}
- } else if (oldState == kStateUnknown) {
- stateValueInfo->state = newState;
+ } else if (oldStateValue == kStateUnknown) {
+ stateValueInfo->state = newStateValue;
stateValueInfo->count = 1;
notifyListeners(eventTimeNs, primaryKey, oldState, newState);
- } else if (oldState == newState) {
+ } else if (oldStateValue == newStateValue) {
stateValueInfo->count++;
} else if (--stateValueInfo->count == 0) {
- stateValueInfo->state = newState;
+ stateValueInfo->state = newStateValue;
stateValueInfo->count = 1;
notifyListeners(eventTimeNs, primaryKey, oldState, newState);
}
}
void StateTracker::notifyListeners(const int64_t eventTimeNs,
- const HashableDimensionKey& primaryKey, const int32_t oldState,
- const int32_t newState) {
+ const HashableDimensionKey& primaryKey,
+ const FieldValue& oldState, const FieldValue& newState) {
for (auto l : mListeners) {
auto sl = l.promote();
if (sl != nullptr) {
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
index c5f6315fc992..abd579e7e302 100644
--- a/cmds/statsd/src/state/StateTracker.h
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -72,19 +72,19 @@ private:
std::set<wp<StateListener>> mListeners;
// Reset all state values in map to the given state.
- void handleReset(const int64_t eventTimeNs, const int32_t newState);
+ void handleReset(const int64_t eventTimeNs, const FieldValue& newState);
// Clears the state value mapped to the given primary key by setting it to kStateUnknown.
void clearStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey);
// Update the StateMap based on the received state value.
void updateStateForPrimaryKey(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey,
- const int32_t newState, const bool nested,
+ const FieldValue& newState, const bool nested,
StateValueInfo* stateValueInfo);
// Notify registered state listeners of state change.
void notifyListeners(const int64_t eventTimeNs, const HashableDimensionKey& primaryKey,
- const int32_t oldState, const int32_t newState);
+ const FieldValue& oldState, const FieldValue& newState);
};
bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output);
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 7c0057d87ca9..2e6043df0e64 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -131,7 +131,7 @@ message SimplePredicate {
UNKNOWN = 0;
FALSE = 1;
}
- optional InitialValue initial_value = 5 [default = FALSE];
+ optional InitialValue initial_value = 5 [default = UNKNOWN];
optional FieldMatcher dimensions = 6;
}
diff --git a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
index 1a7cd5584f3d..04eb40080631 100644
--- a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
@@ -28,6 +28,92 @@ namespace statsd {
#ifdef __ANDROID__
/**
+ * Tests the initial condition and condition after the first log events for
+ * count metrics with either a combination condition or simple condition.
+ *
+ * Metrics should be initialized with condition kUnknown (given that the
+ * predicate is using the default InitialValue of UNKNOWN). The condition should
+ * be updated to either kFalse or kTrue if a condition event is logged for all
+ * children conditions.
+ */
+TEST(CountMetricE2eTest, TestInitialConditionChanges) {
+ // Initialize config.
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+ config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root.
+
+ auto syncStartMatcher = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = syncStartMatcher;
+ *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = CreateBatteryStateNoneMatcher();
+ *config.add_atom_matcher() = CreateBatteryStateUsbMatcher();
+
+ auto screenOnPredicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = screenOnPredicate;
+
+ auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate();
+ *config.add_predicate() = deviceUnpluggedPredicate;
+
+ auto screenOnOnBatteryPredicate = config.add_predicate();
+ screenOnOnBatteryPredicate->set_id(StringToId("screenOnOnBatteryPredicate"));
+ screenOnOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+ addPredicateToPredicateCombination(screenOnPredicate, screenOnOnBatteryPredicate);
+ addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOnOnBatteryPredicate);
+
+ // CountSyncStartWhileScreenOnOnBattery (CombinationCondition)
+ CountMetric* countMetric1 = config.add_count_metric();
+ countMetric1->set_id(StringToId("CountSyncStartWhileScreenOnOnBattery"));
+ countMetric1->set_what(syncStartMatcher.id());
+ countMetric1->set_condition(screenOnOnBatteryPredicate->id());
+ countMetric1->set_bucket(FIVE_MINUTES);
+
+ // CountSyncStartWhileOnBattery (SimpleCondition)
+ CountMetric* countMetric2 = config.add_count_metric();
+ countMetric2->set_id(StringToId("CountSyncStartWhileOnBatterySliceScreen"));
+ countMetric2->set_what(syncStartMatcher.id());
+ countMetric2->set_condition(deviceUnpluggedPredicate.id());
+ countMetric2->set_bucket(FIVE_MINUTES);
+
+ const uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ const uint64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(2, metricsManager->mAllMetricProducers.size());
+
+ sp<MetricProducer> metricProducer1 = metricsManager->mAllMetricProducers[0];
+ sp<MetricProducer> metricProducer2 = metricsManager->mAllMetricProducers[1];
+
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+ auto screenOnEvent =
+ CreateScreenStateChangedEvent(bucketStartTimeNs + 30, android::view::DISPLAY_STATE_ON);
+ processor->OnLogEvent(screenOnEvent.get());
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+ auto pluggedUsbEvent = CreateBatteryStateChangedEvent(
+ bucketStartTimeNs + 50, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+ processor->OnLogEvent(pluggedUsbEvent.get());
+ EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kFalse, metricProducer2->mCondition);
+
+ auto pluggedNoneEvent = CreateBatteryStateChangedEvent(
+ bucketStartTimeNs + 70, BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE);
+ processor->OnLogEvent(pluggedNoneEvent.get());
+ EXPECT_EQ(ConditionState::kTrue, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition);
+}
+
+/**
* Test a count metric that has one slice_by_state with no primary fields.
*
* Once the CountMetricProducer is initialized, it has one atom id in
@@ -85,7 +171,7 @@ TEST(CountMetricE2eTest, TestSlicedState) {
x x x x x x (syncStartEvents)
| | (ScreenIsOnEvent)
| | (ScreenIsOffEvent)
- | (ScreenUnknownEvent)
+ | (ScreenDozeEvent)
*/
// Initialize log events - first bucket.
std::vector<int> attributionUids1 = {123};
@@ -243,9 +329,8 @@ TEST(CountMetricE2eTest, TestSlicedStateWithMap) {
|-----------------------------|-----------------------------|--
x x x x x x x x x (syncStartEvents)
-----------------------------------------------------------SCREEN_OFF events
- | (ScreenStateUnknownEvent = 0)
| | (ScreenStateOffEvent = 1)
- | (ScreenStateDozeEvent = 3)
+ | | (ScreenStateDozeEvent = 3)
| (ScreenStateDozeSuspendEvent =
4)
-----------------------------------------------------------SCREEN_ON events
@@ -262,7 +347,7 @@ TEST(CountMetricE2eTest, TestSlicedStateWithMap) {
attributionTags1, "sync_name")); // 0:30
events.push_back(CreateScreenStateChangedEvent(
bucketStartTimeNs + 30 * NS_PER_SEC,
- android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN)); // 0:40
+ android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 0:40
events.push_back(CreateSyncStartEvent(bucketStartTimeNs + 60 * NS_PER_SEC, attributionUids1,
attributionTags1, "sync_name")); // 1:10
events.push_back(CreateScreenStateChangedEvent(
@@ -625,9 +710,8 @@ TEST(CountMetricE2eTest, TestMultipleSlicedStates) {
|------------------------|------------------------|--
1 1 1 1 1 2 1 1 2 (AppCrashEvents)
---------------------------------------------------SCREEN_OFF events
- | (ScreenUnknownEvent = 0)
| | (ScreenOffEvent = 1)
- | (ScreenDozeEvent = 3)
+ | | (ScreenDozeEvent = 3)
---------------------------------------------------SCREEN_ON events
| | (ScreenOnEvent = 2)
| (ScreenOnSuspendEvent = 6)
@@ -660,7 +744,7 @@ TEST(CountMetricE2eTest, TestMultipleSlicedStates) {
CreateAppCrashOccurredEvent(bucketStartTimeNs + 20 * NS_PER_SEC, 1 /*uid*/)); // 0:30
events.push_back(CreateScreenStateChangedEvent(
bucketStartTimeNs + 30 * NS_PER_SEC,
- android::view::DisplayStateEnum::DISPLAY_STATE_UNKNOWN)); // 0:40
+ android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 0:40
events.push_back(
CreateAppCrashOccurredEvent(bucketStartTimeNs + 60 * NS_PER_SEC, 1 /*uid*/)); // 1:10
events.push_back(CreateUidProcessStateChangedEvent(
diff --git a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
index e595f290ffdf..4d3928277527 100644
--- a/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
@@ -63,8 +63,143 @@ StatsdConfig CreateStatsdConfig(bool useCondition = true) {
return config;
}
+StatsdConfig CreateStatsdConfigWithStates() {
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+ config.add_default_pull_packages("AID_ROOT"); // Fake puller is registered with root.
+
+ auto pulledAtomMatcher = CreateSimpleAtomMatcher("TestMatcher", util::SUBSYSTEM_SLEEP_STATE);
+ *config.add_atom_matcher() = pulledAtomMatcher;
+ *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
+ *config.add_atom_matcher() = CreateScreenTurnedOffAtomMatcher();
+ *config.add_atom_matcher() = CreateBatteryStateNoneMatcher();
+ *config.add_atom_matcher() = CreateBatteryStateUsbMatcher();
+
+ auto screenOnPredicate = CreateScreenIsOnPredicate();
+ *config.add_predicate() = screenOnPredicate;
+
+ auto screenOffPredicate = CreateScreenIsOffPredicate();
+ *config.add_predicate() = screenOffPredicate;
+
+ auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate();
+ *config.add_predicate() = deviceUnpluggedPredicate;
+
+ auto screenOnOnBatteryPredicate = config.add_predicate();
+ screenOnOnBatteryPredicate->set_id(StringToId("screenOnOnBatteryPredicate"));
+ screenOnOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+ addPredicateToPredicateCombination(screenOnPredicate, screenOnOnBatteryPredicate);
+ addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOnOnBatteryPredicate);
+
+ auto screenOffOnBatteryPredicate = config.add_predicate();
+ screenOffOnBatteryPredicate->set_id(StringToId("ScreenOffOnBattery"));
+ screenOffOnBatteryPredicate->mutable_combination()->set_operation(LogicalOperation::AND);
+ addPredicateToPredicateCombination(screenOffPredicate, screenOffOnBatteryPredicate);
+ addPredicateToPredicateCombination(deviceUnpluggedPredicate, screenOffOnBatteryPredicate);
+
+ const State screenState =
+ CreateScreenStateWithSimpleOnOffMap(/*screen on id=*/321, /*screen off id=*/123);
+ *config.add_state() = screenState;
+
+ // ValueMetricSubsystemSleepWhileScreenOnOnBattery
+ auto valueMetric1 = config.add_value_metric();
+ valueMetric1->set_id(metricId);
+ valueMetric1->set_what(pulledAtomMatcher.id());
+ valueMetric1->set_condition(screenOnOnBatteryPredicate->id());
+ *valueMetric1->mutable_value_field() =
+ CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+ valueMetric1->set_bucket(FIVE_MINUTES);
+ valueMetric1->set_use_absolute_value_on_reset(true);
+ valueMetric1->set_skip_zero_diff_output(false);
+ valueMetric1->set_max_pull_delay_sec(INT_MAX);
+
+ // ValueMetricSubsystemSleepWhileScreenOffOnBattery
+ ValueMetric* valueMetric2 = config.add_value_metric();
+ valueMetric2->set_id(StringToId("ValueMetricSubsystemSleepWhileScreenOffOnBattery"));
+ valueMetric2->set_what(pulledAtomMatcher.id());
+ valueMetric2->set_condition(screenOffOnBatteryPredicate->id());
+ *valueMetric2->mutable_value_field() =
+ CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+ valueMetric2->set_bucket(FIVE_MINUTES);
+ valueMetric2->set_use_absolute_value_on_reset(true);
+ valueMetric2->set_skip_zero_diff_output(false);
+ valueMetric2->set_max_pull_delay_sec(INT_MAX);
+
+ // ValueMetricSubsystemSleepWhileOnBatterySliceScreen
+ ValueMetric* valueMetric3 = config.add_value_metric();
+ valueMetric3->set_id(StringToId("ValueMetricSubsystemSleepWhileOnBatterySliceScreen"));
+ valueMetric3->set_what(pulledAtomMatcher.id());
+ valueMetric3->set_condition(deviceUnpluggedPredicate.id());
+ *valueMetric3->mutable_value_field() =
+ CreateDimensions(util::SUBSYSTEM_SLEEP_STATE, {4 /* time sleeping field */});
+ valueMetric3->add_slice_by_state(screenState.id());
+ valueMetric3->set_bucket(FIVE_MINUTES);
+ valueMetric3->set_use_absolute_value_on_reset(true);
+ valueMetric3->set_skip_zero_diff_output(false);
+ valueMetric3->set_max_pull_delay_sec(INT_MAX);
+ return config;
+}
+
} // namespace
+/**
+ * Tests the initial condition and condition after the first log events for
+ * value metrics with either a combination condition or simple condition.
+ *
+ * Metrics should be initialized with condition kUnknown (given that the
+ * predicate is using the default InitialValue of UNKNOWN). The condition should
+ * be updated to either kFalse or kTrue if a condition event is logged for all
+ * children conditions.
+ */
+TEST(ValueMetricE2eTest, TestInitialConditionChanges) {
+ StatsdConfig config = CreateStatsdConfigWithStates();
+ int64_t baseTimeNs = getElapsedRealtimeNs();
+ int64_t configAddedTimeNs = 10 * 60 * NS_PER_SEC + baseTimeNs;
+ int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.value_metric(0).bucket()) * 1000000;
+
+ ConfigKey cfgKey;
+ int32_t tagId = util::SUBSYSTEM_SLEEP_STATE;
+ auto processor =
+ CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey,
+ SharedRefBase::make<FakeSubsystemSleepCallback>(), tagId);
+
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(3, metricsManager->mAllMetricProducers.size());
+
+ // Combination condition metric - screen on and device unplugged
+ sp<MetricProducer> metricProducer1 = metricsManager->mAllMetricProducers[0];
+ // Simple condition metric - device unplugged
+ sp<MetricProducer> metricProducer2 = metricsManager->mAllMetricProducers[2];
+
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+ auto screenOnEvent =
+ CreateScreenStateChangedEvent(configAddedTimeNs + 30, android::view::DISPLAY_STATE_ON);
+ processor->OnLogEvent(screenOnEvent.get());
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+ auto screenOffEvent =
+ CreateScreenStateChangedEvent(configAddedTimeNs + 40, android::view::DISPLAY_STATE_OFF);
+ processor->OnLogEvent(screenOffEvent.get());
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kUnknown, metricProducer2->mCondition);
+
+ auto pluggedUsbEvent = CreateBatteryStateChangedEvent(
+ configAddedTimeNs + 50, BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+ processor->OnLogEvent(pluggedUsbEvent.get());
+ EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kFalse, metricProducer2->mCondition);
+
+ auto pluggedNoneEvent = CreateBatteryStateChangedEvent(
+ configAddedTimeNs + 70, BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE);
+ processor->OnLogEvent(pluggedNoneEvent.get());
+ EXPECT_EQ(ConditionState::kFalse, metricProducer1->mCondition);
+ EXPECT_EQ(ConditionState::kTrue, metricProducer2->mCondition);
+}
+
TEST(ValueMetricE2eTest, TestPulledEvents) {
auto config = CreateStatsdConfig();
int64_t baseTimeNs = getElapsedRealtimeNs();
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index b6e1075bcb72..474aa2234837 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -3898,14 +3898,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) {
data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 5, 5));
return true;
}))
- // Screen state change to VR.
- .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
- vector<std::shared_ptr<LogEvent>>* data, bool) {
- EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 10);
- data->clear();
- data->push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 10, 9));
- return true;
- }))
+ // Screen state change to VR has no pull because it is in the same
+ // state group as ON.
+
+ // Screen state change to ON has no pull because it is in the same
+ // state group as VR.
+
// Screen state change to OFF.
.WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
vector<std::shared_ptr<LogEvent>>* data, bool) {
@@ -3969,23 +3967,33 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) {
EXPECT_EQ(true, it->second[0].hasValue);
EXPECT_EQ(2, it->second[0].value.long_value);
- // Bucket status after screen state change ON->VR (also ON).
+ // Bucket status after screen state change ON->VR.
+ // Both ON and VR are in the same state group, so the base should not change.
screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 10,
android::view::DisplayStateEnum::DISPLAY_STATE_VR);
StateManager::getInstance().onLogEvent(*screenEvent);
- ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size());
+ ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
// Base for dimension key {}
it = valueProducer->mCurrentSlicedBucket.begin();
itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
EXPECT_EQ(true, itBase->second[0].hasBase);
- EXPECT_EQ(9, itBase->second[0].base.long_value);
- // Value for dimension, state key {{}, ON GROUP}
- EXPECT_EQ(screenOnGroup.group_id(),
- it->first.getStateValuesKey().getValues()[0].mValue.long_value);
+ EXPECT_EQ(5, itBase->second[0].base.long_value);
+ // Value for dimension, state key {{}, kStateUnknown}
EXPECT_EQ(true, it->second[0].hasValue);
- EXPECT_EQ(4, it->second[0].value.long_value);
+ EXPECT_EQ(2, it->second[0].value.long_value);
+
+ // Bucket status after screen state change VR->ON.
+ // Both ON and VR are in the same state group, so the base should not change.
+ screenEvent = CreateScreenStateChangedEvent(bucketStartTimeNs + 12,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+ StateManager::getInstance().onLogEvent(*screenEvent);
+ EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
+ // Base for dimension key {}
+ it = valueProducer->mCurrentSlicedBucket.begin();
+ itBase = valueProducer->mCurrentBaseInfo.find(it->first.getDimensionKeyInWhat());
+ EXPECT_EQ(true, itBase->second[0].hasBase);
+ EXPECT_EQ(5, itBase->second[0].base.long_value);
// Value for dimension, state key {{}, kStateUnknown}
- it++;
EXPECT_EQ(true, it->second[0].hasValue);
EXPECT_EQ(2, it->second[0].value.long_value);
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index 13e8f5c343f2..530ac5e01f3e 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -50,8 +50,9 @@ public:
std::vector<Update> updates;
void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
- const HashableDimensionKey& primaryKey, int oldState, int newState) {
- updates.emplace_back(primaryKey, newState);
+ const HashableDimensionKey& primaryKey, const FieldValue& oldState,
+ const FieldValue& newState) {
+ updates.emplace_back(primaryKey, newState.mValue.int_value);
}
};
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 6a7ad1faddea..582df0c1a2a3 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -169,7 +169,6 @@ AtomMatcher CreateScreenStateChangedAtomMatcher(
return atom_matcher;
}
-
AtomMatcher CreateScreenTurnedOnAtomMatcher() {
return CreateScreenStateChangedAtomMatcher("ScreenTurnedOn",
android::view::DisplayStateEnum::DISPLAY_STATE_ON);
@@ -335,22 +334,46 @@ State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId) {
return state;
}
+State CreateScreenStateWithSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId) {
+ State state;
+ state.set_id(StringToId("ScreenStateSimpleOnOff"));
+ state.set_atom_id(util::SCREEN_STATE_CHANGED);
+
+ auto map = CreateScreenStateSimpleOnOffMap(screenOnId, screenOffId);
+ *state.mutable_map() = map;
+
+ return state;
+}
+
StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId) {
StateMap_StateGroup group;
group.set_group_id(screenOnId);
- group.add_value(2);
- group.add_value(5);
- group.add_value(6);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_VR);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND);
return group;
}
StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId) {
StateMap_StateGroup group;
group.set_group_id(screenOffId);
- group.add_value(0);
- group.add_value(1);
- group.add_value(3);
- group.add_value(4);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND);
+ return group;
+}
+
+StateMap_StateGroup CreateScreenStateSimpleOnGroup(int64_t screenOnId) {
+ StateMap_StateGroup group;
+ group.set_group_id(screenOnId);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+ return group;
+}
+
+StateMap_StateGroup CreateScreenStateSimpleOffGroup(int64_t screenOffId) {
+ StateMap_StateGroup group;
+ group.set_group_id(screenOffId);
+ group.add_value(android::view::DisplayStateEnum::DISPLAY_STATE_OFF);
return group;
}
@@ -361,6 +384,13 @@ StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId) {
return map;
}
+StateMap CreateScreenStateSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId) {
+ StateMap map;
+ *map.add_group() = CreateScreenStateSimpleOnGroup(screenOnId);
+ *map.add_group() = CreateScreenStateSimpleOffGroup(screenOffId);
+ return map;
+}
+
void addPredicateToPredicateCombination(const Predicate& predicate,
Predicate* combinationPredicate) {
combinationPredicate->mutable_combination()->add_predicate(predicate.id());
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index dc012c5381eb..6a5d5da2895c 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -149,17 +149,30 @@ State CreateUidProcessState();
// Create State proto for overlay state atom.
State CreateOverlayState();
+// Create State proto for screen state atom with on/off map.
State CreateScreenStateWithOnOffMap(int64_t screenOnId, int64_t screenOffId);
+// Create State proto for screen state atom with simple on/off map.
+State CreateScreenStateWithSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId);
+
// Create StateGroup proto for ScreenState ON group
StateMap_StateGroup CreateScreenStateOnGroup(int64_t screenOnId);
// Create StateGroup proto for ScreenState OFF group
StateMap_StateGroup CreateScreenStateOffGroup(int64_t screenOffId);
+// Create StateGroup proto for simple ScreenState ON group
+StateMap_StateGroup CreateScreenStateSimpleOnGroup(int64_t screenOnId);
+
+// Create StateGroup proto for simple ScreenState OFF group
+StateMap_StateGroup CreateScreenStateSimpleOffGroup(int64_t screenOffId);
+
// Create StateMap proto for ScreenState ON/OFF map
StateMap CreateScreenStateOnOffMap(int64_t screenOnId, int64_t screenOffId);
+// Create StateMap proto for simple ScreenState ON/OFF map
+StateMap CreateScreenStateSimpleOnOffMap(int64_t screenOnId, int64_t screenOffId);
+
// Add a predicate to the predicate combination.
void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index eea1d69b6326..8e43ca3c6739 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1538,6 +1538,12 @@ public final class ActivityThread extends ClientTransactionHandler {
IoUtils.closeQuietly(pfd);
}
+ @Override
+ public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) {
+ PropertyInvalidatedCache.dumpCacheInfo(pfd.getFileDescriptor(), args);
+ IoUtils.closeQuietly(pfd);
+ }
+
private File getDatabasesDir(Context context) {
// There's no simple way to get the databases/ path, so do it this way.
return context.getDatabasePath("a").getParentFile();
@@ -5902,6 +5908,12 @@ public final class ActivityThread extends ClientTransactionHandler {
}
}
+ /**
+ * Sets the supplied {@code overrideConfig} as pending for the {@code activityToken}. Calling
+ * this method prevents any calls to
+ * {@link #handleActivityConfigurationChanged(IBinder, Configuration, int, boolean)} from
+ * processing any configurations older than {@code overrideConfig}.
+ */
@Override
public void updatePendingActivityConfiguration(IBinder activityToken,
Configuration overrideConfig) {
@@ -5918,13 +5930,22 @@ public final class ActivityThread extends ClientTransactionHandler {
}
synchronized (r) {
+ if (r.mPendingOverrideConfig != null
+ && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) {
+ if (DEBUG_CONFIGURATION) {
+ Slog.v(TAG, "Activity has newer configuration pending so drop this"
+ + " transaction. overrideConfig=" + overrideConfig
+ + " r.mPendingOverrideConfig=" + r.mPendingOverrideConfig);
+ }
+ return;
+ }
r.mPendingOverrideConfig = overrideConfig;
}
}
@Override
public void handleActivityConfigurationChanged(IBinder activityToken,
- Configuration overrideConfig, int displayId) {
+ @NonNull Configuration overrideConfig, int displayId) {
handleActivityConfigurationChanged(activityToken, overrideConfig, displayId,
// This is the only place that uses alwaysReportChange=true. The entry point should
// be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side
@@ -5935,15 +5956,18 @@ public final class ActivityThread extends ClientTransactionHandler {
}
/**
- * Handle new activity configuration and/or move to a different display.
+ * Handle new activity configuration and/or move to a different display. This method is a noop
+ * if {@link #updatePendingActivityConfiguration(IBinder, Configuration)} has been called with
+ * a newer config than {@code overrideConfig}.
+ *
* @param activityToken Target activity token.
* @param overrideConfig Activity override config.
* @param displayId Id of the display where activity was moved to, -1 if there was no move and
* value didn't change.
* @param alwaysReportChange If the configuration is changed, always report to activity.
*/
- void handleActivityConfigurationChanged(IBinder activityToken, Configuration overrideConfig,
- int displayId, boolean alwaysReportChange) {
+ void handleActivityConfigurationChanged(IBinder activityToken,
+ @NonNull Configuration overrideConfig, int displayId, boolean alwaysReportChange) {
ActivityClientRecord r = mActivities.get(activityToken);
// Check input params.
if (r == null || r.activity == null) {
@@ -5954,9 +5978,13 @@ public final class ActivityThread extends ClientTransactionHandler {
&& displayId != r.activity.getDisplayId();
synchronized (r) {
- if (r.mPendingOverrideConfig != null
- && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) {
- overrideConfig = r.mPendingOverrideConfig;
+ if (overrideConfig.isOtherSeqNewer(r.mPendingOverrideConfig)) {
+ if (DEBUG_CONFIGURATION) {
+ Slog.v(TAG, "Activity has newer configuration pending so drop this"
+ + " transaction. overrideConfig=" + overrideConfig
+ + " r.mPendingOverrideConfig=" + r.mPendingOverrideConfig);
+ }
+ return;
}
r.mPendingOverrideConfig = null;
}
@@ -6202,6 +6230,12 @@ public final class ActivityThread extends ClientTransactionHandler {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory");
if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);
+ if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
+ for (PropertyInvalidatedCache pic : PropertyInvalidatedCache.getActiveCaches()) {
+ pic.clear();
+ }
+ }
+
ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null);
final int N = callbacks.size();
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 3ce768944e48..be1681bc7cc6 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -229,7 +229,16 @@ interface IActivityTaskManager {
void unregisterTaskStackListener(in ITaskStackListener listener);
void setTaskResizeable(int taskId, int resizeableMode);
void toggleFreeformWindowingMode(in IBinder token);
- void resizeTask(int taskId, in Rect bounds, int resizeMode);
+
+ /**
+ * Resize the task with given bounds
+ *
+ * @param taskId The id of the task to set the bounds for.
+ * @param bounds The new bounds.
+ * @param resizeMode Resize mode defined as {@code ActivityTaskManager#RESIZE_MODE_*} constants.
+ * @return Return true on success. Otherwise false.
+ */
+ boolean resizeTask(int taskId, in Rect bounds, int resizeMode);
void moveStackToDisplay(int stackId, int displayId);
void removeStack(int stackId);
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 1f6e4cac199a..6e9157e2a8c3 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -119,6 +119,7 @@ oneway interface IApplicationThread {
boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable,
in String[] args);
void dumpGfxInfo(in ParcelFileDescriptor fd, in String[] args);
+ void dumpCacheInfo(in ParcelFileDescriptor fd, in String[] args);
void dumpProvider(in ParcelFileDescriptor fd, IBinder servicetoken,
in String[] args);
void dumpDbInfo(in ParcelFileDescriptor fd, in String[] args);
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 3110e18985b0..01cf2b94a842 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -26,12 +26,20 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastPrintWriter;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
+import java.util.Set;
+import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
@@ -197,6 +205,14 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
@GuardedBy("sCorkLock")
private static final HashMap<String, Integer> sCorks = new HashMap<>();
+ /**
+ * Weakly references all cache objects in the current process, allowing us to iterate over
+ * them all for purposes like issuing debug dumps and reacting to memory pressure.
+ */
+ @GuardedBy("sCorkLock")
+ private static final WeakHashMap<PropertyInvalidatedCache, Void> sCaches =
+ new WeakHashMap<>();
+
private final Object mLock = new Object();
/**
@@ -225,6 +241,11 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
private boolean mDisabled = false;
/**
+ * Maximum number of entries the cache will maintain.
+ */
+ private final int mMaxEntries;
+
+ /**
* Make a new property invalidated cache.
*
* @param maxEntries Maximum number of entries to cache; LRU discard
@@ -232,6 +253,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
*/
public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
mPropertyName = propertyName;
+ mMaxEntries = maxEntries;
mCache = new LinkedHashMap<Query, Result>(
2 /* start small */,
0.75f /* default load factor */,
@@ -241,6 +263,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
return size() > maxEntries;
}
};
+ synchronized (sCorkLock) {
+ sCaches.put(this, null);
+ }
}
/**
@@ -248,6 +273,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
*/
public final void clear() {
synchronized (mLock) {
+ if (DEBUG) {
+ Log.d(TAG, "clearing cache for " + mPropertyName);
+ }
mCache.clear();
}
}
@@ -710,4 +738,87 @@ public abstract class PropertyInvalidatedCache<Query, Result> {
Log.d(TAG, "disabling all caches in the process");
sEnabled = false;
}
+
+ /**
+ * Returns a list of caches alive at the current time.
+ */
+ public static ArrayList<PropertyInvalidatedCache> getActiveCaches() {
+ synchronized (sCorkLock) {
+ return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet());
+ }
+ }
+
+ /**
+ * Returns a list of the active corks in a process.
+ */
+ public static ArrayList<Map.Entry<String, Integer>> getActiveCorks() {
+ synchronized (sCorkLock) {
+ return new ArrayList<Map.Entry<String, Integer>>(sCorks.entrySet());
+ }
+ }
+
+ private void dumpContents(PrintWriter pw, String[] args) {
+ synchronized (mLock) {
+ pw.println(String.format(" Cache Property Name: %s", cacheName()));
+ pw.println(String.format(" Last Observed Nonce: %d", mLastSeenNonce));
+ pw.println(String.format(" Current Size: %d, Max Size: %d",
+ mCache.entrySet().size(), mMaxEntries));
+ pw.println(String.format(" Enabled: %s", mDisabled ? "false" : "true"));
+
+ Set<Map.Entry<Query, Result>> cacheEntries = mCache.entrySet();
+ if (cacheEntries.size() == 0) {
+ pw.println("");
+ return;
+ }
+
+ pw.println("");
+ pw.println(" Contents:");
+ for (Map.Entry<Query, Result> entry : cacheEntries) {
+ String key = Objects.toString(entry.getKey());
+ String value = Objects.toString(entry.getValue());
+
+ pw.println(String.format(" Key: %s\n Value: %s\n", key, value));
+ }
+ }
+ }
+
+ /**
+ * Dumps contents of every cache in the process to the provided FileDescriptor.
+ */
+ public static void dumpCacheInfo(FileDescriptor fd, String[] args) {
+ ArrayList<PropertyInvalidatedCache> activeCaches;
+ ArrayList<Map.Entry<String, Integer>> activeCorks;
+
+ try (
+ FileOutputStream fout = new FileOutputStream(fd);
+ PrintWriter pw = new FastPrintWriter(fout);
+ ) {
+ if (!sEnabled) {
+ pw.println(" Caching is disabled in this process.");
+ return;
+ }
+
+ synchronized (sCorkLock) {
+ activeCaches = getActiveCaches();
+ activeCorks = getActiveCorks();
+
+ if (activeCorks.size() > 0) {
+ pw.println(" Corking Status:");
+ for (int i = 0; i < activeCorks.size(); i++) {
+ Map.Entry<String, Integer> entry = activeCorks.get(i);
+ pw.println(String.format(" Property Name: %s Count: %d",
+ entry.getKey(), entry.getValue()));
+ }
+ }
+ }
+
+ for (int i = 0; i < activeCaches.size(); i++) {
+ PropertyInvalidatedCache currentCache = activeCaches.get(i);
+ currentCache.dumpContents(pw, args);
+ pw.flush();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances");
+ }
+ }
}
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index 0d4e16bbb0f3..8b52242a6b6c 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -19,6 +19,7 @@ package android.app.servertransaction;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.view.Display.INVALID_DISPLAY;
+import android.annotation.NonNull;
import android.app.ClientTransactionHandler;
import android.content.res.Configuration;
import android.os.IBinder;
@@ -37,6 +38,8 @@ public class ActivityConfigurationChangeItem extends ClientTransactionItem {
@Override
public void preExecute(android.app.ClientTransactionHandler client, IBinder token) {
+ // Notify the client of an upcoming change in the token configuration. This ensures that
+ // batches of config change items only process the newest configuration.
client.updatePendingActivityConfiguration(token, mConfiguration);
}
@@ -55,7 +58,11 @@ public class ActivityConfigurationChangeItem extends ClientTransactionItem {
private ActivityConfigurationChangeItem() {}
/** Obtain an instance initialized with provided params. */
- public static ActivityConfigurationChangeItem obtain(Configuration config) {
+ public static ActivityConfigurationChangeItem obtain(@NonNull Configuration config) {
+ if (config == null) {
+ throw new IllegalArgumentException("Config must not be null.");
+ }
+
ActivityConfigurationChangeItem instance =
ObjectPool.obtain(ActivityConfigurationChangeItem.class);
if (instance == null) {
@@ -68,7 +75,7 @@ public class ActivityConfigurationChangeItem extends ClientTransactionItem {
@Override
public void recycle() {
- mConfiguration = null;
+ mConfiguration = Configuration.EMPTY;
ObjectPool.recycle(this);
}
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index f6d3dbdf5596..9a457a3aad40 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -18,6 +18,7 @@ package android.app.servertransaction;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import android.annotation.NonNull;
import android.app.ClientTransactionHandler;
import android.content.res.Configuration;
import android.os.IBinder;
@@ -36,6 +37,13 @@ public class MoveToDisplayItem extends ClientTransactionItem {
private Configuration mConfiguration;
@Override
+ public void preExecute(ClientTransactionHandler client, IBinder token) {
+ // Notify the client of an upcoming change in the token configuration. This ensures that
+ // batches of config change items only process the newest configuration.
+ client.updatePendingActivityConfiguration(token, mConfiguration);
+ }
+
+ @Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
@@ -49,7 +57,12 @@ public class MoveToDisplayItem extends ClientTransactionItem {
private MoveToDisplayItem() {}
/** Obtain an instance initialized with provided params. */
- public static MoveToDisplayItem obtain(int targetDisplayId, Configuration configuration) {
+ public static MoveToDisplayItem obtain(int targetDisplayId,
+ @NonNull Configuration configuration) {
+ if (configuration == null) {
+ throw new IllegalArgumentException("Configuration must not be null");
+ }
+
MoveToDisplayItem instance = ObjectPool.obtain(MoveToDisplayItem.class);
if (instance == null) {
instance = new MoveToDisplayItem();
@@ -63,7 +76,7 @@ public class MoveToDisplayItem extends ClientTransactionItem {
@Override
public void recycle() {
mTargetDisplayId = 0;
- mConfiguration = null;
+ mConfiguration = Configuration.EMPTY;
ObjectPool.recycle(this);
}
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 75ce0dcc1d1d..3fef92b203b6 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -625,7 +625,10 @@ public class AppWidgetHostView extends FrameLayout {
}
}
defaultView = inflater.inflate(layoutId, this, false);
- defaultView.setOnClickListener(this::onDefaultViewClicked);
+ if (!(defaultView instanceof AdapterView)) {
+ // AdapterView does not support onClickListener
+ defaultView.setOnClickListener(this::onDefaultViewClicked);
+ }
} else {
Log.w(TAG, "can't inflate defaultView because mInfo is missing");
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9832bc1b79d2..2f488cdc3158 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -385,6 +385,9 @@ public abstract class PackageManager {
* <p>
* Note: this flag may cause less information about currently installed
* applications to be returned.
+ * <p>
+ * Note: use of this flag requires the android.permission.QUERY_ALL_PACKAGES
+ * permission to see uninstalled packages.
*/
public static final int MATCH_UNINSTALLED_PACKAGES = 0x00002000;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 312e98e77636..d086459080f7 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -127,7 +127,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* Parser for package files (APKs) on disk. This supports apps packaged either
@@ -239,11 +238,6 @@ public class PackageParser {
public static final boolean LOG_UNSAFE_BROADCASTS = false;
- /**
- * Total number of packages that were read from the cache. We use it only for logging.
- */
- public static final AtomicInteger sCachedPackageReadCount = new AtomicInteger();
-
// Set of broadcast actions that are safe for manifest receivers
public static final Set<String> SAFE_BROADCASTS = new ArraySet<>();
static {
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 9b809b86eae9..b978ae559390 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -191,6 +191,58 @@ public class DatabaseUtils {
}
}
+ /** {@hide} */
+ public static long executeInsert(@NonNull SQLiteDatabase db, @NonNull String sql,
+ @Nullable Object[] bindArgs) throws SQLException {
+ try (SQLiteStatement st = db.compileStatement(sql)) {
+ bindArgs(st, bindArgs);
+ return st.executeInsert();
+ }
+ }
+
+ /** {@hide} */
+ public static int executeUpdateDelete(@NonNull SQLiteDatabase db, @NonNull String sql,
+ @Nullable Object[] bindArgs) throws SQLException {
+ try (SQLiteStatement st = db.compileStatement(sql)) {
+ bindArgs(st, bindArgs);
+ return st.executeUpdateDelete();
+ }
+ }
+
+ /** {@hide} */
+ private static void bindArgs(@NonNull SQLiteStatement st, @Nullable Object[] bindArgs) {
+ if (bindArgs == null) return;
+
+ for (int i = 0; i < bindArgs.length; i++) {
+ final Object bindArg = bindArgs[i];
+ switch (getTypeOfObject(bindArg)) {
+ case Cursor.FIELD_TYPE_NULL:
+ st.bindNull(i + 1);
+ break;
+ case Cursor.FIELD_TYPE_INTEGER:
+ st.bindLong(i + 1, ((Number) bindArg).longValue());
+ break;
+ case Cursor.FIELD_TYPE_FLOAT:
+ st.bindDouble(i + 1, ((Number) bindArg).doubleValue());
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ st.bindBlob(i + 1, (byte[]) bindArg);
+ break;
+ case Cursor.FIELD_TYPE_STRING:
+ default:
+ if (bindArg instanceof Boolean) {
+ // Provide compatibility with legacy
+ // applications which may pass Boolean values in
+ // bind args.
+ st.bindLong(i + 1, ((Boolean) bindArg).booleanValue() ? 1 : 0);
+ } else {
+ st.bindString(i + 1, bindArg.toString());
+ }
+ break;
+ }
+ }
+ }
+
/**
* Binds the given Object to the given SQLiteProgram using the proper
* typing. For example, bind numbers as longs/doubles, and everything else
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 36ec67ee1471..669d0466fdf2 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -626,7 +626,7 @@ public class SQLiteQueryBuilder {
Log.d(TAG, sql);
}
}
- return db.executeSql(sql, sqlArgs);
+ return DatabaseUtils.executeInsert(db, sql, sqlArgs);
}
/**
@@ -702,7 +702,7 @@ public class SQLiteQueryBuilder {
Log.d(TAG, sql);
}
}
- return db.executeSql(sql, sqlArgs);
+ return DatabaseUtils.executeUpdateDelete(db, sql, sqlArgs);
}
/**
@@ -762,7 +762,7 @@ public class SQLiteQueryBuilder {
Log.d(TAG, sql);
}
}
- return db.executeSql(sql, sqlArgs);
+ return DatabaseUtils.executeUpdateDelete(db, sql, sqlArgs);
}
private void enforceStrictColumns(@Nullable String[] projection) {
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 4bed985489ef..f9ed2f851552 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -314,6 +314,92 @@ public class SoundTrigger {
}
@Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof ModuleProperties)) {
+ return false;
+ }
+ ModuleProperties other = (ModuleProperties) obj;
+ if (mId != other.mId) {
+ return false;
+ }
+ if (!mImplementor.equals(other.mImplementor)) {
+ return false;
+ }
+ if (!mDescription.equals(other.mDescription)) {
+ return false;
+ }
+ if (!mUuid.equals(other.mUuid)) {
+ return false;
+ }
+ if (mVersion != other.mVersion) {
+ return false;
+ }
+ if (!mSupportedModelArch.equals(other.mSupportedModelArch)) {
+ return false;
+ }
+ if (mMaxSoundModels != other.mMaxSoundModels) {
+ return false;
+ }
+ if (mMaxKeyphrases != other.mMaxKeyphrases) {
+ return false;
+ }
+ if (mMaxUsers != other.mMaxUsers) {
+ return false;
+ }
+ if (mRecognitionModes != other.mRecognitionModes) {
+ return false;
+ }
+ if (mSupportsCaptureTransition != other.mSupportsCaptureTransition) {
+ return false;
+ }
+ if (mMaxBufferMillis != other.mMaxBufferMillis) {
+ return false;
+ }
+ if (mSupportsConcurrentCapture != other.mSupportsConcurrentCapture) {
+ return false;
+ }
+ if (mPowerConsumptionMw != other.mPowerConsumptionMw) {
+ return false;
+ }
+ if (mReturnsTriggerInEvent != other.mReturnsTriggerInEvent) {
+ return false;
+ }
+ if (mAudioCapabilities != other.mAudioCapabilities) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + mId;
+ result = prime * result + mImplementor.hashCode();
+ result = prime * result + mDescription.hashCode();
+ result = prime * result + mUuid.hashCode();
+ result = prime * result + mVersion;
+ result = prime * result + mSupportedModelArch.hashCode();
+ result = prime * result + mMaxSoundModels;
+ result = prime * result + mMaxKeyphrases;
+ result = prime * result + mMaxUsers;
+ result = prime * result + mRecognitionModes;
+ result = prime * result + (mSupportsCaptureTransition ? 1 : 0);
+ result = prime * result + mMaxBufferMillis;
+ result = prime * result + (mSupportsConcurrentCapture ? 1 : 0);
+ result = prime * result + mPowerConsumptionMw;
+ result = prime * result + (mReturnsTriggerInEvent ? 1 : 0);
+ result = prime * result + mAudioCapabilities;
+ return result;
+ }
+
+ @Override
public String toString() {
return "ModuleProperties [id=" + getId() + ", implementor=" + getImplementor()
+ ", description=" + getDescription() + ", uuid=" + getUuid()
diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java
index cf7ed9b0566d..da7503d01428 100644
--- a/core/java/android/util/AtomicFile.java
+++ b/core/java/android/util/AtomicFile.java
@@ -29,31 +29,32 @@ import java.io.IOException;
import java.util.function.Consumer;
/**
- * Helper class for performing atomic operations on a file by creating a
- * backup file until a write has successfully completed. If you need this
- * on older versions of the platform you can use
- * {@link android.support.v4.util.AtomicFile} in the v4 support library.
+ * Helper class for performing atomic operations on a file by writing to a new file and renaming it
+ * into the place of the original file after the write has successfully completed. If you need this
+ * on older versions of the platform you can use {@link androidx.core.util.AtomicFile} in AndroidX.
* <p>
- * Atomic file guarantees file integrity by ensuring that a file has
- * been completely written and sync'd to disk before removing its backup.
- * As long as the backup file exists, the original file is considered
- * to be invalid (left over from a previous attempt to write the file).
- * </p><p>
- * Atomic file does not confer any file locking semantics.
- * Do not use this class when the file may be accessed or modified concurrently
- * by multiple threads or processes. The caller is responsible for ensuring
- * appropriate mutual exclusion invariants whenever it accesses the file.
- * </p>
+ * Atomic file guarantees file integrity by ensuring that a file has been completely written and
+ * sync'd to disk before renaming it to the original file. Previously this is done by renaming the
+ * original file to a backup file beforehand, but this approach couldn't handle the case where the
+ * file is created for the first time. This class will also handle the backup file created by the
+ * old implementation properly.
+ * <p>
+ * Atomic file does not confer any file locking semantics. Do not use this class when the file may
+ * be accessed or modified concurrently by multiple threads or processes. The caller is responsible
+ * for ensuring appropriate mutual exclusion invariants whenever it accesses the file.
*/
public class AtomicFile {
+ private static final String LOG_TAG = "AtomicFile";
+
private final File mBaseName;
- private final File mBackupName;
+ private final File mNewName;
+ private final File mLegacyBackupName;
private final String mCommitTag;
private long mStartTime;
/**
* Create a new AtomicFile for a file located at the given File path.
- * The secondary backup file will be the same file path with ".bak" appended.
+ * The new file created when writing will be the same file path with ".new" appended.
*/
public AtomicFile(File baseName) {
this(baseName, null);
@@ -65,7 +66,8 @@ public class AtomicFile {
*/
public AtomicFile(File baseName, String commitTag) {
mBaseName = baseName;
- mBackupName = new File(baseName.getPath() + ".bak");
+ mNewName = new File(baseName.getPath() + ".new");
+ mLegacyBackupName = new File(baseName.getPath() + ".bak");
mCommitTag = commitTag;
}
@@ -78,11 +80,12 @@ public class AtomicFile {
}
/**
- * Delete the atomic file. This deletes both the base and backup files.
+ * Delete the atomic file. This deletes both the base and new files.
*/
public void delete() {
mBaseName.delete();
- mBackupName.delete();
+ mNewName.delete();
+ mLegacyBackupName.delete();
}
/**
@@ -112,36 +115,28 @@ public class AtomicFile {
public FileOutputStream startWrite(long startTime) throws IOException {
mStartTime = startTime;
- // Rename the current file so it may be used as a backup during the next read
- if (mBaseName.exists()) {
- if (!mBackupName.exists()) {
- if (!mBaseName.renameTo(mBackupName)) {
- Log.w("AtomicFile", "Couldn't rename file " + mBaseName
- + " to backup file " + mBackupName);
- }
- } else {
- mBaseName.delete();
+ if (mLegacyBackupName.exists()) {
+ if (!mLegacyBackupName.renameTo(mBaseName)) {
+ Log.e(LOG_TAG, "Failed to rename legacy backup file " + mLegacyBackupName
+ + " to base file " + mBaseName);
}
}
- FileOutputStream str = null;
+
try {
- str = new FileOutputStream(mBaseName);
+ return new FileOutputStream(mNewName);
} catch (FileNotFoundException e) {
- File parent = mBaseName.getParentFile();
+ File parent = mNewName.getParentFile();
if (!parent.mkdirs()) {
- throw new IOException("Couldn't create directory " + mBaseName);
+ throw new IOException("Failed to create directory for " + mNewName);
}
- FileUtils.setPermissions(
- parent.getPath(),
- FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
- -1, -1);
+ FileUtils.setPermissions(parent.getPath(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
+ | FileUtils.S_IXOTH, -1, -1);
try {
- str = new FileOutputStream(mBaseName);
+ return new FileOutputStream(mNewName);
} catch (FileNotFoundException e2) {
- throw new IOException("Couldn't create " + mBaseName);
+ throw new IOException("Failed to create new file " + mNewName, e2);
}
}
- return str;
}
/**
@@ -151,36 +146,45 @@ public class AtomicFile {
* will return the new file stream.
*/
public void finishWrite(FileOutputStream str) {
- if (str != null) {
- FileUtils.sync(str);
- try {
- str.close();
- mBackupName.delete();
- } catch (IOException e) {
- Log.w("AtomicFile", "finishWrite: Got exception:", e);
- }
- if (mCommitTag != null) {
- com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
- mCommitTag, SystemClock.uptimeMillis() - mStartTime);
- }
+ if (str == null) {
+ return;
+ }
+ if (!FileUtils.sync(str)) {
+ Log.e(LOG_TAG, "Failed to sync file output stream");
+ }
+ try {
+ str.close();
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Failed to close file output stream", e);
+ }
+ if (!mNewName.renameTo(mBaseName)) {
+ Log.e(LOG_TAG, "Failed to rename new file " + mNewName + " to base file " + mBaseName);
+ }
+ if (mCommitTag != null) {
+ com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
+ mCommitTag, SystemClock.uptimeMillis() - mStartTime);
}
}
/**
* Call when you have failed for some reason at writing to the stream
* returned by {@link #startWrite()}. This will close the current
- * write stream, and roll back to the previous state of the file.
+ * write stream, and delete the new file.
*/
public void failWrite(FileOutputStream str) {
- if (str != null) {
- FileUtils.sync(str);
- try {
- str.close();
- mBaseName.delete();
- mBackupName.renameTo(mBaseName);
- } catch (IOException e) {
- Log.w("AtomicFile", "failWrite: Got exception:", e);
- }
+ if (str == null) {
+ return;
+ }
+ if (!FileUtils.sync(str)) {
+ Log.e(LOG_TAG, "Failed to sync file output stream");
+ }
+ try {
+ str.close();
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Failed to close file output stream", e);
+ }
+ if (!mNewName.delete()) {
+ Log.e(LOG_TAG, "Failed to delete new file " + mNewName);
}
}
@@ -210,32 +214,34 @@ public class AtomicFile {
}
/**
- * Open the atomic file for reading. If there previously was an
- * incomplete write, this will roll back to the last good data before
- * opening for read. You should call close() on the FileInputStream when
- * you are done reading from it.
- *
- * <p>Note that if another thread is currently performing
- * a write, this will incorrectly consider it to be in the state of a bad
- * write and roll back, causing the new data currently being written to
- * be dropped. You must do your own threading protection for access to
- * AtomicFile.
+ * Open the atomic file for reading. You should call close() on the FileInputStream when you are
+ * done reading from it.
+ * <p>
+ * You must do your own threading protection for access to AtomicFile.
*/
public FileInputStream openRead() throws FileNotFoundException {
- if (mBackupName.exists()) {
- mBaseName.delete();
- mBackupName.renameTo(mBaseName);
+ if (mLegacyBackupName.exists()) {
+ if (!mLegacyBackupName.renameTo(mBaseName)) {
+ Log.e(LOG_TAG, "Failed to rename legacy backup file " + mLegacyBackupName
+ + " to base file " + mBaseName);
+ }
+ }
+
+ if (mNewName.exists()) {
+ if (!mNewName.delete()) {
+ Log.e(LOG_TAG, "Failed to delete outdated new file " + mNewName);
+ }
}
return new FileInputStream(mBaseName);
}
/**
* @hide
- * Checks if the original or backup file exists.
- * @return whether the original or backup file exists.
+ * Checks if the original or legacy backup file exists.
+ * @return whether the original or legacy backup file exists.
*/
public boolean exists() {
- return mBaseName.exists() || mBackupName.exists();
+ return mBaseName.exists() || mLegacyBackupName.exists();
}
/**
@@ -246,8 +252,8 @@ public class AtomicFile {
* the file does not exist or an I/O error is encountered.
*/
public long getLastModifiedTime() {
- if (mBackupName.exists()) {
- return mBackupName.lastModified();
+ if (mLegacyBackupName.exists()) {
+ return mLegacyBackupName.lastModified();
}
return mBaseName.lastModified();
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 39c7210d8dac..301ce9f013e4 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -166,6 +166,8 @@ public abstract class ContentCaptureSession implements AutoCloseable {
public static final int FLUSH_REASON_IDLE_TIMEOUT = 5;
/** @hide */
public static final int FLUSH_REASON_TEXT_CHANGE_TIMEOUT = 6;
+ /** @hide */
+ public static final int FLUSH_REASON_SESSION_CONNECTED = 7;
/** @hide */
@IntDef(prefix = { "FLUSH_REASON_" }, value = {
@@ -174,7 +176,8 @@ public abstract class ContentCaptureSession implements AutoCloseable {
FLUSH_REASON_SESSION_STARTED,
FLUSH_REASON_SESSION_FINISHED,
FLUSH_REASON_IDLE_TIMEOUT,
- FLUSH_REASON_TEXT_CHANGE_TIMEOUT
+ FLUSH_REASON_TEXT_CHANGE_TIMEOUT,
+ FLUSH_REASON_SESSION_CONNECTED
})
@Retention(RetentionPolicy.SOURCE)
public @interface FlushReason{}
@@ -609,6 +612,8 @@ public abstract class ContentCaptureSession implements AutoCloseable {
return "IDLE";
case FLUSH_REASON_TEXT_CHANGE_TIMEOUT:
return "TEXT_CHANGE";
+ case FLUSH_REASON_SESSION_CONNECTED:
+ return "CONNECTED";
default:
return "UNKOWN-" + reason;
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 893d38dcfde7..6eb71f747be6 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -273,6 +273,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
} else {
mState = resultCode;
mDisabled.set(false);
+ // Flush any pending data immediately as buffering forced until now.
+ flushIfNeeded(FLUSH_REASON_SESSION_CONNECTED);
}
if (sVerbose) {
Log.v(TAG, "handleSessionStarted() result: id=" + mId + " resultCode=" + resultCode
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index e224e84a56fe..91b9390745ef 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1121,6 +1121,7 @@ public abstract class WebSettings {
* @deprecated The Application Cache API is deprecated and this method will
* become a no-op on all Android versions once support is
* removed in Chromium. Consider using Service Workers instead.
+ * See https://web.dev/appcache-removal/ for more information.
*/
public abstract void setAppCacheEnabled(boolean flag);
@@ -1136,6 +1137,7 @@ public abstract class WebSettings {
* @deprecated The Application Cache API is deprecated and this method will
* become a no-op on all Android versions once support is
* removed in Chromium. Consider using Service Workers instead.
+ * See https://web.dev/appcache-removal/ for more information.
*/
public abstract void setAppCachePath(String appCachePath);
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 2568d097d404..f1b716143787 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -238,8 +238,9 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
@Override
- protected void onBindView(View view, TargetInfo info) {
- super.onBindView(view, info);
+ protected void onBindView(View view, TargetInfo info, int position) {
+ super.onBindView(view, info, position);
+ if (info == null) return;
// If target is loading, show a special placeholder shape in the label, make unclickable
final ViewHolder holder = (ViewHolder) view.getTag();
@@ -257,11 +258,16 @@ public class ChooserListAdapter extends ResolverListAdapter {
holder.itemView.setBackground(holder.defaultItemViewBackground);
}
- // If the target is grouped show an indicator
if (info instanceof MultiDisplayResolveInfo) {
+ // If the target is grouped show an indicator
Drawable bkg = mContext.getDrawable(R.drawable.chooser_group_background);
holder.text.setPaddingRelative(0, 0, bkg.getIntrinsicWidth() /* end */, 0);
holder.text.setBackground(bkg);
+ } else if (info.isPinned() && getPositionTargetType(position) == TARGET_STANDARD) {
+ // If the target is pinned and in the suggested row show a pinned indicator
+ Drawable bkg = mContext.getDrawable(R.drawable.chooser_pinned_background);
+ holder.text.setPaddingRelative(bkg.getIntrinsicWidth() /* start */, 0, 0, 0);
+ holder.text.setBackground(bkg);
} else {
holder.text.setBackground(null);
holder.text.setPaddingRelative(0, 0, 0, 0);
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index af9c0408ccaa..d942e859ccd0 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -524,7 +524,7 @@ public class ResolverListAdapter extends BaseAdapter {
if (view == null) {
view = createView(parent);
}
- onBindView(view, getItem(position));
+ onBindView(view, getItem(position), position);
return view;
}
@@ -541,10 +541,10 @@ public class ResolverListAdapter extends BaseAdapter {
}
public final void bindView(int position, View view) {
- onBindView(view, getItem(position));
+ onBindView(view, getItem(position), position);
}
- protected void onBindView(View view, TargetInfo info) {
+ protected void onBindView(View view, TargetInfo info, int position) {
final ViewHolder holder = (ViewHolder) view.getTag();
if (info == null) {
holder.icon.setImageDrawable(
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index a420ba67868f..7b708efdb278 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -652,6 +652,7 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p
char dex2oatImageFlagsBuf[PROPERTY_VALUE_MAX];
char extraOptsBuf[PROPERTY_VALUE_MAX];
char voldDecryptBuf[PROPERTY_VALUE_MAX];
+ char perfettoHprofOptBuf[sizeof("-XX:PerfettoHprof=") + PROPERTY_VALUE_MAX];
enum {
kEMDefault,
kEMIntPortable,
@@ -766,6 +767,16 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p
addOption("-verbose:gc");
//addOption("-verbose:class");
+ // On Android, we always want to allow loading the PerfettoHprof plugin.
+ // Even with this option set, we will still only actually load the plugin
+ // if we are on a userdebug build or the app is debuggable or profileable.
+ // This is enforced in art/runtime/runtime.cc.
+ //
+ // We want to be able to disable this, because this does not work on host,
+ // and we do not want to enable it in tests.
+ parseRuntimeOption("dalvik.vm.perfetto_hprof", perfettoHprofOptBuf, "-XX:PerfettoHprof=",
+ "true");
+
if (primary_zygote) {
addOption("-Xprimaryzygote");
}
diff --git a/core/proto/android/view/imefocuscontroller.proto b/core/proto/android/view/imefocuscontroller.proto
new file mode 100644
index 000000000000..ff9dee69207b
--- /dev/null
+++ b/core/proto/android/view/imefocuscontroller.proto
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.ImeFocusController} object.
+ */
+message ImeFocusControllerProto {
+ optional bool has_ime_focus = 1;
+ optional string served_view = 2;
+ optional string next_served_view = 3;
+} \ No newline at end of file
diff --git a/core/proto/android/view/imeinsetssourceconsumer.proto b/core/proto/android/view/imeinsetssourceconsumer.proto
new file mode 100644
index 000000000000..680916345a31
--- /dev/null
+++ b/core/proto/android/view/imeinsetssourceconsumer.proto
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.ImeInsetsSourceConsumer} object.
+ */
+message ImeInsetsSourceConsumerProto {
+ optional .android.view.inputmethod.EditorInfoProto focused_editor = 1;
+ optional bool is_requested_visible_awaiting_control = 2;
+} \ No newline at end of file
diff --git a/core/proto/android/view/inputmethod/editorinfo.proto b/core/proto/android/view/inputmethod/editorinfo.proto
new file mode 100644
index 000000000000..f93096f9d395
--- /dev/null
+++ b/core/proto/android/view/inputmethod/editorinfo.proto
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package android.view.inputmethod;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.inputmethod.EditorInfo} object.
+ */
+message EditorInfoProto {
+ optional int32 input_type = 1;
+ optional int32 ime_options = 2;
+ optional string private_ime_options = 3;
+ optional string package_name = 4;
+ optional int32 field_id = 5;
+ optional int32 target_input_method_user_id = 6;
+} \ No newline at end of file
diff --git a/core/proto/android/view/inputmethod/inputmethodeditortrace.proto b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto
new file mode 100644
index 000000000000..732213966014
--- /dev/null
+++ b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+option java_outer_classname = "InputMethodEditorTraceProto";
+
+package android.view.inputmethod;
+
+import "frameworks/base/core/proto/android/view/inputmethod/inputmethodmanager.proto";
+import "frameworks/base/core/proto/android/view/viewrootimpl.proto";
+import "frameworks/base/core/proto/android/view/insetscontroller.proto";
+import "frameworks/base/core/proto/android/view/insetssourceconsumer.proto";
+import "frameworks/base/core/proto/android/view/imeinsetssourceconsumer.proto";
+import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto";
+import "frameworks/base/core/proto/android/view/imefocuscontroller.proto";
+
+/**
+ * Represents a file full of input method editor trace entries.
+ * Encoded, it should start with 0x9 0x49 0x4d 0x45 0x54 0x52 0x41 0x43 0x45 (.IMETRACE), such
+ * that they can be easily identified.
+ */
+message InputMethodEditorTraceFileProto {
+
+ /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
+ (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
+ constants into .proto files.) */
+ enum MagicNumber {
+ INVALID = 0;
+ MAGIC_NUMBER_L = 0x54454d49; /* IMET (little-endian ASCII) */
+ MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */
+ }
+
+ /* Must be the first field to allow winscope to auto-detect the dump type. Set to value
+ in MagicNumber */
+ optional fixed64 magic_number = 1;
+ repeated InputMethodEditorProto entry = 2;
+}
+
+/* one input method editor dump entry. */
+message InputMethodEditorProto {
+
+ /* required: elapsed realtime in nanos since boot of when this entry was logged */
+ optional fixed64 elapsed_realtime_nanos = 1;
+ optional ClientSideProto client_side_dump = 2;
+
+ /* groups together the dump from ime related client side classes */
+ message ClientSideProto {
+ optional InputMethodManagerProto input_method_manager = 1;
+ optional ViewRootImplProto view_root_impl = 2;
+ optional InsetsControllerProto insets_controller = 3;
+ optional InsetsSourceConsumerProto insets_source_consumer = 4;
+ optional ImeInsetsSourceConsumerProto ime_insets_source_consumer = 5;
+ optional EditorInfoProto editor_info = 6;
+ optional ImeFocusControllerProto ime_focus_controller = 7;
+ }
+} \ No newline at end of file
diff --git a/core/proto/android/view/inputmethod/inputmethodmanager.proto b/core/proto/android/view/inputmethod/inputmethodmanager.proto
new file mode 100644
index 000000000000..9fed0ef95a27
--- /dev/null
+++ b/core/proto/android/view/inputmethod/inputmethodmanager.proto
@@ -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.
+ */
+
+syntax = "proto2";
+
+package android.view.inputmethod;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.inputmethod.InputMethodManager} object.
+ */
+message InputMethodManagerProto {
+ optional string cur_id = 1;
+ optional bool fullscreen_mode = 2;
+ optional int32 display_id = 3;
+ optional bool active = 4;
+ optional bool served_connecting = 5;
+} \ No newline at end of file
diff --git a/core/proto/android/view/insetsanimationcontrolimpl.proto b/core/proto/android/view/insetsanimationcontrolimpl.proto
new file mode 100644
index 000000000000..6eec37b8298e
--- /dev/null
+++ b/core/proto/android/view/insetsanimationcontrolimpl.proto
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.InsetsAnimationControlImpl} object.
+ */
+message InsetsAnimationControlImplProto {
+ optional bool is_cancelled = 1;
+ optional bool is_finished = 2;
+ optional string tmp_matrix = 3;
+ optional string pending_insets = 4;
+ optional float pending_fraction = 5;
+ optional bool shown_on_finish = 6;
+ optional float current_alpha = 7;
+ optional float pending_alpha = 8;
+} \ No newline at end of file
diff --git a/core/proto/android/view/insetscontroller.proto b/core/proto/android/view/insetscontroller.proto
new file mode 100644
index 000000000000..a8bf431ce156
--- /dev/null
+++ b/core/proto/android/view/insetscontroller.proto
@@ -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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/view/insetsstate.proto";
+import "frameworks/base/core/proto/android/view/insetsanimationcontrolimpl.proto";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.InsetsController} object.
+ */
+message InsetsControllerProto {
+ optional InsetsStateProto state = 1;
+ repeated InsetsAnimationControlImplProto control = 2;
+} \ No newline at end of file
diff --git a/core/proto/android/view/insetssource.proto b/core/proto/android/view/insetssource.proto
new file mode 100644
index 000000000000..41b9f432a0ed
--- /dev/null
+++ b/core/proto/android/view/insetssource.proto
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/graphics/rect.proto";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.InsetsSource} object.
+ */
+message InsetsSourceProto {
+ optional string type = 1;
+ optional .android.graphics.RectProto frame = 2;
+ optional .android.graphics.RectProto visible_frame = 3;
+ optional bool visible = 4;
+} \ No newline at end of file
diff --git a/core/proto/android/view/insetssourceconsumer.proto b/core/proto/android/view/insetssourceconsumer.proto
new file mode 100644
index 000000000000..487e06c1ccdf
--- /dev/null
+++ b/core/proto/android/view/insetssourceconsumer.proto
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/view/insetssourcecontrol.proto";
+import "frameworks/base/core/proto/android/graphics/rect.proto";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.InsetsSourceConsumer} object.
+ */
+message InsetsSourceConsumerProto {
+ optional string internal_insets_type = 1;
+ optional bool has_window_focus = 2;
+ optional bool is_requested_visible = 3;
+ optional InsetsSourceControlProto source_control = 4;
+ optional .android.graphics.RectProto pending_frame = 5;
+ optional .android.graphics.RectProto pending_visible_frame = 6;
+} \ No newline at end of file
diff --git a/core/proto/android/view/insetssourcecontrol.proto b/core/proto/android/view/insetssourcecontrol.proto
new file mode 100644
index 000000000000..3ac3cbfafaff
--- /dev/null
+++ b/core/proto/android/view/insetssourcecontrol.proto
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/graphics/point.proto";
+import "frameworks/base/core/proto/android/view/surfacecontrol.proto";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.InsetsSourceControl} object.
+ */
+message InsetsSourceControlProto {
+ optional string type = 1;
+ optional .android.graphics.PointProto position = 2;
+ optional SurfaceControlProto leash = 3;
+} \ No newline at end of file
diff --git a/core/proto/android/view/insetsstate.proto b/core/proto/android/view/insetsstate.proto
new file mode 100644
index 000000000000..9e9933d72c6c
--- /dev/null
+++ b/core/proto/android/view/insetsstate.proto
@@ -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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/view/insetssource.proto";
+import "frameworks/base/core/proto/android/graphics/rect.proto";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.InsetsState} object.
+ */
+message InsetsStateProto {
+ repeated InsetsSourceProto sources = 1;
+ optional .android.graphics.RectProto display_frame = 2;
+} \ No newline at end of file
diff --git a/core/proto/android/view/viewrootimpl.proto b/core/proto/android/view/viewrootimpl.proto
new file mode 100644
index 000000000000..0abe5e0624e3
--- /dev/null
+++ b/core/proto/android/view/viewrootimpl.proto
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/core/proto/android/graphics/rect.proto";
+import "frameworks/base/core/proto/android/view/displaycutout.proto";
+import "frameworks/base/core/proto/android/view/windowlayoutparams.proto";
+
+package android.view;
+
+option java_multiple_files = true;
+
+/**
+ * Represents a {@link android.view.ViewRootImpl} object.
+ */
+message ViewRootImplProto {
+ optional string view = 1;
+ optional int32 display_id = 2;
+ optional bool app_visible = 3;
+ optional int32 width = 4;
+ optional int32 height = 5;
+ optional bool is_animating = 6;
+ optional .android.graphics.RectProto visible_rect = 7;
+ optional bool is_drawing = 8;
+ optional bool added = 9;
+ optional .android.graphics.RectProto win_frame = 10;
+ optional DisplayCutoutProto pending_display_cutout = 11;
+ optional string last_window_insets = 12;
+ optional string soft_input_mode = 13;
+ optional int32 scroll_y = 14;
+ optional int32 cur_scroll_y = 15;
+ optional bool removed = 16;
+ optional .android.view.WindowLayoutParamsProto window_attributes = 17;
+} \ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3cd0f03de727..4bc570ad0adb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5474,7 +5474,8 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
- <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader">
+ <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
+ android:exported="false">
<intent-filter>
<action android:name="android.intent.action.LOAD_DATA" />
</intent-filter>
diff --git a/core/res/res/drawable/chooser_group_background.xml b/core/res/res/drawable/chooser_group_background.xml
index 2bf9337557ed..036028de7bcb 100644
--- a/core/res/res/drawable/chooser_group_background.xml
+++ b/core/res/res/drawable/chooser_group_background.xml
@@ -21,5 +21,5 @@
android:width="12dp"
android:height="12dp"
android:start="4dp"
- android:end="4dp" />
+ android:end="0dp" />
</layer-list>
diff --git a/core/res/res/drawable/chooser_pinned_background.xml b/core/res/res/drawable/chooser_pinned_background.xml
new file mode 100644
index 000000000000..fbbe8c107fb9
--- /dev/null
+++ b/core/res/res/drawable/chooser_pinned_background.xml
@@ -0,0 +1,25 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/ic_chooser_pin"
+ android:gravity="start|center_vertical"
+ android:width="12dp"
+ android:height="12dp"
+ android:start="0dp"
+ android:end="4dp" />
+</layer-list> \ No newline at end of file
diff --git a/core/res/res/drawable/ic_chooser_pin.xml b/core/res/res/drawable/ic_chooser_pin.xml
new file mode 100644
index 000000000000..47851dcbf5bd
--- /dev/null
+++ b/core/res/res/drawable/ic_chooser_pin.xml
@@ -0,0 +1,26 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="12dp"
+ android:height="12dp"
+ android:viewportWidth="12"
+ android:viewportHeight="12"
+ android:tint="?attr/textColorSecondary">
+ <path
+ android:pathData="M8.5,2C8.5,1.45 8.055,1 7.5,1L4.5,1C3.95,1 3.5,1.45 3.5,2L3.5,5.5L2.5,7L2.5,8L5.5,8L5.5,10.5L6,11L6.5,10.5L6.5,8L9.5,8L9.5,7L8.5,5.5L8.5,2Z"
+ android:fillColor="#FF000000" />
+</vector>
diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml
index fdd965f3b157..50e6f33f628a 100644
--- a/core/res/res/layout/resolve_grid_item.xml
+++ b/core/res/res/layout/resolve_grid_item.xml
@@ -44,7 +44,7 @@
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceSmall"
android:textColor="?attr/textColorPrimary"
- android:textSize="14sp"
+ android:textSize="12sp"
android:gravity="top|center_horizontal"
android:lines="1"
android:ellipsize="end" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b62bb33256dc..ac808df7ef5d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -42,8 +42,10 @@
<item><xliff:g id="id">@string/status_bar_phone_evdo_signal</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_phone_signal</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_secure</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_media</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_managed_profile</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
+ <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_vpn</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_bluetooth</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_camera</xliff:g></item>
@@ -59,7 +61,6 @@
<item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_sensors_off</xliff:g></item>
- <item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
</string-array>
<string translatable="false" name="status_bar_rotate">rotate</string>
@@ -96,6 +97,7 @@
<string translatable="false" name="status_bar_airplane">airplane</string>
<string translatable="false" name="status_bar_sensors_off">sensors_off</string>
<string translatable="false" name="status_bar_screen_record">screen_record</string>
+ <string translatable="false" name="status_bar_media">media</string>
<!-- Flag indicating whether the surface flinger has limited
alpha compositing functionality in hardware. If set, the window
@@ -3329,6 +3331,17 @@
<!-- Controls the size of the back gesture inset. -->
<dimen name="config_backGestureInset">0dp</dimen>
+ <!-- Array of values used in Gesture Navigation settings page to reduce/increase the back
+ gesture's inset size. These values will be multiplied into the default width, read from the
+ gesture navigation overlay package, in order to create 4 different sizes which are selectable
+ via a slider component. -->
+ <array name="config_backGestureInsetScales">
+ <item>0.75</item>
+ <item>1.00</item>
+ <item>1.33</item>
+ <item>1.66</item>
+ </array>
+
<!-- Controls whether the navbar needs a scrim with
{@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
<bool name="config_navBarNeedsScrim">true</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b483ee04a7ec..758a4f7baffc 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2747,6 +2747,8 @@
<java-symbol type="dimen" name="chooser_preview_image_max_dimen"/>
<java-symbol type="drawable" name="ic_chooser_group_arrow"/>
<java-symbol type="drawable" name="chooser_group_background"/>
+ <java-symbol type="drawable" name="ic_chooser_pin"/>
+ <java-symbol type="drawable" name="chooser_pinned_background"/>
<java-symbol type="integer" name="config_maxShortcutTargetsPerApp" />
<java-symbol type="layout" name="resolve_grid_item" />
<java-symbol type="id" name="day_picker_view_pager" />
@@ -2816,6 +2818,7 @@
<java-symbol type="bool" name="config_navBarNeedsScrim" />
<java-symbol type="bool" name="config_allowSeamlessRotationDespiteNavBarMoving" />
<java-symbol type="dimen" name="config_backGestureInset" />
+ <java-symbol type="array" name="config_backGestureInsetScales" />
<java-symbol type="color" name="system_bar_background_semi_transparent" />
<java-symbol type="bool" name="config_showGesturalNavigationHints" />
@@ -2913,6 +2916,7 @@
<java-symbol type="string" name="status_bar_camera" />
<java-symbol type="string" name="status_bar_sensors_off" />
<java-symbol type="string" name="status_bar_screen_record" />
+ <java-symbol type="string" name="status_bar_media" />
<!-- Locale picker -->
<java-symbol type="id" name="locale_search_menu" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index a93dacf47050..000e870369db 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -20,6 +20,7 @@ import static android.content.Intent.ACTION_EDIT;
import static android.content.Intent.ACTION_VIEW;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.INVALID_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
@@ -29,6 +30,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.testng.Assert.assertFalse;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityThread;
import android.app.IApplicationThread;
@@ -38,6 +40,7 @@ import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.ConfigurationChangeItem;
import android.app.servertransaction.NewIntentItem;
import android.app.servertransaction.ResumeActivityItem;
import android.app.servertransaction.StopActivityItem;
@@ -225,7 +228,7 @@ public class ActivityThreadTest {
}
@Test
- public void testHandleActivityConfigurationChanged_PickNewerPendingConfiguration() {
+ public void testHandleActivityConfigurationChanged_SkipWhenNewerConfigurationPending() {
final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
@@ -237,23 +240,88 @@ public class ActivityThreadTest {
final ActivityThread activityThread = activity.getActivityThread();
- final Configuration pendingConfig = new Configuration();
- pendingConfig.orientation = orientation == ORIENTATION_LANDSCAPE
- ? ORIENTATION_PORTRAIT
- : ORIENTATION_LANDSCAPE;
- pendingConfig.seq = seq + 2;
+ final Configuration newerConfig = new Configuration();
+ newerConfig.orientation = orientation == ORIENTATION_LANDSCAPE
+ ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+ newerConfig.seq = seq + 2;
activityThread.updatePendingActivityConfiguration(activity.getActivityToken(),
- pendingConfig);
+ newerConfig);
- final Configuration newConfig = new Configuration();
- newConfig.orientation = orientation;
- newConfig.seq = seq + 1;
+ final Configuration olderConfig = new Configuration();
+ olderConfig.orientation = orientation;
+ olderConfig.seq = seq + 1;
activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
- newConfig, Display.INVALID_DISPLAY);
+ olderConfig, INVALID_DISPLAY);
+ assertEquals(numOfConfig, activity.mNumOfConfigChanges);
+ assertEquals(olderConfig.orientation, activity.mConfig.orientation);
+
+ activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+ newerConfig, INVALID_DISPLAY);
assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
- assertEquals(pendingConfig.orientation, activity.mConfig.orientation);
+ assertEquals(newerConfig.orientation, activity.mConfig.orientation);
+ });
+ }
+
+ @Test
+ public void testHandleActivityConfigurationChanged_EnsureUpdatesProcessedInOrder()
+ throws Exception {
+ final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+
+ final ActivityThread activityThread = activity.getActivityThread();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ final Configuration config = new Configuration();
+ config.seq = BASE_SEQ;
+ config.orientation = ORIENTATION_PORTRAIT;
+
+ activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
+ config, INVALID_DISPLAY);
});
+
+ final IApplicationThread appThread = activityThread.getApplicationThread();
+ final int numOfConfig = activity.mNumOfConfigChanges;
+
+ final Configuration processConfigLandscape = new Configuration();
+ processConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 60));
+ processConfigLandscape.seq = BASE_SEQ + 1;
+
+ final Configuration activityConfigLandscape = new Configuration();
+ activityConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 50));
+ activityConfigLandscape.seq = BASE_SEQ + 2;
+
+ final Configuration processConfigPortrait = new Configuration();
+ processConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 60, 100));
+ processConfigPortrait.seq = BASE_SEQ + 3;
+
+ final Configuration activityConfigPortrait = new Configuration();
+ activityConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 50, 100));
+ activityConfigPortrait.seq = BASE_SEQ + 4;
+
+ activity.mConfigLatch = new CountDownLatch(1);
+ activity.mTestLatch = new CountDownLatch(1);
+
+ ClientTransaction transaction = newTransaction(activityThread, null);
+ transaction.addCallback(ConfigurationChangeItem.obtain(processConfigLandscape));
+ appThread.scheduleTransaction(transaction);
+
+ transaction = newTransaction(activityThread, activity.getActivityToken());
+ transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigLandscape));
+ transaction.addCallback(ConfigurationChangeItem.obtain(processConfigPortrait));
+ transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigPortrait));
+ appThread.scheduleTransaction(transaction);
+
+ activity.mTestLatch.await();
+ activity.mConfigLatch.countDown();
+
+ activity.mConfigLatch = null;
+ activity.mTestLatch = null;
+
+ // Check display metrics, bounds should match the portrait activity bounds.
+ final Rect bounds = activity.getWindowManager().getCurrentWindowMetrics().getBounds();
+ assertEquals(activityConfigPortrait.windowConfiguration.getBounds(), bounds);
+
+ // Ensure that Activity#onConfigurationChanged() is only called once.
+ assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
}
@Test
@@ -268,7 +336,7 @@ public class ActivityThreadTest {
config.orientation = ORIENTATION_PORTRAIT;
activityThread.handleActivityConfigurationChanged(activity.getActivityToken(),
- config, Display.INVALID_DISPLAY);
+ config, INVALID_DISPLAY);
});
final int numOfConfig = activity.mNumOfConfigChanges;
@@ -504,7 +572,7 @@ public class ActivityThreadTest {
config.orientation = ORIENTATION_PORTRAIT;
config.seq = seq;
activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
- Display.INVALID_DISPLAY);
+ INVALID_DISPLAY);
if (activity.mNumOfConfigChanges > numOfConfig) {
return config.seq;
@@ -514,7 +582,7 @@ public class ActivityThreadTest {
config.orientation = ORIENTATION_LANDSCAPE;
config.seq = seq + 1;
activityThread.handleActivityConfigurationChanged(activity.getActivityToken(), config,
- Display.INVALID_DISPLAY);
+ INVALID_DISPLAY);
return config.seq;
}
@@ -572,8 +640,12 @@ public class ActivityThreadTest {
}
private static ClientTransaction newTransaction(Activity activity) {
- final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
- return ClientTransaction.obtain(appThread, activity.getActivityToken());
+ return newTransaction(activity.getActivityThread(), activity.getActivityToken());
+ }
+
+ private static ClientTransaction newTransaction(ActivityThread activityThread,
+ @Nullable IBinder activityToken) {
+ return ClientTransaction.obtain(activityThread.getApplicationThread(), activityToken);
}
// Test activity
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 6c23125aaf13..4654f63a2a91 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -63,7 +63,8 @@ public class ObjectPoolTests {
@Test
public void testRecycleActivityConfigurationChangeItem() {
- ActivityConfigurationChangeItem emptyItem = ActivityConfigurationChangeItem.obtain(null);
+ ActivityConfigurationChangeItem emptyItem =
+ ActivityConfigurationChangeItem.obtain(Configuration.EMPTY);
ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config());
assertNotSame(item, emptyItem);
assertFalse(item.equals(emptyItem));
@@ -186,7 +187,7 @@ public class ObjectPoolTests {
@Test
public void testRecycleMoveToDisplayItem() {
- MoveToDisplayItem emptyItem = MoveToDisplayItem.obtain(0, null);
+ MoveToDisplayItem emptyItem = MoveToDisplayItem.obtain(0, Configuration.EMPTY);
MoveToDisplayItem item = MoveToDisplayItem.obtain(4, config());
assertNotSame(item, emptyItem);
assertFalse(item.equals(emptyItem));
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 3f8d9ef964db..f11adef81793 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -563,6 +563,11 @@ public class TransactionParcelTests {
}
@Override
+ public void dumpCacheInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
+ throws RemoteException {
+ }
+
+ @Override
public void dumpProvider(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
String[] strings) throws RemoteException {
}
diff --git a/core/tests/utiltests/src/android/util/AtomicFileTest.java b/core/tests/utiltests/src/android/util/AtomicFileTest.java
new file mode 100644
index 000000000000..547d4d844181
--- /dev/null
+++ b/core/tests/utiltests/src/android/util/AtomicFileTest.java
@@ -0,0 +1,245 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import android.app.Instrumentation;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+@RunWith(Parameterized.class)
+public class AtomicFileTest {
+ private static final String BASE_NAME = "base";
+ private static final String NEW_NAME = BASE_NAME + ".new";
+ private static final String LEGACY_BACKUP_NAME = BASE_NAME + ".bak";
+
+ private enum WriteAction {
+ FINISH,
+ FAIL,
+ ABORT
+ }
+
+ private static final byte[] BASE_BYTES = "base".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] EXISTING_NEW_BYTES = "unnew".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] NEW_BYTES = "new".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] LEGACY_BACKUP_BYTES = "bak".getBytes(StandardCharsets.UTF_8);
+
+ // JUnit wants every parameter to be used so make it happy.
+ @Parameterized.Parameter()
+ public String mUnusedTestName;
+ @Nullable
+ @Parameterized.Parameter(1)
+ public String[] mExistingFileNames;
+ @Nullable
+ @Parameterized.Parameter(2)
+ public WriteAction mWriteAction;
+ @Nullable
+ @Parameterized.Parameter(3)
+ public byte[] mExpectedBytes;
+
+ private final Instrumentation mInstrumentation =
+ InstrumentationRegistry.getInstrumentation();
+ private final Context mContext = mInstrumentation.getContext();
+
+ private final File mDirectory = mContext.getFilesDir();
+ private final File mBaseFile = new File(mDirectory, BASE_NAME);
+ private final File mNewFile = new File(mDirectory, NEW_NAME);
+ private final File mLegacyBackupFile = new File(mDirectory, LEGACY_BACKUP_NAME);
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Object[][] data() {
+ return new Object[][] {
+ { "none + none = none", null, null, null },
+ { "none + finish = new", null, WriteAction.FINISH, NEW_BYTES },
+ { "none + fail = none", null, WriteAction.FAIL, null },
+ { "none + abort = none", null, WriteAction.ABORT, null },
+ { "base + none = base", new String[] { BASE_NAME }, null, BASE_BYTES },
+ { "base + finish = new", new String[] { BASE_NAME }, WriteAction.FINISH,
+ NEW_BYTES },
+ { "base + fail = base", new String[] { BASE_NAME }, WriteAction.FAIL, BASE_BYTES },
+ { "base + abort = base", new String[] { BASE_NAME }, WriteAction.ABORT,
+ BASE_BYTES },
+ { "new + none = none", new String[] { NEW_NAME }, null, null },
+ { "new + finish = new", new String[] { NEW_NAME }, WriteAction.FINISH, NEW_BYTES },
+ { "new + fail = none", new String[] { NEW_NAME }, WriteAction.FAIL, null },
+ { "new + abort = none", new String[] { NEW_NAME }, WriteAction.ABORT, null },
+ { "bak + none = bak", new String[] { LEGACY_BACKUP_NAME }, null,
+ LEGACY_BACKUP_BYTES },
+ { "bak + finish = new", new String[] { LEGACY_BACKUP_NAME }, WriteAction.FINISH,
+ NEW_BYTES },
+ { "bak + fail = bak", new String[] { LEGACY_BACKUP_NAME }, WriteAction.FAIL,
+ LEGACY_BACKUP_BYTES },
+ { "bak + abort = bak", new String[] { LEGACY_BACKUP_NAME }, WriteAction.ABORT,
+ LEGACY_BACKUP_BYTES },
+ { "base & new + none = base", new String[] { BASE_NAME, NEW_NAME }, null,
+ BASE_BYTES },
+ { "base & new + finish = new", new String[] { BASE_NAME, NEW_NAME },
+ WriteAction.FINISH, NEW_BYTES },
+ { "base & new + fail = base", new String[] { BASE_NAME, NEW_NAME },
+ WriteAction.FAIL, BASE_BYTES },
+ { "base & new + abort = base", new String[] { BASE_NAME, NEW_NAME },
+ WriteAction.ABORT, BASE_BYTES },
+ { "base & bak + none = bak", new String[] { BASE_NAME, LEGACY_BACKUP_NAME }, null,
+ LEGACY_BACKUP_BYTES },
+ { "base & bak + finish = new", new String[] { BASE_NAME, LEGACY_BACKUP_NAME },
+ WriteAction.FINISH, NEW_BYTES },
+ { "base & bak + fail = bak", new String[] { BASE_NAME, LEGACY_BACKUP_NAME },
+ WriteAction.FAIL, LEGACY_BACKUP_BYTES },
+ { "base & bak + abort = bak", new String[] { BASE_NAME, LEGACY_BACKUP_NAME },
+ WriteAction.ABORT, LEGACY_BACKUP_BYTES },
+ { "new & bak + none = bak", new String[] { NEW_NAME, LEGACY_BACKUP_NAME }, null,
+ LEGACY_BACKUP_BYTES },
+ { "new & bak + finish = new", new String[] { NEW_NAME, LEGACY_BACKUP_NAME },
+ WriteAction.FINISH, NEW_BYTES },
+ { "new & bak + fail = bak", new String[] { NEW_NAME, LEGACY_BACKUP_NAME },
+ WriteAction.FAIL, LEGACY_BACKUP_BYTES },
+ { "new & bak + abort = bak", new String[] { NEW_NAME, LEGACY_BACKUP_NAME },
+ WriteAction.ABORT, LEGACY_BACKUP_BYTES },
+ { "base & new & bak + none = bak",
+ new String[] { BASE_NAME, NEW_NAME, LEGACY_BACKUP_NAME }, null,
+ LEGACY_BACKUP_BYTES },
+ { "base & new & bak + finish = new",
+ new String[] { BASE_NAME, NEW_NAME, LEGACY_BACKUP_NAME },
+ WriteAction.FINISH, NEW_BYTES },
+ { "base & new & bak + fail = bak",
+ new String[] { BASE_NAME, NEW_NAME, LEGACY_BACKUP_NAME }, WriteAction.FAIL,
+ LEGACY_BACKUP_BYTES },
+ { "base & new & bak + abort = bak",
+ new String[] { BASE_NAME, NEW_NAME, LEGACY_BACKUP_NAME }, WriteAction.ABORT,
+ LEGACY_BACKUP_BYTES },
+ };
+ }
+
+ @Before
+ @After
+ public void deleteFiles() {
+ mBaseFile.delete();
+ mNewFile.delete();
+ mLegacyBackupFile.delete();
+ }
+
+ @Test
+ public void testAtomicFile() throws Exception {
+ if (mExistingFileNames != null) {
+ for (String fileName : mExistingFileNames) {
+ switch (fileName) {
+ case BASE_NAME:
+ writeBytes(mBaseFile, BASE_BYTES);
+ break;
+ case NEW_NAME:
+ writeBytes(mNewFile, EXISTING_NEW_BYTES);
+ break;
+ case LEGACY_BACKUP_NAME:
+ writeBytes(mLegacyBackupFile, LEGACY_BACKUP_BYTES);
+ break;
+ default:
+ throw new AssertionError(fileName);
+ }
+ }
+ }
+
+ AtomicFile atomicFile = new AtomicFile(mBaseFile);
+ if (mWriteAction != null) {
+ try (FileOutputStream outputStream = atomicFile.startWrite()) {
+ outputStream.write(NEW_BYTES);
+ switch (mWriteAction) {
+ case FINISH:
+ atomicFile.finishWrite(outputStream);
+ break;
+ case FAIL:
+ atomicFile.failWrite(outputStream);
+ break;
+ case ABORT:
+ // Neither finishing nor failing is called upon abort.
+ break;
+ default:
+ throw new AssertionError(mWriteAction);
+ }
+ }
+ }
+
+ if (mExpectedBytes != null) {
+ try (FileInputStream inputStream = atomicFile.openRead()) {
+ assertArrayEquals(mExpectedBytes, readAllBytes(inputStream));
+ }
+ } else {
+ assertThrows(FileNotFoundException.class, () -> atomicFile.openRead());
+ }
+ }
+
+ private static void writeBytes(@NonNull File file, @NonNull byte[] bytes) throws IOException {
+ try (FileOutputStream outputStream = new FileOutputStream(file)) {
+ outputStream.write(bytes);
+ }
+ }
+
+ // InputStream.readAllBytes() is introduced in Java 9. Our files are small enough so that a
+ // naive implementation is okay.
+ private static byte[] readAllBytes(@NonNull InputStream inputStream) throws IOException {
+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+ int b;
+ while ((b = inputStream.read()) != -1) {
+ outputStream.write(b);
+ }
+ return outputStream.toByteArray();
+ }
+ }
+
+ @NonNull
+ public static <T extends Throwable> T assertThrows(@NonNull Class<T> expectedType,
+ @NonNull ThrowingRunnable runnable) {
+ try {
+ runnable.run();
+ } catch (Throwable t) {
+ if (!expectedType.isInstance(t)) {
+ sneakyThrow(t);
+ }
+ //noinspection unchecked
+ return (T) t;
+ }
+ throw new AssertionError(String.format("Expected %s wasn't thrown",
+ expectedType.getSimpleName()));
+ }
+
+ private static <T extends Throwable> void sneakyThrow(@NonNull Throwable throwable) throws T {
+ //noinspection unchecked
+ throw (T) throwable;
+ }
+
+ private interface ThrowingRunnable {
+ void run() throws Throwable;
+ }
+}
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 72827a956f78..a5a2221e5532 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -16,6 +16,7 @@
-->
<permissions>
<privapp-permissions package="com.android.systemui">
+ <permission name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
<permission name="android.permission.BATTERY_STATS"/>
<permission name="android.permission.BIND_APPWIDGET"/>
<permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java
index 1275cb9ca4f9..eb940e2f9017 100644
--- a/graphics/java/android/graphics/PorterDuff.java
+++ b/graphics/java/android/graphics/PorterDuff.java
@@ -30,8 +30,6 @@ public class PorterDuff {
/**
* {@usesMathJax}
*
- * <h3>Porter-Duff</h3>
- *
* <p>The name of the parent class is an homage to the work of Thomas Porter and
* Tom Duff, presented in their seminal 1984 paper titled "Compositing Digital Images".
* In this paper, the authors describe 12 compositing operators that govern how to
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index 357c3332fe10..b44d7bba834f 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -29,13 +29,13 @@ import com.android.server.LocalServices;
public abstract class AudioManagerInternal {
public abstract void adjustSuggestedStreamVolumeForUid(int streamType, int direction,
- int flags, String callingPackage, int uid);
+ int flags, String callingPackage, int uid, int pid);
public abstract void adjustStreamVolumeForUid(int streamType, int direction, int flags,
- String callingPackage, int uid);
+ String callingPackage, int uid, int pid);
public abstract void setStreamVolumeForUid(int streamType, int direction, int flags,
- String callingPackage, int uid);
+ String callingPackage, int uid, int pid);
public abstract void setRingerModeDelegate(RingerModeDelegate delegate);
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index bbd739941422..54675d018038 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -321,6 +321,21 @@ final public class MediaMuxer {
@UnsupportedAppUsage
private long mNativeObject;
+ private String convertMuxerStateCodeToString(int aState) {
+ switch (aState) {
+ case MUXER_STATE_UNINITIALIZED:
+ return "UNINITIALIZED";
+ case MUXER_STATE_INITIALIZED:
+ return "INITIALIZED";
+ case MUXER_STATE_STARTED:
+ return "STARTED";
+ case MUXER_STATE_STOPPED:
+ return "STOPPED";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
/**
* Constructor.
* Creates a media muxer that writes to the specified path.
@@ -397,7 +412,7 @@ final public class MediaMuxer {
nativeSetOrientationHint(mNativeObject, degrees);
} else {
throw new IllegalStateException("Can't set rotation degrees due" +
- " to wrong state.");
+ " to wrong state(" + convertMuxerStateCodeToString(mState) + ")");
}
}
@@ -432,7 +447,8 @@ final public class MediaMuxer {
if (mState == MUXER_STATE_INITIALIZED && mNativeObject != 0) {
nativeSetLocation(mNativeObject, latitudex10000, longitudex10000);
} else {
- throw new IllegalStateException("Can't set location due to wrong state.");
+ throw new IllegalStateException("Can't set location due to wrong state("
+ + convertMuxerStateCodeToString(mState) + ")");
}
}
@@ -451,7 +467,8 @@ final public class MediaMuxer {
nativeStart(mNativeObject);
mState = MUXER_STATE_STARTED;
} else {
- throw new IllegalStateException("Can't start due to wrong state.");
+ throw new IllegalStateException("Can't start due to wrong state("
+ + convertMuxerStateCodeToString(mState) + ")");
}
}
@@ -462,10 +479,16 @@ final public class MediaMuxer {
*/
public void stop() {
if (mState == MUXER_STATE_STARTED) {
- nativeStop(mNativeObject);
- mState = MUXER_STATE_STOPPED;
+ try {
+ nativeStop(mNativeObject);
+ } catch (Exception e) {
+ throw e;
+ } finally {
+ mState = MUXER_STATE_STOPPED;
+ }
} else {
- throw new IllegalStateException("Can't stop due to wrong state.");
+ throw new IllegalStateException("Can't stop due to wrong state("
+ + convertMuxerStateCodeToString(mState) + ")");
}
}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 50af60a0ad92..a458b16c551a 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -23,6 +23,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.app.ActivityManager;
import android.content.Context;
import android.hardware.tv.tuner.V1_0.Constants;
import android.media.tv.TvInputService;
@@ -55,6 +56,8 @@ import android.os.Looper;
import android.os.Message;
import android.util.Log;
+import com.android.internal.util.FrameworkStatsLog;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -208,7 +211,7 @@ public class Tuner implements AutoCloseable {
private FrontendInfo mFrontendInfo;
private Integer mFrontendHandle;
private int mFrontendType = FrontendSettings.TYPE_UNDEFINED;
-
+ private int mUserId;
private Lnb mLnb;
private Integer mLnbHandle;
@Nullable
@@ -232,6 +235,11 @@ public class Tuner implements AutoCloseable {
new TunerResourceManager.ResourcesReclaimListener() {
@Override
public void onReclaimResources() {
+ if (mFrontend != null) {
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
+ }
mHandler.sendMessage(mHandler.obtainMessage(MSG_RESOURCE_LOST));
}
};
@@ -261,6 +269,8 @@ public class Tuner implements AutoCloseable {
profile, new HandlerExecutor(mHandler), mResourceListener, clientId);
mClientId = clientId[0];
+ mUserId = ActivityManager.getCurrentUser();
+
setFrontendInfoList();
setLnbIds();
}
@@ -358,6 +368,9 @@ public class Tuner implements AutoCloseable {
TunerUtils.throwExceptionForResult(res, "failed to close frontend");
}
mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
mFrontendHandle = null;
mFrontend = null;
}
@@ -557,9 +570,14 @@ public class Tuner implements AutoCloseable {
*/
@Result
public int tune(@NonNull FrontendSettings settings) {
+ Log.d(TAG, "Tune to " + settings.getFrequency());
mFrontendType = settings.getType();
if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
mFrontendInfo = null;
+ Log.d(TAG, "Write Stats Log for tuning.");
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING);
return nativeTune(settings.getType(), settings);
}
return RESULT_UNAVAILABLE;
@@ -602,6 +620,9 @@ public class Tuner implements AutoCloseable {
mScanCallback = scanCallback;
mScanCallbackExecutor = executor;
mFrontendInfo = null;
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING);
return nativeScan(settings.getType(), settings, scanType);
}
return RESULT_UNAVAILABLE;
@@ -620,6 +641,10 @@ public class Tuner implements AutoCloseable {
*/
@Result
public int cancelScanning() {
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED);
+
int retVal = nativeStopScan();
mScanCallback = null;
mScanCallbackExecutor = null;
@@ -779,12 +804,33 @@ public class Tuner implements AutoCloseable {
}
private void onFrontendEvent(int eventType) {
+ Log.d(TAG, "Got event from tuning. Event type: " + eventType);
if (mOnTunerEventExecutor != null && mOnTuneEventListener != null) {
mOnTunerEventExecutor.execute(() -> mOnTuneEventListener.onTuneEvent(eventType));
}
+
+ Log.d(TAG, "Wrote Stats Log for the events from tuning.");
+ if (eventType == OnTuneEventListener.SIGNAL_LOCKED) {
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED);
+ } else if (eventType == OnTuneEventListener.SIGNAL_NO_SIGNAL) {
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__NOT_LOCKED);
+ } else if (eventType == OnTuneEventListener.SIGNAL_LOST_LOCK) {
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SIGNAL_LOST);
+ }
}
private void onLocked() {
+ Log.d(TAG, "Wrote Stats Log for locked event from scanning.");
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__LOCKED);
+
if (mScanCallbackExecutor != null && mScanCallback != null) {
mScanCallbackExecutor.execute(() -> mScanCallback.onLocked());
}
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 331477f72aa4..43cb25f03fb0 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -43,7 +43,7 @@
#include <android_runtime/android_hardware_HardwareBuffer.h>
-#include <binder/MemoryHeapBase.h>
+#include <binder/MemoryDealer.h>
#include <cutils/compiler.h>
@@ -306,6 +306,7 @@ status_t JMediaCodec::configure(
CHECK(format->findString("mime", &mime));
mGraphicOutput = (mime.startsWithIgnoreCase("video/") || mime.startsWithIgnoreCase("image/"))
&& !(flags & CONFIGURE_FLAG_ENCODE);
+ mHasCryptoOrDescrambler = (crypto != nullptr) || (descrambler != nullptr);
return mCodec->configure(
format, mSurfaceTextureClient, crypto, descrambler, flags);
@@ -1603,14 +1604,13 @@ struct NativeCryptoInfo {
ScopedLocalRef<jobject> patternObj{
env, env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoPatternID)};
- CryptoPlugin::Pattern pattern;
if (patternObj.get() == nullptr) {
- pattern.mEncryptBlocks = 0;
- pattern.mSkipBlocks = 0;
+ mPattern.mEncryptBlocks = 0;
+ mPattern.mSkipBlocks = 0;
} else {
- pattern.mEncryptBlocks = env->GetIntField(
+ mPattern.mEncryptBlocks = env->GetIntField(
patternObj.get(), gFields.patternEncryptBlocksID);
- pattern.mSkipBlocks = env->GetIntField(
+ mPattern.mSkipBlocks = env->GetIntField(
patternObj.get(), gFields.patternSkipBlocksID);
}
@@ -1679,6 +1679,18 @@ struct NativeCryptoInfo {
mIv = env->GetByteArrayElements(mIvObj.get(), nullptr);
}
}
+
+ }
+
+ explicit NativeCryptoInfo(jint size)
+ : mIvObj{nullptr, nullptr},
+ mKeyObj{nullptr, nullptr},
+ mMode{CryptoPlugin::kMode_Unencrypted},
+ mPattern{0, 0} {
+ mSubSamples = new CryptoPlugin::SubSample[1];
+ mNumSubSamples = 1;
+ mSubSamples[0].mNumBytesOfClearData = size;
+ mSubSamples[0].mNumBytesOfEncryptedData = 0;
}
~NativeCryptoInfo() {
@@ -2128,10 +2140,13 @@ static void android_media_MediaCodec_native_queueLinearBlock(
if (env->GetBooleanField(bufferObj, gLinearBlockInfo.validId)) {
JMediaCodecLinearBlock *context =
(JMediaCodecLinearBlock *)env->GetLongField(bufferObj, gLinearBlockInfo.contextId);
- if (cryptoInfoObj != nullptr) {
+ if (codec->hasCryptoOrDescrambler()) {
memory = context->toHidlMemory();
+ // TODO: copy if memory is null
+ offset += context->mHidlMemoryOffset;
} else {
buffer = context->toC2Buffer(offset, size);
+ // TODO: copy if buffer is null
}
}
env->MonitorExit(lock.get());
@@ -2141,13 +2156,19 @@ static void android_media_MediaCodec_native_queueLinearBlock(
}
AString errorDetailMsg;
- if (cryptoInfoObj != nullptr) {
+ if (codec->hasCryptoOrDescrambler()) {
if (!memory) {
+ ALOGI("queueLinearBlock: no ashmem memory for encrypted content");
throwExceptionAsNecessary(env, BAD_VALUE);
return;
}
-
- NativeCryptoInfo cryptoInfo{env, cryptoInfoObj};
+ NativeCryptoInfo cryptoInfo = [env, cryptoInfoObj, size]{
+ if (cryptoInfoObj == nullptr) {
+ return NativeCryptoInfo{size};
+ } else {
+ return NativeCryptoInfo{env, cryptoInfoObj};
+ }
+ }();
err = codec->queueEncryptedLinearBlock(
index,
memory,
@@ -2162,6 +2183,7 @@ static void android_media_MediaCodec_native_queueLinearBlock(
&errorDetailMsg);
} else {
if (!buffer) {
+ ALOGI("queueLinearBlock: no C2Buffer found");
throwExceptionAsNecessary(env, BAD_VALUE);
return;
}
@@ -2955,13 +2977,13 @@ static jobject android_media_MediaCodec_LinearBlock_native_map(
context->mLegacyBuffer->size(),
true, // readOnly
true /* clearBuffer */);
- } else if (context->mHeap) {
+ } else if (context->mMemory) {
return CreateByteBuffer(
env,
- static_cast<uint8_t *>(context->mHeap->getBase()) + context->mHeap->getOffset(),
- context->mHeap->getSize(),
+ context->mMemory->unsecurePointer(),
+ context->mMemory->size(),
0,
- context->mHeap->getSize(),
+ context->mMemory->size(),
false, // readOnly
true /* clearBuffer */);
}
@@ -3011,8 +3033,26 @@ static void android_media_MediaCodec_LinearBlock_native_obtain(
}
}
if (hasSecure && !hasNonSecure) {
- context->mHeap = new MemoryHeapBase(capacity);
- context->mMemory = hardware::fromHeap(context->mHeap);
+ constexpr size_t kInitialDealerCapacity = 1048576; // 1MB
+ thread_local sp<MemoryDealer> sDealer = new MemoryDealer(
+ kInitialDealerCapacity, "JNI(1MB)");
+ context->mMemory = sDealer->allocate(capacity);
+ if (context->mMemory == nullptr) {
+ size_t newDealerCapacity = sDealer->getMemoryHeap()->getSize() * 2;
+ while (capacity * 2 > newDealerCapacity) {
+ newDealerCapacity *= 2;
+ }
+ ALOGI("LinearBlock.native_obtain: "
+ "Dealer capacity increasing from %zuMB to %zuMB",
+ sDealer->getMemoryHeap()->getSize() / 1048576,
+ newDealerCapacity / 1048576);
+ sDealer = new MemoryDealer(
+ newDealerCapacity,
+ AStringPrintf("JNI(%zuMB)", newDealerCapacity).c_str());
+ context->mMemory = sDealer->allocate(capacity);
+ }
+ context->mHidlMemory = hardware::fromHeap(context->mMemory->getMemory(
+ &context->mHidlMemoryOffset, &context->mHidlMemorySize));
} else {
context->mBlock = MediaCodec::FetchLinearBlock(capacity, names);
if (!context->mBlock) {
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index 400ce1bc98e7..5c34341a86a1 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -162,6 +162,8 @@ struct JMediaCodec : public AHandler {
void selectAudioPresentation(const int32_t presentationId, const int32_t programId);
+ bool hasCryptoOrDescrambler() { return mHasCryptoOrDescrambler; }
+
protected:
virtual ~JMediaCodec();
@@ -181,6 +183,7 @@ private:
sp<MediaCodec> mCodec;
AString mNameAtCreation;
bool mGraphicOutput{false};
+ bool mHasCryptoOrDescrambler{false};
std::once_flag mReleaseFlag;
sp<AMessage> mCallbackNotification;
diff --git a/media/jni/android_media_MediaCodecLinearBlock.h b/media/jni/android_media_MediaCodecLinearBlock.h
index 08438340d803..8f1d2fa35d70 100644
--- a/media/jni/android_media_MediaCodecLinearBlock.h
+++ b/media/jni/android_media_MediaCodecLinearBlock.h
@@ -31,8 +31,10 @@ struct JMediaCodecLinearBlock {
std::shared_ptr<C2LinearBlock> mBlock;
std::shared_ptr<C2WriteView> mReadWriteMapping;
- sp<IMemoryHeap> mHeap;
- sp<hardware::HidlMemory> mMemory;
+ sp<IMemory> mMemory;
+ sp<hardware::HidlMemory> mHidlMemory;
+ ssize_t mHidlMemoryOffset;
+ size_t mHidlMemorySize;
sp<MediaCodecBuffer> mLegacyBuffer;
@@ -56,8 +58,8 @@ struct JMediaCodecLinearBlock {
}
sp<hardware::HidlMemory> toHidlMemory() {
- if (mMemory) {
- return mMemory;
+ if (mHidlMemory) {
+ return mHidlMemory;
}
return nullptr;
}
@@ -65,4 +67,4 @@ struct JMediaCodecLinearBlock {
} // namespace android
-#endif // _ANDROID_MEDIA_MEDIACODECLINEARBLOCK_H_ \ No newline at end of file
+#endif // _ANDROID_MEDIA_MEDIACODECLINEARBLOCK_H_
diff --git a/media/jni/android_media_MediaMuxer.cpp b/media/jni/android_media_MediaMuxer.cpp
index 0c1e9a2ad7bd..262ec76da26e 100644
--- a/media/jni/android_media_MediaMuxer.cpp
+++ b/media/jni/android_media_MediaMuxer.cpp
@@ -26,15 +26,11 @@
#include <unistd.h>
#include <fcntl.h>
-#include <android/api-level.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
-#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MediaMuxer.h>
-extern "C" int android_get_application_target_sdk_version();
-
namespace android {
struct fields_t {
@@ -233,31 +229,11 @@ static void android_media_MediaMuxer_stop(JNIEnv *env, jclass /* clazz */,
status_t err = muxer->stop();
- if (android_get_application_target_sdk_version() >= __ANDROID_API_R__) {
- switch (err) {
- case OK:
- break;
- case ERROR_IO: {
- jniThrowException(env, "java/lang/UncheckedIOException",
- "Muxer stopped unexpectedly");
- return;
- }
- case ERROR_MALFORMED: {
- jniThrowException(env, "java/io/IOError",
- "Failure of reading or writing operation");
- return;
- }
- default: {
- jniThrowException(env, "java/lang/IllegalStateException",
- "Failed to stop the muxer");
- return;
- }
- }
- } else {
- if (err != OK) {
- jniThrowException(env, "java/lang/IllegalStateException", "Failed to stop the muxer");
- return;
- }
+ if (err != OK) {
+ ALOGE("Error during stop:%d", err);
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Error during stop(), muxer would have stopped already");
+ return;
}
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index e42e43801936..3dbf5a4d2b6b 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1376,4 +1376,7 @@
<!-- A content description for work profile app [CHAR LIMIT=35] -->
<string name="accessibility_work_profile_app_description">Work <xliff:g id="app_name" example="Camera">%s</xliff:g></string>
+
+ <!-- Name of the 3.5mm and usb audio device. [CHAR LIMIT=40] -->
+ <string name="media_transfer_wired_usb_device_name">Wired headphone</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 9e8c70fcc000..197e34df22a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -145,15 +145,16 @@ public class LocalMediaManager implements BluetoothCallback {
/**
* Connect the MediaDevice to transfer media
* @param connectDevice the MediaDevice
+ * @return {@code true} if successfully call, otherwise return {@code false}
*/
- public void connectDevice(MediaDevice connectDevice) {
+ public boolean connectDevice(MediaDevice connectDevice) {
MediaDevice device = null;
synchronized (mMediaDevicesLock) {
device = getMediaDeviceById(mMediaDevices, connectDevice.getId());
}
if (device == null) {
Log.w(TAG, "connectDevice() connectDevice not in the list!");
- return;
+ return false;
}
if (device instanceof BluetoothMediaDevice) {
final CachedBluetoothDevice cachedDevice =
@@ -162,13 +163,13 @@ public class LocalMediaManager implements BluetoothCallback {
mOnTransferBluetoothDevice = connectDevice;
device.setState(MediaDeviceState.STATE_CONNECTING);
cachedDevice.connect();
- return;
+ return true;
}
}
if (device == mCurrentConnectedDevice) {
Log.d(TAG, "connectDevice() this device all ready connected! : " + device.getName());
- return;
+ return false;
}
if (mCurrentConnectedDevice != null) {
@@ -181,6 +182,7 @@ public class LocalMediaManager implements BluetoothCallback {
} else {
device.connect();
}
+ return true;
}
void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 8ea5ff1bf662..9a3ae1be14c0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -61,11 +61,11 @@ public class PhoneMediaDevice extends MediaDevice {
switch (mRouteInfo.getType()) {
case TYPE_WIRED_HEADSET:
case TYPE_WIRED_HEADPHONES:
- name = mContext.getString(R.string.media_transfer_wired_device_name);
- break;
case TYPE_USB_DEVICE:
case TYPE_USB_HEADSET:
case TYPE_USB_ACCESSORY:
+ name = mContext.getString(R.string.media_transfer_wired_usb_device_name);
+ break;
case TYPE_DOCK:
case TYPE_HDMI:
name = mRouteInfo.getName();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 365a16c50b99..517071b5511f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -141,7 +141,7 @@ public class LocalMediaManagerTest {
when(currentDevice.getId()).thenReturn(TEST_CURRENT_DEVICE_ID);
mLocalMediaManager.registerCallback(mCallback);
- mLocalMediaManager.connectDevice(device);
+ assertThat(mLocalMediaManager.connectDevice(device)).isTrue();
verify(currentDevice).disconnect();
verify(device).connect();
@@ -154,7 +154,7 @@ public class LocalMediaManagerTest {
mLocalMediaManager.mCurrentConnectedDevice = mInfoMediaDevice1;
mLocalMediaManager.registerCallback(mCallback);
- mLocalMediaManager.connectDevice(mInfoMediaDevice2);
+ assertThat(mLocalMediaManager.connectDevice(mInfoMediaDevice2)).isTrue();
assertThat(mInfoMediaDevice2.getState()).isEqualTo(LocalMediaManager.MediaDeviceState
.STATE_CONNECTING);
@@ -167,7 +167,7 @@ public class LocalMediaManagerTest {
mLocalMediaManager.mCurrentConnectedDevice = mInfoMediaDevice1;
mLocalMediaManager.registerCallback(mCallback);
- mLocalMediaManager.connectDevice(mInfoMediaDevice1);
+ assertThat(mLocalMediaManager.connectDevice(mInfoMediaDevice1)).isFalse();
assertThat(mInfoMediaDevice1.getState()).isNotEqualTo(LocalMediaManager.MediaDeviceState
.STATE_CONNECTING);
@@ -185,7 +185,7 @@ public class LocalMediaManagerTest {
when(cachedDevice.isBusy()).thenReturn(false);
mLocalMediaManager.registerCallback(mCallback);
- mLocalMediaManager.connectDevice(device);
+ assertThat(mLocalMediaManager.connectDevice(device)).isTrue();
verify(cachedDevice).connect();
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index 47f6fe3bce02..421e5c0db1a1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -100,12 +100,12 @@ public class PhoneMediaDeviceTest {
when(mInfo.getName()).thenReturn(deviceName);
assertThat(mPhoneMediaDevice.getName())
- .isEqualTo(mContext.getString(R.string.media_transfer_wired_device_name));
+ .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name));
when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE);
assertThat(mPhoneMediaDevice.getName())
- .isEqualTo(deviceName);
+ .isEqualTo(mContext.getString(R.string.media_transfer_wired_usb_device_name));
when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 83d72189de74..b85c7714bf96 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -346,22 +346,6 @@
android:excludeFromRecents="true"
android:exported="false" />
- <!--
- The following is used as a no-op/null home activity when
- no other MAIN/HOME activity is present (e.g., in CSI).
- -->
- <activity android:name=".NullHome"
- android:excludeFromRecents="true"
- android:label=""
- android:screenOrientation="nosensor">
- <!-- The priority here is set to be lower than that for Settings -->
- <intent-filter android:priority="-1100">
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.HOME" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
-
<receiver
android:name=".BugreportRequestedReceiver"
android:permission="android.permission.TRIGGER_SHELL_BUGREPORT">
diff --git a/packages/Shell/res/layout/null_home_finishing_boot.xml b/packages/Shell/res/layout/null_home_finishing_boot.xml
deleted file mode 100644
index 5f9563a5d25c..000000000000
--- a/packages/Shell/res/layout/null_home_finishing_boot.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#80000000"
- android:forceHasOverlappingRendering="false">
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_gravity="center"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="40sp"
- android:textColor="?android:attr/textColorPrimary"
- android:text="@*android:string/android_start_title"/>
- <ProgressBar
- style="@android:style/Widget.Material.ProgressBar.Horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="12.75dp"
- android:colorControlActivated="?android:attr/textColorPrimary"
- android:indeterminate="true"/>
- </LinearLayout>
-</FrameLayout>
diff --git a/packages/Shell/src/com/android/shell/NullHome.java b/packages/Shell/src/com/android/shell/NullHome.java
deleted file mode 100644
index bd975614a50a..000000000000
--- a/packages/Shell/src/com/android/shell/NullHome.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.shell;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-
-/**
- * This covers the fallback case where no launcher is available.
- * Usually Settings.apk has one fallback home activity.
- * Settings.apk, however, is not part of CSI, which needs to be
- * standalone (bootable and testable).
- */
-public class NullHome extends Activity {
- private static final String TAG = "NullHome";
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Log.i(TAG, "onCreate");
- setContentView(R.layout.null_home_finishing_boot);
- }
-
- protected void onDestroy() {
- super.onDestroy();
- Log.i(TAG, "onDestroy");
- }
-}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4510b87556c7..055b2beaaa65 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -155,6 +155,7 @@
<!-- Screen Recording -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT"/>
<!-- Assist -->
<uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
diff --git a/packages/SystemUI/res/anim/media_button_state_list_animator.xml b/packages/SystemUI/res/anim/media_button_state_list_animator.xml
new file mode 100644
index 000000000000..62ebbaa112ea
--- /dev/null
+++ b/packages/SystemUI/res/anim/media_button_state_list_animator.xml
@@ -0,0 +1,50 @@
+<?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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true">
+ <set>
+ <objectAnimator
+ android:interpolator="@interpolator/control_state"
+ android:duration="50"
+ android:propertyName="scaleX"
+ android:valueTo="0.9"
+ android:valueType="floatType" />
+ <objectAnimator
+ android:interpolator="@interpolator/control_state"
+ android:duration="50"
+ android:propertyName="scaleY"
+ android:valueTo="0.9"
+ android:valueType="floatType" />
+ </set>
+ </item>
+ <item>
+ <set>
+ <objectAnimator
+ android:interpolator="@interpolator/control_state"
+ android:duration="250"
+ android:propertyName="scaleX"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ <objectAnimator
+ android:interpolator="@interpolator/control_state"
+ android:duration="250"
+ android:propertyName="scaleY"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </item>
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/stat_sys_media.xml b/packages/SystemUI/res/drawable/stat_sys_media.xml
new file mode 100644
index 000000000000..d48db7bd0d28
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_media.xml
@@ -0,0 +1,31 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M5,7.81l0,8.38l6,-4.19z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M13,8h2v8h-2z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M17,8h2v8h-2z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
index 1b42ceb50bf7..99b9ced53090 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
@@ -14,9 +14,7 @@
limitations under the License.
-->
-<!-- RelativeLayouts have an issue enforcing minimum heights, so just
- work around this for now with LinearLayouts. -->
-<LinearLayout
+<com.android.systemui.globalactions.GlobalActionsItem
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_weight="1"
@@ -42,7 +40,7 @@
android:id="@*android:id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:ellipsize="marquee"
+ android:ellipsize="end"
android:marqueeRepeatLimit="marquee_forever"
android:maxLines="2"
android:textSize="12sp"
@@ -51,4 +49,4 @@
android:breakStrategy="high_quality"
android:hyphenationFrequency="full"
android:textAppearance="?android:attr/textAppearanceSmall" />
-</LinearLayout>
+</com.android.systemui.globalactions.GlobalActionsItem>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
index e4e9d2975220..ab4732de7124 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_v2.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
@@ -46,8 +46,6 @@
<com.android.systemui.globalactions.MinHeightScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset"
- android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset"
android:orientation="vertical"
android:scrollbars="none"
>
diff --git a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
index e4ae7c1f5827..46396e3e62b4 100644
--- a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
@@ -20,23 +20,29 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/screenshot_action_chip_margin_right"
+ android:paddingVertical="@dimen/screenshot_action_chip_margin_vertical"
android:layout_gravity="center"
- android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical"
- android:background="@drawable/action_chip_background"
- android:alpha="0.0"
- android:gravity="center">
- <ImageView
- android:id="@+id/screenshot_action_chip_icon"
- android:layout_width="@dimen/screenshot_action_chip_icon_size"
- android:layout_height="@dimen/screenshot_action_chip_icon_size"
- android:layout_marginStart="@dimen/screenshot_action_chip_padding_start"
- android:layout_marginEnd="@dimen/screenshot_action_chip_padding_middle"/>
- <TextView
- android:id="@+id/screenshot_action_chip_text"
+ android:gravity="center"
+ android:alpha="0.0">
+ <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/screenshot_action_chip_padding_end"
- android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
- android:textSize="@dimen/screenshot_action_chip_text_size"
- android:textColor="@color/global_screenshot_button_text"/>
+ android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical"
+ android:background="@drawable/action_chip_background"
+ android:gravity="center">
+ <ImageView
+ android:id="@+id/screenshot_action_chip_icon"
+ android:layout_width="@dimen/screenshot_action_chip_icon_size"
+ android:layout_height="@dimen/screenshot_action_chip_icon_size"
+ android:layout_marginStart="@dimen/screenshot_action_chip_padding_start"
+ android:layout_marginEnd="@dimen/screenshot_action_chip_padding_middle"/>
+ <TextView
+ android:id="@+id/screenshot_action_chip_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/screenshot_action_chip_padding_end"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:textSize="@dimen/screenshot_action_chip_text_size"
+ android:textColor="@color/global_screenshot_button_text"/>
+ </LinearLayout>
</com.android.systemui.screenshot.ScreenshotActionChip>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a3d32c12d1c0..31edcf643285 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -314,13 +314,14 @@
<dimen name="screenshot_dismiss_button_margin">8dp</dimen>
<dimen name="screenshot_action_container_offset_y">32dp</dimen>
<dimen name="screenshot_action_container_corner_radius">10dp</dimen>
- <dimen name="screenshot_action_container_padding_vertical">16dp</dimen>
+ <dimen name="screenshot_action_container_padding_vertical">6dp</dimen>
<dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
<dimen name="screenshot_action_container_padding_left">96dp</dimen>
<dimen name="screenshot_action_container_padding_right">8dp</dimen>
<!-- Radius of the chip background on global screenshot actions -->
<dimen name="screenshot_button_corner_radius">20dp</dimen>
<dimen name="screenshot_action_chip_margin_right">8dp</dimen>
+ <dimen name="screenshot_action_chip_margin_vertical">10dp</dimen>
<dimen name="screenshot_action_chip_padding_vertical">7dp</dimen>
<dimen name="screenshot_action_chip_icon_size">18dp</dimen>
<dimen name="screenshot_action_chip_padding_start">8dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8bbcfa0e7898..de483179a92a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -790,6 +790,9 @@
<!-- Accessibility text describing sensors off active. [CHAR LIMIT=NONE] -->
<string name="accessibility_sensors_off_active">Sensors off active</string>
+ <!-- Accessibility text describing that media is playing. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_media_active">Media is active</string>
+
<!-- Content description of the clear button in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_clear_all">Clear all notifications.</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index b461295dc384..4f42370483c4 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -623,6 +623,7 @@
<style name="MediaPlayer.Button" parent="@android:style/Widget.Material.Button.Borderless.Small">
<item name="android:background">@null</item>
<item name="android:tint">@android:color/white</item>
+ <item name="android:stateListAnimator">@anim/media_button_state_list_animator</item>
</style>
<!-- Used to style charging animation AVD animation -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 0350f2d47569..114472b15f02 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -16,6 +16,7 @@
package com.android.systemui.shared.recents;
+import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
import android.view.MotionEvent;
@@ -69,4 +70,9 @@ oneway interface IOverviewProxy {
* Sent when some system ui state changes.
*/
void onSystemUiStateChanged(int stateFlags) = 16;
+
+ /**
+ * Sent when the split screen is resized
+ */
+ void onSplitScreenSecondaryBoundsChanged(in Rect bounds, in Rect insets) = 17;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 136935080824..5a78c903109c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -86,6 +86,8 @@ public class QuickStepContract {
// enabled (since it's used to navigate back within the bubbled app, or to collapse the bubble
// stack.
public static final int SYSUI_STATE_BUBBLES_EXPANDED = 1 << 14;
+ // The global actions dialog is showing
+ public static final int SYSUI_STATE_GLOBAL_ACTIONS_SHOWING = 1 << 15;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SYSUI_STATE_SCREEN_PINNING,
@@ -102,7 +104,8 @@ public class QuickStepContract {
SYSUI_STATE_SEARCH_DISABLED,
SYSUI_STATE_TRACING_ENABLED,
SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED,
- SYSUI_STATE_BUBBLES_EXPANDED
+ SYSUI_STATE_BUBBLES_EXPANDED,
+ SYSUI_STATE_GLOBAL_ACTIONS_SHOWING
})
public @interface SystemUiStateFlags {}
@@ -119,6 +122,7 @@ public class QuickStepContract {
str.add((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0
? "keygrd_occluded" : "");
str.add((flags & SYSUI_STATE_BOUNCER_SHOWING) != 0 ? "bouncer_visible" : "");
+ str.add((flags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0 ? "global_actions" : "");
str.add((flags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0 ? "a11y_click" : "");
str.add((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0 ? "a11y_long_click" : "");
str.add((flags & SYSUI_STATE_TRACING_ENABLED) != 0 ? "tracing" : "");
@@ -192,8 +196,9 @@ public class QuickStepContract {
* disabled.
*/
public static boolean isBackGestureDisabled(int sysuiStateFlags) {
- // Always allow when the bouncer is showing (even on top of the keyguard)
- if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0) {
+ // Always allow when the bouncer/global actions is showing (even on top of the keyguard)
+ if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
+ || (sysuiStateFlags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0) {
return false;
}
// Disable when in immersive, or the notifications are interactive
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
index 8cd89ddabe72..525e98971266 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
@@ -22,6 +22,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
import android.os.SystemClock;
+import android.provider.Settings;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
@@ -61,6 +62,7 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac
private static final long DEFAULT_SHOWN_FREQUENCY_THRESHOLD_MS = 0;
private static final long DEFAULT_SHOW_AND_GO_DURATION_MS = TimeUnit.SECONDS.toMillis(3);
+ private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
/**
* This is the default behavior that will be used once the system is up. It will be set once the
@@ -203,6 +205,10 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac
}
private boolean handlesUnblocked(boolean ignoreThreshold) {
+ if (!isUserSetupComplete()) {
+ return false;
+ }
+
long timeSinceHidden = SystemClock.elapsedRealtime() - mHandlesLastHiddenAt;
boolean notThrottled = ignoreThreshold || timeSinceHidden >= getShownFrequencyThreshold();
ComponentName assistantComponent =
@@ -284,6 +290,11 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac
mShowAndGoEndsAt = 0;
}
+ private boolean isUserSetupComplete() {
+ return Settings.Secure.getInt(
+ mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+ }
+
@VisibleForTesting
void setInGesturalModeForTest(boolean inGesturalMode) {
mInGesturalMode = inGesturalMode;
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 9055479c47fe..10e913743224 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -58,7 +58,7 @@ class ControlActionCoordinatorImpl @Inject constructor(
override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
bouncerOrRun {
- val effect = if (isChecked) Vibrations.toggleOnEffect else Vibrations.toggleOffEffect
+ val effect = if (!isChecked) Vibrations.toggleOnEffect else Vibrations.toggleOffEffect
vibrate(effect)
cvh.action(BooleanAction(templateId, !isChecked))
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 52d564da7890..3aa417ab904b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -59,6 +59,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.phone.ShadeController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
@@ -79,7 +80,8 @@ class ControlsUiControllerImpl @Inject constructor (
@Main val sharedPreferences: SharedPreferences,
val controlActionCoordinator: ControlActionCoordinator,
private val activityStarter: ActivityStarter,
- private val keyguardStateController: KeyguardStateController
+ private val keyguardStateController: KeyguardStateController,
+ private val shadeController: ShadeController
) : ControlsUiController {
companion object {
@@ -254,14 +256,11 @@ class ControlsUiControllerImpl @Inject constructor (
intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
dismissGlobalActions.run()
- if (!keyguardStateController.isUnlocked()) {
- activityStarter.dismissKeyguardThenExecute({
- context.startActivity(intent)
- true
- }, null, true)
- } else {
+ activityStarter.dismissKeyguardThenExecute({
+ shadeController.collapsePanel(false)
context.startActivity(intent)
- }
+ true
+ }, null, true)
}
private fun showControlsView(items: List<SelectionItem>) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index 236fa2d29aca..f97015282222 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -18,7 +18,6 @@ package com.android.systemui.controls.ui
import android.app.ActivityView
import android.app.Dialog
-import android.content.ComponentName
import android.content.Intent
import android.provider.Settings
import android.view.View
@@ -58,17 +57,13 @@ class DetailDialog(
launchIntent.putExtra(EXTRA_USE_PANEL, true)
// Apply flags to make behaviour match documentLaunchMode=always.
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
view.startActivity(launchIntent)
}
override fun onActivityViewDestroyed(view: ActivityView) {}
-
- override fun onTaskCreated(taskId: Int, componentName: ComponentName) {}
-
- override fun onTaskRemovalStarted(taskId: Int) {}
}
init {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt
index a97113cc598b..c0f6aabe2427 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt
@@ -20,7 +20,7 @@ import android.os.VibrationEffect
import android.os.VibrationEffect.Composition.PRIMITIVE_TICK
object Vibrations {
- private const val TOGGLE_TICK_COUNT = 12
+ private const val TOGGLE_TICK_COUNT = 40
val toggleOnEffect = initToggleOnEffect()
val toggleOffEffect = initToggleOffEffect()
@@ -29,6 +29,7 @@ object Vibrations {
private fun initToggleOnEffect(): VibrationEffect {
val composition = VibrationEffect.startComposition()
+ composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 200)
var i = 0
while (i++ < TOGGLE_TICK_COUNT) {
composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 0)
@@ -43,7 +44,7 @@ object Vibrations {
composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 100)
var i = 0
while (i++ < TOGGLE_TICK_COUNT) {
- composition?.addPrimitive(PRIMITIVE_TICK, 0.05f, 0)
+ composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 0)
}
return composition.compose()
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 5e367046bd2b..65729372363a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -34,6 +34,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.wakelock.DelayedWakeLock;
@@ -56,6 +57,7 @@ public class DozeFactory {
private final ProximitySensor mProximitySensor;
private final DelayedWakeLock.Builder mDelayedWakeLockBuilder;
private final Handler mHandler;
+ private final DelayableExecutor mDelayableExecutor;
private final BiometricUnlockController mBiometricUnlockController;
private final BroadcastDispatcher mBroadcastDispatcher;
private final DozeHost mDozeHost;
@@ -68,6 +70,7 @@ public class DozeFactory {
DockManager dockManager, @Nullable IWallpaperManager wallpaperManager,
ProximitySensor proximitySensor,
DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
+ DelayableExecutor delayableExecutor,
BiometricUnlockController biometricUnlockController,
BroadcastDispatcher broadcastDispatcher, DozeHost dozeHost) {
mFalsingManager = falsingManager;
@@ -83,6 +86,7 @@ public class DozeFactory {
mProximitySensor = proximitySensor;
mDelayedWakeLockBuilder = delayedWakeLockBuilder;
mHandler = handler;
+ mDelayableExecutor = delayableExecutor;
mBiometricUnlockController = biometricUnlockController;
mBroadcastDispatcher = broadcastDispatcher;
mDozeHost = dozeHost;
@@ -107,8 +111,8 @@ public class DozeFactory {
new DozePauser(mHandler, machine, mAlarmManager, mDozeParameters.getPolicy()),
new DozeFalsingManagerAdapter(mFalsingManager),
createDozeTriggers(dozeService, mAsyncSensorManager, mDozeHost,
- mAlarmManager, config, mDozeParameters, mHandler, wakeLock, machine,
- mDockManager, mDozeLog),
+ mAlarmManager, config, mDozeParameters, mDelayableExecutor, wakeLock,
+ machine, mDockManager, mDozeLog),
createDozeUi(dozeService, mDozeHost, wakeLock, machine, mHandler,
mAlarmManager, mDozeParameters, mDozeLog),
new DozeScreenState(wrappedService, mHandler, mDozeHost, mDozeParameters,
@@ -135,11 +139,11 @@ public class DozeFactory {
private DozeTriggers createDozeTriggers(Context context, AsyncSensorManager sensorManager,
DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config,
- DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine,
- DockManager dockManager, DozeLog dozeLog) {
+ DozeParameters params, DelayableExecutor delayableExecutor, WakeLock wakeLock,
+ DozeMachine machine, DockManager dockManager, DozeLog dozeLog) {
boolean allowPulseTriggers = true;
return new DozeTriggers(context, machine, host, alarmManager, config, params,
- sensorManager, handler, wakeLock, allowPulseTriggers, dockManager,
+ sensorManager, delayableExecutor, wakeLock, allowPulseTriggers, dockManager,
mProximitySensor, dozeLog, mBroadcastDispatcher);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index e1081cd5ef82..78f8f673cab9 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -101,7 +101,8 @@ public class DozeSensors {
public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager,
DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
- Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog) {
+ Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog,
+ ProximitySensor proximitySensor) {
mContext = context;
mAlarmManager = alarmManager;
mSensorManager = sensorManager;
@@ -111,6 +112,7 @@ public class DozeSensors {
mProxCallback = proxCallback;
mResolver = mContext.getContentResolver();
mCallback = callback;
+ mProximitySensor = proximitySensor;
boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
mSensors = new TriggerSensor[] {
@@ -173,7 +175,6 @@ public class DozeSensors {
dozeLog),
};
- mProximitySensor = new ProximitySensor(context.getResources(), sensorManager);
setProxListening(false); // Don't immediately start listening when we register.
mProximitySensor.register(
proximityEvent -> {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 3510e07d2cea..6a5501445a4d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -26,7 +26,6 @@ import android.content.IntentFilter;
import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
import android.metrics.LogMaker;
-import android.os.Handler;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.Formatter;
@@ -43,6 +42,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dock.DockManager;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.wakelock.WakeLock;
@@ -152,9 +152,9 @@ public class DozeTriggers implements DozeMachine.Part {
public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
AlarmManager alarmManager, AmbientDisplayConfiguration config,
- DozeParameters dozeParameters, AsyncSensorManager sensorManager, Handler handler,
- WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager,
- ProximitySensor proximitySensor,
+ DozeParameters dozeParameters, AsyncSensorManager sensorManager,
+ DelayableExecutor delayableExecutor, WakeLock wakeLock, boolean allowPulseTriggers,
+ DockManager dockManager, ProximitySensor proximitySensor,
DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher) {
mContext = context;
mMachine = machine;
@@ -165,10 +165,10 @@ public class DozeTriggers implements DozeMachine.Part {
mWakeLock = wakeLock;
mAllowPulseTriggers = allowPulseTriggers;
mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters,
- config, wakeLock, this::onSensor, this::onProximityFar, dozeLog);
+ config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mDockManager = dockManager;
- mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, handler);
+ mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, delayableExecutor);
mDozeLog = dozeLog;
mBroadcastDispatcher = broadcastDispatcher;
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 62744673e76e..d69c3b01493f 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -23,6 +23,7 @@ import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -124,9 +125,11 @@ import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsUiController;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
+import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
@@ -200,6 +203,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
private final UiEventLogger mUiEventLogger;
private final NotificationShadeDepthController mDepthController;
private final BlurUtils mBlurUtils;
+ private final SysUiState mSysUiState;
// Used for RingerModeTracker
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
@@ -301,7 +305,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
@Background Executor backgroundExecutor,
ControlsListingController controlsListingController,
ControlsController controlsController, UiEventLogger uiEventLogger,
- RingerModeTracker ringerModeTracker, @Main Handler handler) {
+ RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) {
mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
@@ -330,6 +334,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
mBlurUtils = blurUtils;
mRingerModeTracker = ringerModeTracker;
mControlsController = controlsController;
+ mSysUiState = sysUiState;
mMainHandler = handler;
// receive broadcasts
@@ -638,7 +643,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
getWalletPanelViewController(), mDepthController, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
- shouldShowControls() ? mControlsUiController : null, mBlurUtils,
+ shouldShowControls() ? mControlsUiController : null, mBlurUtils, mSysUiState,
shouldUseControlsLayout(), this::onRotate, mKeyguardShowing);
dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
dialog.setOnDismissListener(this);
@@ -1920,6 +1925,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final NotificationShadeDepthController mDepthController;
private final BlurUtils mBlurUtils;
+ private final SysUiState mSysUiState;
private final boolean mUseControlsLayout;
private ListPopupWindow mOverflowPopup;
private final Runnable mOnRotateCallback;
@@ -1934,7 +1940,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
NotificationShadeWindowController notificationShadeWindowController,
ControlsUiController controlsUiController, BlurUtils blurUtils,
- boolean useControlsLayout, Runnable onRotateCallback, boolean keyguardShowing) {
+ SysUiState sysuiState, boolean useControlsLayout, Runnable onRotateCallback,
+ boolean keyguardShowing) {
super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
mContext = context;
mAdapter = adapter;
@@ -1945,6 +1952,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
mNotificationShadeWindowController = notificationShadeWindowController;
mControlsUiController = controlsUiController;
mBlurUtils = blurUtils;
+ mSysUiState = sysuiState;
mUseControlsLayout = useControlsLayout;
mOnRotateCallback = onRotateCallback;
mKeyguardShowing = keyguardShowing;
@@ -2203,6 +2211,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
mShowing = true;
mHadTopUi = mNotificationShadeWindowController.getForceHasTopUi();
mNotificationShadeWindowController.setForceHasTopUi(true);
+ mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true)
+ .commitUpdate(mContext.getDisplayId());
ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView();
root.setOnApplyWindowInsetsListener((v, windowInsets) -> {
@@ -2303,6 +2313,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
if (mControlsUiController != null) mControlsUiController.hide();
mNotificationShadeWindowController.setForceHasTopUi(mHadTopUi);
mDepthController.updateGlobalDialogVisibility(0, null /* view */);
+ mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false)
+ .commitUpdate(mContext.getDisplayId());
super.dismiss();
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
index c7612d41c45d..83046ef450c3 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
@@ -23,6 +23,7 @@ import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.HardwareBgDrawable;
@@ -78,6 +79,31 @@ public class GlobalActionsFlatLayout extends GlobalActionsLayout {
}
}
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ boolean anyTruncated = false;
+ ViewGroup listView = getListView();
+ // Check to see if any of the GlobalActionsItems have had their messages truncated
+ for (int i = 0; i < listView.getChildCount(); i++) {
+ View child = listView.getChildAt(i);
+ if (child instanceof GlobalActionsItem) {
+ GlobalActionsItem item = (GlobalActionsItem) child;
+ anyTruncated = anyTruncated || item.isTruncated();
+ }
+ }
+ // If any of the items have been truncated, set the all to single-line marquee
+ if (anyTruncated) {
+ for (int i = 0; i < listView.getChildCount(); i++) {
+ View child = listView.getChildAt(i);
+ if (child instanceof GlobalActionsItem) {
+ GlobalActionsItem item = (GlobalActionsItem) child;
+ item.setMarquee(true);
+ }
+ }
+ }
+ }
+
@VisibleForTesting
protected float getGridItemSize() {
return getContext().getResources().getDimension(R.dimen.global_actions_grid_item_height);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsItem.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsItem.java
new file mode 100644
index 000000000000..07fa59200a1d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsItem.java
@@ -0,0 +1,76 @@
+/*
+ * 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.globalactions;
+
+import android.content.Context;
+import android.text.Layout;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+/**
+ * Layout for GlobalActions items.
+ */
+public class GlobalActionsItem extends LinearLayout {
+ public GlobalActionsItem(Context context) {
+ super(context);
+ }
+
+ public GlobalActionsItem(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public GlobalActionsItem(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ private TextView getTextView() {
+ return (TextView) findViewById(R.id.message);
+ }
+
+ /**
+ * Sets this item to marquee or not.
+ */
+ public void setMarquee(boolean marquee) {
+ TextView text = getTextView();
+ text.setSingleLine(marquee);
+ text.setEllipsize(marquee ? TextUtils.TruncateAt.MARQUEE : TextUtils.TruncateAt.END);
+ }
+
+ /**
+ * Determines whether the message for this item has been truncated.
+ */
+ public boolean isTruncated() {
+ TextView message = getTextView();
+ if (message != null) {
+ Layout messageLayout = message.getLayout();
+ if (messageLayout != null) {
+ if (messageLayout.getLineCount() > 0) {
+ // count the number of ellipses in the last line.
+ int ellipses = messageLayout.getEllipsisCount(
+ messageLayout.getLineCount() - 1);
+ // If ellipses are present, the line was forced to truncate.
+ return ellipses > 0;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
index 524c6955ba4a..4ee4ad46d4c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -67,5 +67,4 @@ class KeyguardMediaController @Inject constructor(
statusBarStateController.state == StatusBarState.FULLSCREEN_USER_SWITCHER)
view?.visibility = if (shouldBeVisible) View.VISIBLE else View.GONE
}
-}
-
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt
new file mode 100644
index 000000000000..94a0835f22f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.content.Context
+
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.media.InfoMediaManager
+import com.android.settingslib.media.LocalMediaManager
+
+import javax.inject.Inject
+
+/**
+ * Factory to create [LocalMediaManager] objects.
+ */
+class LocalMediaManagerFactory @Inject constructor(
+ private val context: Context,
+ private val localBluetoothManager: LocalBluetoothManager?
+) {
+ /** Creates a [LocalMediaManager] for the given package. */
+ fun create(packageName: String): LocalMediaManager {
+ return InfoMediaManager(context, packageName, null, localBluetoothManager).run {
+ LocalMediaManager(context, localBluetoothManager, this, packageName)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 1691c53386d6..f90798bd30b8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -41,7 +41,6 @@ import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
@@ -56,14 +55,11 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import com.android.settingslib.Utils;
-import com.android.settingslib.media.LocalMediaManager;
-import com.android.settingslib.media.MediaDevice;
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.Assert;
import com.android.systemui.util.concurrency.DelayableExecutor;
import org.jetbrains.annotations.NotNull;
@@ -77,7 +73,6 @@ import java.util.concurrent.Executor;
*/
public class MediaControlPanel {
private static final String TAG = "MediaControlPanel";
- @Nullable private final LocalMediaManager mLocalMediaManager;
// Button IDs for QS controls
static final int[] ACTION_IDS = {
@@ -100,7 +95,6 @@ public class MediaControlPanel {
private MediaSession.Token mToken;
private MediaController mController;
private int mBackgroundColor;
- private MediaDevice mDevice;
protected ComponentName mServiceComponent;
private boolean mIsRegistered = false;
private List<KeyFrames> mKeyFrames;
@@ -113,7 +107,6 @@ public class MediaControlPanel {
public static final String MEDIA_PREFERENCE_KEY = "browser_components";
private SharedPreferences mSharedPrefs;
private boolean mCheckedForResumption = false;
- private boolean mIsRemotePlayback;
private QSMediaBrowser mQSMediaBrowser;
private final MediaController.Callback mSessionCallback = new MediaController.Callback() {
@@ -122,7 +115,6 @@ public class MediaControlPanel {
Log.d(TAG, "session destroyed");
mController.unregisterCallback(mSessionCallback);
clearControls();
- makeInactive();
}
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -130,31 +122,6 @@ public class MediaControlPanel {
if (s == PlaybackState.STATE_NONE) {
Log.d(TAG, "playback state change will trigger resumption, state=" + state);
clearControls();
- makeInactive();
- }
- }
- };
-
- private final LocalMediaManager.DeviceCallback mDeviceCallback =
- new LocalMediaManager.DeviceCallback() {
- @Override
- public void onDeviceListUpdate(List<MediaDevice> devices) {
- if (mLocalMediaManager == null) {
- return;
- }
- MediaDevice currentDevice = mLocalMediaManager.getCurrentConnectedDevice();
- // Check because this can be called several times while changing devices
- if (mDevice == null || !mDevice.equals(currentDevice)) {
- mDevice = currentDevice;
- updateDevice(mDevice);
- }
- }
-
- @Override
- public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
- if (mDevice == null || !mDevice.equals(device)) {
- mDevice = device;
- updateDevice(mDevice);
}
}
};
@@ -162,16 +129,13 @@ public class MediaControlPanel {
/**
* Initialize a new control panel
* @param context
- * @param routeManager Manager used to listen for device change events.
* @param foregroundExecutor foreground executor
* @param backgroundExecutor background executor, used for processing artwork
* @param activityStarter activity starter
*/
- public MediaControlPanel(Context context, @Nullable LocalMediaManager routeManager,
- Executor foregroundExecutor, DelayableExecutor backgroundExecutor,
- ActivityStarter activityStarter) {
+ public MediaControlPanel(Context context, Executor foregroundExecutor,
+ DelayableExecutor backgroundExecutor, ActivityStarter activityStarter) {
mContext = context;
- mLocalMediaManager = routeManager;
mForegroundExecutor = foregroundExecutor;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
@@ -183,7 +147,6 @@ public class MediaControlPanel {
if (mSeekBarObserver != null) {
mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver);
}
- makeInactive();
}
private void loadDimens() {
@@ -228,7 +191,7 @@ public class MediaControlPanel {
mLayoutAnimationHelper = new LayoutAnimationHelper(motionView);
GoneChildrenHideHelper.clipGoneChildrenOnLayout(motionView);
mKeyFrames = motionView.getDefinedTransitions().get(0).getKeyFrameList();
- mSeekBarObserver = new SeekBarObserver(motionView);
+ mSeekBarObserver = new SeekBarObserver(vh);
mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver);
SeekBar bar = vh.getSeekBar();
bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener());
@@ -318,30 +281,67 @@ public class MediaControlPanel {
artistText.setText(data.getArtist());
// Transfer chip
- if (mLocalMediaManager != null) {
- mViewHolder.getSeamless().setVisibility(View.VISIBLE);
- setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
- setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
- updateDevice(mLocalMediaManager.getCurrentConnectedDevice());
- mViewHolder.getSeamless().setOnClickListener(v -> {
- final Intent intent = new Intent()
- .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
- .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
- mController.getPackageName())
- .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
- mActivityStarter.startActivity(intent, false, true /* dismissShade */,
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- });
- } else {
- Log.d(TAG, "LocalMediaManager is null. Not binding output chip for pkg=" + pkgName);
- }
+ mViewHolder.getSeamless().setVisibility(View.VISIBLE);
+ setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
+ setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
+ mViewHolder.getSeamless().setOnClickListener(v -> {
+ final Intent intent = new Intent()
+ .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
+ .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
+ mController.getPackageName())
+ .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
+ mActivityStarter.startActivity(intent, false, true /* dismissShade */,
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ });
+ final boolean isRemotePlayback;
PlaybackInfo playbackInfo = mController.getPlaybackInfo();
if (playbackInfo != null) {
- mIsRemotePlayback = playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
+ isRemotePlayback = playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
} else {
Log.d(TAG, "PlaybackInfo was null. Defaulting to local playback.");
- mIsRemotePlayback = false;
+ isRemotePlayback = false;
+ }
+
+ ImageView iconView = mViewHolder.getSeamlessIcon();
+ TextView deviceName = mViewHolder.getSeamlessText();
+
+ // Update the outline color
+ RippleDrawable bkgDrawable = (RippleDrawable) mViewHolder.getSeamless().getBackground();
+ GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
+ rect.setStroke(2, deviceName.getCurrentTextColor());
+ rect.setColor(Color.TRANSPARENT);
+
+ if (isRemotePlayback) {
+ mViewHolder.getSeamless().setEnabled(false);
+ // TODO(b/156875717): setEnabled should cause the alpha to change.
+ mViewHolder.getSeamless().setAlpha(0.38f);
+ iconView.setImageResource(R.drawable.ic_hardware_speaker);
+ iconView.setVisibility(View.VISIBLE);
+ deviceName.setText(R.string.media_seamless_remote_device);
+ } else if (data.getDevice() != null && data.getDevice().getIcon() != null
+ && data.getDevice().getName() != null) {
+ mViewHolder.getSeamless().setEnabled(true);
+ mViewHolder.getSeamless().setAlpha(1f);
+ Drawable icon = data.getDevice().getIcon();
+ iconView.setVisibility(View.VISIBLE);
+
+ if (icon instanceof AdaptiveIcon) {
+ AdaptiveIcon aIcon = (AdaptiveIcon) icon;
+ aIcon.setBackgroundColor(mBackgroundColor);
+ iconView.setImageDrawable(aIcon);
+ } else {
+ iconView.setImageDrawable(icon);
+ }
+ deviceName.setText(data.getDevice().getName());
+ } else {
+ // Reset to default
+ Log.w(TAG, "device is null. Not binding output chip.");
+ mViewHolder.getSeamless().setEnabled(true);
+ mViewHolder.getSeamless().setAlpha(1f);
+ iconView.setVisibility(View.GONE);
+ deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
}
+
List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
// Media controls
int i = 0;
@@ -382,8 +382,6 @@ public class MediaControlPanel {
// Set up long press menu
// TODO: b/156036025 bring back media guts
- makeActive();
-
// Update both constraint sets to regenerate the animation.
mViewHolder.getPlayer().updateState(R.id.collapsed, collapsedSet);
mViewHolder.getPlayer().updateState(R.id.expanded, expandedSet);
@@ -515,60 +513,6 @@ public class MediaControlPanel {
}
/**
- * Update the current device information
- * @param device device information to display
- */
- private void updateDevice(MediaDevice device) {
- mForegroundExecutor.execute(() -> {
- updateChipInternal(device);
- });
- }
-
- private void updateChipInternal(MediaDevice device) {
- if (mViewHolder == null) {
- return;
- }
- ImageView iconView = mViewHolder.getSeamlessIcon();
- TextView deviceName = mViewHolder.getSeamlessText();
-
- // Update the outline color
- LinearLayout viewLayout = (LinearLayout) mViewHolder.getSeamless();
- RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
- GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
- rect.setStroke(2, deviceName.getCurrentTextColor());
- rect.setColor(Color.TRANSPARENT);
-
- if (mIsRemotePlayback) {
- mViewHolder.getSeamless().setEnabled(false);
- mViewHolder.getSeamless().setAlpha(0.38f);
- iconView.setImageResource(R.drawable.ic_hardware_speaker);
- iconView.setVisibility(View.VISIBLE);
- deviceName.setText(R.string.media_seamless_remote_device);
- } else if (device != null) {
- mViewHolder.getSeamless().setEnabled(true);
- mViewHolder.getSeamless().setAlpha(1f);
- Drawable icon = device.getIcon();
- iconView.setVisibility(View.VISIBLE);
-
- if (icon instanceof AdaptiveIcon) {
- AdaptiveIcon aIcon = (AdaptiveIcon) icon;
- aIcon.setBackgroundColor(mBackgroundColor);
- iconView.setImageDrawable(aIcon);
- } else {
- iconView.setImageDrawable(icon);
- }
- deviceName.setText(device.getName());
- } else {
- // Reset to default
- Log.d(TAG, "device is null. Not binding output chip.");
- mViewHolder.getSeamless().setEnabled(true);
- mViewHolder.getSeamless().setAlpha(1f);
- iconView.setVisibility(View.GONE);
- deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
- }
- }
-
- /**
* Puts controls into a resumption state if possible, or calls removePlayer if no component was
* found that could resume playback
*/
@@ -642,27 +586,6 @@ public class MediaControlPanel {
set.setAlpha(actionId, visible ? 1.0f : 0.0f);
}
- private void makeActive() {
- Assert.isMainThread();
- if (!mIsRegistered) {
- if (mLocalMediaManager != null) {
- mLocalMediaManager.registerCallback(mDeviceCallback);
- mLocalMediaManager.startScan();
- }
- mIsRegistered = true;
- }
- }
-
- private void makeInactive() {
- Assert.isMainThread();
- if (mIsRegistered) {
- if (mLocalMediaManager != null) {
- mLocalMediaManager.stopScan();
- mLocalMediaManager.unregisterCallback(mDeviceCallback);
- }
- mIsRegistered = false;
- }
- }
/**
* Verify that we can connect to the given component with a MediaBrowser, and if so, add that
* component to the list of resumption components
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 85965d03a096..41d411019921 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -34,7 +34,8 @@ data class MediaData(
val actionsToShowInCompact: List<Int>,
val packageName: String?,
val token: MediaSession.Token?,
- val clickIntent: PendingIntent?
+ val clickIntent: PendingIntent?,
+ val device: MediaDeviceData?
)
/** State of a media action. */
@@ -43,3 +44,9 @@ data class MediaAction(
val intent: PendingIntent?,
val contentDescription: CharSequence?
)
+
+/** State of the media device. */
+data class MediaDeviceData(
+ val icon: Drawable?,
+ val name: String?
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
new file mode 100644
index 000000000000..cce9838bb8e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Combines updates from [MediaDataManager] with [MediaDeviceManager].
+ */
+@Singleton
+class MediaDataCombineLatest @Inject constructor(
+ private val dataSource: MediaDataManager,
+ private val deviceSource: MediaDeviceManager
+) {
+ private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
+ private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf()
+
+ init {
+ dataSource.addListener(object : MediaDataManager.Listener {
+ override fun onMediaDataLoaded(key: String, data: MediaData) {
+ entries[key] = data to entries[key]?.second
+ update(key)
+ }
+ override fun onMediaDataRemoved(key: String) {
+ remove(key)
+ }
+ })
+ deviceSource.addListener(object : MediaDeviceManager.Listener {
+ override fun onMediaDeviceChanged(key: String, data: MediaDeviceData?) {
+ entries[key] = entries[key]?.first to data
+ update(key)
+ }
+ override fun onKeyRemoved(key: String) {
+ remove(key)
+ }
+ })
+ }
+
+ /**
+ * Add a listener for [MediaData] changes that has been combined with latest [MediaDeviceData].
+ */
+ fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
+
+ /**
+ * Remove a listener registered with addListener.
+ */
+ fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
+
+ private fun update(key: String) {
+ val (entry, device) = entries[key] ?: null to null
+ if (entry != null && device != null) {
+ val data = entry.copy(device = device)
+ listeners.forEach {
+ it.onMediaDataLoaded(key, data)
+ }
+ }
+ }
+
+ private fun remove(key: String) {
+ entries.remove(key)?.let {
+ listeners.forEach {
+ it.onMediaDataRemoved(key)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 90c558a1ee97..8cbe3ecf5387 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -55,7 +55,19 @@ 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)
+ emptyList(), emptyList(), null, null, null, null)
+
+fun isMediaNotification(sbn: StatusBarNotification): Boolean {
+ if (!sbn.notification.hasMediaSession()) {
+ return false
+ }
+ val notificationStyle = sbn.notification.notificationStyle
+ if (Notification.DecoratedMediaCustomViewStyle::class.java.equals(notificationStyle) ||
+ Notification.MediaStyle::class.java.equals(notificationStyle)) {
+ return true
+ }
+ return false
+}
/**
* A class that facilitates management and loading of Media Data, ready for binding.
@@ -65,14 +77,14 @@ class MediaDataManager @Inject constructor(
private val context: Context,
private val mediaControllerFactory: MediaControllerFactory,
@Background private val backgroundExecutor: Executor,
- @Main private val foregroundExcecutor: Executor
+ @Main private val foregroundExecutor: Executor
) {
private val listeners: MutableSet<Listener> = mutableSetOf()
private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
- if (isMediaNotification(sbn)) {
+ if (Utils.useQsMediaPlayer(context) && isMediaNotification(sbn)) {
if (!mediaEntries.containsKey(key)) {
mediaEntries.put(key, LOADING)
}
@@ -201,10 +213,10 @@ class MediaDataManager @Inject constructor(
}
}
- foregroundExcecutor.execute {
+ foregroundExecutor.execute {
onMediaDataLoaded(key, MediaData(true, bgColor, app, smallIconDrawable, artist, song,
artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
- notif.contentIntent))
+ notif.contentIntent, null))
}
}
@@ -270,25 +282,10 @@ class MediaDataManager @Inject constructor(
}
}
- private fun isMediaNotification(sbn: StatusBarNotification): Boolean {
- if (!Utils.useQsMediaPlayer(context)) {
- return false
- }
- if (!sbn.notification.hasMediaSession()) {
- return false
- }
- val notificationStyle = sbn.notification.notificationStyle
- if (Notification.DecoratedMediaCustomViewStyle::class.java.equals(notificationStyle) ||
- Notification.MediaStyle::class.java.equals(notificationStyle)) {
- return true
- }
- return false
- }
-
/**
* Are there any media notifications active?
*/
- fun hasActiveMedia() = mediaEntries.size > 0
+ fun hasActiveMedia() = mediaEntries.isNotEmpty()
fun hasAnyMedia(): Boolean {
// TODO: implement this when we implemented resumption
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
new file mode 100644
index 000000000000..2d16e2930365
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.content.Context
+import android.service.notification.StatusBarNotification
+import com.android.settingslib.media.LocalMediaManager
+import com.android.settingslib.media.MediaDevice
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Provides information about the route (ie. device) where playback is occurring.
+ */
+@Singleton
+class MediaDeviceManager @Inject constructor(
+ private val context: Context,
+ private val localMediaManagerFactory: LocalMediaManagerFactory,
+ private val featureFlag: MediaFeatureFlag,
+ @Main private val fgExecutor: Executor
+) {
+ private val listeners: MutableSet<Listener> = mutableSetOf()
+ private val entries: MutableMap<String, Token> = mutableMapOf()
+
+ /**
+ * Add a listener for changes to the media route (ie. device).
+ */
+ fun addListener(listener: Listener) = listeners.add(listener)
+
+ /**
+ * Remove a listener that has been registered with addListener.
+ */
+ fun removeListener(listener: Listener) = listeners.remove(listener)
+
+ fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
+ if (featureFlag.enabled && isMediaNotification(sbn)) {
+ var tok = entries[key]
+ if (tok == null) {
+ tok = Token(key, localMediaManagerFactory.create(sbn.packageName))
+ entries[key] = tok
+ tok.start()
+ }
+ } else {
+ onNotificationRemoved(key)
+ }
+ }
+
+ fun onNotificationRemoved(key: String) {
+ val token = entries.remove(key)
+ token?.stop()
+ token?.let {
+ listeners.forEach {
+ it.onKeyRemoved(key)
+ }
+ }
+ }
+
+ private fun processDevice(key: String, device: MediaDevice?) {
+ val data = MediaDeviceData(device?.icon, device?.name)
+ listeners.forEach {
+ it.onMediaDeviceChanged(key, data)
+ }
+ }
+
+ interface Listener {
+ /** Called when the route has changed for a given notification. */
+ fun onMediaDeviceChanged(key: String, data: MediaDeviceData?)
+ /** Called when the notification was removed. */
+ fun onKeyRemoved(key: String)
+ }
+
+ private inner class Token(
+ val key: String,
+ val localMediaManager: LocalMediaManager
+ ) : LocalMediaManager.DeviceCallback {
+ private var current: MediaDevice? = null
+ set(value) {
+ if (value != field) {
+ field = value
+ processDevice(key, value)
+ }
+ }
+ fun start() {
+ localMediaManager.registerCallback(this)
+ localMediaManager.startScan()
+ current = localMediaManager.getCurrentConnectedDevice()
+ }
+ fun stop() {
+ localMediaManager.stopScan()
+ localMediaManager.unregisterCallback(this)
+ }
+ override fun onDeviceListUpdate(devices: List<MediaDevice>?) = fgExecutor.execute {
+ current = localMediaManager.getCurrentConnectedDevice()
+ }
+ override fun onSelectedDeviceStateChanged(device: MediaDevice, state: Int) {
+ fgExecutor.execute {
+ current = device
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt b/packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt
new file mode 100644
index 000000000000..75eb33da64d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.content.Context
+import com.android.systemui.util.Utils
+import javax.inject.Inject
+
+/**
+ * Provides access to the current value of the feature flag.
+ */
+class MediaFeatureFlag @Inject constructor(private val context: Context) {
+ val enabled
+ get() = Utils.useQsMediaPlayer(context)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index 6e7b6bcb7502..240e44cb8db4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -26,8 +26,8 @@ class MediaHost @Inject constructor(
/**
* Get the current Media state. This also updates the location on screen
*/
- val currentState : MediaState
- get () {
+ val currentState: MediaState
+ get() {
hostView.getLocationOnScreen(tmpLocationOnScreen)
var left = tmpLocationOnScreen[0] + hostView.paddingLeft
var top = tmpLocationOnScreen[1] + hostView.paddingTop
@@ -37,11 +37,11 @@ class MediaHost @Inject constructor(
// the above could return negative widths, which is wrong
if (right < left) {
left = 0
- right = 0;
+ right = 0
}
if (bottom < top) {
bottom = 0
- top = 0;
+ top = 0
}
state.boundsOnScreen.set(left, top, right, bottom)
return state
@@ -64,7 +64,7 @@ class MediaHost @Inject constructor(
* transitions.
*/
fun init(@MediaLocation location: Int) {
- this.location = location;
+ this.location = location
hostView = mediaHierarchyManager.register(this)
hostView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View?) {
@@ -95,7 +95,7 @@ class MediaHost @Inject constructor(
override var showsOnlyActiveMedia: Boolean = false
override val boundsOnScreen: Rect = Rect()
- override fun copy() : MediaState {
+ override fun copy(): MediaState {
val mediaHostState = MediaHostState()
mediaHostState.expansion = expansion
mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
@@ -104,7 +104,7 @@ class MediaHost @Inject constructor(
return mediaHostState
}
- override fun interpolate(other: MediaState, amount: Float) : MediaState {
+ override fun interpolate(other: MediaState, amount: Float): MediaState {
val result = MediaHostState()
result.expansion = MathUtils.lerp(expansion, other.expansion, amount)
val left = MathUtils.lerp(boundsOnScreen.left.toFloat(),
@@ -121,10 +121,10 @@ class MediaHost @Inject constructor(
if (other is MediaHostState) {
result.measurementInput = other.measurementInput
}
- } else {
+ } else {
result.measurementInput
}
- return result
+ return result
}
override fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput {
@@ -138,8 +138,8 @@ interface MediaState {
var expansion: Float
var showsOnlyActiveMedia: Boolean
val boundsOnScreen: Rect
- fun copy() : MediaState
- fun interpolate(other: MediaState, amount: Float) : MediaState
+ fun copy(): MediaState
+ fun interpolate(other: MediaState, amount: Float): MediaState
fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput
}
/**
@@ -147,7 +147,8 @@ interface MediaState {
*/
data class MediaMeasurementInput(
private val viewInput: MeasurementInput,
- val expansion: Float) : MeasurementInput by viewInput {
+ val expansion: Float
+) : MeasurementInput by viewInput {
override fun sameAs(input: MeasurementInput?): Boolean {
if (!(input is MediaMeasurementInput)) {
@@ -155,5 +156,4 @@ data class MediaMeasurementInput(
}
return width == input.width && expansion == input.expansion
}
-}
-
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
index 8db9dcc1ecec..17e8404fe705 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
@@ -6,9 +6,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import android.widget.LinearLayout
-import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.settingslib.media.InfoMediaManager
-import com.android.settingslib.media.LocalMediaManager
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -30,10 +27,9 @@ class MediaViewManager @Inject constructor(
private val context: Context,
@Main private val foregroundExecutor: Executor,
@Background private val backgroundExecutor: DelayableExecutor,
- private val localBluetoothManager: LocalBluetoothManager?,
private val visualStabilityManager: VisualStabilityManager,
private val activityStarter: ActivityStarter,
- mediaManager: MediaDataManager
+ mediaManager: MediaDataCombineLatest
) {
private var playerWidth: Int = 0
private var playerWidthPlusPadding: Int = 0
@@ -42,7 +38,7 @@ class MediaViewManager @Inject constructor(
val mediaCarousel: HorizontalScrollView
private val mediaContent: ViewGroup
private val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
- private val visualStabilityCallback : VisualStabilityManager.Callback
+ private val visualStabilityCallback: VisualStabilityManager.Callback
private var activeMediaIndex: Int = 0
private var needsReordering: Boolean = false
private var scrollIntoCurrentMedia: Int = 0
@@ -151,15 +147,8 @@ class MediaViewManager @Inject constructor(
private fun updateView(key: String, data: MediaData) {
var existingPlayer = mediaPlayers[key]
if (existingPlayer == null) {
- // Set up listener for device changes
- // TODO: integrate with MediaTransferManager?
- val imm = InfoMediaManager(context, data.packageName,
- null /* notification */, localBluetoothManager)
- val routeManager = LocalMediaManager(context, localBluetoothManager,
- imm, data.packageName)
-
- existingPlayer = MediaControlPanel(context, routeManager, foregroundExecutor,
- backgroundExecutor, activityStarter)
+ existingPlayer = MediaControlPanel(context, foregroundExecutor, backgroundExecutor,
+ activityStarter)
existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context),
mediaContent))
mediaPlayers[key] = existingPlayer
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
index 110b08c4b808..cd8ed265bd53 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
@@ -16,58 +16,41 @@
package com.android.systemui.media
-import android.content.res.ColorStateList
-import android.graphics.Color
import android.text.format.DateUtils
-import android.view.View
-import android.widget.SeekBar
-import android.widget.TextView
import androidx.annotation.UiThread
import androidx.lifecycle.Observer
-import com.android.systemui.R
-
/**
* Observer for changes from SeekBarViewModel.
*
* <p>Updates the seek bar views in response to changes to the model.
*/
-class SeekBarObserver(view: View) : Observer<SeekBarViewModel.Progress> {
-
- private val seekBarView: SeekBar
- private val elapsedTimeView: TextView
- private val totalTimeView: TextView
-
- init {
- seekBarView = view.findViewById(R.id.media_progress_bar)
- elapsedTimeView = view.findViewById(R.id.media_elapsed_time)
- totalTimeView = view.findViewById(R.id.media_total_time)
- }
+class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarViewModel.Progress> {
/** Updates seek bar views when the data model changes. */
@UiThread
override fun onChanged(data: SeekBarViewModel.Progress) {
if (!data.enabled) {
- seekBarView.setEnabled(false)
- seekBarView.getThumb().setAlpha(0)
- seekBarView.setProgress(0)
- elapsedTimeView.setText("")
- totalTimeView.setText("")
+ holder.seekBar.setEnabled(false)
+ holder.seekBar.getThumb().setAlpha(0)
+ holder.seekBar.setProgress(0)
+ holder.elapsedTimeView.setText("")
+ holder.totalTimeView.setText("")
return
}
- seekBarView.getThumb().setAlpha(if (data.seekAvailable) 255 else 0)
- seekBarView.setEnabled(data.seekAvailable)
+ holder.seekBar.getThumb().setAlpha(if (data.seekAvailable) 255 else 0)
+ holder.seekBar.setEnabled(data.seekAvailable)
data.elapsedTime?.let {
- seekBarView.setProgress(it)
- elapsedTimeView.setText(DateUtils.formatElapsedTime(
+ holder.seekBar.setProgress(it)
+ holder.elapsedTimeView.setText(DateUtils.formatElapsedTime(
it / DateUtils.SECOND_IN_MILLIS))
}
data.duration?.let {
- seekBarView.setMax(it)
- totalTimeView.setText(DateUtils.formatElapsedTime(
+ holder.seekBar.setMax(it)
+ holder.totalTimeView.setText(DateUtils.formatElapsedTime(
it / DateUtils.SECOND_IN_MILLIS))
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index 13516a9e03c4..7f7e1085d497 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -170,9 +170,9 @@ public class PipAnimationController {
private final @AnimationType int mAnimationType;
private final Rect mDestinationBounds = new Rect();
- private T mStartValue;
+ protected T mCurrentValue;
+ protected T mStartValue;
private T mEndValue;
- private T mCurrentValue;
private PipAnimationCallback mPipAnimationCallback;
private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
@@ -288,7 +288,6 @@ public class PipAnimationController {
*/
void updateEndValue(T endValue) {
mEndValue = endValue;
- mStartValue = mCurrentValue;
}
SurfaceControl.Transaction newSurfaceControlTransaction() {
@@ -337,6 +336,12 @@ public class PipAnimationController {
tx.show(leash);
tx.apply();
}
+
+ @Override
+ void updateEndValue(Float endValue) {
+ super.updateEndValue(endValue);
+ mStartValue = mCurrentValue;
+ }
};
}
@@ -392,6 +397,14 @@ public class PipAnimationController {
getSurfaceTransactionHelper().resetScale(tx, leash, getDestinationBounds())
.crop(tx, leash, getDestinationBounds());
}
+
+ @Override
+ void updateEndValue(Rect endValue) {
+ super.updateEndValue(endValue);
+ if (mStartValue != null && mCurrentValue != null) {
+ mStartValue.set(mCurrentValue);
+ }
+ }
};
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 394f9975579d..93605170f22e 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -61,12 +61,6 @@ public class PipBoundsHandler {
private final DisplayInfo mDisplayInfo = new DisplayInfo();
private final Rect mTmpInsets = new Rect();
- /**
- * Tracks the destination bounds, used for any following
- * {@link #onMovementBoundsChanged(Rect, Rect, Rect, DisplayInfo)} calculations.
- */
- private final Rect mLastDestinationBounds = new Rect();
-
private ComponentName mLastPipComponentName;
private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
private Size mReentrySize;
@@ -198,11 +192,6 @@ public class PipBoundsHandler {
mReentrySnapFraction = INVALID_SNAP_FRACTION;
mReentrySize = null;
mLastPipComponentName = null;
- mLastDestinationBounds.setEmpty();
- }
-
- public Rect getLastDestinationBounds() {
- return mLastDestinationBounds;
}
public Rect getDisplayBounds() {
@@ -262,7 +251,6 @@ public class PipBoundsHandler {
false /* useCurrentMinEdgeSize */);
}
mAspectRatio = aspectRatio;
- mLastDestinationBounds.set(destinationBounds);
return destinationBounds;
}
@@ -276,8 +264,8 @@ public class PipBoundsHandler {
*
* @return {@code true} if internal {@link DisplayInfo} is rotated, {@code false} otherwise.
*/
- public boolean onDisplayRotationChanged(Rect outBounds, int displayId, int fromRotation,
- int toRotation, WindowContainerTransaction t) {
+ public boolean onDisplayRotationChanged(Rect outBounds, Rect oldBounds, int displayId,
+ int fromRotation, int toRotation, WindowContainerTransaction t) {
// Bail early if the event is not sent to current {@link #mDisplayInfo}
if ((displayId != mDisplayInfo.displayId) || (fromRotation == toRotation)) {
return false;
@@ -295,7 +283,7 @@ public class PipBoundsHandler {
}
// Calculate the snap fraction of the current stack along the old movement bounds
- final Rect postChangeStackBounds = new Rect(mLastDestinationBounds);
+ final Rect postChangeStackBounds = new Rect(oldBounds);
final float snapFraction = getSnapFraction(postChangeStackBounds);
// Populate the new {@link #mDisplayInfo}.
@@ -313,7 +301,6 @@ public class PipBoundsHandler {
snapFraction);
outBounds.set(postChangeStackBounds);
- mLastDestinationBounds.set(outBounds);
t.setBounds(pinnedStackInfo.stackToken, outBounds);
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 78d2d9857628..ae6100675cb4 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -600,6 +600,7 @@ public class PipTaskOrganizer extends TaskOrganizer {
Log.w(TAG, "Abort animation, invalid leash");
return;
}
+ mLastReportedBounds.set(destinationBounds);
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper
.crop(tx, mLeash, destinationBounds)
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 30d6cd9d23b7..64df2ffee1c6 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -95,7 +95,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
private final DisplayChangeController.OnDisplayChangingListener mRotationController = (
int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
final boolean changed = mPipBoundsHandler.onDisplayRotationChanged(mTmpNormalBounds,
- displayId, fromRotation, toRotation, t);
+ mPipTaskOrganizer.getLastReportedBounds(), displayId, fromRotation, toRotation, t);
if (changed) {
updateMovementBounds(mTmpNormalBounds, true /* fromRotation */,
false /* fromImeAdjustment */, false /* fromShelfAdjustment */);
@@ -293,7 +293,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
final boolean changed = mPipBoundsHandler.setShelfHeight(visible, height);
if (changed) {
mTouchHandler.onShelfVisibilityChanged(visible, height);
- updateMovementBounds(mPipBoundsHandler.getLastDestinationBounds(),
+ updateMovementBounds(mPipTaskOrganizer.getLastReportedBounds(),
false /* fromRotation */, false /* fromImeAdjustment */,
true /* fromShelfAdjustment */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 59c962da7baa..af9dd574c8af 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -424,20 +424,28 @@ public class PipTouchHandler {
// If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
// occluded by the IME or shelf.
- if (fromImeAdjustment || fromShelfAdjustment || fromDisplayRotationChanged) {
+ if (fromImeAdjustment || fromShelfAdjustment) {
if (mTouchState.isUserInteracting()) {
// Defer the update of the current movement bounds until after the user finishes
// touching the screen
} else {
final float offsetBufferPx = BOTTOM_OFFSET_BUFFER_DP
* mContext.getResources().getDisplayMetrics().density;
- final Rect toMovementBounds = mMenuState == MENU_STATE_FULL && willResizeMenu()
+ final boolean isExpanded = mMenuState == MENU_STATE_FULL && willResizeMenu();
+ final Rect toMovementBounds = isExpanded
? new Rect(expandedMovementBounds)
: new Rect(normalMovementBounds);
final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets;
final int toBottom = toMovementBounds.bottom < toMovementBounds.top
? toMovementBounds.bottom
: toMovementBounds.bottom - extraOffset;
+
+ if (isExpanded) {
+ curBounds.set(mExpandedBounds);
+ mSnapAlgorithm.applySnapFraction(curBounds, toMovementBounds,
+ mSavedSnapFraction);
+ }
+
if ((Math.min(prevBottom, toBottom) - offsetBufferPx) <= curBounds.top
&& curBounds.top <= (Math.max(prevBottom, toBottom) + offsetBufferPx)) {
mMotionHelper.animateToOffset(curBounds, toBottom - curBounds.top);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 34a9e28b943a..b272b60f3593 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -842,7 +842,26 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
Log.e(TAG_OPS, "Failed to get overview proxy for assistant visibility.");
}
} catch (RemoteException e) {
- Log.e(TAG_OPS, "Failed to call onAssistantVisibilityChanged()", e);
+ Log.e(TAG_OPS, "Failed to call notifyAssistantVisibilityChanged()", e);
+ }
+ }
+
+ /**
+ * Notifies the Launcher of split screen size changes
+ * @param secondaryWindowBounds Bounds of the secondary window including the insets
+ * @param secondaryWindowInsets stable insets received by the secondary window
+ */
+ public void notifySplitScreenBoundsChanged(
+ Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
+ try {
+ if (mOverviewProxy != null) {
+ mOverviewProxy.onSplitScreenSecondaryBoundsChanged(
+ secondaryWindowBounds, secondaryWindowInsets);
+ } else {
+ Log.e(TAG_OPS, "Failed to get overview proxy for split screen bounds.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG_OPS, "Failed to call onSplitScreenSecondaryBoundsChanged()", e);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 839ea69953af..f2d2eb3a0836 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -642,13 +642,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
private void startAnimation(final Consumer<Uri> finisher, int w, int h,
@Nullable Rect screenRect) {
// If power save is on, show a toast so there is some visual indication that a
- // screenshot
- // has been taken.
- PowerManager powerManager = (PowerManager) mContext.getSystemService(
- Context.POWER_SERVICE);
+ // screenshot has been taken.
+ PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
if (powerManager.isPowerSaveMode()) {
- Toast.makeText(mContext, R.string.screenshot_saved_title,
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
}
mScreenshotAnimation = createScreenshotDropInAnimation(w, h, screenRect);
@@ -706,24 +703,27 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mScreenshotPreview.buildLayer();
mScreenshotAnimation.start();
});
-
});
-
}
private AnimatorSet createScreenshotDropInAnimation(int width, int height, Rect bounds) {
+ float screenWidth = mDisplayMetrics.widthPixels;
+ float screenHeight = mDisplayMetrics.heightPixels;
+
int rotation = mContext.getDisplay().getRotation();
float cornerScale;
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
- cornerScale = (mCornerSizeX / (float) height);
+ cornerScale = (mCornerSizeX / screenHeight);
} else {
- cornerScale = (mCornerSizeX / (float) width);
+ cornerScale = (mCornerSizeX / screenWidth);
}
+ float currentScale = width / screenWidth;
- mScreenshotAnimatedView.setScaleX(1);
- mScreenshotAnimatedView.setScaleY(1);
- mScreenshotAnimatedView.setX(0);
- mScreenshotAnimatedView.setY(0);
+ mScreenshotAnimatedView.setScaleX(currentScale);
+ mScreenshotAnimatedView.setScaleY(currentScale);
+
+ mScreenshotAnimatedView.setPivotX(0);
+ mScreenshotAnimatedView.setPivotY(0);
mScreenshotAnimatedView.setImageBitmap(mScreenBitmap);
mScreenshotPreview.setImageBitmap(mScreenBitmap);
@@ -744,12 +744,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
float finalX;
if (mDirectionLTR) {
- finalX = mScreenshotOffsetXPx + width * cornerScale / 2f;
+ finalX = mScreenshotOffsetXPx + screenWidth * cornerScale / 2f;
} else {
- finalX = width - mScreenshotOffsetXPx - width * cornerScale / 2f;
+ finalX = screenWidth - mScreenshotOffsetXPx - screenWidth * cornerScale / 2f;
}
- float finalY =
- mDisplayMetrics.heightPixels - mScreenshotOffsetYPx - height * cornerScale / 2f;
+ float finalY = screenHeight - mScreenshotOffsetYPx - screenHeight * cornerScale / 2f;
final PointF finalPos = new PointF(finalX, finalY);
ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
@@ -757,13 +756,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
float xPositionPct =
SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
float scalePct =
- SCREENSHOT_TO_CORNER_SCALE_DURATION_MS
- / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
+ SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
toCorner.addUpdateListener(animation -> {
float t = animation.getAnimatedFraction();
if (t < scalePct) {
float scale = MathUtils.lerp(
- 1, cornerScale, mFastOutSlowIn.getInterpolation(t / scalePct));
+ currentScale, cornerScale, mFastOutSlowIn.getInterpolation(t / scalePct));
mScreenshotAnimatedView.setScaleX(scale);
mScreenshotAnimatedView.setScaleY(scale);
} else {
@@ -777,13 +775,13 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
if (t < xPositionPct) {
float xCenter = MathUtils.lerp(startPos.x, finalPos.x,
mFastOutSlowIn.getInterpolation(t / xPositionPct));
- mScreenshotAnimatedView.setX(xCenter - width * currentScaleX / 2f);
+ mScreenshotAnimatedView.setX(xCenter - screenWidth * currentScaleX / 2f);
} else {
- mScreenshotAnimatedView.setX(finalPos.x - width * currentScaleX / 2f);
+ mScreenshotAnimatedView.setX(finalPos.x - screenWidth * currentScaleX / 2f);
}
float yCenter = MathUtils.lerp(startPos.y, finalPos.y,
mFastOutSlowIn.getInterpolation(t));
- mScreenshotAnimatedView.setY(yCenter - height * currentScaleY / 2f);
+ mScreenshotAnimatedView.setY(yCenter - screenHeight * currentScaleY / 2f);
});
toCorner.addListener(new AnimatorListenerAdapter() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
index 44b20e535cce..b5209bbbdd21 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
@@ -22,8 +22,8 @@ import android.content.Context;
import android.graphics.drawable.Icon;
import android.util.AttributeSet;
import android.util.Log;
+import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.systemui.R;
@@ -31,7 +31,7 @@ import com.android.systemui.R;
/**
* View for a chip with an icon and text.
*/
-public class ScreenshotActionChip extends LinearLayout {
+public class ScreenshotActionChip extends FrameLayout {
private static final String TAG = "ScreenshotActionChip";
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index cdd1280dd86c..379bb9d247db 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -18,15 +18,10 @@ package com.android.systemui.stackdivider;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
-import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.content.Context;
@@ -36,15 +31,11 @@ import android.os.Handler;
import android.provider.Settings;
import android.util.Slog;
import android.view.LayoutInflater;
-import android.view.SurfaceControl;
import android.view.View;
-import android.window.TaskOrganizer;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowOrganizer;
-import androidx.annotation.Nullable;
-
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
@@ -81,7 +72,6 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
static final boolean DEBUG = false;
static final int DEFAULT_APP_TRANSITION_DURATION = 336;
- static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
private final Optional<Lazy<Recents>> mRecentsOptionalLazy;
@@ -139,303 +129,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
}
};
- private class DividerImeController implements DisplayImeController.ImePositionProcessor {
- /**
- * These are the y positions of the top of the IME surface when it is hidden and when it is
- * shown respectively. These are NOT necessarily the top of the visible IME itself.
- */
- private int mHiddenTop = 0;
- private int mShownTop = 0;
-
- // The following are target states (what we are curretly animating towards).
- /**
- * {@code true} if, at the end of the animation, the split task positions should be
- * adjusted by height of the IME. This happens when the secondary split is the IME target.
- */
- private boolean mTargetAdjusted = false;
- /**
- * {@code true} if, at the end of the animation, the IME should be shown/visible
- * regardless of what has focus.
- */
- private boolean mTargetShown = false;
- private float mTargetPrimaryDim = 0.f;
- private float mTargetSecondaryDim = 0.f;
-
- // The following are the current (most recent) states set during animation
- /** {@code true} if the secondary split has IME focus. */
- private boolean mSecondaryHasFocus = false;
- /** The dimming currently applied to the primary/secondary splits. */
- private float mLastPrimaryDim = 0.f;
- private float mLastSecondaryDim = 0.f;
- /** The most recent y position of the top of the IME surface */
- private int mLastAdjustTop = -1;
-
- // The following are states reached last time an animation fully completed.
- /** {@code true} if the IME was shown/visible by the last-completed animation. */
- private boolean mImeWasShown = false;
- /** {@code true} if the split positions were adjusted by the last-completed animation. */
- private boolean mAdjusted = false;
-
- /**
- * When some aspect of split-screen needs to animate independent from the IME,
- * this will be non-null and control split animation.
- */
- @Nullable
- private ValueAnimator mAnimation = null;
-
- private boolean mPaused = true;
- private boolean mPausedTargetAdjusted = false;
-
- private boolean getSecondaryHasFocus(int displayId) {
- WindowContainerToken imeSplit = TaskOrganizer.getImeTarget(displayId);
- return imeSplit != null
- && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder());
- }
-
- private void updateDimTargets() {
- final boolean splitIsVisible = !mView.isHidden();
- mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible)
- ? ADJUSTED_NONFOCUS_DIM : 0.f;
- mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible)
- ? ADJUSTED_NONFOCUS_DIM : 0.f;
- }
-
- @Override
- public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
- boolean imeShouldShow, SurfaceControl.Transaction t) {
- if (!isDividerVisible()) {
- return;
- }
- final boolean splitIsVisible = !mView.isHidden();
- mSecondaryHasFocus = getSecondaryHasFocus(displayId);
- final boolean targetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus
- && !mSplitLayout.mDisplayLayout.isLandscape();
- mHiddenTop = hiddenTop;
- mShownTop = shownTop;
- mTargetShown = imeShouldShow;
- if (mLastAdjustTop < 0) {
- mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop;
- } else if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) {
- if (mTargetAdjusted != targetAdjusted && targetAdjusted == mAdjusted) {
- // Check for an "interruption" of an existing animation. In this case, we
- // need to fake-flip the last-known state direction so that the animation
- // completes in the other direction.
- mAdjusted = mTargetAdjusted;
- } else if (targetAdjusted && mTargetAdjusted && mAdjusted) {
- // Already fully adjusted for IME, but IME height has changed; so, force-start
- // an async animation to the new IME height.
- mAdjusted = false;
- }
- }
- if (mPaused) {
- mPausedTargetAdjusted = targetAdjusted;
- if (DEBUG) Slog.d(TAG, " ime starting but paused " + dumpState());
- return;
- }
- mTargetAdjusted = targetAdjusted;
- updateDimTargets();
- if (DEBUG) Slog.d(TAG, " ime starting. vis:" + splitIsVisible + " " + dumpState());
- if (mAnimation != null || (mImeWasShown && imeShouldShow
- && mTargetAdjusted != mAdjusted)) {
- // We need to animate adjustment independently of the IME position, so
- // start our own animation to drive adjustment. This happens when a
- // different split's editor has gained focus while the IME is still visible.
- startAsyncAnimation();
- }
- if (splitIsVisible) {
- // If split is hidden, we don't want to trigger any relayouts that would cause the
- // divider to show again.
- updateImeAdjustState();
- }
- }
-
- private void updateImeAdjustState() {
- // Reposition the server's secondary split position so that it evaluates
- // insets properly.
- WindowContainerTransaction wct = new WindowContainerTransaction();
- if (mTargetAdjusted) {
- mSplitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop);
- wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mAdjustedSecondary);
- // "Freeze" the configuration size so that the app doesn't get a config
- // or relaunch. This is required because normally nav-bar contributes
- // to configuration bounds (via nondecorframe).
- Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration
- .windowConfiguration.getAppBounds());
- adjustAppBounds.offset(0, mSplitLayout.mAdjustedSecondary.top
- - mSplitLayout.mSecondary.top);
- wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds);
- wct.setScreenSizeDp(mSplits.mSecondary.token,
- mSplits.mSecondary.configuration.screenWidthDp,
- mSplits.mSecondary.configuration.screenHeightDp);
-
- wct.setBounds(mSplits.mPrimary.token, mSplitLayout.mAdjustedPrimary);
- adjustAppBounds = new Rect(mSplits.mPrimary.configuration
- .windowConfiguration.getAppBounds());
- adjustAppBounds.offset(0, mSplitLayout.mAdjustedPrimary.top
- - mSplitLayout.mPrimary.top);
- wct.setAppBounds(mSplits.mPrimary.token, adjustAppBounds);
- wct.setScreenSizeDp(mSplits.mPrimary.token,
- mSplits.mPrimary.configuration.screenWidthDp,
- mSplits.mPrimary.configuration.screenHeightDp);
- } else {
- wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mSecondary);
- wct.setAppBounds(mSplits.mSecondary.token, null);
- wct.setScreenSizeDp(mSplits.mSecondary.token,
- SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
- wct.setBounds(mSplits.mPrimary.token, mSplitLayout.mPrimary);
- wct.setAppBounds(mSplits.mPrimary.token, null);
- wct.setScreenSizeDp(mSplits.mPrimary.token,
- SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
- }
-
- WindowOrganizer.applyTransaction(wct);
-
- // Update all the adjusted-for-ime states
- if (!mPaused) {
- mView.setAdjustedForIme(mTargetShown, mTargetShown
- ? DisplayImeController.ANIMATION_DURATION_SHOW_MS
- : DisplayImeController.ANIMATION_DURATION_HIDE_MS);
- }
- setAdjustedForIme(mTargetShown && !mPaused);
- }
-
- @Override
- public void onImePositionChanged(int displayId, int imeTop,
- SurfaceControl.Transaction t) {
- if (mAnimation != null || !isDividerVisible() || mPaused) {
- // Not synchronized with IME anymore, so return.
- return;
- }
- final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop);
- final float progress = mTargetShown ? fraction : 1.f - fraction;
- onProgress(progress, t);
- }
-
- @Override
- public void onImeEndPositioning(int displayId, boolean cancelled,
- SurfaceControl.Transaction t) {
- if (mAnimation != null || !isDividerVisible() || mPaused) {
- // Not synchronized with IME anymore, so return.
- return;
- }
- onEnd(cancelled, t);
- }
-
- private void onProgress(float progress, SurfaceControl.Transaction t) {
- if (mTargetAdjusted != mAdjusted && !mPaused) {
- final float fraction = mTargetAdjusted ? progress : 1.f - progress;
- mLastAdjustTop = (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop);
- mSplitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop);
- mView.resizeSplitSurfaces(t, mSplitLayout.mAdjustedPrimary,
- mSplitLayout.mAdjustedSecondary);
- }
- final float invProg = 1.f - progress;
- mView.setResizeDimLayer(t, true /* primary */,
- mLastPrimaryDim * invProg + progress * mTargetPrimaryDim);
- mView.setResizeDimLayer(t, false /* primary */,
- mLastSecondaryDim * invProg + progress * mTargetSecondaryDim);
- }
-
- private void onEnd(boolean cancelled, SurfaceControl.Transaction t) {
- if (!cancelled) {
- onProgress(1.f, t);
- mAdjusted = mTargetAdjusted;
- mImeWasShown = mTargetShown;
- mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop;
- mLastPrimaryDim = mTargetPrimaryDim;
- mLastSecondaryDim = mTargetSecondaryDim;
- }
- }
-
- private void startAsyncAnimation() {
- if (mAnimation != null) {
- mAnimation.cancel();
- }
- mAnimation = ValueAnimator.ofFloat(0.f, 1.f);
- mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS);
- if (mTargetAdjusted != mAdjusted) {
- final float fraction =
- ((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop);
- final float progress = mTargetAdjusted ? fraction : 1.f - fraction;
- mAnimation.setCurrentFraction(progress);
- }
-
- mAnimation.addUpdateListener(animation -> {
- SurfaceControl.Transaction t = mTransactionPool.acquire();
- float value = (float) animation.getAnimatedValue();
- onProgress(value, t);
- t.apply();
- mTransactionPool.release(t);
- });
- mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR);
- mAnimation.addListener(new AnimatorListenerAdapter() {
- private boolean mCancel = false;
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancel = true;
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- SurfaceControl.Transaction t = mTransactionPool.acquire();
- onEnd(mCancel, t);
- t.apply();
- mTransactionPool.release(t);
- mAnimation = null;
- }
- });
- mAnimation.start();
- }
-
- private String dumpState() {
- return "top:" + mHiddenTop + "->" + mShownTop
- + " adj:" + mAdjusted + "->" + mTargetAdjusted + "(" + mLastAdjustTop + ")"
- + " shw:" + mImeWasShown + "->" + mTargetShown
- + " dims:" + mLastPrimaryDim + "," + mLastSecondaryDim
- + "->" + mTargetPrimaryDim + "," + mTargetSecondaryDim
- + " shf:" + mSecondaryHasFocus + " desync:" + (mAnimation != null)
- + " paus:" + mPaused + "[" + mPausedTargetAdjusted + "]";
- }
-
- /** Completely aborts/resets adjustment state */
- public void pause(int displayId) {
- if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState());
- mHandler.post(() -> {
- if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState());
- if (mPaused) {
- return;
- }
- mPaused = true;
- mPausedTargetAdjusted = mTargetAdjusted;
- mTargetAdjusted = false;
- mTargetPrimaryDim = mTargetSecondaryDim = 0.f;
- updateImeAdjustState();
- startAsyncAnimation();
- if (mAnimation != null) {
- mAnimation.end();
- }
- });
- }
-
- public void resume(int displayId) {
- if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState());
- mHandler.post(() -> {
- if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState());
- if (!mPaused) {
- return;
- }
- mPaused = false;
- mTargetAdjusted = mPausedTargetAdjusted;
- updateDimTargets();
- if ((mTargetAdjusted != mAdjusted) && !mMinimized && mView != null) {
- // End unminimize animations since they conflict with adjustment animations.
- mView.finishAnimations();
- }
- updateImeAdjustState();
- startAsyncAnimation();
- });
- }
- }
- private final DividerImeController mImePositionProcessor = new DividerImeController();
+ private final DividerImeController mImePositionProcessor;
private TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
@Override
@@ -465,6 +159,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
mRecentsOptionalLazy = recentsOptionalLazy;
mForcedResizableController = new ForcedResizableInfoActivityController(context, this);
mTransactionPool = transactionPool;
+ mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler);
}
@Override
@@ -830,6 +525,10 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
}
}
+ SplitDisplayLayout getSplitLayout() {
+ return mSplitLayout;
+ }
+
/** @return the container token for the secondary split root task. */
public WindowContainerToken getSecondaryRoot() {
if (mSplits == null || mSplits.mSecondary == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
new file mode 100644
index 000000000000..533fd5fa6352
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java
@@ -0,0 +1,368 @@
+/*
+ * 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.stackdivider;
+
+import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.window.TaskOrganizer;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+import android.window.WindowOrganizer;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.TransactionPool;
+import com.android.systemui.wm.DisplayImeController;
+
+class DividerImeController implements DisplayImeController.ImePositionProcessor {
+ private static final String TAG = "DividerImeController";
+ private static final boolean DEBUG = Divider.DEBUG;
+
+ private static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
+
+ private final SplitScreenTaskOrganizer mSplits;
+ private final TransactionPool mTransactionPool;
+ private final Handler mHandler;
+
+ /**
+ * These are the y positions of the top of the IME surface when it is hidden and when it is
+ * shown respectively. These are NOT necessarily the top of the visible IME itself.
+ */
+ private int mHiddenTop = 0;
+ private int mShownTop = 0;
+
+ // The following are target states (what we are curretly animating towards).
+ /**
+ * {@code true} if, at the end of the animation, the split task positions should be
+ * adjusted by height of the IME. This happens when the secondary split is the IME target.
+ */
+ private boolean mTargetAdjusted = false;
+ /**
+ * {@code true} if, at the end of the animation, the IME should be shown/visible
+ * regardless of what has focus.
+ */
+ private boolean mTargetShown = false;
+ private float mTargetPrimaryDim = 0.f;
+ private float mTargetSecondaryDim = 0.f;
+
+ // The following are the current (most recent) states set during animation
+ /** {@code true} if the secondary split has IME focus. */
+ private boolean mSecondaryHasFocus = false;
+ /** The dimming currently applied to the primary/secondary splits. */
+ private float mLastPrimaryDim = 0.f;
+ private float mLastSecondaryDim = 0.f;
+ /** The most recent y position of the top of the IME surface */
+ private int mLastAdjustTop = -1;
+
+ // The following are states reached last time an animation fully completed.
+ /** {@code true} if the IME was shown/visible by the last-completed animation. */
+ private boolean mImeWasShown = false;
+ /** {@code true} if the split positions were adjusted by the last-completed animation. */
+ private boolean mAdjusted = false;
+
+ /**
+ * When some aspect of split-screen needs to animate independent from the IME,
+ * this will be non-null and control split animation.
+ */
+ @Nullable
+ private ValueAnimator mAnimation = null;
+
+ private boolean mPaused = true;
+ private boolean mPausedTargetAdjusted = false;
+
+ DividerImeController(SplitScreenTaskOrganizer splits, TransactionPool pool, Handler handler) {
+ mSplits = splits;
+ mTransactionPool = pool;
+ mHandler = handler;
+ }
+
+ private DividerView getView() {
+ return mSplits.mDivider.getView();
+ }
+
+ private SplitDisplayLayout getLayout() {
+ return mSplits.mDivider.getSplitLayout();
+ }
+
+ private boolean isDividerVisible() {
+ return mSplits.mDivider.isDividerVisible();
+ }
+
+ private boolean getSecondaryHasFocus(int displayId) {
+ WindowContainerToken imeSplit = TaskOrganizer.getImeTarget(displayId);
+ return imeSplit != null
+ && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder());
+ }
+
+ private void updateDimTargets() {
+ final boolean splitIsVisible = !getView().isHidden();
+ mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible)
+ ? ADJUSTED_NONFOCUS_DIM : 0.f;
+ mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible)
+ ? ADJUSTED_NONFOCUS_DIM : 0.f;
+ }
+
+ @Override
+ public void onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
+ boolean imeShouldShow, SurfaceControl.Transaction t) {
+ if (!isDividerVisible()) {
+ return;
+ }
+ final boolean splitIsVisible = !getView().isHidden();
+ mSecondaryHasFocus = getSecondaryHasFocus(displayId);
+ final boolean targetAdjusted = splitIsVisible && imeShouldShow && mSecondaryHasFocus
+ && !getLayout().mDisplayLayout.isLandscape();
+ mHiddenTop = hiddenTop;
+ mShownTop = shownTop;
+ mTargetShown = imeShouldShow;
+ if (mLastAdjustTop < 0) {
+ mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop;
+ } else if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) {
+ if (mTargetAdjusted != targetAdjusted && targetAdjusted == mAdjusted) {
+ // Check for an "interruption" of an existing animation. In this case, we
+ // need to fake-flip the last-known state direction so that the animation
+ // completes in the other direction.
+ mAdjusted = mTargetAdjusted;
+ } else if (targetAdjusted && mTargetAdjusted && mAdjusted) {
+ // Already fully adjusted for IME, but IME height has changed; so, force-start
+ // an async animation to the new IME height.
+ mAdjusted = false;
+ }
+ }
+ if (mPaused) {
+ mPausedTargetAdjusted = targetAdjusted;
+ if (DEBUG) Slog.d(TAG, " ime starting but paused " + dumpState());
+ return;
+ }
+ mTargetAdjusted = targetAdjusted;
+ updateDimTargets();
+ if (DEBUG) Slog.d(TAG, " ime starting. vis:" + splitIsVisible + " " + dumpState());
+ if (mAnimation != null || (mImeWasShown && imeShouldShow
+ && mTargetAdjusted != mAdjusted)) {
+ // We need to animate adjustment independently of the IME position, so
+ // start our own animation to drive adjustment. This happens when a
+ // different split's editor has gained focus while the IME is still visible.
+ startAsyncAnimation();
+ }
+ if (splitIsVisible) {
+ // If split is hidden, we don't want to trigger any relayouts that would cause the
+ // divider to show again.
+ updateImeAdjustState();
+ }
+ }
+
+ private void updateImeAdjustState() {
+ // Reposition the server's secondary split position so that it evaluates
+ // insets properly.
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ final SplitDisplayLayout splitLayout = getLayout();
+ if (mTargetAdjusted) {
+ splitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop);
+ wct.setBounds(mSplits.mSecondary.token, splitLayout.mAdjustedSecondary);
+ // "Freeze" the configuration size so that the app doesn't get a config
+ // or relaunch. This is required because normally nav-bar contributes
+ // to configuration bounds (via nondecorframe).
+ Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration
+ .windowConfiguration.getAppBounds());
+ adjustAppBounds.offset(0, splitLayout.mAdjustedSecondary.top
+ - splitLayout.mSecondary.top);
+ wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds);
+ wct.setScreenSizeDp(mSplits.mSecondary.token,
+ mSplits.mSecondary.configuration.screenWidthDp,
+ mSplits.mSecondary.configuration.screenHeightDp);
+
+ wct.setBounds(mSplits.mPrimary.token, splitLayout.mAdjustedPrimary);
+ adjustAppBounds = new Rect(mSplits.mPrimary.configuration
+ .windowConfiguration.getAppBounds());
+ adjustAppBounds.offset(0, splitLayout.mAdjustedPrimary.top
+ - splitLayout.mPrimary.top);
+ wct.setAppBounds(mSplits.mPrimary.token, adjustAppBounds);
+ wct.setScreenSizeDp(mSplits.mPrimary.token,
+ mSplits.mPrimary.configuration.screenWidthDp,
+ mSplits.mPrimary.configuration.screenHeightDp);
+ } else {
+ wct.setBounds(mSplits.mSecondary.token, splitLayout.mSecondary);
+ wct.setAppBounds(mSplits.mSecondary.token, null);
+ wct.setScreenSizeDp(mSplits.mSecondary.token,
+ SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
+ wct.setBounds(mSplits.mPrimary.token, splitLayout.mPrimary);
+ wct.setAppBounds(mSplits.mPrimary.token, null);
+ wct.setScreenSizeDp(mSplits.mPrimary.token,
+ SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
+ }
+
+ WindowOrganizer.applyTransaction(wct);
+
+ // Update all the adjusted-for-ime states
+ if (!mPaused) {
+ final DividerView view = getView();
+ if (view != null) {
+ view.setAdjustedForIme(mTargetShown, mTargetShown
+ ? DisplayImeController.ANIMATION_DURATION_SHOW_MS
+ : DisplayImeController.ANIMATION_DURATION_HIDE_MS);
+ }
+ }
+ mSplits.mDivider.setAdjustedForIme(mTargetShown && !mPaused);
+ }
+
+ @Override
+ public void onImePositionChanged(int displayId, int imeTop,
+ SurfaceControl.Transaction t) {
+ if (mAnimation != null || !isDividerVisible() || mPaused) {
+ // Not synchronized with IME anymore, so return.
+ return;
+ }
+ final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop);
+ final float progress = mTargetShown ? fraction : 1.f - fraction;
+ onProgress(progress, t);
+ }
+
+ @Override
+ public void onImeEndPositioning(int displayId, boolean cancelled,
+ SurfaceControl.Transaction t) {
+ if (mAnimation != null || !isDividerVisible() || mPaused) {
+ // Not synchronized with IME anymore, so return.
+ return;
+ }
+ onEnd(cancelled, t);
+ }
+
+ private void onProgress(float progress, SurfaceControl.Transaction t) {
+ final DividerView view = getView();
+ if (mTargetAdjusted != mAdjusted && !mPaused) {
+ final SplitDisplayLayout splitLayout = getLayout();
+ final float fraction = mTargetAdjusted ? progress : 1.f - progress;
+ mLastAdjustTop = (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop);
+ splitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop);
+ view.resizeSplitSurfaces(t, splitLayout.mAdjustedPrimary,
+ splitLayout.mAdjustedSecondary);
+ }
+ final float invProg = 1.f - progress;
+ view.setResizeDimLayer(t, true /* primary */,
+ mLastPrimaryDim * invProg + progress * mTargetPrimaryDim);
+ view.setResizeDimLayer(t, false /* primary */,
+ mLastSecondaryDim * invProg + progress * mTargetSecondaryDim);
+ }
+
+ private void onEnd(boolean cancelled, SurfaceControl.Transaction t) {
+ if (!cancelled) {
+ onProgress(1.f, t);
+ mAdjusted = mTargetAdjusted;
+ mImeWasShown = mTargetShown;
+ mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop;
+ mLastPrimaryDim = mTargetPrimaryDim;
+ mLastSecondaryDim = mTargetSecondaryDim;
+ }
+ }
+
+ private void startAsyncAnimation() {
+ if (mAnimation != null) {
+ mAnimation.cancel();
+ }
+ mAnimation = ValueAnimator.ofFloat(0.f, 1.f);
+ mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS);
+ if (mTargetAdjusted != mAdjusted) {
+ final float fraction =
+ ((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop);
+ final float progress = mTargetAdjusted ? fraction : 1.f - fraction;
+ mAnimation.setCurrentFraction(progress);
+ }
+
+ mAnimation.addUpdateListener(animation -> {
+ SurfaceControl.Transaction t = mTransactionPool.acquire();
+ float value = (float) animation.getAnimatedValue();
+ onProgress(value, t);
+ t.apply();
+ mTransactionPool.release(t);
+ });
+ mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR);
+ mAnimation.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancel = false;
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancel = true;
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ SurfaceControl.Transaction t = mTransactionPool.acquire();
+ onEnd(mCancel, t);
+ t.apply();
+ mTransactionPool.release(t);
+ mAnimation = null;
+ }
+ });
+ mAnimation.start();
+ }
+
+ private String dumpState() {
+ return "top:" + mHiddenTop + "->" + mShownTop
+ + " adj:" + mAdjusted + "->" + mTargetAdjusted + "(" + mLastAdjustTop + ")"
+ + " shw:" + mImeWasShown + "->" + mTargetShown
+ + " dims:" + mLastPrimaryDim + "," + mLastSecondaryDim
+ + "->" + mTargetPrimaryDim + "," + mTargetSecondaryDim
+ + " shf:" + mSecondaryHasFocus + " desync:" + (mAnimation != null)
+ + " paus:" + mPaused + "[" + mPausedTargetAdjusted + "]";
+ }
+
+ /** Completely aborts/resets adjustment state */
+ public void pause(int displayId) {
+ if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState());
+ mHandler.post(() -> {
+ if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState());
+ if (mPaused) {
+ return;
+ }
+ mPaused = true;
+ mPausedTargetAdjusted = mTargetAdjusted;
+ mTargetAdjusted = false;
+ mTargetPrimaryDim = mTargetSecondaryDim = 0.f;
+ updateImeAdjustState();
+ startAsyncAnimation();
+ if (mAnimation != null) {
+ mAnimation.end();
+ }
+ });
+ }
+
+ public void resume(int displayId) {
+ if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState());
+ mHandler.post(() -> {
+ if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState());
+ if (!mPaused) {
+ return;
+ }
+ mPaused = false;
+ mTargetAdjusted = mPausedTargetAdjusted;
+ updateDimTargets();
+ final DividerView view = getView();
+ if ((mTargetAdjusted != mAdjusted) && !mSplits.mDivider.isMinimized() && view != null) {
+ // End unminimize animations since they conflict with adjustment animations.
+ view.finishAnimations();
+ }
+ updateImeAdjustState();
+ startAsyncAnimation();
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index db89cea385b7..8ca50cdddf71 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -19,7 +19,6 @@ package com.android.systemui.stackdivider;
import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
import static android.view.WindowManager.DOCKED_RIGHT;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
import android.animation.AnimationHandler;
import android.animation.Animator;
@@ -39,7 +38,6 @@ import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.Slog;
import android.view.Display;
-import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.SurfaceControl;
@@ -48,10 +46,8 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
-import android.view.ViewRootImpl;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -65,9 +61,10 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.internal.policy.DockedDividerUtils;
+import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.FlingAnimationUtils;
import java.util.function.Consumer;
@@ -120,7 +117,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
private int mStartY;
private int mStartPosition;
private int mDockSide;
- private final int[] mTempInt2 = new int[2];
private boolean mMoving;
private int mTouchSlop;
private boolean mBackgroundLifted;
@@ -148,7 +144,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
private FlingAnimationUtils mFlingAnimationUtils;
private SplitDisplayLayout mSplitLayout;
private DividerCallbacks mCallback;
- private final Rect mStableInsets = new Rect();
private final AnimationHandler mAnimationHandler = new AnimationHandler();
private boolean mGrowRecents;
@@ -336,29 +331,6 @@ public class DividerView extends FrameLayout implements OnTouchListener,
}
@Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- if (isAttachedToWindow()
- && ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL) {
- // Our window doesn't cover entire display, so we use the display frame to re-calculate
- // the insets.
- final InsetsState state = getWindowInsetsController().getState();
- insets = state.calculateInsets(state.getDisplayFrame(),
- null /* ignoringVisibilityState */, insets.isRound(),
- insets.shouldAlwaysConsumeSystemBars(), insets.getDisplayCutout(),
- 0 /* legacySystemUiFlags */,
- SOFT_INPUT_ADJUST_NOTHING, null /* typeSideMap */);
- }
- if (mStableInsets.left != insets.getStableInsetLeft()
- || mStableInsets.top != insets.getStableInsetTop()
- || mStableInsets.right != insets.getStableInsetRight()
- || mStableInsets.bottom != insets.getStableInsetBottom()) {
- mStableInsets.set(insets.getStableInsetLeft(), insets.getStableInsetTop(),
- insets.getStableInsetRight(), insets.getStableInsetBottom());
- }
- return super.onApplyWindowInsets(insets);
- }
-
- @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mFirstLayout) {
@@ -381,6 +353,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
if (changed) {
mWindowManagerProxy.setTouchRegion(new Rect(mHandle.getLeft(), mHandle.getTop(),
mHandle.getRight(), mHandle.getBottom()));
+ notifySplitScreenBoundsChanged();
}
}
@@ -405,19 +378,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
}
public Rect getNonMinimizedSplitScreenSecondaryBounds() {
- calculateBoundsForPosition(mSnapTargetBeforeMinimized.position,
- DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
- mOtherTaskRect.bottom -= mStableInsets.bottom;
- switch (mDockSide) {
- case WindowManager.DOCKED_LEFT:
- mOtherTaskRect.top += mStableInsets.top;
- mOtherTaskRect.right -= mStableInsets.right;
- break;
- case WindowManager.DOCKED_RIGHT:
- mOtherTaskRect.top += mStableInsets.top;
- mOtherTaskRect.left += mStableInsets.left;
- break;
- }
+ mOtherTaskRect.set(mSplitLayout.mSecondary);
return mOtherTaskRect;
}
@@ -681,6 +642,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
saveSnapTargetBeforeMinimized(saveTarget);
}
}
+ notifySplitScreenBoundsChanged();
};
anim.addListener(new AnimatorListenerAdapter() {
@@ -713,6 +675,25 @@ public class DividerView extends FrameLayout implements OnTouchListener,
return anim;
}
+ private void notifySplitScreenBoundsChanged() {
+ mOtherTaskRect.set(mSplitLayout.mSecondary);
+
+ mTmpRect.set(mSplitLayout.mDisplayLayout.stableInsets());
+ switch (mSplitLayout.getPrimarySplitSide()) {
+ case WindowManager.DOCKED_LEFT:
+ mTmpRect.left = 0;
+ break;
+ case WindowManager.DOCKED_RIGHT:
+ mTmpRect.right = 0;
+ break;
+ case WindowManager.DOCKED_TOP:
+ mTmpRect.top = 0;
+ break;
+ }
+ Dependency.get(OverviewProxyService.class)
+ .notifySplitScreenBoundsChanged(mOtherTaskRect, mTmpRect);
+ }
+
private void cancelFlingAnimation() {
if (mCurrentAnimator != null) {
mCurrentAnimator.cancel();
@@ -846,8 +827,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
mDockedStackMinimized = minimized;
if (mSplitLayout.mDisplayLayout.rotation() != mDefaultDisplay.getRotation()) {
// Splitscreen to minimize is about to starts after rotating landscape to seascape,
- // update insets, display info and snap algorithm targets
- WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
+ // update display info and snap algorithm targets
repositionSnapTargetBeforeMinimized();
}
if (mIsInMinimizeInteraction != minimized || mCurrentAnimator != null) {
@@ -1149,7 +1129,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
// Move a right-docked-app to line up with the divider while dragging it
if (mDockSide == DOCKED_RIGHT) {
- mDockedTaskRect.offset(Math.max(position, mStableInsets.left - mDividerSize)
+ mDockedTaskRect.offset(Math.max(position, -mDividerSize)
- mDockedTaskRect.left + mDividerSize, 0);
}
resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect,
@@ -1164,7 +1144,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
// Move a docked app if from the right in position with the divider up to insets
if (mDockSide == DOCKED_RIGHT) {
- mDockedTaskRect.offset(Math.max(position, mStableInsets.left - mDividerSize)
+ mDockedTaskRect.offset(Math.max(position, -mDividerSize)
- mDockedTaskRect.left + mDividerSize, 0);
}
calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide),
@@ -1180,7 +1160,7 @@ public class DividerView extends FrameLayout implements OnTouchListener,
// Move a right-docked-app to line up with the divider while dragging it
if (mDockSide == DOCKED_RIGHT) {
- mDockedTaskRect.offset(position - mStableInsets.left + mDividerSize, 0);
+ mDockedTaskRect.offset(position + mDividerSize, 0);
}
resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
} else if (taskPosition != TASK_POSITION_SAME) {
@@ -1238,34 +1218,10 @@ public class DividerView extends FrameLayout implements OnTouchListener,
float fraction = getSnapAlgorithm().calculateDismissingFraction(position);
fraction = Math.max(0, Math.min(fraction, 1f));
fraction = DIM_INTERPOLATOR.getInterpolation(fraction);
- if (hasInsetsAtDismissTarget(dismissTarget)) {
-
- // Less darkening with system insets.
- fraction *= 0.8f;
- }
return fraction;
}
/**
- * @return true if and only if there are system insets at the location of the dismiss target
- */
- private boolean hasInsetsAtDismissTarget(SnapTarget dismissTarget) {
- if (isHorizontalDivision()) {
- if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) {
- return mStableInsets.top != 0;
- } else {
- return mStableInsets.bottom != 0;
- }
- } else {
- if (dismissTarget == getSnapAlgorithm().getDismissStartTarget()) {
- return mStableInsets.left != 0;
- } else {
- return mStableInsets.right != 0;
- }
- }
- }
-
- /**
* When the snap target is dismissing one side, make sure that the dismissing side doesn't get
* 0 size.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 9f4932e74eaa..8ed69d8fb982 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_MEDIA_FAKE_ARTWORK;
import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_LOCKSCREEN_WALLPAPER;
import static com.android.systemui.statusbar.phone.StatusBar.SHOW_LOCKSCREEN_MEDIA_ARTWORK;
@@ -44,8 +45,6 @@ import android.util.Log;
import android.view.View;
import android.widget.ImageView;
-import androidx.annotation.NonNull;
-
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
@@ -53,8 +52,8 @@ import com.android.systemui.Dumpable;
import com.android.systemui.Interpolators;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.media.MediaData;
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;
@@ -70,6 +69,7 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.Utils;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -79,7 +79,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import dagger.Lazy;
@@ -90,6 +90,7 @@ import dagger.Lazy;
public class NotificationMediaManager implements Dumpable {
private static final String TAG = "NotificationMediaManager";
public static final boolean DEBUG_MEDIA = false;
+ private static final long PAUSED_MEDIA_TIMEOUT = TimeUnit.MINUTES.toMillis(10);
private final StatusBarStateController mStatusBarStateController
= Dependency.get(StatusBarStateController.class);
@@ -106,6 +107,7 @@ public class NotificationMediaManager implements Dumpable {
}
private final NotificationEntryManager mEntryManager;
+ private final MediaDataManager mMediaDataManager;
@Nullable
private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
@@ -117,7 +119,7 @@ public class NotificationMediaManager implements Dumpable {
@Nullable
private LockscreenWallpaper mLockscreenWallpaper;
- private final Executor mMainExecutor;
+ private final DelayableExecutor mMainExecutor;
private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
@@ -130,6 +132,7 @@ public class NotificationMediaManager implements Dumpable {
private MediaController mMediaController;
private String mMediaNotificationKey;
private MediaMetadata mMediaMetadata;
+ private Runnable mMediaTimeoutCancellation;
private BackDropView mBackdrop;
private ImageView mBackdropFront;
@@ -159,11 +162,36 @@ public class NotificationMediaManager implements Dumpable {
if (DEBUG_MEDIA) {
Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
}
+ if (mMediaTimeoutCancellation != null) {
+ mMediaTimeoutCancellation.run();
+ mMediaTimeoutCancellation = null;
+ }
if (state != null) {
if (!isPlaybackActive(state.getState())) {
clearCurrentMediaNotification();
}
findAndUpdateMediaNotifications();
+ scheduleMediaTimeout(state);
+ }
+ }
+
+ private void scheduleMediaTimeout(PlaybackState state) {
+ final NotificationEntry entry;
+ synchronized (mEntryManager) {
+ entry = mEntryManager.getActiveNotificationUnfiltered(mMediaNotificationKey);
+ }
+ if (entry != null) {
+ if (!isPlayingState(state.getState())) {
+ mMediaTimeoutCancellation = mMainExecutor.executeDelayed(() -> {
+ synchronized (mEntryManager) {
+ if (mMediaNotificationKey == null) {
+ return;
+ }
+ mEntryManager.removeNotification(mMediaNotificationKey, null,
+ UNDEFINED_DISMISS_REASON);
+ }
+ }, PAUSED_MEDIA_TIMEOUT);
+ }
}
}
@@ -189,9 +217,10 @@ public class NotificationMediaManager implements Dumpable {
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
- @Main Executor mainExecutor,
+ @Main DelayableExecutor mainExecutor,
DeviceConfigProxy deviceConfig,
- MediaDataManager mediaDataManager) {
+ MediaDataManager mediaDataManager,
+ MediaDeviceManager mediaDeviceManager) {
mContext = context;
mMediaArtworkProcessor = mediaArtworkProcessor;
mKeyguardBypassController = keyguardBypassController;
@@ -205,17 +234,20 @@ public class NotificationMediaManager implements Dumpable {
mNotificationShadeWindowController = notificationShadeWindowController;
mEntryManager = notificationEntryManager;
mMainExecutor = mainExecutor;
+ mMediaDataManager = mediaDataManager;
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@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
@@ -236,6 +268,7 @@ public class NotificationMediaManager implements Dumpable {
int reason) {
onNotificationRemoved(entry.getKey());
mediaDataManager.onNotificationRemoved(entry.getKey());
+ mediaDeviceManager.onNotificationRemoved(entry.getKey());
}
});
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 b08eb9fafe41..ac2a9c118672 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -24,6 +24,7 @@ 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;
@@ -47,6 +48,7 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.concurrent.Executor;
@@ -98,9 +100,10 @@ public interface StatusBarDependenciesModule {
NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
- @Main Executor mainExecutor,
+ @Main DelayableExecutor mainExecutor,
DeviceConfigProxy deviceConfigProxy,
- MediaDataManager mediaDataManager) {
+ MediaDataManager mediaDataManager,
+ MediaDeviceManager mediaDeviceManager) {
return new NotificationMediaManager(
context,
statusBarLazy,
@@ -110,7 +113,8 @@ public interface StatusBarDependenciesModule {
keyguardBypassController,
mainExecutor,
deviceConfigProxy,
- mediaDataManager);
+ mediaDataManager,
+ mediaDeviceManager);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 2be1531850c5..d6471243e053 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -151,16 +151,6 @@ public class NotificationEntryManager implements
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("NotificationEntryManager state:");
- pw.println(" mAllNotifications=");
- if (mAllNotifications.size() == 0) {
- pw.println("null");
- } else {
- int i = 0;
- for (NotificationEntry entry : mAllNotifications) {
- dumpEntry(pw, " ", i, entry);
- i++;
- }
- }
pw.print(" mPendingNotifications=");
if (mPendingNotifications.size() == 0) {
pw.println("null");
@@ -360,8 +350,8 @@ public class NotificationEntryManager implements
private final NotificationHandler mNotifListener = new NotificationHandler() {
@Override
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
- final boolean isUpdateToInflatedNotif = mActiveNotifications.containsKey(sbn.getKey());
- if (isUpdateToInflatedNotif) {
+ final boolean isUpdate = mActiveNotifications.containsKey(sbn.getKey());
+ if (isUpdate) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap);
@@ -452,12 +442,16 @@ public class NotificationEntryManager implements
}
if (!lifetimeExtended) {
// At this point, we are guaranteed the notification will be removed
- abortExistingInflation(key, "removeNotification");
mAllNotifications.remove(pendingEntry);
- mLeakDetector.trackGarbage(pendingEntry);
}
}
- } else {
+ }
+
+ if (!lifetimeExtended) {
+ abortExistingInflation(key, "removeNotification");
+ }
+
+ if (entry != null) {
// If a manager needs to keep the notification around for whatever reason, we
// keep the notification
boolean entryDismissed = entry.isRowDismissed();
@@ -475,8 +469,6 @@ public class NotificationEntryManager implements
if (!lifetimeExtended) {
// At this point, we are guaranteed the notification will be removed
- abortExistingInflation(key, "removeNotification");
- mAllNotifications.remove(entry);
// Ensure any managers keeping the lifetime extended stop managing the entry
cancelLifetimeExtension(entry);
@@ -485,10 +477,13 @@ public class NotificationEntryManager implements
entry.removeRow();
}
+ mAllNotifications.remove(entry);
+
// Let's remove the children if this was a summary
handleGroupSummaryRemoved(key);
removeVisibleNotification(key);
updateNotifications("removeNotificationInternal");
+ mLeakDetector.trackGarbage(entry);
removedByUser |= entryDismissed;
mLogger.logNotifRemoved(entry.getKey(), removedByUser);
@@ -502,7 +497,6 @@ public class NotificationEntryManager implements
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryCleanUp(entry);
}
- mLeakDetector.trackGarbage(entry);
}
}
}
@@ -562,24 +556,17 @@ public class NotificationEntryManager implements
Ranking ranking = new Ranking();
rankingMap.getRanking(key, ranking);
- NotificationEntry entry = mPendingNotifications.get(key);
- if (entry != null) {
- entry.setSbn(notification);
- } else {
- entry = new NotificationEntry(
- notification,
- ranking,
- mFgsFeatureController.isForegroundServiceDismissalEnabled(),
- SystemClock.uptimeMillis());
- mAllNotifications.add(entry);
- mLeakDetector.trackInstance(entry);
- }
-
- abortExistingInflation(key, "addNotification");
-
+ NotificationEntry entry = new NotificationEntry(
+ notification,
+ ranking,
+ mFgsFeatureController.isForegroundServiceDismissalEnabled(),
+ SystemClock.uptimeMillis());
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryBind(entry, notification);
}
+ mAllNotifications.add(entry);
+
+ mLeakDetector.trackInstance(entry);
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryInit(entry);
@@ -594,6 +581,7 @@ public class NotificationEntryManager implements
mInflationCallback);
}
+ abortExistingInflation(key, "addNotification");
mPendingNotifications.put(key, entry);
mLogger.logNotifAdded(entry.getKey());
for (NotificationEntryListener listener : mNotificationEntryListeners) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index b1b6a1c12a0a..3517e245f624 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -92,6 +92,9 @@ public class PropertyAnimator {
AnimatorListenerAdapter listener = properties.getAnimationFinishListener(property);
if (currentValue.equals(newEndValue)) {
// Skip the animation!
+ if (previousAnimator != null) {
+ previousAnimator.cancel();
+ }
if (listener != null) {
listener.onAnimationEnd(null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
index 3ee267362bc0..90d30dcf38ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
@@ -154,12 +154,14 @@ public final class NotifBindPipeline {
* the real work once rather than repeatedly start and cancel it.
*/
private void requestPipelineRun(NotificationEntry entry) {
+ mLogger.logRequestPipelineRun(entry.getKey());
+
final BindEntry bindEntry = getBindEntry(entry);
if (bindEntry.row == null) {
// Row is not managed yet but may be soon. Stop for now.
+ mLogger.logRequestPipelineRowNotSet(entry.getKey());
return;
}
- mLogger.logRequestPipelineRun(entry.getKey());
// Abort any existing pipeline run
mStage.abortStage(entry, bindEntry.row);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index 199730427aec..f26598db27a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.row
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.NotificationLog
import javax.inject.Inject
@@ -48,6 +49,14 @@ class NotifBindPipelineLogger @Inject constructor(
})
}
+ fun logRequestPipelineRowNotSet(notifKey: String) {
+ buffer.log(TAG, WARNING, {
+ str1 = notifKey
+ }, {
+ "Row is not set so pipeline will not run. notif = $str1"
+ })
+ }
+
fun logStartPipeline(notifKey: String) {
buffer.log(TAG, INFO, {
str1 = notifKey
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 e9849ec84987..9925909c3e16 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
@@ -64,6 +64,7 @@ import com.android.systemui.statusbar.policy.SmartReplyView;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
/**
* A frame layout containing the actual payload of the notification, including the contracted,
@@ -518,9 +519,12 @@ public class NotificationContentView extends FrameLayout {
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
updateVisibility();
- if (visibility != VISIBLE) {
+ if (visibility != VISIBLE && !mOnContentViewInactiveListeners.isEmpty()) {
// View is no longer visible so all content views are inactive.
- for (Runnable r : mOnContentViewInactiveListeners.values()) {
+ // Clone list as runnables may modify the list of listeners
+ ArrayList<Runnable> listeners = new ArrayList<>(
+ mOnContentViewInactiveListeners.values());
+ for (Runnable r : listeners) {
r.run();
}
mOnContentViewInactiveListeners.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index 65633a2e209f..e39a4a0c799f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -360,7 +360,6 @@ class NotificationSectionsManager @Inject internal constructor(
}
}
}
- else -> throw IllegalStateException("Cannot find section bucket for view")
}
prev = child
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 3db4b6f7ffbb..e33cc6027c4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5983,6 +5983,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
// ANIMATION_TYPE_ADD
new AnimationFilter()
+ .animateAlpha()
.animateHeight()
.animateTopInset()
.animateY()
@@ -5991,6 +5992,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
// ANIMATION_TYPE_REMOVE
new AnimationFilter()
+ .animateAlpha()
.animateHeight()
.animateTopInset()
.animateY()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index a065b74bda99..2a4475bbe6b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -46,6 +46,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.media.MediaData;
+import com.android.systemui.media.MediaDataManager;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.screenrecord.RecordingController;
@@ -80,14 +82,14 @@ import javax.inject.Inject;
*/
public class PhoneStatusBarPolicy
implements BluetoothController.Callback,
- CommandQueue.Callbacks,
- RotationLockControllerCallback,
- Listener,
- ZenModeController.Callback,
- DeviceProvisionedListener,
- KeyguardStateController.Callback,
- LocationController.LocationChangeCallback,
- RecordingController.RecordingStateChangeCallback {
+ CommandQueue.Callbacks,
+ RotationLockControllerCallback,
+ Listener,
+ ZenModeController.Callback,
+ DeviceProvisionedListener,
+ KeyguardStateController.Callback,
+ LocationController.LocationChangeCallback,
+ RecordingController.RecordingStateChangeCallback, MediaDataManager.Listener {
private static final String TAG = "PhoneStatusBarPolicy";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -108,6 +110,7 @@ public class PhoneStatusBarPolicy
private final String mSlotLocation;
private final String mSlotSensorsOff;
private final String mSlotScreenRecord;
+ private final String mSlotMedia;
private final int mDisplayId;
private final SharedPreferences mSharedPreferences;
private final DateFormatUtil mDateFormatUtil;
@@ -135,6 +138,7 @@ public class PhoneStatusBarPolicy
private final SensorPrivacyController mSensorPrivacyController;
private final RecordingController mRecordingController;
private final RingerModeTracker mRingerModeTracker;
+ private final MediaDataManager mMediaDataManager;
private boolean mZenVisible;
private boolean mVolumeVisible;
@@ -159,6 +163,7 @@ public class PhoneStatusBarPolicy
SensorPrivacyController sensorPrivacyController, IActivityManager iActivityManager,
AlarmManager alarmManager, UserManager userManager,
RecordingController recordingController,
+ MediaDataManager mediaDataManager,
@Nullable TelecomManager telecomManager, @DisplayId int displayId,
@Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
RingerModeTracker ringerModeTracker) {
@@ -185,6 +190,7 @@ public class PhoneStatusBarPolicy
mUiBgExecutor = uiBgExecutor;
mTelecomManager = telecomManager;
mRingerModeTracker = ringerModeTracker;
+ mMediaDataManager = mediaDataManager;
mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -202,6 +208,7 @@ public class PhoneStatusBarPolicy
mSlotSensorsOff = resources.getString(com.android.internal.R.string.status_bar_sensors_off);
mSlotScreenRecord = resources.getString(
com.android.internal.R.string.status_bar_screen_record);
+ mSlotMedia = resources.getString(com.android.internal.R.string.status_bar_media);
mDisplayId = displayId;
mSharedPreferences = sharedPreferences;
@@ -280,6 +287,11 @@ public class PhoneStatusBarPolicy
mIconController.setIconVisibility(mSlotSensorsOff,
mSensorPrivacyController.isSensorPrivacyEnabled());
+ // play/pause icon when media is active
+ mIconController.setIcon(mSlotMedia, R.drawable.stat_sys_media,
+ mResources.getString(R.string.accessibility_media_active));
+ mIconController.setIconVisibility(mSlotMedia, mMediaDataManager.hasActiveMedia());
+
// screen record
mIconController.setIcon(mSlotScreenRecord, R.drawable.stat_sys_screen_record, null);
mIconController.setIconVisibility(mSlotScreenRecord, false);
@@ -296,6 +308,7 @@ public class PhoneStatusBarPolicy
mSensorPrivacyController.addCallback(mSensorPrivacyListener);
mLocationController.addCallback(this);
mRecordingController.addCallback(this);
+ mMediaDataManager.addListener(this);
mCommandQueue.addCallback(this);
}
@@ -700,4 +713,18 @@ public class PhoneStatusBarPolicy
if (DEBUG) Log.d(TAG, "screenrecord: hiding icon");
mHandler.post(() -> mIconController.setIconVisibility(mSlotScreenRecord, false));
}
+
+ @Override
+ public void onMediaDataLoaded(String key, MediaData data) {
+ updateMediaIcon();
+ }
+
+ @Override
+ public void onMediaDataRemoved(String key) {
+ updateMediaIcon();
+ }
+
+ private void updateMediaIcon() {
+ mIconController.setIconVisibility(mSlotMedia, mMediaDataManager.hasActiveMedia());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
index ba323271faaa..de9c745cb357 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationButtonController.java
@@ -37,8 +37,9 @@ import android.view.View;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.UiEventLoggerImpl;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -60,7 +61,7 @@ public class RotationButtonController {
private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
- private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+ private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
private final ViewRippler mViewRippler = new ViewRippler();
private @StyleRes int mStyleRes;
@@ -323,7 +324,7 @@ public class RotationButtonController {
}
private void onRotateSuggestionClick(View v) {
- mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED);
+ mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED);
incrementNumAcceptedRotationSuggestionsIfNeeded();
setRotationLockedAtAngle(mLastRotationSuggestion);
}
@@ -345,7 +346,7 @@ public class RotationButtonController {
private void showAndLogRotationSuggestion() {
setRotateSuggestionButtonState(true /* visible */);
rescheduleRotationTimeout(false /* reasonHover */);
- mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
+ mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_SHOWN);
}
private boolean shouldOverrideUserLockPrefs(final int rotation) {
@@ -474,4 +475,19 @@ public class RotationButtonController {
}
};
}
+
+ enum RotationButtonEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The rotation button was shown")
+ ROTATION_SUGGESTION_SHOWN(206),
+ @UiEvent(doc = "The rotation button was clicked")
+ ROTATION_SUGGESTION_ACCEPTED(207);
+
+ private final int mId;
+ RotationButtonEvent(int id) {
+ mId = id;
+ }
+ @Override public int getId() {
+ return mId;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index 378dde284747..708b5a7a45f7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -21,16 +21,17 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
-import android.os.Handler;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -49,8 +50,9 @@ public class ProximitySensor {
private String mTag = null;
@VisibleForTesting ProximityEvent mLastEvent;
private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;
- private boolean mPaused;
+ @VisibleForTesting protected boolean mPaused;
private boolean mRegistered;
+ private final AtomicBoolean mAlerting = new AtomicBoolean();
private SensorEventListener mSensorEventListener = new SensorEventListener() {
@Override
@@ -217,8 +219,12 @@ public class ProximitySensor {
/** Update all listeners with the last value this class received from the sensor. */
public void alertListeners() {
+ if (mAlerting.getAndSet(true)) {
+ return;
+ }
mListeners.forEach(proximitySensorListener ->
proximitySensorListener.onSensorEvent(mLastEvent));
+ mAlerting.set(false);
}
private void onSensorEvent(SensorEvent event) {
@@ -239,14 +245,14 @@ public class ProximitySensor {
public static class ProximityCheck implements Runnable {
private final ProximitySensor mSensor;
- private final Handler mHandler;
+ private final DelayableExecutor mDelayableExecutor;
private List<Consumer<Boolean>> mCallbacks = new ArrayList<>();
@Inject
- public ProximityCheck(ProximitySensor sensor, Handler handler) {
+ public ProximityCheck(ProximitySensor sensor, DelayableExecutor delayableExecutor) {
mSensor = sensor;
mSensor.setTag("prox_check");
- mHandler = handler;
+ mDelayableExecutor = delayableExecutor;
mSensor.pause();
ProximitySensorListener listener = proximityEvent -> {
mCallbacks.forEach(
@@ -280,7 +286,7 @@ public class ProximitySensor {
mCallbacks.add(callback);
if (!mSensor.isRegistered()) {
mSensor.resume();
- mHandler.postDelayed(this, timeoutMs);
+ mDelayableExecutor.executeDelayed(this, timeoutMs);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
index c7e9accce093..2968b92f51b7 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -178,6 +178,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
ValueAnimator mAnimation = null;
int mRotation = Surface.ROTATION_0;
boolean mImeShowing = false;
+ final Rect mImeFrame = new Rect();
PerDisplay(int displayId, int initialRotation) {
mDisplayId = displayId;
@@ -254,8 +255,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
}
- private int imeTop(InsetsSource imeSource, float surfaceOffset) {
- return imeSource.getFrame().top + (int) surfaceOffset;
+ private int imeTop(float surfaceOffset) {
+ return mImeFrame.top + (int) surfaceOffset;
}
private void startAnimation(final boolean show, final boolean forceRestart) {
@@ -263,6 +264,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
if (imeSource == null || mImeSourceControl == null) {
return;
}
+ // Set frame, but only if the new frame isn't empty -- this maintains continuity
+ final Rect newFrame = imeSource.getFrame();
+ if (newFrame.height() != 0) {
+ mImeFrame.set(newFrame);
+ }
mHandler.post(() -> {
if (DEBUG) {
Slog.d(TAG, "Run startAnim show:" + show + " was:"
@@ -284,7 +290,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
final float defaultY = mImeSourceControl.getSurfacePosition().y;
final float x = mImeSourceControl.getSurfacePosition().x;
- final float hiddenY = defaultY + imeSource.getFrame().height();
+ final float hiddenY = defaultY + mImeFrame.height();
final float shownY = defaultY;
final float startY = show ? hiddenY : shownY;
final float endY = show ? shownY : hiddenY;
@@ -306,7 +312,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
SurfaceControl.Transaction t = mTransactionPool.acquire();
float value = (float) animation.getAnimatedValue();
t.setPosition(mImeSourceControl.getLeash(), x, value);
- dispatchPositionChanged(mDisplayId, imeTop(imeSource, value), t);
+ dispatchPositionChanged(mDisplayId, imeTop(value), t);
t.apply();
mTransactionPool.release(t);
});
@@ -319,11 +325,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
t.setPosition(mImeSourceControl.getLeash(), x, startY);
if (DEBUG) {
Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:"
- + imeTop(imeSource, hiddenY) + "->" + imeTop(imeSource, shownY)
+ + imeTop(hiddenY) + "->" + imeTop(shownY)
+ " showing:" + (mAnimationDirection == DIRECTION_SHOW));
}
- dispatchStartPositioning(mDisplayId, imeTop(imeSource, hiddenY),
- imeTop(imeSource, shownY), mAnimationDirection == DIRECTION_SHOW,
+ dispatchStartPositioning(mDisplayId, imeTop(hiddenY),
+ imeTop(shownY), mAnimationDirection == DIRECTION_SHOW,
t);
if (mAnimationDirection == DIRECTION_SHOW) {
t.show(mImeSourceControl.getLeash());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 50b7af27f7d9..e0049d1349f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.appops;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -29,9 +30,8 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static java.lang.Thread.sleep;
-
import android.app.AppOpsManager;
+import android.os.Looper;
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -229,12 +229,7 @@ public class AppOpsControllerTest extends SysuiTestCase {
@Test
public void testActiveOpNotRemovedAfterNoted() throws InterruptedException {
// Replaces the timeout delay with 5 ms
- AppOpsControllerImpl.H testHandler = mController.new H(mTestableLooper.getLooper()) {
- @Override
- public void scheduleRemoval(AppOpItem item, long timeToRemoval) {
- super.scheduleRemoval(item, 5L);
- }
- };
+ TestHandler testHandler = new TestHandler(mTestableLooper.getLooper());
mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
mController.setBGHandler(testHandler);
@@ -245,6 +240,10 @@ public class AppOpsControllerTest extends SysuiTestCase {
mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
AppOpsManager.MODE_ALLOWED);
+ // Check that we "scheduled" the removal. Don't actually schedule until we are ready to
+ // process messages at a later time.
+ assertNotNull(testHandler.mDelayScheduled);
+
mTestableLooper.processAllMessages();
List<AppOpItem> list = mController.getActiveAppOps();
verify(mCallback).onActiveStateChanged(
@@ -253,8 +252,8 @@ public class AppOpsControllerTest extends SysuiTestCase {
// Duplicates are not removed between active and noted
assertEquals(2, list.size());
- sleep(10L);
-
+ // Now is later, so we can schedule delayed messages.
+ testHandler.scheduleDelayed();
mTestableLooper.processAllMessages();
verify(mCallback, never()).onActiveStateChanged(
@@ -321,4 +320,24 @@ public class AppOpsControllerTest extends SysuiTestCase {
verify(mCallback).onActiveStateChanged(
AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
}
+
+ private class TestHandler extends AppOpsControllerImpl.H {
+ TestHandler(Looper looper) {
+ mController.super(looper);
+ }
+
+ Runnable mDelayScheduled;
+
+ void scheduleDelayed() {
+ if (mDelayScheduled != null) {
+ mDelayScheduled.run();
+ mDelayScheduled = null;
+ }
+ }
+
+ @Override
+ public void scheduleRemoval(AppOpItem item, long timeToRemoval) {
+ mDelayScheduled = () -> super.scheduleRemoval(item, 0L);
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 317500cf5b02..a5675360a57e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -18,6 +18,7 @@ package com.android.systemui.doze;
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
+import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -46,6 +47,7 @@ import com.android.systemui.doze.DozeSensors.TriggerSensor;
import com.android.systemui.plugins.SensorManagerPlugin;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.wakelock.WakeLock;
import org.junit.Before;
@@ -82,7 +84,7 @@ public class DozeSensorsTest extends SysuiTestCase {
@Mock
private DozeLog mDozeLog;
@Mock
- private Sensor mProximitySensor;
+ private ProximitySensor mProximitySensor;
private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
private TestableLooper mTestableLooper;
private DozeSensors mDozeSensors;
@@ -93,7 +95,6 @@ public class DozeSensorsTest extends SysuiTestCase {
mTestableLooper = TestableLooper.get(this);
when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
- when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(mProximitySensor);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
@@ -103,10 +104,9 @@ public class DozeSensorsTest extends SysuiTestCase {
@Test
public void testRegisterProx() {
- // We should not register with the sensor manager initially.
- verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt());
+ assertFalse(mProximitySensor.isRegistered());
mDozeSensors.setProxListening(true);
- verify(mSensorManager).registerListener(any(), any(Sensor.class), anyInt());
+ verify(mProximitySensor).resume();
}
@Test
@@ -169,7 +169,8 @@ public class DozeSensorsTest extends SysuiTestCase {
TestableDozeSensors() {
super(getContext(), mAlarmManager, mSensorManager, mDozeParameters,
- mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog);
+ mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
+ mProximitySensor);
for (TriggerSensor sensor : mSensors) {
if (sensor instanceof PluginSensor
&& ((PluginSensor) sensor).mPluginSensor.getType()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index debc9d6430e0..73aaeffd6044 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -31,7 +31,6 @@ import android.app.AlarmManager;
import android.hardware.Sensor;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
-import android.os.Looper;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -42,10 +41,12 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dock.DockManager;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.sensors.FakeProximitySensor;
import com.android.systemui.util.sensors.FakeSensorManager;
import com.android.systemui.util.sensors.ProximitySensor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.wakelock.WakeLock;
import com.android.systemui.util.wakelock.WakeLockFake;
@@ -75,6 +76,7 @@ public class DozeTriggersTest extends SysuiTestCase {
private FakeSensorManager mSensors;
private Sensor mTapSensor;
private FakeProximitySensor mProximitySensor;
+ private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
@Before
public void setUp() throws Exception {
@@ -89,7 +91,7 @@ public class DozeTriggersTest extends SysuiTestCase {
mProximitySensor = new FakeProximitySensor(getContext().getResources(), asyncSensorManager);
mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, config, parameters,
- asyncSensorManager, Handler.createAsync(Looper.myLooper()), wakeLock, true,
+ asyncSensorManager, mFakeExecutor, wakeLock, true,
mDockManager, mProximitySensor, mock(DozeLog.class), mBroadcastDispatcher);
waitForSensorManager();
}
@@ -111,9 +113,8 @@ public class DozeTriggersTest extends SysuiTestCase {
verify(mMachine, never()).requestState(any());
verify(mMachine, never()).requestPulse(anyInt());
- captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
- waitForSensorManager();
mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(false, 2));
+ captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
mProximitySensor.alertListeners();
verify(mMachine).requestPulse(anyInt());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index ee7733aa2be7..4a8598009381 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -53,6 +53,7 @@ import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.controls.controller.ControlsController;
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsUiController;
+import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.statusbar.BlurUtils;
@@ -107,6 +108,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase {
@Mock private UiEventLogger mUiEventLogger;
@Mock private RingerModeTracker mRingerModeTracker;
@Mock private RingerModeLiveData mRingerModeLiveData;
+ @Mock private SysUiState mSysUiState;
@Mock private Handler mHandler;
private TestableLooper mTestableLooper;
@@ -150,6 +152,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase {
mControlsController,
mUiEventLogger,
mRingerModeTracker,
+ mSysUiState,
mHandler
);
mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
new file mode 100644
index 000000000000..e8fb41a18ce9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.content.res.ColorStateList
+import android.graphics.Color
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.RippleDrawable
+import android.media.MediaMetadata
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.SeekBar
+import android.widget.TextView
+
+import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.constraintlayout.motion.widget.MotionScene
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when` as whenever
+
+import java.util.ArrayList
+
+private const val KEY = "TEST_KEY"
+private const val APP = "APP"
+private const val BG_COLOR = Color.RED
+private const val PACKAGE = "PKG"
+private const val ARTIST = "ARTIST"
+private const val TITLE = "TITLE"
+private const val DEVICE_NAME = "DEVICE_NAME"
+private const val SESSION_KEY = "SESSION_KEY"
+private const val SESSION_ARTIST = "SESSION_ARTIST"
+private const val SESSION_TITLE = "SESSION_TITLE"
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class MediaControlPanelTest : SysuiTestCase() {
+
+ private lateinit var player: MediaControlPanel
+
+ private lateinit var fgExecutor: FakeExecutor
+ private lateinit var bgExecutor: FakeExecutor
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ @Mock private lateinit var holder: PlayerViewHolder
+ @Mock private lateinit var motion: MotionLayout
+ private lateinit var background: TextView
+ private lateinit var appIcon: ImageView
+ private lateinit var appName: TextView
+ private lateinit var albumView: ImageView
+ private lateinit var titleText: TextView
+ private lateinit var artistText: TextView
+ private lateinit var seamless: ViewGroup
+ private lateinit var seamlessIcon: ImageView
+ private lateinit var seamlessText: TextView
+ private lateinit var seekBar: SeekBar
+ private lateinit var elapsedTimeView: TextView
+ private lateinit var totalTimeView: TextView
+ private lateinit var action0: ImageButton
+ private lateinit var action1: ImageButton
+ private lateinit var action2: ImageButton
+ private lateinit var action3: ImageButton
+ private lateinit var action4: ImageButton
+
+ private lateinit var session: MediaSession
+
+ @Before
+ fun setUp() {
+ fgExecutor = FakeExecutor(FakeSystemClock())
+ bgExecutor = FakeExecutor(FakeSystemClock())
+
+ activityStarter = mock(ActivityStarter::class.java)
+
+ player = MediaControlPanel(context, fgExecutor, bgExecutor, activityStarter)
+
+ // Mock out a view holder for the player to attach to.
+ holder = mock(PlayerViewHolder::class.java)
+ motion = mock(MotionLayout::class.java)
+ val trans: ArrayList<MotionScene.Transition> = ArrayList()
+ trans.add(mock(MotionScene.Transition::class.java))
+ whenever(motion.definedTransitions).thenReturn(trans)
+ val constraintSet = mock(ConstraintSet::class.java)
+ whenever(motion.getConstraintSet(R.id.expanded)).thenReturn(constraintSet)
+ whenever(motion.getConstraintSet(R.id.collapsed)).thenReturn(constraintSet)
+ whenever(holder.player).thenReturn(motion)
+ background = TextView(context)
+ whenever(holder.background).thenReturn(background)
+ appIcon = ImageView(context)
+ whenever(holder.appIcon).thenReturn(appIcon)
+ appName = TextView(context)
+ whenever(holder.appName).thenReturn(appName)
+ albumView = ImageView(context)
+ whenever(holder.albumView).thenReturn(albumView)
+ titleText = TextView(context)
+ whenever(holder.titleText).thenReturn(titleText)
+ artistText = TextView(context)
+ whenever(holder.artistText).thenReturn(artistText)
+ seamless = FrameLayout(context)
+ val seamlessBackground = mock(RippleDrawable::class.java)
+ seamless.setBackground(seamlessBackground)
+ whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
+ whenever(holder.seamless).thenReturn(seamless)
+ seamlessIcon = ImageView(context)
+ whenever(holder.seamlessIcon).thenReturn(seamlessIcon)
+ seamlessText = TextView(context)
+ whenever(holder.seamlessText).thenReturn(seamlessText)
+ seekBar = SeekBar(context)
+ whenever(holder.seekBar).thenReturn(seekBar)
+ elapsedTimeView = TextView(context)
+ whenever(holder.elapsedTimeView).thenReturn(elapsedTimeView)
+ totalTimeView = TextView(context)
+ whenever(holder.totalTimeView).thenReturn(totalTimeView)
+ action0 = ImageButton(context)
+ whenever(holder.action0).thenReturn(action0)
+ action1 = ImageButton(context)
+ whenever(holder.action1).thenReturn(action1)
+ action2 = ImageButton(context)
+ whenever(holder.action2).thenReturn(action2)
+ action3 = ImageButton(context)
+ whenever(holder.action3).thenReturn(action3)
+ action4 = ImageButton(context)
+ whenever(holder.action4).thenReturn(action4)
+
+ // Create media session
+ val metadataBuilder = MediaMetadata.Builder().apply {
+ putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+ putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+ }
+ val 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)
+ }
+
+ @After
+ fun tearDown() {
+ session.release()
+ player.onDestroy()
+ }
+
+ @Test
+ fun bindWhenUnattached() {
+ val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ emptyList(), PACKAGE, null, null, MediaDeviceData(null, DEVICE_NAME))
+ player.bind(state)
+ assertThat(player.isPlaying()).isFalse()
+ }
+
+ @Test
+ fun bindText() {
+ player.attach(holder)
+ val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ emptyList(), PACKAGE, session.getSessionToken(), null,
+ MediaDeviceData(null, DEVICE_NAME))
+ player.bind(state)
+ assertThat(appName.getText()).isEqualTo(APP)
+ assertThat(titleText.getText()).isEqualTo(TITLE)
+ assertThat(artistText.getText()).isEqualTo(ARTIST)
+ }
+
+ @Test
+ fun bindBackgroundColor() {
+ player.attach(holder)
+ val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ emptyList(), PACKAGE, session.getSessionToken(), null,
+ MediaDeviceData(null, DEVICE_NAME))
+ player.bind(state)
+ assertThat(background.getBackgroundTintList()).isEqualTo(ColorStateList.valueOf(BG_COLOR))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
new file mode 100644
index 000000000000..64a180f8aaab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.graphics.Color;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class MediaDataCombineLatestTest extends SysuiTestCase {
+
+ private static final String KEY = "TEST_KEY";
+ private static final String APP = "APP";
+ private static final String PACKAGE = "PKG";
+ private static final int BG_COLOR = Color.RED;
+ private static final String ARTIST = "ARTIST";
+ private static final String TITLE = "TITLE";
+ private static final String DEVICE_NAME = "DEVICE_NAME";
+
+ private MediaDataCombineLatest mManager;
+
+ @Mock private MediaDataManager mDataSource;
+ @Mock private MediaDeviceManager mDeviceSource;
+ @Mock private MediaDataManager.Listener mListener;
+
+ private MediaDataManager.Listener mDataListener;
+ private MediaDeviceManager.Listener mDeviceListener;
+
+ private MediaData mMediaData;
+ private MediaDeviceData mDeviceData;
+
+ @Before
+ public void setUp() {
+ mDataSource = mock(MediaDataManager.class);
+ mDeviceSource = mock(MediaDeviceManager.class);
+ mListener = mock(MediaDataManager.Listener.class);
+
+ mManager = new MediaDataCombineLatest(mDataSource, mDeviceSource);
+
+ mDataListener = captureDataListener();
+ mDeviceListener = captureDeviceListener();
+
+ mManager.addListener(mListener);
+
+ mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null,
+ new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null);
+ mDeviceData = new MediaDeviceData(null, DEVICE_NAME);
+ }
+
+ @Test
+ public void eventNotEmittedWithoutDevice() {
+ // WHEN data source emits an event without device data
+ mDataListener.onMediaDataLoaded(KEY, mMediaData);
+ // THEN an event isn't emitted
+ verify(mListener, never()).onMediaDataLoaded(eq(KEY), any());
+ }
+
+ @Test
+ public void eventNotEmittedWithoutMedia() {
+ // 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());
+ }
+
+ @Test
+ public void emitEventAfterDeviceFirst() {
+ // GIVEN that a device event has already been received
+ mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+ // WHEN media event is received
+ mDataListener.onMediaDataLoaded(KEY, mMediaData);
+ // THEN the listener receives a combined event
+ ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
+ verify(mListener).onMediaDataLoaded(eq(KEY), captor.capture());
+ assertThat(captor.getValue().getDevice()).isNotNull();
+ }
+
+ @Test
+ public void emitEventAfterMediaFirst() {
+ // GIVEN that media event has already been received
+ mDataListener.onMediaDataLoaded(KEY, 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());
+ assertThat(captor.getValue().getDevice()).isNotNull();
+ }
+
+ @Test
+ public void mediaDataRemoved() {
+ // WHEN media data is removed without first receiving device or data
+ mDataListener.onMediaDataRemoved(KEY);
+ // THEN a removed event isn't emitted
+ verify(mListener, never()).onMediaDataRemoved(eq(KEY));
+ }
+
+ @Test
+ public void mediaDataRemovedAfterMediaEvent() {
+ mDataListener.onMediaDataLoaded(KEY, mMediaData);
+ mDataListener.onMediaDataRemoved(KEY);
+ verify(mListener).onMediaDataRemoved(eq(KEY));
+ }
+
+ @Test
+ public void mediaDataRemovedAfterDeviceEvent() {
+ mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+ mDataListener.onMediaDataRemoved(KEY);
+ verify(mListener).onMediaDataRemoved(eq(KEY));
+ }
+
+ private MediaDataManager.Listener captureDataListener() {
+ ArgumentCaptor<MediaDataManager.Listener> captor = ArgumentCaptor.forClass(
+ MediaDataManager.Listener.class);
+ verify(mDataSource).addListener(captor.capture());
+ return captor.getValue();
+ }
+
+ private MediaDeviceManager.Listener captureDeviceListener() {
+ ArgumentCaptor<MediaDeviceManager.Listener> captor = ArgumentCaptor.forClass(
+ MediaDeviceManager.Listener.class);
+ verify(mDeviceSource).addListener(captor.capture());
+ return captor.getValue();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
new file mode 100644
index 000000000000..ac6b5f6bca66
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.Notification
+import android.media.MediaMetadata
+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
+
+import com.android.settingslib.media.LocalMediaManager
+import com.android.settingslib.media.MediaDevice
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+private const val KEY = "TEST_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 const val DEVICE_NAME = "DEVICE_NAME"
+
+private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+public class MediaDeviceManagerTest : SysuiTestCase() {
+
+ private lateinit var manager: MediaDeviceManager
+
+ @Mock private lateinit var lmmFactory: LocalMediaManagerFactory
+ @Mock private lateinit var lmm: LocalMediaManager
+ @Mock private lateinit var featureFlag: MediaFeatureFlag
+ private lateinit var fakeExecutor: FakeExecutor
+
+ @Mock private lateinit var device: MediaDevice
+ private lateinit var session: MediaSession
+ private lateinit var metadataBuilder: MediaMetadata.Builder
+ private lateinit var playbackBuilder: PlaybackState.Builder
+ private lateinit var notifBuilder: Notification.Builder
+ private lateinit var sbn: StatusBarNotification
+
+ @Before
+ fun setup() {
+ lmmFactory = mock(LocalMediaManagerFactory::class.java)
+ lmm = mock(LocalMediaManager::class.java)
+ device = mock(MediaDevice::class.java)
+ whenever(device.name).thenReturn(DEVICE_NAME)
+ whenever(lmmFactory.create(PACKAGE)).thenReturn(lmm)
+ whenever(lmm.getCurrentConnectedDevice()).thenReturn(device)
+ featureFlag = mock(MediaFeatureFlag::class.java)
+ whenever(featureFlag.enabled).thenReturn(true)
+
+ fakeExecutor = FakeExecutor(FakeSystemClock())
+
+ manager = MediaDeviceManager(context, lmmFactory, featureFlag, fakeExecutor)
+
+ // Create a media sesssion 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)
+ notifBuilder = Notification.Builder(context, "NONE").apply {
+ setContentTitle(SESSION_TITLE)
+ setContentText(SESSION_ARTIST)
+ 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)
+ }
+
+ @After
+ fun tearDown() {
+ session.release()
+ }
+
+ @Test
+ fun removeUnknown() {
+ manager.onNotificationRemoved("unknown")
+ }
+
+ @Test
+ fun addNotification() {
+ manager.onNotificationAdded(KEY, sbn)
+ verify(lmmFactory).create(PACKAGE)
+ }
+
+ @Test
+ fun featureDisabled() {
+ whenever(featureFlag.enabled).thenReturn(false)
+ manager.onNotificationAdded(KEY, sbn)
+ verify(lmmFactory, never()).create(PACKAGE)
+ }
+
+ @Test
+ fun addAndRemoveNotification() {
+ manager.onNotificationAdded(KEY, sbn)
+ manager.onNotificationRemoved(KEY)
+ verify(lmm).unregisterCallback(any())
+ }
+
+ @Test
+ fun deviceListUpdate() {
+ val listener = mock(MediaDeviceManager.Listener::class.java)
+ manager.addListener(listener)
+ manager.onNotificationAdded(KEY, sbn)
+ val deviceCallback = captureCallback()
+ // WHEN the device list changes
+ deviceCallback.onDeviceListUpdate(mutableListOf(device))
+ assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
+ // THEN the update is dispatched to the listener
+ val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java)
+ verify(listener).onMediaDeviceChanged(eq(KEY), captor.capture())
+ val data = captor.getValue()
+ assertThat(data.name).isEqualTo(DEVICE_NAME)
+ }
+
+ @Test
+ fun selectedDeviceStateChanged() {
+ val listener = mock(MediaDeviceManager.Listener::class.java)
+ manager.addListener(listener)
+ manager.onNotificationAdded(KEY, sbn)
+ val deviceCallback = captureCallback()
+ // WHEN the selected device changes state
+ deviceCallback.onSelectedDeviceStateChanged(device, 1)
+ assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
+ // THEN the update is dispatched to the listener
+ val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java)
+ verify(listener).onMediaDeviceChanged(eq(KEY), captor.capture())
+ val data = captor.getValue()
+ assertThat(data.name).isEqualTo(DEVICE_NAME)
+ }
+
+ @Test
+ fun listenerReceivesKeyRemoved() {
+ manager.onNotificationAdded(KEY, sbn)
+ val listener = mock(MediaDeviceManager.Listener::class.java)
+ manager.addListener(listener)
+ // WHEN the notification is removed
+ manager.onNotificationRemoved(KEY)
+ // THEN the listener receives key removed event
+ verify(listener).onKeyRemoved(eq(KEY))
+ }
+
+ fun captureCallback(): LocalMediaManager.DeviceCallback {
+ val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java)
+ verify(lmm).registerCallback(captor.capture())
+ return captor.getValue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt
new file mode 100644
index 000000000000..767852582dc3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.FrameLayout
+
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for PlayerViewHolder.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class PlayerViewHolderTest : SysuiTestCase() {
+
+ private lateinit var inflater: LayoutInflater
+ private lateinit var parent: ViewGroup
+
+ @Before
+ fun setUp() {
+ inflater = LayoutInflater.from(context)
+ parent = FrameLayout(context)
+ }
+
+ @Test
+ fun create() {
+ val holder = PlayerViewHolder.create(inflater, parent)
+ assertThat(holder.player).isNotNull()
+ }
+
+ @Test
+ fun backgroundIsIlluminationDrawable() {
+ val holder = PlayerViewHolder.create(inflater, parent)
+ assertThat(holder.background.background as IlluminationDrawable).isNotNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
index 58ee79e39279..75018df023cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
@@ -22,7 +22,6 @@ import android.view.View
import android.widget.SeekBar
import android.widget.TextView
import androidx.test.filters.SmallTest
-import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -38,23 +37,21 @@ import org.mockito.Mockito.`when` as whenever
public class SeekBarObserverTest : SysuiTestCase() {
private lateinit var observer: SeekBarObserver
- @Mock private lateinit var mockView: View
+ @Mock private lateinit var mockHolder: PlayerViewHolder
private lateinit var seekBarView: SeekBar
private lateinit var elapsedTimeView: TextView
private lateinit var totalTimeView: TextView
@Before
fun setUp() {
- mockView = mock(View::class.java)
+ mockHolder = mock(PlayerViewHolder::class.java)
seekBarView = SeekBar(context)
elapsedTimeView = TextView(context)
totalTimeView = TextView(context)
- whenever<SeekBar>(
- mockView.findViewById(R.id.media_progress_bar)).thenReturn(seekBarView)
- whenever<TextView>(
- mockView.findViewById(R.id.media_elapsed_time)).thenReturn(elapsedTimeView)
- whenever<TextView>(mockView.findViewById(R.id.media_total_time)).thenReturn(totalTimeView)
- observer = SeekBarObserver(mockView)
+ whenever(mockHolder.seekBar).thenReturn(seekBarView)
+ whenever(mockHolder.elapsedTimeView).thenReturn(elapsedTimeView)
+ whenever(mockHolder.totalTimeView).thenReturn(totalTimeView)
+ observer = SeekBarObserver(mockHolder)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index a5a5f81bdffe..d583048fbb26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -210,28 +210,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
}
@Test
- public void testAddNotification_noDuplicateEntriesCreated() {
- // GIVEN a notification has been added
- mEntryManager.addNotification(mSbn, mRankingMap);
-
- // WHEN the same notification is added multiple times before the previous entry (with
- // the same key) didn't finish inflating
- mEntryManager.addNotification(mSbn, mRankingMap);
- mEntryManager.addNotification(mSbn, mRankingMap);
- mEntryManager.addNotification(mSbn, mRankingMap);
-
- // THEN getAllNotifs() only contains exactly one notification with this key
- int count = 0;
- for (NotificationEntry entry : mEntryManager.getAllNotifs()) {
- if (entry.getKey().equals(mSbn.getKey())) {
- count++;
- }
- }
- assertEquals("Should only be one entry with key=" + mSbn.getKey() + " in mAllNotifs. "
- + "Instead there are " + count, 1, count);
- }
-
- @Test
public void testAddNotification_setsUserSentiment() {
mEntryManager.addNotification(mSbn, mRankingMap);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.java
new file mode 100644
index 000000000000..a14d57556360
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.app.IActivityManager;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.os.UserManager;
+import android.telecom.TelecomManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.SensorPrivacyController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.util.RingerModeLiveData;
+import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.time.DateFormatUtil;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class PhoneStatusBarPolicyTest extends SysuiTestCase {
+
+ private static final int DISPLAY_ID = 0;
+ @Mock
+ private StatusBarIconController mIconController;
+ @Mock
+ private CommandQueue mCommandQueue;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private Executor mBackgroundExecutor;
+ @Mock
+ private CastController mCastController;
+ @Mock
+ private HotspotController mHotSpotController;
+ @Mock
+ private BluetoothController mBluetoothController;
+ @Mock
+ private NextAlarmController mNextAlarmController;
+ @Mock
+ private UserInfoController mUserInfoController;
+ @Mock
+ private RotationLockController mRotationLockController;
+ @Mock
+ private DataSaverController mDataSaverController;
+ @Mock
+ private ZenModeController mZenModeController;
+ @Mock
+ private DeviceProvisionedController mDeviceProvisionerController;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private LocationController mLocationController;
+ @Mock
+ private SensorPrivacyController mSensorPrivacyController;
+ @Mock
+ private IActivityManager mIActivityManager;
+ @Mock
+ private AlarmManager mAlarmManager;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private RecordingController mRecordingController;
+ @Mock
+ private MediaDataManager mMediaDataManager;
+ @Mock
+ private TelecomManager mTelecomManager;
+ @Mock
+ private SharedPreferences mSharedPreferences;
+ @Mock
+ private DateFormatUtil mDateFormatUtil;
+ @Mock
+ private RingerModeTracker mRingerModeTracker;
+ @Mock
+ private RingerModeLiveData mRingerModeLiveData;
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+ private Resources mResources;
+ private PhoneStatusBarPolicy mPhoneStatusBarPolicy;
+
+ @Before
+ public void setup() {
+ mResources = spy(getContext().getResources());
+ mPhoneStatusBarPolicy = new PhoneStatusBarPolicy(mIconController, mCommandQueue,
+ mBroadcastDispatcher, mBackgroundExecutor, mResources, mCastController,
+ mHotSpotController, mBluetoothController, mNextAlarmController, mUserInfoController,
+ mRotationLockController, mDataSaverController, mZenModeController,
+ mDeviceProvisionerController, mKeyguardStateController, mLocationController,
+ mSensorPrivacyController, mIActivityManager, mAlarmManager, mUserManager,
+ mRecordingController, mMediaDataManager, mTelecomManager, DISPLAY_ID,
+ mSharedPreferences, mDateFormatUtil, mRingerModeTracker);
+ when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
+ when(mRingerModeTracker.getRingerModeInternal()).thenReturn(mRingerModeLiveData);
+ clearInvocations(mIconController);
+ }
+
+ @Test
+ public void testInit_registerMediaCallback() {
+ mPhoneStatusBarPolicy.init();
+ verify(mMediaDataManager).addListener(eq(mPhoneStatusBarPolicy));
+ }
+
+ @Test
+ public void testOnMediaDataLoaded_updatesIcon_hasMedia() {
+ String mediaSlot = mResources.getString(com.android.internal.R.string.status_bar_media);
+ when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+ mPhoneStatusBarPolicy.onMediaDataLoaded(null, null);
+ verify(mMediaDataManager).hasActiveMedia();
+ verify(mIconController).setIconVisibility(eq(mediaSlot), eq(true));
+ }
+
+ @Test
+ public void testOnMediaDataRemoved_updatesIcon_noMedia() {
+ String mediaSlot = mResources.getString(com.android.internal.R.string.status_bar_media);
+ mPhoneStatusBarPolicy.onMediaDataRemoved(null);
+ verify(mMediaDataManager).hasActiveMedia();
+ verify(mIconController).setIconVisibility(eq(mediaSlot), eq(false));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
index 31d884c38f58..bd697fe394b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java
@@ -20,6 +20,7 @@ import android.content.res.Resources;
public class FakeProximitySensor extends ProximitySensor {
private boolean mAvailable;
+ private boolean mRegistered;
public FakeProximitySensor(Resources resources, AsyncSensorManager sensorManager) {
super(resources, sensorManager);
@@ -35,17 +36,22 @@ public class FakeProximitySensor extends ProximitySensor {
}
@Override
+ public boolean isRegistered() {
+ return mRegistered;
+ }
+
+ @Override
public boolean getSensorAvailable() {
return mAvailable;
}
@Override
protected void registerInternal() {
- // no-op
+ mRegistered = !mPaused;
}
@Override
protected void unregisterInternal() {
- // no-op
+ mRegistered = false;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
new file mode 100644
index 000000000000..7221095a1eaf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.sensors;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ProximityCheckTest extends SysuiTestCase {
+
+ private FakeProximitySensor mFakeProximitySensor;
+ private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+
+ private TestableCallback mTestableCallback = new TestableCallback();
+
+ private ProximitySensor.ProximityCheck mProximityCheck;
+
+ @Before
+ public void setUp() throws Exception {
+ AsyncSensorManager asyncSensorManager =
+ new AsyncSensorManager(new FakeSensorManager(mContext), null, new Handler());
+ mFakeProximitySensor = new FakeProximitySensor(mContext.getResources(), asyncSensorManager);
+
+ mProximityCheck = new ProximitySensor.ProximityCheck(mFakeProximitySensor, mFakeExecutor);
+ }
+
+ @Test
+ public void testCheck() {
+ mProximityCheck.check(100, mTestableCallback);
+
+ assertNull(mTestableCallback.mLastResult);
+
+ mFakeProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(true, 0));
+ mFakeProximitySensor.alertListeners();
+
+ assertTrue(mTestableCallback.mLastResult);
+ }
+
+ @Test
+ public void testTimeout() {
+ mProximityCheck.check(100, mTestableCallback);
+
+ assertTrue(mFakeProximitySensor.isRegistered());
+
+ mFakeExecutor.advanceClockToNext();
+ mFakeExecutor.runAllReady();
+
+ assertFalse(mFakeProximitySensor.isRegistered());
+ }
+
+ private static class TestableCallback implements Consumer<Boolean> {
+ Boolean mLastResult;
+ @Override
+ public void accept(Boolean result) {
+ mLastResult = result;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
index 526fba726e9d..914790b53a82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java
@@ -219,6 +219,26 @@ public class ProximitySensorTest extends SysuiTestCase {
waitForSensorManager();
}
+ @Test
+ public void testPreventRecursiveAlert() {
+ TestableListener listenerA = new TestableListener() {
+ @Override
+ public void onSensorEvent(ProximitySensor.ProximityEvent proximityEvent) {
+ super.onSensorEvent(proximityEvent);
+ if (mCallCount < 2) {
+ mProximitySensor.alertListeners();
+ }
+ }
+ };
+
+ mProximitySensor.register(listenerA);
+
+ mProximitySensor.alertListeners();
+
+ assertEquals(1, listenerA.mCallCount);
+ }
+
+
class TestableListener implements ProximitySensor.ProximitySensorListener {
ProximitySensor.ProximityEvent mLastEvent;
int mCallCount = 0;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2b86d7fe057e..4ace676eca7e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -337,6 +337,7 @@ import com.android.internal.util.Preconditions;
import com.android.internal.util.function.HexFunction;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.TriFunction;
+import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.AlarmManagerInternal;
import com.android.server.AttributeCache;
import com.android.server.DeviceIdleInternal;
@@ -2098,6 +2099,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
ServiceManager.addService("permission", new PermissionController(this));
ServiceManager.addService("processinfo", new ProcessInfoService(this));
+ ServiceManager.addService("cacheinfo", new CacheBinder(this));
ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
"android", STOCK_PM_FLAGS | MATCH_SYSTEM_ONLY);
@@ -2191,16 +2193,18 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
- Process.enableFreezer(false);
- }
-
- if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
- "meminfo", pw)) return;
- PriorityDump.dump(mPriorityDumper, fd, pw, args);
+ try {
+ if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+ Process.enableFreezer(false);
+ }
- if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
- Process.enableFreezer(true);
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
+ "meminfo", pw)) return;
+ PriorityDump.dump(mPriorityDumper, fd, pw, args);
+ } finally {
+ if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+ Process.enableFreezer(true);
+ }
}
}
}
@@ -2213,16 +2217,18 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
- Process.enableFreezer(false);
- }
-
- if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
- "gfxinfo", pw)) return;
- mActivityManagerService.dumpGraphicsHardwareUsage(fd, pw, args);
+ try {
+ if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+ Process.enableFreezer(false);
+ }
- if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
- Process.enableFreezer(true);
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
+ "gfxinfo", pw)) return;
+ mActivityManagerService.dumpGraphicsHardwareUsage(fd, pw, args);
+ } finally {
+ if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+ Process.enableFreezer(true);
+ }
}
}
}
@@ -2235,16 +2241,18 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
- Process.enableFreezer(false);
- }
-
- if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
- "dbinfo", pw)) return;
- mActivityManagerService.dumpDbInfo(fd, pw, args);
+ try {
+ if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+ Process.enableFreezer(false);
+ }
- if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
- Process.enableFreezer(true);
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
+ "dbinfo", pw)) return;
+ mActivityManagerService.dumpDbInfo(fd, pw, args);
+ } finally {
+ if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+ Process.enableFreezer(true);
+ }
}
}
}
@@ -2280,6 +2288,34 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ static class CacheBinder extends Binder {
+ ActivityManagerService mActivityManagerService;
+
+ CacheBinder(ActivityManagerService activityManagerService) {
+ mActivityManagerService = activityManagerService;
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ try {
+ if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+ Process.enableFreezer(false);
+ }
+
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
+ "cacheinfo", pw)) {
+ return;
+ }
+
+ mActivityManagerService.dumpBinderCacheContents(fd, pw, args);
+ } finally {
+ if (mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.useFreezer()) {
+ Process.enableFreezer(true);
+ }
+ }
+ }
+ }
+
public static final class Lifecycle extends SystemService {
private final ActivityManagerService mService;
private static ActivityTaskManagerService sAtm;
@@ -12722,6 +12758,39 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ final void dumpBinderCacheContents(FileDescriptor fd, PrintWriter pw, String[] args) {
+ ArrayList<ProcessRecord> procs = collectProcesses(pw, 0, false, args);
+ if (procs == null) {
+ pw.println("No process found for: " + args[0]);
+ return;
+ }
+
+ pw.println("Per-process Binder Cache Contents");
+
+ for (int i = procs.size() - 1; i >= 0; i--) {
+ ProcessRecord r = procs.get(i);
+ if (r.thread != null) {
+ pw.println("\n\n** Cache info for pid " + r.pid + " [" + r.processName + "] **");
+ pw.flush();
+ try {
+ TransferPipe tp = new TransferPipe();
+ try {
+ r.thread.dumpCacheInfo(tp.getWriteFd(), args);
+ tp.go(fd);
+ } finally {
+ tp.kill();
+ }
+ } catch (IOException e) {
+ pw.println("Failure while dumping the app " + r);
+ pw.flush();
+ } catch (RemoteException e) {
+ pw.println("Got a RemoteException while dumping the app " + r);
+ pw.flush();
+ }
+ }
+ }
+ }
+
final void dumpDbInfo(FileDescriptor fd, PrintWriter pw, String[] args) {
ArrayList<ProcessRecord> procs = collectProcesses(pw, 0, false, args);
if (procs == null) {
@@ -18763,28 +18832,39 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public int checkContentProviderUriPermission(Uri uri, int userId,
int callingUid, int modeFlags) {
- // We can find ourselves needing to check Uri permissions while
- // already holding the WM lock, which means reaching back here for
- // the AM lock would cause an inversion. The WM team has requested
- // that we use the strategy below instead of shifting where Uri
- // grants are calculated.
-
- // Since we could also arrive here while holding the AM lock, we
- // can't always delegate the call through the handler, and we need
- // to delicately dance between the deadlocks.
- if (Thread.currentThread().holdsLock(ActivityManagerService.this)) {
+ final Object wmLock = mActivityTaskManager.getGlobalLock();
+ if (Thread.currentThread().holdsLock(wmLock)
+ && !Thread.currentThread().holdsLock(ActivityManagerService.this)) {
+ // We can find ourselves needing to check Uri permissions while already holding the
+ // WM lock, which means reaching back here for the AM lock would cause an inversion.
+ // The WM team has requested that we use the strategy below instead of shifting
+ // where Uri grants are calculated.
+ synchronized (wmLock) {
+ final int[] result = new int[1];
+ final Message msg = PooledLambda.obtainMessage(
+ LocalService::checkContentProviderUriPermission,
+ this, uri, userId, callingUid, modeFlags, wmLock, result);
+ mHandler.sendMessage(msg);
+ try {
+ wmLock.wait();
+ } catch (InterruptedException ignore) {
+
+ }
+ return result[0];
+ }
+ } else {
return ActivityManagerService.this.checkContentProviderUriPermission(uri,
userId, callingUid, modeFlags);
- } else {
- final CompletableFuture<Integer> res = new CompletableFuture<>();
- mHandler.post(() -> {
- res.complete(ActivityManagerService.this.checkContentProviderUriPermission(uri,
- userId, callingUid, modeFlags));
- });
- try {
- return res.get();
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
+ }
+ }
+
+ void checkContentProviderUriPermission(
+ Uri uri, int userId, int callingUid, int modeFlags, Object wmLock, int[] result) {
+ synchronized (ActivityManagerService.this) {
+ synchronized (wmLock) {
+ result[0] = ActivityManagerService.this.checkContentProviderUriPermission(
+ uri, userId, callingUid, modeFlags);
+ wmLock.notify();
}
}
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index f9d204fa008e..43e3a04ad032 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -45,6 +45,7 @@ import com.android.server.ServiceThread;
import java.io.FileOutputStream;
import java.io.FileReader;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -114,6 +115,14 @@ public final class CachedAppOptimizer {
}
private PropertyChangedCallbackForTest mTestCallback;
+ // This interface is for functions related to the Process object that need a different
+ // implementation in the tests as we are not creating real processes when testing compaction.
+ @VisibleForTesting
+ interface ProcessDependencies {
+ long[] getRss(int pid);
+ void performCompaction(String action, int pid) throws IOException;
+ }
+
// Handler constants.
static final int COMPACT_PROCESS_SOME = 1;
static final int COMPACT_PROCESS_FULL = 2;
@@ -215,13 +224,16 @@ public final class CachedAppOptimizer {
@VisibleForTesting final Set<Integer> mProcStateThrottle;
// Handler on which compaction runs.
- private Handler mCompactionHandler;
+ @VisibleForTesting
+ Handler mCompactionHandler;
private Handler mFreezeHandler;
// Maps process ID to last compaction statistics for processes that we've fully compacted. Used
// when evaluating throttles that we only consider for "full" compaction, so we don't store
- // data for "some" compactions.
- private Map<Integer, LastCompactionStats> mLastCompactionStats =
+ // data for "some" compactions. Uses LinkedHashMap to ensure insertion order is kept and
+ // facilitate removal of the oldest entry.
+ @VisibleForTesting
+ LinkedHashMap<Integer, LastCompactionStats> mLastCompactionStats =
new LinkedHashMap<Integer, LastCompactionStats>() {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
@@ -233,17 +245,20 @@ public final class CachedAppOptimizer {
private int mFullCompactionCount;
private int mPersistentCompactionCount;
private int mBfgsCompactionCount;
+ private final ProcessDependencies mProcessDependencies;
public CachedAppOptimizer(ActivityManagerService am) {
- mAm = am;
- mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
- THREAD_PRIORITY_FOREGROUND, true);
- mProcStateThrottle = new HashSet<>();
+ this(am, null, new DefaultProcessDependencies());
}
@VisibleForTesting
- CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
- this(am);
+ CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback,
+ ProcessDependencies processDependencies) {
+ mAm = am;
+ mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
+ THREAD_PRIORITY_FOREGROUND, true);
+ mProcStateThrottle = new HashSet<>();
+ mProcessDependencies = processDependencies;
mTestCallback = callback;
}
@@ -659,7 +674,8 @@ public final class CachedAppOptimizer {
}
}
- private static final class LastCompactionStats {
+ @VisibleForTesting
+ static final class LastCompactionStats {
private final long[] mRssAfterCompaction;
LastCompactionStats(long[] rss) {
@@ -712,9 +728,7 @@ public final class CachedAppOptimizer {
lastCompactAction = proc.lastCompactAction;
lastCompactTime = proc.lastCompactTime;
- // remove rather than get so that insertion order will be updated when we
- // put the post-compaction stats back into the map.
- lastCompactionStats = mLastCompactionStats.remove(pid);
+ lastCompactionStats = mLastCompactionStats.get(pid);
}
if (pid == 0) {
@@ -806,7 +820,7 @@ public final class CachedAppOptimizer {
return;
}
- long[] rssBefore = Process.getRss(pid);
+ long[] rssBefore = mProcessDependencies.getRss(pid);
long anonRssBefore = rssBefore[2];
if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0
@@ -863,16 +877,13 @@ public final class CachedAppOptimizer {
default:
break;
}
-
try {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
+ ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
+ ": " + name);
long zramFreeKbBefore = Debug.getZramFreeKb();
- FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
- fos.write(action.getBytes());
- fos.close();
- long[] rssAfter = Process.getRss(pid);
+ mProcessDependencies.performCompaction(action, pid);
+ long[] rssAfter = mProcessDependencies.getRss(pid);
long end = SystemClock.uptimeMillis();
long time = end - start;
long zramFreeKbAfter = Debug.getZramFreeKb();
@@ -882,7 +893,6 @@ public final class CachedAppOptimizer {
rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time,
lastCompactAction, lastCompactTime, lastOomAdj, procState,
zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore);
-
// Note that as above not taking mPhenoTypeFlagLock here to avoid locking
// on every single compaction for a flag that will seldom change and the
// impact of reading the wrong value here is low.
@@ -894,14 +904,14 @@ public final class CachedAppOptimizer {
lastOomAdj, ActivityManager.processStateAmToProto(procState),
zramFreeKbBefore, zramFreeKbAfter);
}
-
synchronized (mAm) {
proc.lastCompactTime = end;
proc.lastCompactAction = pendingAction;
}
-
if (action.equals(COMPACT_ACTION_FULL)
|| action.equals(COMPACT_ACTION_ANON)) {
+ // Remove entry and insert again to update insertion order.
+ mLastCompactionStats.remove(pid);
mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter));
}
} catch (Exception e) {
@@ -1018,4 +1028,23 @@ public final class CachedAppOptimizer {
}
}
}
+
+ /**
+ * Default implementation for ProcessDependencies, public vor visibility to OomAdjuster class.
+ */
+ private static final class DefaultProcessDependencies implements ProcessDependencies {
+ // Get memory RSS from process.
+ @Override
+ public long[] getRss(int pid) {
+ return Process.getRss(pid);
+ }
+
+ // Compact process.
+ @Override
+ public void performCompaction(String action, int pid) throws IOException {
+ try (FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim")) {
+ fos.write(action.getBytes());
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 98d662a1a9b5..2423f43d8283 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -955,6 +955,8 @@ public class AudioService extends IAudioService.Stub
mMicMuteFromSystemCached = mAudioSystem.isMicrophoneMuted();
setMicMuteFromSwitchInput();
+
+ initMinStreamVolumeWithoutModifyAudioSettings();
}
RoleObserver mRoleObserver;
@@ -1308,7 +1310,7 @@ public class AudioService extends IAudioService.Stub
mStreamStates[streamType].setIndex(
mStreamStates[mStreamVolumeAlias[streamType]]
.getIndex(AudioSystem.DEVICE_OUT_DEFAULT),
- device, caller);
+ device, caller, true /*hasModifyAudioSettings*/);
}
mStreamStates[streamType].checkFixedVolumeDevices();
}
@@ -1873,13 +1875,16 @@ public class AudioService extends IAudioService.Stub
direction, 0 /*ignored*/,
extVolCtlr, 0 /*delay*/);
} else {
+ final boolean hasModifyAudioSettings =
+ mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
+ == PackageManager.PERMISSION_GRANTED;
adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
- caller, Binder.getCallingUid());
+ caller, Binder.getCallingUid(), hasModifyAudioSettings);
}
}
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
- String callingPackage, String caller, int uid) {
+ String callingPackage, String caller, int uid, boolean hasModifyAudioSettings) {
if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType
+ ", flags=" + flags + ", caller=" + caller
+ ", volControlStream=" + mVolumeControlStream
@@ -1933,10 +1938,12 @@ public class AudioService extends IAudioService.Stub
if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
}
- adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid);
+ adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid,
+ hasModifyAudioSettings);
}
- /** @see AudioManager#adjustStreamVolume(int, int, int) */
+ /** @see AudioManager#adjustStreamVolume(int, int, int)
+ * Part of service interface, check permissions here */
public void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage) {
if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
@@ -1944,14 +1951,17 @@ public class AudioService extends IAudioService.Stub
+ "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
return;
}
+ final boolean hasModifyAudioSettings =
+ mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
+ == PackageManager.PERMISSION_GRANTED;
sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
direction/*val1*/, flags/*val2*/, callingPackage));
adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
- Binder.getCallingUid());
+ Binder.getCallingUid(), hasModifyAudioSettings);
}
protected void adjustStreamVolume(int streamType, int direction, int flags,
- String callingPackage, String caller, int uid) {
+ String callingPackage, String caller, int uid, boolean hasModifyAudioSettings) {
if (mUseFixedVolume) {
return;
}
@@ -2105,7 +2115,8 @@ public class AudioService extends IAudioService.Stub
Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
mVolumeController.postDisplaySafeVolumeWarning(flags);
} else if (!isFullVolumeDevice(device)
- && (streamState.adjustIndex(direction * step, device, caller)
+ && (streamState.adjustIndex(direction * step, device, caller,
+ hasModifyAudioSettings)
|| streamState.mIsMuted)) {
// Post message to set system volume (it in turn will post a
// message to persist).
@@ -2327,9 +2338,9 @@ public class AudioService extends IAudioService.Stub
}
private void onSetStreamVolume(int streamType, int index, int flags, int device,
- String caller) {
+ String caller, boolean hasModifyAudioSettings) {
final int stream = mStreamVolumeAlias[streamType];
- setStreamVolumeInt(stream, index, device, false, caller);
+ setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings);
// setting volume on ui sounds stream type also controls silent mode
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(stream == getUiSoundsStreamType())) {
@@ -2377,7 +2388,7 @@ public class AudioService extends IAudioService.Stub
continue;
}
setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage,
- Binder.getCallingUid());
+ Binder.getCallingUid(), true /*hasModifyAudioSettings*/);
}
}
@@ -2419,7 +2430,8 @@ public class AudioService extends IAudioService.Stub
return AudioSystem.getMinVolumeIndexForAttributes(attr);
}
- /** @see AudioManager#setStreamVolume(int, int, int) */
+ /** @see AudioManager#setStreamVolume(int, int, int)
+ * Part of service interface, check permissions here */
public void setStreamVolume(int streamType, int index, int flags, String callingPackage) {
if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
Log.w(TAG, "Trying to call setStreamVolume() for a11y without"
@@ -2442,10 +2454,13 @@ public class AudioService extends IAudioService.Stub
+ " MODIFY_AUDIO_ROUTING callingPackage=" + callingPackage);
return;
}
+ final boolean hasModifyAudioSettings =
+ mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
+ == PackageManager.PERMISSION_GRANTED;
sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
index/*val1*/, flags/*val2*/, callingPackage));
setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
- Binder.getCallingUid());
+ Binder.getCallingUid(), hasModifyAudioSettings);
}
private boolean canChangeAccessibilityVolume() {
@@ -2569,7 +2584,7 @@ public class AudioService extends IAudioService.Stub
}
private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
- String caller, int uid) {
+ String caller, int uid, boolean hasModifyAudioSettings) {
if (DEBUG_VOL) {
Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index
+ ", calling=" + callingPackage + ")");
@@ -2660,7 +2675,7 @@ public class AudioService extends IAudioService.Stub
mPendingVolumeCommand = new StreamVolumeCommand(
streamType, index, flags, device);
} else {
- onSetStreamVolume(streamType, index, flags, device, caller);
+ onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings);
index = mStreamStates[streamType].getIndex(device);
}
}
@@ -2872,19 +2887,22 @@ public class AudioService extends IAudioService.Stub
* @param index Desired volume index of the stream
* @param device the device whose volume must be changed
* @param force If true, set the volume even if the desired volume is same
+ * @param caller
+ * @param hasModifyAudioSettings true if the caller is granted MODIFY_AUDIO_SETTINGS or
+ * MODIFY_AUDIO_ROUTING permission
* as the current volume.
*/
private void setStreamVolumeInt(int streamType,
int index,
int device,
boolean force,
- String caller) {
+ String caller, boolean hasModifyAudioSettings) {
if (isFullVolumeDevice(device)) {
return;
}
VolumeStreamState streamState = mStreamStates[streamType];
- if (streamState.setIndex(index, device, caller) || force) {
+ if (streamState.setIndex(index, device, caller, hasModifyAudioSettings) || force) {
// Post message to set system volume (it in turn will post a message
// to persist).
sendMsg(mAudioHandler,
@@ -3417,7 +3435,7 @@ public class AudioService extends IAudioService.Stub
int device = vss.mIndexMap.keyAt(i);
int value = vss.mIndexMap.valueAt(i);
if (value == 0) {
- vss.setIndex(10, device, TAG);
+ vss.setIndex(10, device, TAG, true /*hasModifyAudioSettings*/);
}
}
// Persist volume for stream ring when it is changed here
@@ -3762,7 +3780,8 @@ public class AudioService extends IAudioService.Stub
int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
int device = getDeviceForStream(streamType);
int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device);
- setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, caller);
+ setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, caller,
+ true /*hasModifyAudioSettings*/);
updateStreamVolumeAlias(true /*updateVolumes*/, caller);
@@ -4671,6 +4690,44 @@ public class AudioService extends IAudioService.Stub
return false;
}
+ /**
+ * Minimum attenuation that can be set for alarms over speaker by an application that
+ * doesn't have the MODIFY_AUDIO_SETTINGS permission.
+ */
+ protected static final float MIN_ALARM_ATTENUATION_NON_PRIVILEGED_DB = -36.0f;
+
+ /**
+ * Configures the VolumeStreamState instances for minimum stream index that can be accessed
+ * without MODIFY_AUDIO_SETTINGS permission.
+ * Can only be done successfully once audio policy has finished reading its configuration files
+ * for the volume curves. If not, getStreamVolumeDB will return NaN, and the min value will
+ * remain at the stream min index value.
+ */
+ protected void initMinStreamVolumeWithoutModifyAudioSettings() {
+ int idx;
+ int deviceForAlarm = AudioSystem.DEVICE_OUT_SPEAKER_SAFE;
+ if (AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_ALARM,
+ MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM], deviceForAlarm) == Float.NaN) {
+ deviceForAlarm = AudioSystem.DEVICE_OUT_SPEAKER;
+ }
+ for (idx = MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM];
+ idx >= MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM]; idx--) {
+ if (AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_ALARM, idx, deviceForAlarm)
+ < MIN_ALARM_ATTENUATION_NON_PRIVILEGED_DB) {
+ break;
+ }
+ }
+ final int safeIndex = idx <= MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM]
+ ? MIN_STREAM_VOLUME[AudioSystem.STREAM_ALARM]
+ : Math.min(idx + 1, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
+ // update the VolumeStreamState for STREAM_ALARM and its aliases
+ for (int stream : mStreamVolumeAlias) {
+ if (mStreamVolumeAlias[stream] == AudioSystem.STREAM_ALARM) {
+ mStreamStates[stream].updateNoPermMinIndex(safeIndex);
+ }
+ }
+ }
+
/** only public for mocking/spying, do not call outside of AudioService */
@VisibleForTesting
public int getDeviceForStream(int stream) {
@@ -5335,6 +5392,8 @@ public class AudioService extends IAudioService.Stub
private class VolumeStreamState {
private final int mStreamType;
private int mIndexMin;
+ // min index when user doesn't have permission to change audio settings
+ private int mIndexMinNoPerm;
private int mIndexMax;
private boolean mIsMuted;
@@ -5376,6 +5435,7 @@ public class AudioService extends IAudioService.Stub
mStreamType = streamType;
mIndexMin = MIN_STREAM_VOLUME[streamType] * 10;
+ mIndexMinNoPerm = mIndexMin; // may be overwritten later in updateNoPermMinIndex()
mIndexMax = MAX_STREAM_VOLUME[streamType] * 10;
AudioSystem.initStreamVolume(streamType, mIndexMin / 10, mIndexMax / 10);
@@ -5386,6 +5446,18 @@ public class AudioService extends IAudioService.Stub
mStreamDevicesChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType);
}
+ /**
+ * Update the minimum index that can be used without MODIFY_AUDIO_SETTINGS permission
+ * @param index minimum index expressed in "UI units", i.e. no 10x factor
+ */
+ public void updateNoPermMinIndex(int index) {
+ mIndexMinNoPerm = index * 10;
+ if (mIndexMinNoPerm < mIndexMin) {
+ Log.e(TAG, "Invalid mIndexMinNoPerm for stream " + mStreamType);
+ mIndexMinNoPerm = mIndexMin;
+ }
+ }
+
public int observeDevicesForStream_syncVSS(boolean checkOthers) {
if (!mSystemServer.isPrivileged()) {
return AudioSystem.DEVICE_NONE;
@@ -5467,7 +5539,8 @@ public class AudioService extends IAudioService.Stub
continue;
}
- mIndexMap.put(device, getValidIndex(10 * index));
+ mIndexMap.put(device, getValidIndex(10 * index,
+ true /*hasModifyAudioSettings*/));
}
}
}
@@ -5555,17 +5628,20 @@ public class AudioService extends IAudioService.Stub
}
}
- public boolean adjustIndex(int deltaIndex, int device, String caller) {
- return setIndex(getIndex(device) + deltaIndex, device, caller);
+ public boolean adjustIndex(int deltaIndex, int device, String caller,
+ boolean hasModifyAudioSettings) {
+ return setIndex(getIndex(device) + deltaIndex, device, caller,
+ hasModifyAudioSettings);
}
- public boolean setIndex(int index, int device, String caller) {
+ public boolean setIndex(int index, int device, String caller,
+ boolean hasModifyAudioSettings) {
boolean changed;
int oldIndex;
synchronized (mSettingsLock) {
synchronized (VolumeStreamState.class) {
oldIndex = getIndex(device);
- index = getValidIndex(index);
+ index = getValidIndex(index, hasModifyAudioSettings);
if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
index = mIndexMax;
}
@@ -5585,10 +5661,12 @@ public class AudioService extends IAudioService.Stub
mStreamVolumeAlias[streamType] == mStreamType &&
(changed || !aliasStreamState.hasIndexForDevice(device))) {
final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
- aliasStreamState.setIndex(scaledIndex, device, caller);
+ aliasStreamState.setIndex(scaledIndex, device, caller,
+ hasModifyAudioSettings);
if (isCurrentDevice) {
aliasStreamState.setIndex(scaledIndex,
- getDeviceForStream(streamType), caller);
+ getDeviceForStream(streamType), caller,
+ hasModifyAudioSettings);
}
}
}
@@ -5678,7 +5756,7 @@ public class AudioService extends IAudioService.Stub
index = srcMap.valueAt(i);
index = rescaleIndex(index, srcStreamType, mStreamType);
- setIndex(index, device, caller);
+ setIndex(index, device, caller, true /*hasModifyAudioSettings*/);
}
}
@@ -5745,9 +5823,10 @@ public class AudioService extends IAudioService.Stub
}
}
- private int getValidIndex(int index) {
- if (index < mIndexMin) {
- return mIndexMin;
+ private int getValidIndex(int index, boolean hasModifyAudioSettings) {
+ final int indexMin = hasModifyAudioSettings ? mIndexMin : mIndexMinNoPerm;
+ if (index < indexMin) {
+ return indexMin;
} else if (mUseFixedVolume || index > mIndexMax) {
return mIndexMax;
}
@@ -5759,7 +5838,13 @@ public class AudioService extends IAudioService.Stub
pw.print(" Muted: ");
pw.println(mIsMuted);
pw.print(" Min: ");
- pw.println((mIndexMin + 5) / 10);
+ pw.print((mIndexMin + 5) / 10);
+ if (mIndexMin != mIndexMinNoPerm) {
+ pw.print(" w/o perm:");
+ pw.println((mIndexMinNoPerm + 5) / 10);
+ } else {
+ pw.println();
+ }
pw.print(" Max: ");
pw.println((mIndexMax + 5) / 10);
pw.print(" streamVolume:"); pw.println(getStreamVolume(mStreamType));
@@ -5880,7 +5965,9 @@ public class AudioService extends IAudioService.Stub
final VolumeStreamState streamState = mStreamStates[update.mStreamType];
if (update.hasVolumeIndex()) {
final int index = update.getVolumeIndex();
- streamState.setIndex(index, update.mDevice, update.mCaller);
+ streamState.setIndex(index, update.mDevice, update.mCaller,
+ // trusted as index is always validated before message is posted
+ true /*hasModifyAudioSettings*/);
sVolumeLogger.log(new AudioEventLogger.StringEvent(update.mCaller + " dev:0x"
+ Integer.toHexString(update.mDevice) + " volIdx:" + index));
} else {
@@ -6823,7 +6910,8 @@ public class AudioService extends IAudioService.Stub
for (int device : devices) {
int index = streamState.getIndex(device);
if (index > safeMediaVolumeIndex(device)) {
- streamState.setIndex(safeMediaVolumeIndex(device), device, caller);
+ streamState.setIndex(safeMediaVolumeIndex(device), device, caller,
+ true /*hasModifyAudioSettings*/);
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
@@ -6857,7 +6945,7 @@ public class AudioService extends IAudioService.Stub
mPendingVolumeCommand.mIndex,
mPendingVolumeCommand.mFlags,
mPendingVolumeCommand.mDevice,
- callingPackage);
+ callingPackage, true /*hasModifyAudioSettings*/);
mPendingVolumeCommand = null;
}
}
@@ -7465,29 +7553,39 @@ public class AudioService extends IAudioService.Stub
@Override
public void adjustSuggestedStreamVolumeForUid(int streamType, int direction, int flags,
- String callingPackage, int uid) {
+ String callingPackage, int uid, int pid) {
+ final boolean hasModifyAudioSettings =
+ mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid)
+ == PackageManager.PERMISSION_GRANTED;
// direction and stream type swap here because the public
// adjustSuggested has a different order than the other methods.
adjustSuggestedStreamVolume(direction, streamType, flags, callingPackage,
- callingPackage, uid);
+ callingPackage, uid, hasModifyAudioSettings);
}
@Override
public void adjustStreamVolumeForUid(int streamType, int direction, int flags,
- String callingPackage, int uid) {
+ String callingPackage, int uid, int pid) {
if (direction != AudioManager.ADJUST_SAME) {
sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_VOL_UID, streamType,
direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage)
.append(" uid:").append(uid).toString()));
}
+ final boolean hasModifyAudioSettings =
+ mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid)
+ == PackageManager.PERMISSION_GRANTED;
adjustStreamVolume(streamType, direction, flags, callingPackage,
- callingPackage, uid);
+ callingPackage, uid, hasModifyAudioSettings);
}
@Override
public void setStreamVolumeForUid(int streamType, int direction, int flags,
- String callingPackage, int uid) {
- setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid);
+ String callingPackage, int uid, int pid) {
+ final boolean hasModifyAudioSettings =
+ mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid)
+ == PackageManager.PERMISSION_GRANTED;
+ setStreamVolume(streamType, direction, flags, callingPackage, callingPackage, uid,
+ hasModifyAudioSettings);
}
@Override
diff --git a/services/core/java/com/android/server/compat/TEST_MAPPING b/services/core/java/com/android/server/compat/TEST_MAPPING
index 0c30c790c5dd..bc1c7287d04a 100644
--- a/services/core/java/com/android/server/compat/TEST_MAPPING
+++ b/services/core/java/com/android/server/compat/TEST_MAPPING
@@ -15,7 +15,7 @@
},
// CTS tests
{
- "name": "CtsAppCompatHostTestCases#"
+ "name": "CtsAppCompatHostTestCases"
}
]
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index a099606852d0..b9cd43d0803d 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -51,6 +51,11 @@ public abstract class BrightnessMappingStrategy {
private static final float MAX_GRAD = 1.0f;
private static final float SHORT_TERM_MODEL_THRESHOLD_RATIO = 0.6f;
+ // Constant that ensures that each step of the curve can increase by up to at least
+ // MIN_PERMISSABLE_INCREASE. Otherwise when the brightness is set to 0, the curve will never
+ // increase and will always be 0.
+ private static final float MIN_PERMISSABLE_INCREASE = 0.004f;
+
protected boolean mLoggingEnabled;
private static final Plog PLOG = Plog.createSystemPlog(TAG);
@@ -400,7 +405,9 @@ public abstract class BrightnessMappingStrategy {
for (int i = idx+1; i < lux.length; i++) {
float currLux = lux[i];
float currBrightness = brightness[i];
- float maxBrightness = prevBrightness * permissibleRatio(currLux, prevLux);
+ float maxBrightness = MathUtils.max(
+ prevBrightness * permissibleRatio(currLux, prevLux),
+ prevBrightness + MIN_PERMISSABLE_INCREASE);
float newBrightness = MathUtils.constrain(
currBrightness, prevBrightness, maxBrightness);
if (newBrightness == currBrightness) {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index a435f1e16b80..53205add0b38 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -60,7 +60,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
private Connection mActiveConnection;
private boolean mConnectionReady;
- private RouteDiscoveryPreference mPendingDiscoveryPreference = null;
+ private RouteDiscoveryPreference mLastDiscoveryPreference = null;
MediaRoute2ProviderServiceProxy(@NonNull Context context, @NonNull ComponentName componentName,
int userId) {
@@ -98,11 +98,10 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
@Override
public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
+ mLastDiscoveryPreference = discoveryPreference;
if (mConnectionReady) {
mActiveConnection.updateDiscoveryPreference(discoveryPreference);
updateBinding();
- } else {
- mPendingDiscoveryPreference = discoveryPreference;
}
}
@@ -277,9 +276,8 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
private void onConnectionReady(Connection connection) {
if (mActiveConnection == connection) {
mConnectionReady = true;
- if (mPendingDiscoveryPreference != null) {
- updateDiscoveryPreference(mPendingDiscoveryPreference);
- mPendingDiscoveryPreference = null;
+ if (mLastDiscoveryPreference != null) {
+ updateDiscoveryPreference(mLastDiscoveryPreference);
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 67f9782d1a6d..02b7582a8637 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -328,7 +328,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
public void run() {
try {
mAudioManagerInternal.setStreamVolumeForUid(stream, volumeValue, flags,
- opPackageName, uid);
+ opPackageName, uid, pid);
} catch (IllegalArgumentException | SecurityException e) {
Log.e(TAG, "Cannot set volume: stream=" + stream + ", value=" + volumeValue
+ ", flags=" + flags, e);
@@ -501,12 +501,15 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
// Must use opPackageName for adjusting volumes with UID.
final String opPackageName;
final int uid;
+ final int pid;
if (asSystemService) {
opPackageName = mContext.getOpPackageName();
uid = Process.SYSTEM_UID;
+ pid = Process.myPid();
} else {
opPackageName = callingOpPackageName;
uid = callingUid;
+ pid = callingPid;
}
mHandler.post(new Runnable() {
@Override
@@ -515,15 +518,15 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR
if (useSuggested) {
if (AudioSystem.isStreamActive(stream, 0)) {
mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream,
- direction, flags, opPackageName, uid);
+ direction, flags, opPackageName, uid, pid);
} else {
mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
- flags | previousFlagPlaySound, opPackageName, uid);
+ flags | previousFlagPlaySound, opPackageName, uid, pid);
}
} else {
mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
- opPackageName, uid);
+ opPackageName, uid, pid);
}
} catch (IllegalArgumentException | SecurityException e) {
Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", stream="
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 84ec440dde0e..e16582f8e916 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -2107,16 +2107,19 @@ public class MediaSessionService extends SystemService implements Monitor {
public void run() {
final String callingOpPackageName;
final int callingUid;
+ final int callingPid;
if (asSystemService) {
callingOpPackageName = mContext.getOpPackageName();
callingUid = Process.myUid();
+ callingPid = Process.myPid();
} else {
callingOpPackageName = opPackageName;
callingUid = uid;
+ callingPid = pid;
}
try {
mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(suggestedStream,
- direction, flags, callingOpPackageName, callingUid);
+ direction, flags, callingOpPackageName, callingUid, callingPid);
} catch (SecurityException | IllegalArgumentException e) {
Log.e(TAG, "Cannot adjust volume: direction=" + direction
+ ", suggestedStream=" + suggestedStream + ", flags=" + flags
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index fdee9f86bfaf..d7e724780c94 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -45,7 +45,7 @@ import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -58,7 +58,8 @@ import java.util.Objects;
// TODO: check thread safety. We may need to use lock to protect variables.
class SystemMediaRoute2Provider extends MediaRoute2Provider {
private static final String TAG = "MR2SystemProvider";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ // TODO(b/156996903): Revert it when releasing the framework.
+ private static final boolean DEBUG = true;
static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
@@ -269,7 +270,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
builder.addRoute(route);
}
}
- setProviderState(builder.build());
+ MediaRoute2ProviderInfo providerInfo = builder.build();
+ setProviderState(providerInfo);
+ if (DEBUG) {
+ Slog.d(TAG, "Updating system provider info : " + providerInfo);
+ }
}
/**
@@ -327,6 +332,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
if (Objects.equals(oldSessionInfo, newSessionInfo)) {
return false;
} else {
+ if (DEBUG) {
+ Slog.d(TAG, "Updating system routing session info : " + newSessionInfo);
+ }
mSessionInfos.clear();
mSessionInfos.add(newSessionInfo);
mDefaultSessionInfo = new RoutingSessionInfo.Builder(
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 38c65f11a717..9d56d817440b 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1936,6 +1936,9 @@ public class PreferencesHelper implements RankingConfig {
event.writeInt(channel.getImportance());
event.writeInt(channel.getUserLockedFields());
event.writeBoolean(channel.isDeleted());
+ event.writeBoolean(channel.getConversationId() != null);
+ event.writeBoolean(channel.isDemoted());
+ event.writeBoolean(channel.isImportantConversation());
events.add(event.build());
}
}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 70d1adecc6f3..f9d805e57305 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -417,7 +417,7 @@ public class AppsFilter {
public void grantImplicitAccess(int recipientUid, int visibleUid) {
if (recipientUid != visibleUid
&& mImplicitlyQueryable.add(recipientUid, visibleUid) && DEBUG_LOGGING) {
- Slog.wtf(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid);
+ Slog.i(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid);
}
}
@@ -720,7 +720,7 @@ public class AppsFilter {
return false;
}
if (callingSetting == null) {
- Slog.wtf(TAG, "No setting found for non system uid " + callingUid);
+ Slog.w(TAG, "No setting found for non system uid " + callingUid);
return true;
}
final PackageSetting callingPkgSetting;
@@ -760,7 +760,7 @@ public class AppsFilter {
final AndroidPackage targetPkg = targetPkgSetting.pkg;
if (targetPkg == null) {
if (DEBUG_LOGGING) {
- Slog.wtf(TAG, "shouldFilterApplication: " + "targetPkg is null");
+ Slog.w(TAG, "shouldFilterApplication: " + "targetPkg is null");
}
return true;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 3e587bf01521..f3619f22d231 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3181,7 +3181,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- final int cachedSystemApps = PackageParser.sCachedPackageReadCount.get();
+ final int cachedSystemApps = PackageCacher.sCachedPackageReadCount.get();
// Remove any shared userIDs that have no associated packages
mSettings.pruneSharedUsersLPw();
@@ -3315,7 +3315,7 @@ public class PackageManagerService extends IPackageManager.Stub
// This must be done last to ensure all stubs are replaced or disabled.
installSystemStubPackages(stubSystemApps, scanFlags);
- final int cachedNonSystemApps = PackageParser.sCachedPackageReadCount.get()
+ final int cachedNonSystemApps = PackageCacher.sCachedPackageReadCount.get()
- cachedSystemApps;
final long dataScanTime = SystemClock.uptimeMillis() - systemScanTime - startTime;
@@ -13221,7 +13221,9 @@ public class PackageManagerService extends IPackageManager.Stub
private void enforceCanSetPackagesSuspendedAsUser(String callingPackage, int callingUid,
int userId, String callingMethod) {
- if (callingUid == Process.ROOT_UID || callingUid == Process.SYSTEM_UID) {
+ if (callingUid == Process.ROOT_UID
+ // Need to compare app-id to allow system dialogs access on secondary users
+ || UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
return;
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 9de34a92cdf7..a5b1bf98cdb7 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -5595,10 +5595,7 @@ public final class Settings {
userId);
} else if (packageSetting.sharedUser == null && !isUpgradeToR) {
Slog.w(TAG, "Missing permission state for package: " + packageName);
- generateFallbackPermissionsStateLpr(
- packageSetting.pkg.getRequestedPermissions(),
- packageSetting.pkg.getTargetSdkVersion(),
- packageSetting.getPermissionsState(), userId);
+ packageSetting.getPermissionsState().setMissing(true, userId);
}
}
@@ -5616,22 +5613,7 @@ public final class Settings {
userId);
} else if (!isUpgradeToR) {
Slog.w(TAG, "Missing permission state for shared user: " + sharedUserName);
- ArraySet<String> requestedPermissions = new ArraySet<>();
- int targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- int sharedUserPackagesSize = sharedUserSetting.packages.size();
- for (int packagesI = 0; packagesI < sharedUserPackagesSize; packagesI++) {
- PackageSetting packageSetting = sharedUserSetting.packages.valueAt(
- packagesI);
- if (packageSetting == null || packageSetting.pkg == null
- || !packageSetting.getInstalled(userId)) {
- continue;
- }
- AndroidPackage pkg = packageSetting.pkg;
- requestedPermissions.addAll(pkg.getRequestedPermissions());
- targetSdkVersion = Math.min(targetSdkVersion, pkg.getTargetSdkVersion());
- }
- generateFallbackPermissionsStateLpr(requestedPermissions, targetSdkVersion,
- sharedUserSetting.getPermissionsState(), userId);
+ sharedUserSetting.getPermissionsState().setMissing(true, userId);
}
}
}
@@ -5663,30 +5645,6 @@ public final class Settings {
}
}
- private void generateFallbackPermissionsStateLpr(
- @NonNull Collection<String> requestedPermissions, int targetSdkVersion,
- @NonNull PermissionsState permissionsState, @UserIdInt int userId) {
- for (String permissionName : requestedPermissions) {
- BasePermission permission = mPermissions.getPermission(permissionName);
- if (Objects.equals(permission.getSourcePackageName(), PLATFORM_PACKAGE_NAME)
- && permission.isRuntime() && !permission.isRemoved()) {
- if (permission.isHardOrSoftRestricted() || permission.isImmutablyRestricted()) {
- permissionsState.updatePermissionFlags(permission, userId,
- PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
- PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT);
- }
- if (targetSdkVersion < Build.VERSION_CODES.M) {
- permissionsState.updatePermissionFlags(permission, userId,
- PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
- | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
- PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
- | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT);
- permissionsState.grantRuntimePermission(permission, userId);
- }
- }
- }
- }
-
@GuardedBy("Settings.this.mLock")
private void readLegacyStateForUserSyncLPr(int userId) {
File permissionsFile = getUserRuntimePermissionsFile(userId);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 7d49f788c063..b0d4d957fc21 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -124,6 +124,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DumpUtils;
import com.android.internal.util.IntPair;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -154,6 +155,7 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -421,6 +423,10 @@ public class PermissionManagerService extends IPermissionManager.Stub {
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+ return;
+ }
+
mContext.getSystemService(PermissionControllerManager.class).dump(fd, args);
}
@@ -2473,13 +2479,60 @@ public class PermissionManagerService extends IPermissionManager.Stub {
}
final PermissionsState permissionsState = ps.getPermissionsState();
- PermissionsState origPermissions = permissionsState;
final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
boolean runtimePermissionsRevoked = false;
int[] updatedUserIds = EMPTY_INT_ARRAY;
+ for (int userId : currentUserIds) {
+ if (permissionsState.isMissing(userId)) {
+ Collection<String> requestedPermissions;
+ int targetSdkVersion;
+ if (!ps.isSharedUser()) {
+ requestedPermissions = pkg.getRequestedPermissions();
+ targetSdkVersion = pkg.getTargetSdkVersion();
+ } else {
+ requestedPermissions = new ArraySet<>();
+ targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ List<AndroidPackage> packages = ps.getSharedUser().getPackages();
+ int packagesSize = packages.size();
+ for (int i = 0; i < packagesSize; i++) {
+ AndroidPackage sharedUserPackage = packages.get(i);
+ requestedPermissions.addAll(sharedUserPackage.getRequestedPermissions());
+ targetSdkVersion = Math.min(targetSdkVersion,
+ sharedUserPackage.getTargetSdkVersion());
+ }
+ }
+
+ for (String permissionName : requestedPermissions) {
+ BasePermission permission = mSettings.getPermission(permissionName);
+ if (Objects.equals(permission.getSourcePackageName(), PLATFORM_PACKAGE_NAME)
+ && permission.isRuntime() && !permission.isRemoved()) {
+ if (permission.isHardOrSoftRestricted()
+ || permission.isImmutablyRestricted()) {
+ permissionsState.updatePermissionFlags(permission, userId,
+ PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
+ PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT);
+ }
+ if (targetSdkVersion < Build.VERSION_CODES.M) {
+ permissionsState.updatePermissionFlags(permission, userId,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+ PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+ | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT);
+ permissionsState.grantRuntimePermission(permission, userId);
+ }
+ }
+ }
+
+ permissionsState.setMissing(false, userId);
+ updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+ }
+ }
+
+ PermissionsState origPermissions = permissionsState;
+
boolean changedInstallPermission = false;
if (replace) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionsState.java b/services/core/java/com/android/server/pm/permission/PermissionsState.java
index 11e29a02068c..bad59cb1b567 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionsState.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionsState.java
@@ -16,6 +16,8 @@
package com.android.server.pm.permission;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -30,6 +32,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -70,6 +73,9 @@ public final class PermissionsState {
private int[] mGlobalGids = NO_GIDS;
+ @Nullable
+ private SparseBooleanArray mMissing;
+
private SparseBooleanArray mPermissionReviewRequired;
public PermissionsState() {
@@ -132,6 +138,23 @@ public final class PermissionsState {
other.mGlobalGids.length);
}
+ if (mMissing != null) {
+ if (other.mMissing == null) {
+ mMissing = null;
+ } else {
+ mMissing.clear();
+ }
+ }
+ if (other.mMissing != null) {
+ if (mMissing == null) {
+ mMissing = new SparseBooleanArray();
+ }
+ final int missingSize = other.mMissing.size();
+ for (int i = 0; i < missingSize; i++) {
+ mMissing.put(other.mMissing.keyAt(i), other.mMissing.valueAt(i));
+ }
+ }
+
if (mPermissionReviewRequired != null) {
if (other.mPermissionReviewRequired == null) {
mPermissionReviewRequired = null;
@@ -175,6 +198,10 @@ public final class PermissionsState {
}
}
+ if (!Objects.equals(mMissing, other.mMissing)) {
+ return false;
+ }
+
if (mPermissionReviewRequired == null) {
if (other.mPermissionReviewRequired != null) {
return false;
@@ -185,6 +212,35 @@ public final class PermissionsState {
return Arrays.equals(mGlobalGids, other.mGlobalGids);
}
+ /**
+ * Check whether the permissions state is missing for a user. This can happen if permission
+ * state is rolled back and we'll need to generate a reasonable default state to keep the app
+ * usable.
+ */
+ public boolean isMissing(@UserIdInt int userId) {
+ return mMissing != null && mMissing.get(userId);
+ }
+
+ /**
+ * Set whether the permissions state is missing for a user. This can happen if permission state
+ * is rolled back and we'll need to generate a reasonable default state to keep the app usable.
+ */
+ public void setMissing(boolean missing, @UserIdInt int userId) {
+ if (missing) {
+ if (mMissing == null) {
+ mMissing = new SparseBooleanArray();
+ }
+ mMissing.put(userId, true);
+ } else {
+ if (mMissing != null) {
+ mMissing.delete(userId);
+ if (mMissing.size() == 0) {
+ mMissing = null;
+ }
+ }
+ }
+ }
+
public boolean isPermissionReviewRequired(int userId) {
return mPermissionReviewRequired != null && mPermissionReviewRequired.get(userId);
}
@@ -569,6 +625,7 @@ public final class PermissionsState {
invalidateCache();
}
+ mMissing = null;
mPermissionReviewRequired = null;
}
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 3336697ef359..f075790a2fa0 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -301,10 +301,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
}
if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
String packageName = intent.getData().getSchemeSpecificPart();
- if (LOCAL_LOGV) {
- Slog.v(TAG, "broadcast=ACTION_PACKAGE_FULLY_REMOVED"
- + " pkg=" + packageName);
- }
+ Slog.i(TAG, "broadcast=ACTION_PACKAGE_FULLY_REMOVED pkg=" + packageName);
onPackageFullyRemoved(packageName);
}
}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index a859a42d8a8f..9871623becf5 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -321,17 +321,10 @@ public class StatsPullAtomService extends SystemService {
try {
switch (atomTag) {
case FrameworkStatsLog.WIFI_BYTES_TRANSFER:
- return pullDataBytesTransfer(atomTag, data, TRANSPORT_WIFI,
- /*withFgbg=*/ false);
case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG:
- return pullDataBytesTransfer(atomTag, data, TRANSPORT_WIFI,
- /*withFgbg=*/ true);
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
- return pullDataBytesTransfer(atomTag, data, TRANSPORT_CELLULAR,
- /*withFgbg=*/ false);
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG:
- return pullDataBytesTransfer(atomTag, data, TRANSPORT_CELLULAR,
- /*withFgbg=*/ true);
+ return pullDataBytesTransfer(atomTag, data);
case FrameworkStatsLog.BLUETOOTH_BYTES_TRANSFER:
return pullBluetoothBytesTransfer(atomTag, data);
case FrameworkStatsLog.KERNEL_WAKELOCK:
@@ -639,10 +632,14 @@ public class StatsPullAtomService extends SystemService {
Slog.d(TAG, "Registering NetworkStats pullers with statsd");
}
// Initialize NetworkStats baselines.
- mNetworkStatsBaselines.addAll(collectWifiBytesTransferSnapshot(/*withFgbg=*/ false));
- mNetworkStatsBaselines.addAll(collectWifiBytesTransferSnapshot(/*withFgbg=*/ true));
- mNetworkStatsBaselines.addAll(collectMobileBytesTransferSnapshot(/*withFgbg=*/ false));
- mNetworkStatsBaselines.addAll(collectMobileBytesTransferSnapshot(/*withFgbg=*/ true));
+ mNetworkStatsBaselines.addAll(
+ collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER));
+ mNetworkStatsBaselines.addAll(
+ collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG));
+ mNetworkStatsBaselines.addAll(
+ collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER));
+ mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+ FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG));
registerWifiBytesTransfer();
registerWifiBytesTransferBackground();
@@ -800,36 +797,42 @@ public class StatsPullAtomService extends SystemService {
}
@NonNull
- private List<NetworkStatsExt> collectWifiBytesTransferSnapshot(boolean withFgbg) {
- final List<NetworkStatsExt> ret = new ArrayList<>();
- final NetworkTemplate template = NetworkTemplate.buildTemplateWifiWildcard();
- final NetworkStats stats = getUidNetworkStatsSnapshot(template, withFgbg);
- if (stats != null) {
- ret.add(new NetworkStatsExt(stats, TRANSPORT_WIFI, withFgbg));
- }
- return ret;
- }
-
- // Get a snapshot of mobile data usage. The snapshot contains NetworkStats with its associated
+ private List<NetworkStatsExt> collectNetworkStatsSnapshotForAtom(int atomTag) {
+ switch(atomTag) {
+ case FrameworkStatsLog.WIFI_BYTES_TRANSFER:
+ return collectUidNetworkStatsSnapshot(TRANSPORT_WIFI, /*withFgbg=*/false);
+ case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG:
+ return collectUidNetworkStatsSnapshot(TRANSPORT_WIFI, /*withFgbg=*/true);
+ case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
+ return collectUidNetworkStatsSnapshot(TRANSPORT_CELLULAR, /*withFgbg=*/false);
+ case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG:
+ return collectUidNetworkStatsSnapshot(TRANSPORT_CELLULAR, /*withFgbg=*/true);
+ default:
+ throw new IllegalArgumentException("Unknown atomTag " + atomTag);
+ }
+ }
+
+ // Get a snapshot of Uid NetworkStats. The snapshot contains NetworkStats with its associated
// information, and wrapped by a list since multiple NetworkStatsExt objects might be collected.
- // TODO: Slice NetworkStats to multiple objects by RAT type or subscription.
@NonNull
- private List<NetworkStatsExt> collectMobileBytesTransferSnapshot(boolean withFgbg) {
+ private List<NetworkStatsExt> collectUidNetworkStatsSnapshot(int transport, boolean withFgbg) {
final List<NetworkStatsExt> ret = new ArrayList<>();
- final NetworkTemplate template =
- NetworkTemplate.buildTemplateMobileWithRatType(null, NETWORK_TYPE_ALL);
+ final NetworkTemplate template = (transport == TRANSPORT_CELLULAR
+ ? NetworkTemplate.buildTemplateMobileWithRatType(
+ /*subscriptionId=*/null, NETWORK_TYPE_ALL)
+ : NetworkTemplate.buildTemplateWifiWildcard());
+
final NetworkStats stats = getUidNetworkStatsSnapshot(template, withFgbg);
if (stats != null) {
- ret.add(new NetworkStatsExt(stats, TRANSPORT_CELLULAR, withFgbg));
+ ret.add(new NetworkStatsExt(stats, transport, withFgbg));
}
return ret;
}
+
private int pullDataBytesTransfer(
- int atomTag, @NonNull List<StatsEvent> pulledData, int transport, boolean withFgbg) {
- final List<NetworkStatsExt> current =
- (transport == TRANSPORT_CELLULAR ? collectMobileBytesTransferSnapshot(withFgbg)
- : collectWifiBytesTransferSnapshot(withFgbg));
+ int atomTag, @NonNull List<StatsEvent> pulledData) {
+ final List<NetworkStatsExt> current = collectNetworkStatsSnapshotForAtom(atomTag);
if (current == null) {
Slog.e(TAG, "current snapshot is null for " + atomTag + ", return.");
@@ -844,7 +847,7 @@ public class StatsPullAtomService extends SystemService {
// skip reporting anything since the snapshot is invalid.
if (baseline == null) {
Slog.e(TAG, "baseline is null for " + atomTag + ", transport="
- + item.transport + " , withFgbg=" + withFgbg + ", return.");
+ + item.transport + " , withFgbg=" + item.withFgbg + ", return.");
return StatsManager.PULL_SKIP;
}
final NetworkStatsExt diff = new NetworkStatsExt(item.stats.subtract(
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 72cdf4afb007..15b6a8df0151 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -114,7 +114,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
private static final String TAG = "UriGrantsManagerService";
// Maximum number of persisted Uri grants a package is allowed
private static final int MAX_PERSISTED_URI_GRANTS = 128;
- private static final boolean ENABLE_DYNAMIC_PERMISSIONS = false;
+ private static final boolean ENABLE_DYNAMIC_PERMISSIONS = true;
private final Object mLock = new Object();
private final H mH;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0598680f8c5d..b5b82d39b921 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3589,7 +3589,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Override
ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom,
- WindowContainer boundary) {
+ ActivityRecord boundary) {
return callback.test(this) ? this : null;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 2f868d949970..9b9b61340332 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -2708,7 +2708,9 @@ class ActivityStack extends Task {
*/
@Nullable
private ActivityRecord getOccludingActivityAbove(ActivityRecord activity) {
- return getActivity((ar) -> ar.occludesParent(), true /* traverseTopToBottom */, activity);
+ ActivityRecord top = getActivity((ar) -> ar.occludesParent(),
+ true /* traverseTopToBottom */, activity);
+ return top != activity ? top : null;
}
boolean willActivityBeVisible(IBinder token) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index dfa3fe088770..c28d47cdbe80 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -19,7 +19,6 @@ package com.android.server.wm;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL;
@@ -193,9 +192,7 @@ public class ActivityStartController {
final ActivityStack homeStack;
try {
// Make sure home stack exists on display area.
- // TODO(b/153624902): Replace with TaskDisplayArea#getOrCreateRootHomeTask()
- homeStack = taskDisplayArea.getOrCreateStack(WINDOWING_MODE_UNDEFINED,
- ACTIVITY_TYPE_HOME, ON_TOP);
+ homeStack = taskDisplayArea.getOrCreateRootHomeTask(ON_TOP);
} finally {
mSupervisor.endDeferResume();
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 78e4237eb4a7..fdbb2b25bd39 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3319,7 +3319,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
- public void resizeTask(int taskId, Rect bounds, int resizeMode) {
+ public boolean resizeTask(int taskId, Rect bounds, int resizeMode) {
mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "resizeTask()");
long ident = Binder.clearCallingIdentity();
try {
@@ -3328,10 +3328,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
MATCH_TASK_IN_STACKS_ONLY);
if (task == null) {
Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found");
- return;
+ return false;
}
if (!task.getWindowConfiguration().canResizeTask()) {
- throw new IllegalArgumentException("resizeTask not allowed on task=" + task);
+ Slog.w(TAG, "resizeTask not allowed on task=" + task);
+ return false;
}
// Reparent the task to the right stack if necessary
@@ -3339,7 +3340,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
// After reparenting (which only resizes the task to the stack bounds), resize the
// task to the actual bounds provided
- task.resize(bounds, resizeMode, preserveWindow);
+ return task.resize(bounds, resizeMode, preserveWindow);
}
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 345cfb0aad71..a45a15ba2012 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -66,6 +66,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
final int mFeatureId;
private final DisplayAreaOrganizerController mOrganizerController;
IDisplayAreaOrganizer mOrganizer;
+ private final Configuration mTmpConfiguration = new Configuration();
DisplayArea(WindowManagerService wms, Type type, String name) {
this(wms, type, name, FEATURE_UNDEFINED);
@@ -162,8 +163,10 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
+ mTmpConfiguration.setTo(getConfiguration());
super.onConfigurationChanged(newParentConfig);
- if (mOrganizer != null) {
+
+ if (mOrganizer != null && getConfiguration().diff(mTmpConfiguration) != 0) {
mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0b2bd811bb84..0efb9698f4b0 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -498,8 +498,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
*/
ActivityRecord mFixedRotationLaunchingApp;
- FixedRotationAnimationController mFixedRotationAnimationController;
-
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
@@ -1540,11 +1538,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
private void startFixedRotationTransform(WindowToken token, int rotation) {
- if (mFixedRotationAnimationController == null) {
- mFixedRotationAnimationController = new FixedRotationAnimationController(
- this);
- }
- mFixedRotationAnimationController.hide(rotation);
mTmpConfiguration.unset();
final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation);
final WmDisplayCutout cutout = calculateDisplayCutoutForRotation(rotation);
@@ -1566,13 +1559,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
}
}
- void finishFixedRotationAnimation() {
- if (mFixedRotationAnimationController != null
- && mFixedRotationAnimationController.show()) {
- mFixedRotationAnimationController = null;
- }
- }
-
/**
* Update rotation of the display.
*
@@ -4700,9 +4686,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
boolean supportsSystemDecorations() {
return (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
|| (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
- || (mWmService.mForceDesktopModeOnExternalDisplays && !isUntrustedVirtualDisplay()))
+ || mWmService.mForceDesktopModeOnExternalDisplays)
// VR virtual display will be used to run and render 2D app within a VR experience.
- && mDisplayId != mWmService.mVr2dDisplayId;
+ && mDisplayId != mWmService.mVr2dDisplayId
+ // Do not show system decorations on untrusted virtual display.
+ && !isUntrustedVirtualDisplay();
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index c3f906135a00..ebfe70c0c371 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -560,7 +560,6 @@ public class DisplayRotation {
}, true /* traverseTopToBottom */);
mSeamlessRotationCount = 0;
mRotatingSeamlessly = false;
- mDisplayContent.finishFixedRotationAnimation();
}
private void prepareSeamlessRotation() {
@@ -647,7 +646,6 @@ public class DisplayRotation {
"Performing post-rotate rotation after seamless rotation");
// Finish seamless rotation.
mRotatingSeamlessly = false;
- mDisplayContent.finishFixedRotationAnimation();
updateRotationAndSendNewConfigIfChanged();
}
diff --git a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
deleted file mode 100644
index 7aca63774889..000000000000
--- a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static com.android.server.wm.AnimationSpecProto.WINDOW;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
-import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
-
-import android.content.res.Configuration;
-import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Transformation;
-
-import com.android.internal.R;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/**
- * Controller to fade out and in system ui when applying a fixed rotation transform to a window
- * token.
- *
- * The system bars will be fade out when the fixed rotation transform starts and will be fade in
- * once all surfaces have been rotated.
- */
-public class FixedRotationAnimationController {
-
- private final WindowManagerService mWmService;
- private boolean mShowRequested = true;
- private int mTargetRotation = Configuration.ORIENTATION_UNDEFINED;
- private final ArrayList<WindowState> mAnimatedWindowStates = new ArrayList<>(2);
- private final Runnable[] mDeferredFinishCallbacks;
-
- public FixedRotationAnimationController(DisplayContent displayContent) {
- mWmService = displayContent.mWmService;
- addAnimatedWindow(displayContent.getDisplayPolicy().getStatusBar());
- addAnimatedWindow(displayContent.getDisplayPolicy().getNavigationBar());
- mDeferredFinishCallbacks = new Runnable[mAnimatedWindowStates.size()];
- }
-
- private void addAnimatedWindow(WindowState windowState) {
- if (windowState != null) {
- mAnimatedWindowStates.add(windowState);
- }
- }
-
- /**
- * Show the previously hidden {@link WindowToken} if their surfaces have already been rotated.
- *
- * @return True if the show animation has been started, in which case the caller no longer needs
- * this {@link FixedRotationAnimationController}.
- */
- boolean show() {
- if (!mShowRequested && readyToShow()) {
- mShowRequested = true;
- for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
- WindowState windowState = mAnimatedWindowStates.get(i);
- fadeWindowToken(true, windowState.getParent(), i);
- }
- return true;
- }
- return false;
- }
-
- void hide(int targetRotation) {
- mTargetRotation = targetRotation;
- if (mShowRequested) {
- mShowRequested = false;
- for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
- WindowState windowState = mAnimatedWindowStates.get(i);
- fadeWindowToken(false /* show */, windowState.getParent(), i);
- }
- }
- }
-
- void cancel() {
- for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
- WindowState windowState = mAnimatedWindowStates.get(i);
- mShowRequested = true;
- fadeWindowToken(true /* show */, windowState.getParent(), i);
- }
- }
-
- private void fadeWindowToken(boolean show, WindowContainer<WindowToken> windowToken,
- int index) {
- Animation animation = AnimationUtils.loadAnimation(mWmService.mContext,
- show ? R.anim.fade_in : R.anim.fade_out);
- LocalAnimationAdapter.AnimationSpec windowAnimationSpec = createAnimationSpec(animation);
-
- FixedRotationAnimationAdapter animationAdapter = new FixedRotationAnimationAdapter(
- windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, index);
-
- // We deferred the end of the animation when hiding the token, so we need to end it now that
- // it's shown again.
- SurfaceAnimator.OnAnimationFinishedCallback finishedCallback = show ? (t, r) -> {
- if (mDeferredFinishCallbacks[index] != null) {
- mDeferredFinishCallbacks[index].run();
- mDeferredFinishCallbacks[index] = null;
- }
- } : null;
- windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter,
- mShowRequested, ANIMATION_TYPE_FIXED_TRANSFORM, finishedCallback);
- }
-
- /**
- * Check if all the mAnimatedWindowState's surfaces have been rotated to the
- * mTargetRotation.
- */
- private boolean readyToShow() {
- for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
- WindowState windowState = mAnimatedWindowStates.get(i);
- if (windowState.getConfiguration().windowConfiguration.getRotation()
- != mTargetRotation || windowState.mPendingSeamlessRotate != null) {
- return false;
- }
- }
- return true;
- }
-
-
- private LocalAnimationAdapter.AnimationSpec createAnimationSpec(Animation animation) {
- return new LocalAnimationAdapter.AnimationSpec() {
-
- Transformation mTransformation = new Transformation();
-
- @Override
- public boolean getShowWallpaper() {
- return true;
- }
-
- @Override
- public long getDuration() {
- return animation.getDuration();
- }
-
- @Override
- public void apply(SurfaceControl.Transaction t, SurfaceControl leash,
- long currentPlayTime) {
- mTransformation.clear();
- animation.getTransformation(currentPlayTime, mTransformation);
- t.setAlpha(leash, mTransformation.getAlpha());
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix);
- pw.println(animation);
- }
-
- @Override
- public void dumpDebugInner(ProtoOutputStream proto) {
- final long token = proto.start(WINDOW);
- proto.write(ANIMATION, animation.toString());
- proto.end(token);
- }
- };
- }
-
- private class FixedRotationAnimationAdapter extends LocalAnimationAdapter {
- private final boolean mShow;
- private final int mIndex;
-
- FixedRotationAnimationAdapter(AnimationSpec windowAnimationSpec,
- SurfaceAnimationRunner surfaceAnimationRunner, boolean show, int index) {
- super(windowAnimationSpec, surfaceAnimationRunner);
- mShow = show;
- mIndex = index;
- }
-
- @Override
- public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
- // We defer the end of the hide animation to ensure the tokens stay hidden until
- // we show them again.
- if (!mShow) {
- mDeferredFinishCallbacks[mIndex] = endDeferFinishCallback;
- return true;
- }
- return false;
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index c02e0a11a0c5..c7f78342c829 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -387,9 +387,11 @@ class RemoteAnimationController implements DeathRecipient {
int getMode() {
final DisplayContent dc = mWindowContainer.getDisplayContent();
final ActivityRecord topActivity = mWindowContainer.getTopMostActivity();
+ // Note that opening/closing transitions are per-activity while changing transitions
+ // are per-task.
if (dc.mOpeningApps.contains(topActivity)) {
return RemoteAnimationTarget.MODE_OPENING;
- } else if (dc.mChangingContainers.contains(topActivity)) {
+ } else if (dc.mChangingContainers.contains(mWindowContainer)) {
return RemoteAnimationTarget.MODE_CHANGING;
} else {
return RemoteAnimationTarget.MODE_CLOSING;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 888a6e986e88..0ecde72cc566 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1369,7 +1369,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
calculateDefaultMinimalSizeOfResizeableTasks();
final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea();
- defaultTaskDisplayArea.getOrCreateRootHomeTask();
+ defaultTaskDisplayArea.getOrCreateRootHomeTask(ON_TOP);
positionChildAt(POSITION_TOP, defaultTaskDisplayArea.mDisplayContent,
false /* includingParents */);
}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 0143eb1abe03..42342a60ba16 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -489,12 +489,6 @@ class SurfaceAnimator {
static final int ANIMATION_TYPE_INSETS_CONTROL = 1 << 5;
/**
- * Animation when a fixed rotation transform is applied to a window token.
- * @hide
- */
- static final int ANIMATION_TYPE_FIXED_TRANSFORM = 1 << 6;
-
- /**
* Bitmask to include all animation types. This is NOT an {@link AnimationType}
* @hide
*/
@@ -511,8 +505,7 @@ class SurfaceAnimator {
ANIMATION_TYPE_DIMMER,
ANIMATION_TYPE_RECENTS,
ANIMATION_TYPE_WINDOW_ANIMATION,
- ANIMATION_TYPE_INSETS_CONTROL,
- ANIMATION_TYPE_FIXED_TRANSFORM
+ ANIMATION_TYPE_INSETS_CONTROL
})
@Retention(RetentionPolicy.SOURCE)
@interface AnimationType {}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 37a4c1f6849b..6ce36f1a3eb6 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1461,16 +1461,23 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> {
return mChildren.get(index);
}
+ @Nullable
+ ActivityStack getOrCreateRootHomeTask() {
+ return getOrCreateRootHomeTask(false /* onTop */);
+ }
+
/**
* Returns the existing home stack or creates and returns a new one if it should exist for the
* display.
+ * @param onTop Only be used when there is no existing home stack. If true the home stack will
+ * be created at the top of the display, else at the bottom.
*/
@Nullable
- ActivityStack getOrCreateRootHomeTask() {
+ ActivityStack getOrCreateRootHomeTask(boolean onTop) {
ActivityStack homeTask = getRootHomeTask();
if (homeTask == null && mDisplayContent.supportsSystemDecorations()
&& !mDisplayContent.isUntrustedVirtualDisplay()) {
- homeTask = createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, false /* onTop */);
+ homeTask = createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, onTop);
}
return homeTask;
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 45023acf4466..c6e1c954be12 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -28,12 +28,14 @@ import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.os.Process;
import android.os.SystemClock;
+import android.os.UserManagerInternal;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
import java.io.File;
@@ -72,6 +74,7 @@ class TaskSnapshotPersister {
private final float mLowResScaleFactor;
private boolean mEnableLowResSnapshots;
private final boolean mUse16BitFormat;
+ private final UserManagerInternal mUserManagerInternal;
/**
* The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
@@ -82,6 +85,8 @@ class TaskSnapshotPersister {
TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) {
mDirectoryResolver = resolver;
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+
final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
com.android.internal.R.dimen.config_highResTaskSnapshotScale);
final float lowResTaskSnapshotScale = service.mContext.getResources().getFloat(
@@ -191,7 +196,7 @@ class TaskSnapshotPersister {
return;
}
}
- SystemClock.sleep(100);
+ SystemClock.sleep(DELAY_MS);
}
}
@@ -233,7 +238,7 @@ class TaskSnapshotPersister {
private boolean createDirectory(int userId) {
final File dir = getDirectory(userId);
- return dir.exists() || dir.mkdirs();
+ return dir.exists() || dir.mkdir();
}
private void deleteSnapshot(int taskId, int userId) {
@@ -258,18 +263,26 @@ class TaskSnapshotPersister {
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
WriteQueueItem next;
+ boolean isReadyToWrite = false;
synchronized (mLock) {
if (mPaused) {
next = null;
} else {
next = mWriteQueue.poll();
if (next != null) {
- next.onDequeuedLocked();
+ if (next.isReady()) {
+ isReadyToWrite = true;
+ next.onDequeuedLocked();
+ } else {
+ mWriteQueue.addLast(next);
+ }
}
}
}
if (next != null) {
- next.write();
+ if (isReadyToWrite) {
+ next.write();
+ }
SystemClock.sleep(DELAY_MS);
}
synchronized (mLock) {
@@ -289,6 +302,13 @@ class TaskSnapshotPersister {
};
private abstract class WriteQueueItem {
+ /**
+ * @return {@code true} if item is ready to have {@link WriteQueueItem#write} called
+ */
+ boolean isReady() {
+ return true;
+ }
+
abstract void write();
/**
@@ -328,6 +348,11 @@ class TaskSnapshotPersister {
}
@Override
+ boolean isReady() {
+ return mUserManagerInternal.isUserUnlocked(mUserId);
+ }
+
+ @Override
void write() {
if (!createDirectory(mUserId)) {
Slog.e(TAG, "Unable to create snapshot directory for user dir="
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 7bfddd79f8a4..7757b3a50941 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1415,11 +1415,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom,
- WindowContainer boundary) {
+ ActivityRecord boundary) {
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
- if (wc == boundary) return null;
+ // TODO(b/156986561): Improve the correctness of the boundary check.
+ if (wc == boundary) return boundary;
final ActivityRecord r = wc.getActivity(callback, traverseTopToBottom, boundary);
if (r != null) {
@@ -1430,7 +1431,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
final WindowContainer wc = mChildren.get(i);
- if (wc == boundary) return null;
+ // TODO(b/156986561): Improve the correctness of the boundary check.
+ if (wc == boundary) return boundary;
final ActivityRecord r = wc.getActivity(callback, traverseTopToBottom, boundary);
if (r != null) {
@@ -2182,7 +2184,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
final int appStackClipMode = getDisplayContent().mAppTransition.getAppStackClipMode();
// Separate position and size for use in animators.
- mTmpRect.set(getAnimationBounds(appStackClipMode));
+ final Rect screenBounds = getAnimationBounds(appStackClipMode);
+ mTmpRect.set(screenBounds);
getAnimationPosition(mTmpPoint);
if (!sHierarchicalAnimations) {
// Non-hierarchical animation uses position in global coordinates.
@@ -2201,7 +2204,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
localBounds.offsetTo(mTmpPoint.x, mTmpPoint.y);
final RemoteAnimationController.RemoteAnimationRecord adapters =
controller.createRemoteAnimationRecord(this, mTmpPoint, localBounds,
- mTmpRect, (isChanging ? mSurfaceFreezer.mFreezeBounds : null));
+ screenBounds, (isChanging ? mSurfaceFreezer.mFreezeBounds : null));
resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter);
} else if (isChanging) {
final float durationScale = mWmService.getTransitionAnimationScaleLocked();
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 8739bad4398b..768f89eff774 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -642,9 +642,6 @@ class WindowToken extends WindowContainer<WindowState> {
final int originalRotation = getWindowConfiguration().getRotation();
onConfigurationChanged(parent.getConfiguration());
onCancelFixedRotationTransform(originalRotation);
- if (mDisplayContent.mFixedRotationAnimationController != null) {
- mDisplayContent.mFixedRotationAnimationController.cancel();
- }
}
/**
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 78439dba2724..f0dca772adaa 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -63,6 +63,7 @@ struct Constants {
static constexpr auto libDir = "lib"sv;
static constexpr auto libSuffix = ".so"sv;
static constexpr auto blockSize = 4096;
+ static constexpr auto systemPackage = "android"sv;
};
static const Constants& constants() {
@@ -377,7 +378,8 @@ void IncrementalService::onSystemReady() {
std::lock_guard l(mLock);
mounts.reserve(mMounts.size());
for (auto&& [id, ifs] : mMounts) {
- if (ifs->mountId == id) {
+ if (ifs->mountId == id &&
+ ifs->dataLoaderStub->params().packageName == Constants::systemPackage) {
mounts.push_back(ifs);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index e5ec1f76c554..96a44a46bbaf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -16,14 +16,23 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+
import static com.android.server.am.ActivityManagerService.Injector;
import static com.android.server.am.CachedAppOptimizer.compactActionIntToString;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.MessageQueue;
import android.os.Process;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
@@ -31,9 +40,11 @@ import android.text.TextUtils;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.appop.AppOpsService;
import com.android.server.testables.TestableDeviceConfig;
+import com.android.server.wm.ActivityTaskManagerService;
import org.junit.After;
import org.junit.Assume;
@@ -45,6 +56,7 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.File;
+import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -68,25 +80,36 @@ public final class CachedAppOptimizerTest {
private HandlerThread mHandlerThread;
private Handler mHandler;
private CountDownLatch mCountDown;
+ private ActivityManagerService mAms;
+ private Context mContext;
+ private TestInjector mInjector;
+ private TestProcessDependencies mProcessDependencies;
+
+ @Mock
+ private PackageManagerInternal mPackageManagerInt;
@Rule
- public TestableDeviceConfig.TestableDeviceConfigRule
+ public final TestableDeviceConfig.TestableDeviceConfigRule
mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+ @Rule
+ public final ApplicationExitInfoTest.ServiceThreadRule
+ mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
@Before
public void setUp() {
mHandlerThread = new HandlerThread("");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
-
mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT,
true /* allowIo */);
mThread.start();
-
- ActivityManagerService ams = new ActivityManagerService(
- new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()),
- mThread);
- mCachedAppOptimizerUnderTest = new CachedAppOptimizer(ams,
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ mInjector = new TestInjector(mContext);
+ mAms = new ActivityManagerService(
+ new TestInjector(mContext), mServiceThreadRule.getThread());
+ doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+ mProcessDependencies = new TestProcessDependencies();
+ mCachedAppOptimizerUnderTest = new CachedAppOptimizer(mAms,
new CachedAppOptimizer.PropertyChangedCallbackForTest() {
@Override
public void onPropertyChanged() {
@@ -94,7 +117,9 @@ public final class CachedAppOptimizerTest {
mCountDown.countDown();
}
}
- });
+ }, mProcessDependencies);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
}
@After
@@ -104,6 +129,19 @@ public final class CachedAppOptimizerTest {
mCountDown = null;
}
+ private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, String processName,
+ String packageName) {
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
+ app.pid = pid;
+ app.info.uid = packageUid;
+ // Exact value does not mater, it can be any state for which compaction is allowed.
+ app.setProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ app.setAdj = 905;
+ return app;
+ }
+
@Test
public void init_setsDefaults() {
mCachedAppOptimizerUnderTest.init();
@@ -197,7 +235,7 @@ public final class CachedAppOptimizerTest {
CachedAppOptimizer.DEFAULT_USE_FREEZER);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
CachedAppOptimizer.KEY_USE_FREEZER, CachedAppOptimizer.DEFAULT_USE_FREEZER
- ? "false" : "true" , false);
+ ? "false" : "true", false);
// Then calling init will read and set that flag.
mCachedAppOptimizerUnderTest.init();
@@ -790,6 +828,174 @@ public final class CachedAppOptimizerTest {
.containsExactlyElementsIn(expected);
}
+ @Test
+ public void processWithDeltaRSSTooSmall_notFullCompacted() throws Exception {
+ // Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set RSS
+ // throttle to 12000.
+ mCachedAppOptimizerUnderTest.init();
+ setFlag(CachedAppOptimizer.KEY_USE_COMPACTION, "true", true);
+ setFlag(CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "12000", false);
+ initActivityManagerService();
+
+ // Simulate RSS anon memory larger than throttle.
+ long[] rssBefore1 =
+ new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 10000, /*anonRSS*/ 12000, /*swap*/
+ 10000};
+ long[] rssAfter1 =
+ new long[]{/*totalRSS*/ 9000, /*fileRSS*/ 9000, /*anonRSS*/ 11000, /*swap*/9000};
+ // Delta between rssAfter1 and rssBefore2 is below threshold (500).
+ long[] rssBefore2 =
+ new long[]{/*totalRSS*/ 9500, /*fileRSS*/ 9500, /*anonRSS*/ 11500, /*swap*/9500};
+ long[] rssAfter2 =
+ new long[]{/*totalRSS*/ 8000, /*fileRSS*/ 8000, /*anonRSS*/ 9000, /*swap*/8000};
+ // Delta between rssAfter1 and rssBefore3 is above threshold (13000).
+ long[] rssBefore3 =
+ new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 18000, /*anonRSS*/ 13000, /*swap*/ 7000};
+ long[] rssAfter3 =
+ new long[]{/*totalRSS*/ 10000, /*fileRSS*/ 11000, /*anonRSS*/ 10000, /*swap*/ 6000};
+ long[] valuesAfter = {};
+ // Process that passes properties.
+ int pid = 1;
+ ProcessRecord processRecord = makeProcessRecord(pid, 2, 3, "p1", "app1");
+
+ // GIVEN we simulate RSS memory before above thresholds and it is the first time 'p1' is
+ // compacted.
+ mProcessDependencies.setRss(rssBefore1);
+ mProcessDependencies.setRssAfterCompaction(rssAfter1); //
+ // WHEN we try to run compaction
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ waitForHandler();
+ // THEN process IS compacted.
+ assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
+ valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get(
+ pid).getRssAfterCompaction();
+ assertThat(valuesAfter).isEqualTo(rssAfter1);
+
+ // WHEN delta is below threshold (500).
+ mProcessDependencies.setRss(rssBefore2);
+ mProcessDependencies.setRssAfterCompaction(rssAfter2);
+ // This is to avoid throttle of compacting too soon.
+ processRecord.lastCompactTime = processRecord.lastCompactTime - 10_000;
+ // WHEN we try to run compaction.
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ waitForHandler();
+ // THEN process IS NOT compacted - values after compaction for process 1 should remain the
+ // same as from the last compaction.
+ assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
+ valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get(
+ pid).getRssAfterCompaction();
+ assertThat(valuesAfter).isEqualTo(rssAfter1);
+
+ // WHEN delta is above threshold (13000).
+ mProcessDependencies.setRss(rssBefore3);
+ mProcessDependencies.setRssAfterCompaction(rssAfter3);
+ // This is to avoid throttle of compacting too soon.
+ processRecord.lastCompactTime = processRecord.lastCompactTime - 10_000;
+ // WHEN we try to run compaction
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ waitForHandler();
+ // THEN process IS compacted - values after compaction for process 1 should be updated.
+ assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
+ valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get(
+ pid).getRssAfterCompaction();
+ assertThat(valuesAfter).isEqualTo(rssAfter3);
+
+ }
+
+ @Test
+ public void processWithAnonRSSTooSmall_notFullCompacted() throws Exception {
+ // Initialize CachedAppOptimizer and set flags to (1) enable compaction, (2) set RSS
+ // throttle to 8000.
+ mCachedAppOptimizerUnderTest.init();
+ setFlag(CachedAppOptimizer.KEY_USE_COMPACTION, "true", true);
+ setFlag(CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "8000", false);
+ initActivityManagerService();
+
+ // Simulate RSS anon memory larger than throttle.
+ long[] rssBelowThreshold =
+ new long[]{/*Total RSS*/ 10000, /*File RSS*/ 10000, /*Anon RSS*/ 7000, /*Swap*/
+ 10000};
+ long[] rssBelowThresholdAfter =
+ new long[]{/*Total RSS*/ 9000, /*File RSS*/ 7000, /*Anon RSS*/ 4000, /*Swap*/
+ 8000};
+ long[] rssAboveThreshold =
+ new long[]{/*Total RSS*/ 10000, /*File RSS*/ 10000, /*Anon RSS*/ 9000, /*Swap*/
+ 10000};
+ long[] rssAboveThresholdAfter =
+ new long[]{/*Total RSS*/ 8000, /*File RSS*/ 9000, /*Anon RSS*/ 6000, /*Swap*/5000};
+ // Process that passes properties.
+ int pid = 1;
+ ProcessRecord processRecord =
+ makeProcessRecord(pid, 2, 3, "p1",
+ "app1");
+
+ // GIVEN we simulate RSS memory before below threshold.
+ mProcessDependencies.setRss(rssBelowThreshold);
+ mProcessDependencies.setRssAfterCompaction(rssBelowThresholdAfter);
+ // WHEN we try to run compaction
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ waitForHandler();
+ // THEN process IS NOT compacted.
+ assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNull();
+
+ // GIVEN we simulate RSS memory before above threshold.
+ mProcessDependencies.setRss(rssAboveThreshold);
+ mProcessDependencies.setRssAfterCompaction(rssAboveThresholdAfter);
+ // WHEN we try to run compaction
+ mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
+ waitForHandler();
+ // THEN process IS compacted.
+ assertThat(mCachedAppOptimizerUnderTest.mLastCompactionStats.get(pid)).isNotNull();
+ long[] valuesAfter = mCachedAppOptimizerUnderTest.mLastCompactionStats.get(
+ pid).getRssAfterCompaction();
+ assertThat(valuesAfter).isEqualTo(rssAboveThresholdAfter);
+ }
+
+
+ private void setFlag(String key, String value, boolean defaultValue) throws Exception {
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, key, value, defaultValue);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ }
+
+ private void waitForHandler() {
+ Idle idle = new Idle();
+ mCachedAppOptimizerUnderTest.mCompactionHandler.getLooper().getQueue().addIdleHandler(idle);
+ mCachedAppOptimizerUnderTest.mCompactionHandler.post(() -> { });
+ idle.waitForIdle();
+ }
+
+ private void initActivityManagerService() {
+ mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
+ mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+ mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+ mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal());
+ mAms.mPackageManagerInt = mPackageManagerInt;
+ }
+
+ private static final class Idle implements MessageQueue.IdleHandler {
+ private boolean mIdle;
+
+ @Override
+ public boolean queueIdle() {
+ synchronized (this) {
+ mIdle = true;
+ notifyAll();
+ }
+ return false;
+ }
+
+ public synchronized void waitForIdle() {
+ while (!mIdle) {
+ try {
+ // Wait with a timeout of 10s.
+ wait(10000);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+
private class TestInjector extends Injector {
TestInjector(Context context) {
@@ -806,4 +1012,29 @@ public final class CachedAppOptimizerTest {
return mHandler;
}
}
+
+ // Test implementation for ProcessDependencies.
+ private static final class TestProcessDependencies
+ implements CachedAppOptimizer.ProcessDependencies {
+ private long[] mRss;
+ private long[] mRssAfterCompaction;
+
+ @Override
+ public long[] getRss(int pid) {
+ return mRss;
+ }
+
+ @Override
+ public void performCompaction(String action, int pid) throws IOException {
+ mRss = mRssAfterCompaction;
+ }
+
+ public void setRss(long[] newValues) {
+ mRss = newValues;
+ }
+
+ public void setRssAfterCompaction(long[] newValues) {
+ mRssAfterCompaction = newValues;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index efa25bd7721b..320dacff4888 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1582,6 +1582,41 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
"s2");
}
+ public void testCachedShortcuts_canPassShortcutLimit() {
+ // Change the max number of shortcuts.
+ mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=4");
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(makeLongLivedShortcut("s1"),
+ makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
+ makeLongLivedShortcut("s4"))));
+ });
+
+ // Cache All
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3", "s4"),
+ HANDLE_USER_0);
+ });
+
+ setCaller(CALLING_PACKAGE_1);
+
+ // Get dynamic shortcuts
+ assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
+ "s1", "s2", "s3", "s4");
+ // Get cached shortcuts
+ assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
+ "s1", "s2", "s3", "s4");
+
+ assertTrue(mManager.setDynamicShortcuts(makeShortcuts("sx1", "sx2", "sx3", "sx4")));
+
+ // Get dynamic shortcuts
+ assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC),
+ "sx1", "sx2", "sx3", "sx4");
+ // Get cached shortcuts
+ assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
+ "s1", "s2", "s3", "s4");
+ }
+
// === Test for launcher side APIs ===
public void testGetShortcuts() {
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index e4102205ddbb..2d45f9ea40c7 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -387,7 +387,7 @@ public class AppStandbyControllerTests {
@Test
public void testBoundWidgetPackageExempt() throws Exception {
assumeTrue(mInjector.getContext().getSystemService(AppWidgetManager.class) != null);
- assertEquals(STANDBY_BUCKET_EXEMPTED,
+ assertEquals(STANDBY_BUCKET_ACTIVE,
mController.getAppStandbyBucket(PACKAGE_EXEMPTED_1, USER_ID,
mInjector.mElapsedRealtime, false));
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index f4e5d569512a..078c21e04512 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -81,6 +81,7 @@ import android.testing.TestableContentResolver;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
+import android.util.StatsEvent;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
@@ -89,6 +90,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.FastXmlSerializer;
import com.android.server.UiServiceTestCase;
+
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.Before;
@@ -2996,6 +2998,31 @@ public class PreferencesHelperTest extends UiServiceTestCase {
PKG_O, UID_O, parent.getId(), conversationId, false, false), conversationId);
}
+
+ @Test
+ public void testPullConversationNotificationChannel() {
+ String conversationId = "friend";
+
+ NotificationChannel parent =
+ new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+ String channelId = String.format(
+ CONVERSATION_CHANNEL_ID_FORMAT, parent.getId(), conversationId);
+ NotificationChannel friend = new NotificationChannel(channelId,
+ "messages", IMPORTANCE_DEFAULT);
+ friend.setConversationId(parent.getId(), conversationId);
+ mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+ ArrayList<StatsEvent> events = new ArrayList<>();
+ mHelper.pullPackageChannelPreferencesStats(events);
+ boolean found = false;
+ for (StatsEvent event : events) {
+ // TODO(b/153195691): inspect the content once it is possible to do so
+ found = true;
+ }
+ assertTrue("conversation was not in the pull", found);
+ }
+
@Test
public void testGetNotificationChannel_conversationProvidedByNotCustomizedYet() {
String conversationId = "friend";
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
index 6a1f50d7e58a..f4b50dc6b553 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
import android.os.RemoteException;
@@ -91,5 +92,11 @@ public class DisplayAreaOrganizerTest extends WindowTestsBase {
mDisplayContent.setBounds(new Rect(0, 0, 1000, 1000));
verify(organizer).onDisplayAreaInfoChanged(any());
+
+ Configuration tmpConfiguration = new Configuration();
+ tmpConfiguration.setTo(mDisplayContent.getRequestedOverrideConfiguration());
+ mDisplayContent.onRequestedOverrideConfigurationChanged(tmpConfiguration);
+ // Ensure it was still only called once if the bounds didn't change
+ verify(organizer).onDisplayAreaInfoChanged(any());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 7b23bfb48a1a..ac95a817bec9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -57,7 +57,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.same;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
@@ -1060,8 +1059,6 @@ public class DisplayContentTests extends WindowTestsBase {
@Test
public void testApplyTopFixedRotationTransform() {
mWm.mIsFixedRotationTransformEnabled = true;
- mDisplayContent.getDisplayPolicy().addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
- mDisplayContent.getDisplayPolicy().addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
final Configuration config90 = new Configuration();
mDisplayContent.computeScreenConfiguration(config90, ROTATION_90);
@@ -1082,12 +1079,6 @@ public class DisplayContentTests extends WindowTestsBase {
ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
false /* forceUpdate */));
- assertNotNull(mDisplayContent.mFixedRotationAnimationController);
- assertTrue(mStatusBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
- ANIMATION_TYPE_FIXED_TRANSFORM));
- assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
- ANIMATION_TYPE_FIXED_TRANSFORM));
-
final Rect outFrame = new Rect();
final Rect outInsets = new Rect();
final Rect outStableInsets = new Rect();
@@ -1140,9 +1131,6 @@ public class DisplayContentTests extends WindowTestsBase {
assertFalse(app.hasFixedRotationTransform());
assertFalse(app2.hasFixedRotationTransform());
assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
-
- mDisplayContent.finishFixedRotationAnimation();
- assertNull(mDisplayContent.mFixedRotationAnimationController);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 88de34dd36b0..bdcae481b378 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -24,6 +24,7 @@ import static android.graphics.GraphicBuffer.USAGE_SW_READ_RARELY;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -39,10 +40,15 @@ import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.UserManager;
+import android.os.UserManagerInternal;
import android.view.Surface;
+import com.android.server.LocalServices;
+
import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import java.io.File;
import java.util.function.Predicate;
@@ -70,11 +76,26 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase {
mLowResScale = lowResScale;
}
+ @BeforeClass
+ public static void setUpOnce() {
+ final UserManagerInternal userManager = mock(UserManagerInternal.class);
+ LocalServices.addService(UserManagerInternal.class, userManager);
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ }
+
@Before
public void setUp() {
final UserManager um = UserManager.get(getInstrumentation().getTargetContext());
mTestUserId = um.getUserHandle();
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ when(userManagerInternal.isUserUnlocked(mTestUserId)).thenReturn(true);
+
mContextSpy = spy(new ContextWrapper(mWm.mContext));
mResourcesSpy = spy(mContextSpy.getResources());
when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 42e2bbf08834..6c13cd799bc2 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -346,12 +346,15 @@ public class SoundTriggerService extends SystemService {
sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = "
+ soundModelId));
- // Unload the model if it is loaded.
- mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
- mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
+ if (isInitialized()) {
+ // Unload the model if it is loaded.
+ mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
- // Stop recognition if it is started.
- mSoundModelStatTracker.onStop(soundModelId.getUuid());
+ // Stop tracking recognition if it is started.
+ mSoundModelStatTracker.onStop(soundModelId.getUuid());
+ }
+
+ mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
}
@Override
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index bb6f154335a9..b35b3236afc6 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -536,13 +536,16 @@ public final class SmsApplication {
// Assign permission to special system apps
assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
- PHONE_PACKAGE_NAME);
+ PHONE_PACKAGE_NAME, true);
assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
- BLUETOOTH_PACKAGE_NAME);
+ BLUETOOTH_PACKAGE_NAME, true);
assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
- MMS_SERVICE_PACKAGE_NAME);
+ MMS_SERVICE_PACKAGE_NAME, true);
assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
- TELEPHONY_PROVIDER_PACKAGE_NAME);
+ TELEPHONY_PROVIDER_PACKAGE_NAME, true);
+ // CellbroadcastReceiver is a mainline module thus skip signature match.
+ assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps,
+ CellBroadcastUtils.getDefaultCellBroadcastReceiverPackageName(context), false);
// Give AppOps permission to UID 1001 which contains multiple
// apps, all of them should be able to write to telephony provider.
@@ -744,17 +747,23 @@ public final class SmsApplication {
* @param packageManager The package manager instance
* @param appOps The AppOps manager instance
* @param packageName The package name of the system app
+ * @param sigatureMatch whether to check signature match
*/
private static void assignExclusiveSmsPermissionsToSystemApp(Context context,
- PackageManager packageManager, AppOpsManager appOps, String packageName) {
+ PackageManager packageManager, AppOpsManager appOps, String packageName,
+ boolean sigatureMatch) {
// First check package signature matches the caller's package signature.
// Since this class is only used internally by the system, this check makes sure
// the package signature matches system signature.
- final int result = packageManager.checkSignatures(context.getPackageName(), packageName);
- if (result != PackageManager.SIGNATURE_MATCH) {
- Log.e(LOG_TAG, packageName + " does not have system signature");
- return;
+ if (sigatureMatch) {
+ final int result = packageManager.checkSignatures(context.getPackageName(),
+ packageName);
+ if (result != PackageManager.SIGNATURE_MATCH) {
+ Log.e(LOG_TAG, packageName + " does not have system signature");
+ return;
+ }
}
+
try {
PackageInfo info = packageManager.getPackageInfo(packageName, 0);
int mode = appOps.unsafeCheckOp(AppOpsManager.OPSTR_WRITE_SMS, info.applicationInfo.uid,
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 35464340550b..d62cd0a63b44 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -837,20 +837,20 @@ public class SubscriptionInfo implements Parcelable {
+ " carrierId=" + mCarrierId + " displayName=" + mDisplayName
+ " carrierName=" + mCarrierName + " nameSource=" + mNameSource
+ " iconTint=" + mIconTint
- + " mNumber=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mNumber)
- + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc
- + " mnc " + mMnc + "mCountryIso=" + mCountryIso + " isEmbedded " + mIsEmbedded
- + " nativeAccessRules " + Arrays.toString(mNativeAccessRules)
+ + " number=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mNumber)
+ + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc=" + mMcc
+ + " mnc=" + mMnc + " countryIso=" + mCountryIso + " isEmbedded=" + mIsEmbedded
+ + " nativeAccessRules=" + Arrays.toString(mNativeAccessRules)
+ " cardString=" + cardStringToPrint + " cardId=" + mCardId
- + " isOpportunistic=" + mIsOpportunistic + " mGroupUUID=" + mGroupUUID
- + " mIsGroupDisabled=" + mIsGroupDisabled
+ + " isOpportunistic=" + mIsOpportunistic + " groupUUID=" + mGroupUUID
+ + " isGroupDisabled=" + mIsGroupDisabled
+ " profileClass=" + mProfileClass
+ " ehplmns=" + Arrays.toString(mEhplmns)
+ " hplmns=" + Arrays.toString(mHplmns)
+ " subscriptionType=" + mSubscriptionType
- + " mGroupOwner=" + mGroupOwner
+ + " groupOwner=" + mGroupOwner
+ " carrierConfigAccessRules=" + Arrays.toString(mCarrierConfigAccessRules)
- + " mAreUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled + "}";
+ + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled + "}";
}
@Override
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index ede67dd9fd61..94407f1dcd3a 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -56,14 +56,15 @@ public class ImsRcsManager {
* Activity Action: Show the opt-in dialog for enabling or disabling RCS contact discovery
* using User Capability Exchange (UCE).
* <p>
- * An application that depends on contact discovery being enabled may send this intent
+ * An application that depends on RCS contact discovery being enabled must send this intent
* using {@link Context#startActivity(Intent)} to ask the user to opt-in for contacts upload for
- * capability exchange if it is currently disabled. Whether or not this setting has been enabled
- * can be queried using {@link RcsUceAdapter#isUceSettingEnabled()}.
+ * capability exchange if it is currently disabled. Whether or not RCS contact discovery has
+ * been enabled by the user can be queried using {@link RcsUceAdapter#isUceSettingEnabled()}.
* <p>
- * This intent should only be sent if the carrier supports RCS capability exchange, which can be
- * queried using the key {@link CarrierConfigManager#KEY_USE_RCS_PRESENCE_BOOL}. Otherwise, the
- * setting will not be present.
+ * This intent will always be handled by the system, however the application should only send
+ * this Intent if the carrier supports RCS contact discovery, which can be queried using the key
+ * {@link CarrierConfigManager#KEY_USE_RCS_PRESENCE_BOOL}. Otherwise, the RCS contact discovery
+ * opt-in dialog will not be shown.
* <p>
* Input: A mandatory {@link Settings#EXTRA_SUB_ID} extra containing the subscription that the
* setting will be be shown for.
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 05ab6bd75878..ec112790a144 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -363,9 +363,10 @@ public class RcsUceAdapter {
/**
* Change the user’s setting for whether or not UCE is enabled for the associated subscription.
* <p>
- * If an application Requires UCE, they may launch an Activity using the Intent
+ * If an application Requires UCE, they will launch an Activity using the Intent
* {@link ImsRcsManager#ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN}, which will ask the user if
- * they wish to enable this feature.
+ * they wish to enable this feature. This setting should only be enabled after the user has
+ * opted-in to capability exchange.
* <p>
* Note: This setting does not affect whether or not the device publishes its service
* capabilities if the subscription supports presence publication.