summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp20
-rw-r--r--apex/jobscheduler/service/aconfig/job.aconfig21
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java1
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java381
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java5
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java297
-rw-r--r--cmds/uinput/src/com/android/commands/uinput/Device.java2
-rw-r--r--core/api/current.txt11
-rw-r--r--core/api/system-current.txt2
-rw-r--r--core/java/android/app/Activity.java32
-rw-r--r--core/java/android/app/ActivityThread.java6
-rw-r--r--core/java/android/app/ComponentCaller.java137
-rw-r--r--core/java/android/app/Instrumentation.java4
-rw-r--r--core/java/android/app/servertransaction/ClientTransaction.java27
-rw-r--r--core/java/android/app/servertransaction/LaunchActivityItem.java19
-rw-r--r--core/java/android/app/servertransaction/TransactionExecutor.java4
-rw-r--r--core/java/android/content/pm/ProcessInfo.java27
-rw-r--r--core/java/android/content/pm/multiuser.aconfig21
-rw-r--r--core/java/android/content/pm/parsing/ApkLiteParseUtils.java25
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java4
-rw-r--r--core/java/android/os/IUserManager.aidl1
-rw-r--r--core/java/android/os/PerformanceHintManager.java24
-rw-r--r--core/java/android/os/UserManager.java19
-rw-r--r--core/java/android/os/WorkDuration.java13
-rw-r--r--core/java/android/provider/Settings.java10
-rw-r--r--core/java/android/tracing/transition/TransitionDataSource.java24
-rw-r--r--core/java/android/view/WindowManager.java28
-rw-r--r--core/java/android/view/accessibility/MagnificationAnimationCallback.java14
-rw-r--r--core/java/android/window/SnapshotDrawerUtils.java4
-rw-r--r--core/java/android/window/WindowMetricsController.java13
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java3
-rw-r--r--core/java/android/window/flags/windowing_sdk.aconfig8
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedProcess.java2
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java29
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java7
-rw-r--r--core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig9
-rw-r--r--core/jni/android_view_InputEventSender.cpp6
-rw-r--r--core/proto/android/providers/settings/global.proto1
-rw-r--r--core/res/res/values/attrs.xml5
-rw-r--r--core/res/res/values/attrs_manifest.xml17
-rw-r--r--core/res/res/values/public-staging.xml2
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java7
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java2
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TestUtils.java12
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java1
-rw-r--r--core/tests/coretests/src/android/os/PerformanceHintManagerTest.java18
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java10
-rw-r--r--core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java63
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java87
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java61
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java41
-rw-r--r--native/android/performance_hint.cpp5
-rw-r--r--nfc/api/current.txt1
-rw-r--r--nfc/java/android/nfc/INfcAdapter.aidl1
-rw-r--r--nfc/java/android/nfc/NfcAdapter.java16
-rw-r--r--nfc/java/android/nfc/cardemulation/ApduServiceInfo.java34
-rw-r--r--packages/PackageInstaller/AndroidManifest.xml11
-rw-r--r--packages/PackageInstaller/res/values/themes.xml9
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java4
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/SystemUI/AndroidManifest.xml4
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig3
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/painter/DrawablePainter.kt177
-rw-r--r--packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt (renamed from packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutoutProvider.kt)17
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt100
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt40
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt)405
-rw-r--r--packages/SystemUI/res/color/brightness_slider_track.xml19
-rw-r--r--packages/SystemUI/res/drawable/brightness_progress_drawable.xml2
-rw-r--r--packages/SystemUI/res/layout/notification_snooze.xml2
-rw-r--r--packages/SystemUI/res/values/config.xml5
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml21
-rw-r--r--packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt107
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java107
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingServiceStrings.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt)36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt70
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt127
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java2
-rw-r--r--services/accessibility/accessibility.aconfig7
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java292
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java115
-rw-r--r--services/companion/java/com/android/server/companion/CompanionApplicationController.java28
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java12
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java3
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java14
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java63
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerControllerInterface.java12
-rw-r--r--services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java17
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java5
-rw-r--r--services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java21
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java58
-rw-r--r--services/core/java/com/android/server/notification/NotificationAttentionHelper.java3
-rw-r--r--services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java16
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java45
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java2
-rw-r--r--services/core/java/com/android/server/power/hint/HintManagerService.java16
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java33
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java35
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java8
-rw-r--r--services/core/java/com/android/server/wm/ClientLifecycleManager.java35
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java1
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java30
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java11
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java4
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java7
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java91
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java93
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java2
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java16
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java295
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java138
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java111
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java287
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java22
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java33
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java30
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java74
-rwxr-xr-xtelecomm/java/android/telecom/ConnectionService.java18
-rw-r--r--telephony/java/android/telephony/CarrierRestrictionRules.java27
169 files changed, 4694 insertions, 1091 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 0fdd880a80f4..cd991c70d719 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -69,6 +69,7 @@ aconfig_srcjars = [
":com.android.hardware.input-aconfig-java{.generated_srcjars}",
":com.android.input.flags-aconfig-java{.generated_srcjars}",
":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.internal.pm.pkg.component.flags-aconfig-java{.generated_srcjars}",
":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
":com.android.media.flags.editing-aconfig-java{.generated_srcjars}",
":com.android.net.thread.flags-aconfig-java{.generated_srcjars}",
@@ -1138,3 +1139,22 @@ java_aconfig_library {
aconfig_declarations: "android.app.wearable.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+aconfig_declarations {
+ name: "com.android.internal.pm.pkg.component.flags-aconfig",
+ package: "com.android.internal.pm.pkg.component.flags",
+ srcs: ["core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "com.android.internal.pm.pkg.component.flags-aconfig-java",
+ aconfig_declarations: "com.android.internal.pm.pkg.component.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+java_aconfig_library {
+ name: "com.android.internal.pm.pkg.component.flags-aconfig-java-host",
+ aconfig_declarations: "com.android.internal.pm.pkg.component.flags-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index 5d65d9d0629f..8a5206fa3f09 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -1,6 +1,20 @@
package: "com.android.server.job"
flag {
+ name: "batch_active_bucket_jobs"
+ namespace: "backstage_power"
+ description: "Include jobs in the ACTIVE bucket in the job batching effort. Don't let them run as freely as they're ready."
+ bug: "299329948"
+}
+
+flag {
+ name: "batch_connectivity_jobs_per_network"
+ namespace: "backstage_power"
+ description: "Have JobScheduler attempt to delay the start of some connectivity jobs until there are several ready or the network is active"
+ bug: "28382445"
+}
+
+flag {
name: "do_not_force_rush_execution_at_boot"
namespace: "backstage_power"
description: "Don't force rush job execution right after boot completion"
@@ -20,10 +34,3 @@ flag {
description: "Throw an exception if an unsupported app uses JobInfo.setBias"
bug: "300477393"
}
-
-flag {
- name: "batch_jobs_on_network_activation"
- namespace: "backstage_power"
- description: "Have JobScheduler attempt to delay the start of some connectivity jobs until the network is actually active"
- bug: "318394184"
-}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 6550f26436d4..012ede274bc1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -96,7 +96,6 @@ class JobConcurrencyManager {
static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_";
private static final String KEY_CONCURRENCY_LIMIT = CONFIG_KEY_PREFIX_CONCURRENCY + "limit";
- @VisibleForTesting
static final int DEFAULT_CONCURRENCY_LIMIT;
static {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 57467e3cc83d..cea16d6213af 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -65,6 +65,7 @@ import android.content.pm.ParceledListSlice;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
@@ -89,6 +90,7 @@ import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
+import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -538,7 +540,9 @@ public class JobSchedulerService extends com.android.server.SystemService
apiQuotaScheduleUpdated = true;
}
break;
+ case Constants.KEY_MIN_READY_CPU_ONLY_JOBS_COUNT:
case Constants.KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT:
+ case Constants.KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS:
case Constants.KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS:
mConstants.updateBatchingConstantsLocked();
break;
@@ -554,6 +558,8 @@ public class JobSchedulerService extends com.android.server.SystemService
case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
case Constants.KEY_CONN_PREFETCH_RELAX_FRAC:
case Constants.KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC:
+ case Constants.KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS:
+ case Constants.KEY_CONN_TRANSPORT_BATCH_THRESHOLD:
case Constants.KEY_CONN_USE_CELL_SIGNAL_STRENGTH:
case Constants.KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS:
mConstants.updateConnectivityConstantsLocked();
@@ -602,6 +608,8 @@ public class JobSchedulerService extends com.android.server.SystemService
sc.onConstantsUpdatedLocked();
}
}
+
+ mHandler.sendEmptyMessage(MSG_CHECK_JOB);
}
@Override
@@ -646,8 +654,12 @@ public class JobSchedulerService extends com.android.server.SystemService
*/
public static class Constants {
// Key names stored in the settings value.
+ private static final String KEY_MIN_READY_CPU_ONLY_JOBS_COUNT =
+ "min_ready_cpu_only_jobs_count";
private static final String KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT =
"min_ready_non_active_jobs_count";
+ private static final String KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS =
+ "max_cpu_only_job_batch_delay_ms";
private static final String KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS =
"max_non_active_job_batch_delay_ms";
private static final String KEY_HEAVY_USE_FACTOR = "heavy_use_factor";
@@ -665,6 +677,10 @@ public class JobSchedulerService extends com.android.server.SystemService
"conn_update_all_jobs_min_interval_ms";
private static final String KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
"conn_low_signal_strength_relax_frac";
+ private static final String KEY_CONN_TRANSPORT_BATCH_THRESHOLD =
+ "conn_transport_batch_threshold";
+ private static final String KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS =
+ "conn_max_connectivity_job_batch_delay_ms";
private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
"prefetch_force_batch_relax_threshold_ms";
// This has been enabled for 3+ full releases. We're unlikely to disable it.
@@ -713,7 +729,11 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final String KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS =
"max_num_persisted_job_work_items";
- private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
+ private static final int DEFAULT_MIN_READY_CPU_ONLY_JOBS_COUNT =
+ Math.min(3, JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3);
+ private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT =
+ Math.min(5, JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3);
+ private static final long DEFAULT_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
@@ -725,6 +745,15 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true;
private static final long DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = MINUTE_IN_MILLIS;
private static final float DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = 0.5f;
+ private static final SparseIntArray DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD =
+ new SparseIntArray();
+ private static final long DEFAULT_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS =
+ 31 * MINUTE_IN_MILLIS;
+ static {
+ DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.put(
+ NetworkCapabilities.TRANSPORT_CELLULAR,
+ Math.min(3, JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3));
+ }
private static final long DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = HOUR_IN_MILLIS;
private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
@@ -762,11 +791,23 @@ public class JobSchedulerService extends com.android.server.SystemService
static final int DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 100_000;
/**
- * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early.
+ * Minimum # of jobs that have to be ready for JS to be happy running work.
+ * Only valid if {@link Flags#batchActiveBucketJobs()} is true.
+ */
+ int MIN_READY_CPU_ONLY_JOBS_COUNT = DEFAULT_MIN_READY_CPU_ONLY_JOBS_COUNT;
+
+ /**
+ * Minimum # of non-ACTIVE jobs that have to be ready for JS to be happy running work.
*/
int MIN_READY_NON_ACTIVE_JOBS_COUNT = DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT;
/**
+ * Don't batch a CPU-only job if it's been delayed due to force batching attempts for
+ * at least this amount of time.
+ */
+ long MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = DEFAULT_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS;
+
+ /**
* Don't batch a non-ACTIVE job if it's been delayed due to force batching attempts for
* at least this amount of time.
*/
@@ -822,6 +863,17 @@ public class JobSchedulerService extends com.android.server.SystemService
*/
public float CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC;
+ /**
+ * The minimum batch requirement per each transport type before allowing a network to run
+ * on a network with that transport.
+ */
+ public SparseIntArray CONN_TRANSPORT_BATCH_THRESHOLD = new SparseIntArray();
+ /**
+ * Don't batch a connectivity job if it's been delayed due to force batching attempts for
+ * at least this amount of time.
+ */
+ public long CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS =
+ DEFAULT_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS;
/**
* The amount of time within which we would consider the app to be launching relatively soon
@@ -972,11 +1024,31 @@ public class JobSchedulerService extends com.android.server.SystemService
public boolean USE_TARE_POLICY = EconomyManager.DEFAULT_ENABLE_POLICY_JOB_SCHEDULER
&& EconomyManager.DEFAULT_ENABLE_TARE_MODE == EconomyManager.ENABLED_MODE_ON;
+ public Constants() {
+ copyTransportBatchThresholdDefaults();
+ }
+
private void updateBatchingConstantsLocked() {
- MIN_READY_NON_ACTIVE_JOBS_COUNT = DeviceConfig.getInt(
+ // The threshold should be in the range
+ // [0, DEFAULT_CONCURRENCY_LIMIT / 3].
+ MIN_READY_CPU_ONLY_JOBS_COUNT =
+ Math.max(0, Math.min(JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3,
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_MIN_READY_CPU_ONLY_JOBS_COUNT,
+ DEFAULT_MIN_READY_CPU_ONLY_JOBS_COUNT)));
+ // The threshold should be in the range
+ // [0, DEFAULT_CONCURRENCY_LIMIT / 3].
+ MIN_READY_NON_ACTIVE_JOBS_COUNT =
+ Math.max(0, Math.min(JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3,
+ DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
+ DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT)));
+ MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
- KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
- DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT);
+ KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS,
+ DEFAULT_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS);
MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
@@ -1024,6 +1096,46 @@ public class JobSchedulerService extends com.android.server.SystemService
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC,
DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC);
+ final String batchThresholdConfigString = DeviceConfig.getString(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_CONN_TRANSPORT_BATCH_THRESHOLD,
+ null);
+ final KeyValueListParser parser = new KeyValueListParser(',');
+ CONN_TRANSPORT_BATCH_THRESHOLD.clear();
+ try {
+ parser.setString(batchThresholdConfigString);
+
+ for (int t = parser.size() - 1; t >= 0; --t) {
+ final String transportString = parser.keyAt(t);
+ try {
+ final int transport = Integer.parseInt(transportString);
+ // The threshold should be in the range
+ // [0, DEFAULT_CONCURRENCY_LIMIT / 3].
+ CONN_TRANSPORT_BATCH_THRESHOLD.put(transport, Math.max(0,
+ Math.min(JobConcurrencyManager.DEFAULT_CONCURRENCY_LIMIT / 3,
+ parser.getInt(transportString, 1))));
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Bad transport string", e);
+ }
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.wtf(TAG, "Bad string for " + KEY_CONN_TRANSPORT_BATCH_THRESHOLD, e);
+ // Use the defaults.
+ copyTransportBatchThresholdDefaults();
+ }
+ CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS = Math.max(0, Math.min(24 * HOUR_IN_MILLIS,
+ DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS,
+ DEFAULT_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS)));
+ }
+
+ private void copyTransportBatchThresholdDefaults() {
+ for (int i = DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.size() - 1; i >= 0; --i) {
+ CONN_TRANSPORT_BATCH_THRESHOLD.put(
+ DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.keyAt(i),
+ DEFAULT_CONN_TRANSPORT_BATCH_THRESHOLD.valueAt(i));
+ }
}
private void updatePersistingConstantsLocked() {
@@ -1168,8 +1280,11 @@ public class JobSchedulerService extends com.android.server.SystemService
void dump(IndentingPrintWriter pw) {
pw.println("Settings:");
pw.increaseIndent();
+ pw.print(KEY_MIN_READY_CPU_ONLY_JOBS_COUNT, MIN_READY_CPU_ONLY_JOBS_COUNT).println();
pw.print(KEY_MIN_READY_NON_ACTIVE_JOBS_COUNT,
MIN_READY_NON_ACTIVE_JOBS_COUNT).println();
+ pw.print(KEY_MAX_CPU_ONLY_JOB_BATCH_DELAY_MS,
+ MAX_CPU_ONLY_JOB_BATCH_DELAY_MS).println();
pw.print(KEY_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS,
MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS).println();
pw.print(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
@@ -1185,6 +1300,10 @@ public class JobSchedulerService extends com.android.server.SystemService
.println();
pw.print(KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC, CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC)
.println();
+ pw.print(KEY_CONN_TRANSPORT_BATCH_THRESHOLD, CONN_TRANSPORT_BATCH_THRESHOLD.toString())
+ .println();
+ pw.print(KEY_CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS,
+ CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS).println();
pw.print(KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS).println();
@@ -2835,9 +2954,9 @@ public class JobSchedulerService extends com.android.server.SystemService
mJobPackageTracker.notePending(job);
}
- void noteJobsPending(List<JobStatus> jobs) {
- for (int i = jobs.size() - 1; i >= 0; i--) {
- noteJobPending(jobs.get(i));
+ void noteJobsPending(ArraySet<JobStatus> jobs) {
+ for (int i = jobs.size() - 1; i >= 0; --i) {
+ noteJobPending(jobs.valueAt(i));
}
}
@@ -3463,7 +3582,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
final class ReadyJobQueueFunctor implements Consumer<JobStatus> {
- final ArrayList<JobStatus> newReadyJobs = new ArrayList<>();
+ final ArraySet<JobStatus> newReadyJobs = new ArraySet<>();
@Override
public void accept(JobStatus job) {
@@ -3491,9 +3610,27 @@ public class JobSchedulerService extends com.android.server.SystemService
* policies on when we want to execute jobs.
*/
final class MaybeReadyJobQueueFunctor implements Consumer<JobStatus> {
- int forceBatchedCount;
- int unbatchedCount;
+ /**
+ * Set of jobs that will be force batched, mapped by network. A {@code null} network is
+ * reserved/intended for CPU-only (non-networked) jobs.
+ * The set may include already running jobs.
+ */
+ @VisibleForTesting
+ final ArrayMap<Network, ArraySet<JobStatus>> mBatches = new ArrayMap<>();
+ /** List of all jobs that could run if allowed. Already running jobs are excluded. */
+ @VisibleForTesting
final List<JobStatus> runnableJobs = new ArrayList<>();
+ /**
+ * Convenience holder of all jobs ready to run that won't be force batched.
+ * Already running jobs are excluded.
+ */
+ final ArraySet<JobStatus> mUnbatchedJobs = new ArraySet<>();
+ /**
+ * Count of jobs that won't be force batched, mapped by network. A {@code null} network is
+ * reserved/intended for CPU-only (non-networked) jobs.
+ * The set may include already running jobs.
+ */
+ final ArrayMap<Network, Integer> mUnbatchedJobCount = new ArrayMap<>();
public MaybeReadyJobQueueFunctor() {
reset();
@@ -3540,27 +3677,77 @@ public class JobSchedulerService extends com.android.server.SystemService
shouldForceBatchJob = false;
} else {
final long nowElapsed = sElapsedRealtimeClock.millis();
- final boolean batchDelayExpired = job.getFirstForceBatchedTimeElapsed() > 0
- && nowElapsed - job.getFirstForceBatchedTimeElapsed()
- >= mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
- shouldForceBatchJob =
- mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
- && job.getEffectiveStandbyBucket() != ACTIVE_INDEX
- && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
- && !batchDelayExpired;
+ final long timeUntilDeadlineMs = job.hasDeadlineConstraint()
+ ? job.getLatestRunTimeElapsed() - nowElapsed
+ : Long.MAX_VALUE;
+ // Differentiate behavior based on whether the job needs network or not.
+ if (Flags.batchConnectivityJobsPerNetwork()
+ && job.hasConnectivityConstraint()) {
+ // For connectivity jobs, let them run immediately if the network is already
+ // active (in a state for job run), otherwise, only run them if there are
+ // enough to meet the batching requirement or the job has been waiting
+ // long enough.
+ final boolean batchDelayExpired =
+ job.getFirstForceBatchedTimeElapsed() > 0
+ && nowElapsed - job.getFirstForceBatchedTimeElapsed()
+ >= mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS;
+ shouldForceBatchJob = !batchDelayExpired
+ && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
+ && timeUntilDeadlineMs
+ > mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS / 2
+ && !mConnectivityController.isNetworkInStateForJobRunLocked(job);
+ } else {
+ final boolean batchDelayExpired;
+ final boolean batchingEnabled;
+ if (Flags.batchActiveBucketJobs()) {
+ batchingEnabled = mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT > 1
+ && timeUntilDeadlineMs
+ > mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS / 2
+ // Active UIDs' jobs were by default treated as in the ACTIVE
+ // bucket, so we must explicitly exclude them when batching
+ // ACTIVE jobs.
+ && !job.uidActive
+ && !job.getJob().isExemptedFromAppStandby();
+ batchDelayExpired = job.getFirstForceBatchedTimeElapsed() > 0
+ && nowElapsed - job.getFirstForceBatchedTimeElapsed()
+ >= mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS;
+ } else {
+ batchingEnabled = mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT > 1
+ && job.getEffectiveStandbyBucket() != ACTIVE_INDEX;
+ batchDelayExpired = job.getFirstForceBatchedTimeElapsed() > 0
+ && nowElapsed - job.getFirstForceBatchedTimeElapsed()
+ >= mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
+ }
+ shouldForceBatchJob = batchingEnabled
+ && job.getEffectiveStandbyBucket() != EXEMPTED_INDEX
+ && !batchDelayExpired;
+ }
}
+ // If connectivity job batching isn't enabled, treat every job as
+ // a non-connectivity job since that mimics the old behavior.
+ final Network network =
+ Flags.batchConnectivityJobsPerNetwork() ? job.network : null;
+ ArraySet<JobStatus> batch = mBatches.get(network);
+ if (batch == null) {
+ batch = new ArraySet<>();
+ mBatches.put(network, batch);
+ }
+ batch.add(job);
+
if (shouldForceBatchJob) {
- // Force batching non-ACTIVE jobs. Don't include them in the other counts.
- forceBatchedCount++;
if (job.getFirstForceBatchedTimeElapsed() == 0) {
job.setFirstForceBatchedTimeElapsed(sElapsedRealtimeClock.millis());
}
} else {
- unbatchedCount++;
+ mUnbatchedJobCount.put(network,
+ mUnbatchedJobCount.getOrDefault(job.network, 0) + 1);
}
if (!isRunning) {
runnableJobs.add(job);
+ if (!shouldForceBatchJob) {
+ mUnbatchedJobs.add(job);
+ }
}
} else {
if (isRunning) {
@@ -3600,34 +3787,131 @@ public class JobSchedulerService extends com.android.server.SystemService
@GuardedBy("mLock")
@VisibleForTesting
void postProcessLocked() {
- if (unbatchedCount > 0
- || forceBatchedCount >= mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT) {
+ final ArraySet<JobStatus> jobsToRun = mUnbatchedJobs;
+
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: "
+ + mUnbatchedJobs.size() + " unbatched jobs.");
+ }
+
+ int unbatchedCount = 0;
+
+ for (int n = mBatches.size() - 1; n >= 0; --n) {
+ final Network network = mBatches.keyAt(n);
+
+ // Count all of the unbatched jobs, including the ones without a network.
+ final Integer unbatchedJobCountObj = mUnbatchedJobCount.get(network);
+ final int unbatchedJobCount;
+ if (unbatchedJobCountObj != null) {
+ unbatchedJobCount = unbatchedJobCountObj;
+ unbatchedCount += unbatchedJobCount;
+ } else {
+ unbatchedJobCount = 0;
+ }
+
+ // Skip the non-networked jobs here. They'll be handled after evaluating
+ // everything else.
+ if (network == null) {
+ continue;
+ }
+
+ final ArraySet<JobStatus> batchedJobs = mBatches.valueAt(n);
+ if (unbatchedJobCount > 0) {
+ // Some job is going to activate the network anyway. Might as well run all
+ // the other jobs that will use this network.
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: piggybacking "
+ + batchedJobs.size() + " jobs on " + network
+ + " because of unbatched job");
+ }
+ jobsToRun.addAll(batchedJobs);
+ continue;
+ }
+
+ final NetworkCapabilities networkCapabilities =
+ mConnectivityController.getNetworkCapabilities(network);
+ if (networkCapabilities == null) {
+ Slog.e(TAG, "Couldn't get NetworkCapabilities for network " + network);
+ continue;
+ }
+
+ final int[] transports = networkCapabilities.getTransportTypes();
+ int maxNetworkBatchReq = 1;
+ for (int transport : transports) {
+ maxNetworkBatchReq = Math.max(maxNetworkBatchReq,
+ mConstants.CONN_TRANSPORT_BATCH_THRESHOLD.get(transport));
+ }
+
+ if (batchedJobs.size() >= maxNetworkBatchReq) {
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: "
+ + batchedJobs.size()
+ + " batched network jobs meet requirement for " + network);
+ }
+ jobsToRun.addAll(batchedJobs);
+ }
+ }
+
+ final ArraySet<JobStatus> batchedNonNetworkedJobs = mBatches.get(null);
+ if (batchedNonNetworkedJobs != null) {
+ final int minReadyCount = Flags.batchActiveBucketJobs()
+ ? mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT
+ : mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT;
+ if (jobsToRun.size() > 0) {
+ // Some job is going to use the CPU anyway. Might as well run all the other
+ // CPU-only jobs.
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: piggybacking "
+ + batchedNonNetworkedJobs.size() + " non-network jobs");
+ }
+ jobsToRun.addAll(batchedNonNetworkedJobs);
+ } else if (batchedNonNetworkedJobs.size() >= minReadyCount) {
+ if (DEBUG) {
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: adding "
+ + batchedNonNetworkedJobs.size() + " batched non-network jobs.");
+ }
+ jobsToRun.addAll(batchedNonNetworkedJobs);
+ }
+ }
+
+ // In order to properly determine an accurate batch count, the running jobs must be
+ // included in the earlier lists and can only be removed after checking if the batch
+ // count requirement is satisfied.
+ jobsToRun.removeIf(JobSchedulerService.this::isCurrentlyRunningLocked);
+
+ if (unbatchedCount > 0 || jobsToRun.size() > 0) {
if (DEBUG) {
- Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running jobs.");
+ Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Running "
+ + jobsToRun + " jobs.");
}
- noteJobsPending(runnableJobs);
- mPendingJobQueue.addAll(runnableJobs);
+ noteJobsPending(jobsToRun);
+ mPendingJobQueue.addAll(jobsToRun);
} else {
if (DEBUG) {
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
}
- final int numRunnableJobs = runnableJobs.size();
- if (numRunnableJobs > 0) {
- synchronized (mPendingJobReasonCache) {
- for (int i = 0; i < numRunnableJobs; ++i) {
- final JobStatus job = runnableJobs.get(i);
- SparseIntArray reasons =
- mPendingJobReasonCache.get(job.getUid(), job.getNamespace());
- if (reasons == null) {
- reasons = new SparseIntArray();
- mPendingJobReasonCache
- .add(job.getUid(), job.getNamespace(), reasons);
- }
- // We're force batching these jobs, so consider it an optimization
- // policy reason.
- reasons.put(job.getJobId(),
- JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
+ }
+
+ // Update the pending reason for any jobs that aren't going to be run.
+ final int numRunnableJobs = runnableJobs.size();
+ if (numRunnableJobs > 0 && numRunnableJobs != jobsToRun.size()) {
+ synchronized (mPendingJobReasonCache) {
+ for (int i = 0; i < numRunnableJobs; ++i) {
+ final JobStatus job = runnableJobs.get(i);
+ if (jobsToRun.contains(job)) {
+ // We're running this job. Skip updating the pending reason.
+ continue;
+ }
+ SparseIntArray reasons =
+ mPendingJobReasonCache.get(job.getUid(), job.getNamespace());
+ if (reasons == null) {
+ reasons = new SparseIntArray();
+ mPendingJobReasonCache.add(job.getUid(), job.getNamespace(), reasons);
}
+ // We're force batching these jobs, so consider it an optimization
+ // policy reason.
+ reasons.put(job.getJobId(),
+ JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
}
}
}
@@ -3638,9 +3922,10 @@ public class JobSchedulerService extends com.android.server.SystemService
@VisibleForTesting
void reset() {
- forceBatchedCount = 0;
- unbatchedCount = 0;
runnableJobs.clear();
+ mBatches.clear();
+ mUnbatchedJobs.clear();
+ mUnbatchedJobCount.clear();
}
}
@@ -5468,8 +5753,14 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.println("Aconfig flags:");
pw.increaseIndent();
+ pw.print(Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS, Flags.batchActiveBucketJobs());
+ pw.println();
+ pw.print(Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK,
+ Flags.batchConnectivityJobsPerNetwork());
+ pw.println();
pw.print(Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT,
Flags.doNotForceRushExecutionAtBoot());
+ pw.println();
pw.print(Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE,
Flags.throwOnUnsupportedBiasUsage());
pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 0cf6a7a8a4f6..90b4630e9cff 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -350,6 +350,12 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
case android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS:
pw.println(android.app.job.Flags.jobDebugInfoApis());
break;
+ case com.android.server.job.Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS:
+ pw.println(com.android.server.job.Flags.batchActiveBucketJobs());
+ break;
+ case com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK:
+ pw.println(com.android.server.job.Flags.batchConnectivityJobsPerNetwork());
+ break;
case com.android.server.job.Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT:
pw.println(com.android.server.job.Flags.doNotForceRushExecutionAtBoot());
break;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
index 4f4096f69ad5..813cf8710ab1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
@@ -18,6 +18,7 @@ package com.android.server.job;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.ArraySet;
import android.util.Pools;
import android.util.SparseArray;
@@ -96,10 +97,10 @@ class PendingJobQueue {
}
}
- void addAll(@NonNull List<JobStatus> jobs) {
+ void addAll(@NonNull ArraySet<JobStatus> jobs) {
final SparseArray<List<JobStatus>> jobsByUid = new SparseArray<>();
for (int i = jobs.size() - 1; i >= 0; --i) {
- final JobStatus job = jobs.get(i);
+ final JobStatus job = jobs.valueAt(i);
List<JobStatus> appJobs = jobsByUid.get(job.getSourceUid());
if (appJobs == null) {
appJobs = new ArrayList<>();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index f40508302ee3..6ed42ec990ea 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -22,11 +22,11 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_MET
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.Flags.FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER;
-import static com.android.server.job.Flags.relaxPrefetchConnectivityConstraintOnlyOnCharger;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -66,6 +66,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.AppSchedulingModuleThread;
import com.android.server.LocalServices;
+import com.android.server.job.Flags;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
import com.android.server.job.StateControllerProto;
@@ -166,6 +167,10 @@ public final class ConnectivityController extends RestrictingController implemen
@GuardedBy("mLock")
private final ArrayMap<Network, CachedNetworkMetadata> mAvailableNetworks = new ArrayMap<>();
+ @GuardedBy("mLock")
+ @Nullable
+ private Network mSystemDefaultNetwork;
+
private final SparseArray<UidDefaultNetworkCallback> mCurrentDefaultNetworkCallbacks =
new SparseArray<>();
private final Comparator<UidStats> mUidStatsComparator = new Comparator<UidStats>() {
@@ -286,6 +291,7 @@ public final class ConnectivityController extends RestrictingController implemen
private static final int MSG_UPDATE_ALL_TRACKED_JOBS = 1;
private static final int MSG_DATA_SAVER_TOGGLED = 2;
private static final int MSG_UID_POLICIES_CHANGED = 3;
+ private static final int MSG_PROCESS_ACTIVE_NETWORK = 4;
private final Handler mHandler;
@@ -313,6 +319,14 @@ public final class ConnectivityController extends RestrictingController implemen
}
}
+ @Override
+ public void startTrackingLocked() {
+ if (Flags.batchConnectivityJobsPerNetwork()) {
+ mConnManager.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
+ mConnManager.addDefaultNetworkActiveListener(this);
+ }
+ }
+
@GuardedBy("mLock")
@Override
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
@@ -911,8 +925,8 @@ public final class ConnectivityController extends RestrictingController implemen
return true;
}
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
- // Exclude VPNs because it's currently not possible to determine the VPN's underlying
- // network, and thus the correct signal strength of the VPN's network.
+ // VPNs may have multiple underlying networks and determining the correct strength
+ // may not be straightforward.
// Transmitting data over a VPN is generally more battery-expensive than on the
// underlying network, so:
// TODO: find a good way to reduce job use of VPN when it'll be very expensive
@@ -1007,7 +1021,7 @@ public final class ConnectivityController extends RestrictingController implemen
// Need to at least know the estimated download bytes for a prefetch job.
return false;
}
- if (relaxPrefetchConnectivityConstraintOnlyOnCharger()) {
+ if (Flags.relaxPrefetchConnectivityConstraintOnlyOnCharger()) {
// Since the constraint relaxation isn't required by the job, only do it when the
// device is charging and the battery level is above the "low battery" threshold.
if (!mService.isBatteryCharging() || !mService.isBatteryNotLow()) {
@@ -1309,7 +1323,7 @@ public final class ConnectivityController extends RestrictingController implemen
}
@Nullable
- private NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
+ public NetworkCapabilities getNetworkCapabilities(@Nullable Network network) {
final CachedNetworkMetadata metadata = getNetworkMetadata(network);
return metadata == null ? null : metadata.networkCapabilities;
}
@@ -1527,26 +1541,138 @@ public final class ConnectivityController extends RestrictingController implemen
}
/**
+ * Returns {@code true} if the job's assigned network is active or otherwise considered to be
+ * in a good state to run the job now.
+ */
+ @GuardedBy("mLock")
+ public boolean isNetworkInStateForJobRunLocked(@NonNull JobStatus jobStatus) {
+ if (jobStatus.network == null) {
+ return false;
+ }
+ if (jobStatus.shouldTreatAsExpeditedJob() || jobStatus.shouldTreatAsUserInitiatedJob()
+ || mService.getUidProcState(jobStatus.getSourceUid())
+ <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+ // EJs, UIJs, and BFGS+ jobs should be able to activate the network.
+ return true;
+ }
+ return isNetworkInStateForJobRunLocked(jobStatus.network);
+ }
+
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ boolean isNetworkInStateForJobRunLocked(@NonNull Network network) {
+ if (!Flags.batchConnectivityJobsPerNetwork()) {
+ // Active network batching isn't enabled. We don't care about the network state.
+ return true;
+ }
+
+ CachedNetworkMetadata cachedNetworkMetadata = mAvailableNetworks.get(network);
+ if (cachedNetworkMetadata == null) {
+ return false;
+ }
+
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ if (cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed
+ + mCcConfig.NETWORK_ACTIVATION_EXPIRATION_MS > nowElapsed) {
+ // Network is still presumed to be active.
+ return true;
+ }
+
+ final boolean inactiveForTooLong =
+ cachedNetworkMetadata.capabilitiesFirstAcquiredTimeElapsed
+ < nowElapsed - mCcConfig.NETWORK_ACTIVATION_MAX_WAIT_TIME_MS
+ && cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed
+ < nowElapsed - mCcConfig.NETWORK_ACTIVATION_MAX_WAIT_TIME_MS;
+ // We can only know the state of the system default network. If that's not available
+ // or the network in question isn't the system default network,
+ // then return true if we haven't gotten an active signal in a long time.
+ if (mSystemDefaultNetwork == null) {
+ return inactiveForTooLong;
+ }
+
+ if (!mSystemDefaultNetwork.equals(network)) {
+ final NetworkCapabilities capabilities = cachedNetworkMetadata.networkCapabilities;
+ if (capabilities != null
+ && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
+ // VPNs won't have an active signal sent for them. Check their underlying networks
+ // instead, prioritizing the system default if it's one of them.
+ final List<Network> underlyingNetworks = capabilities.getUnderlyingNetworks();
+ if (underlyingNetworks == null) {
+ return inactiveForTooLong;
+ }
+
+ if (underlyingNetworks.contains(mSystemDefaultNetwork)) {
+ if (DEBUG) {
+ Slog.i(TAG, "Substituting system default network "
+ + mSystemDefaultNetwork + " for VPN " + network);
+ }
+ return isNetworkInStateForJobRunLocked(mSystemDefaultNetwork);
+ }
+
+ for (int i = underlyingNetworks.size() - 1; i >= 0; --i) {
+ if (isNetworkInStateForJobRunLocked(underlyingNetworks.get(i))) {
+ return true;
+ }
+ }
+ }
+ return inactiveForTooLong;
+ }
+
+ if (cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed
+ + mCcConfig.NETWORK_ACTIVATION_EXPIRATION_MS < nowElapsed) {
+ // We haven't checked the state recently enough. Let's check if the network is active.
+ // However, if we checked after the last confirmed active time and it wasn't active,
+ // then the network is still not active (we would be told when it becomes active
+ // via onNetworkActive()).
+ if (cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed
+ > cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed) {
+ return inactiveForTooLong;
+ }
+ // We need to explicitly check because there's no callback telling us when the network
+ // leaves the high power state.
+ cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed = nowElapsed;
+ final boolean isActive = mConnManager.isDefaultNetworkActive();
+ if (isActive) {
+ cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed = nowElapsed;
+ return true;
+ }
+ return inactiveForTooLong;
+ }
+
+ // We checked the state recently enough, but the network wasn't active. Assume it still
+ // isn't active.
+ return false;
+ }
+
+ /**
* We know the network has just come up. We want to run any jobs that are ready.
*/
@Override
public void onNetworkActive() {
synchronized (mLock) {
- for (int i = mTrackedJobs.size()-1; i >= 0; i--) {
- final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(i);
- for (int j = jobs.size() - 1; j >= 0; j--) {
- final JobStatus js = jobs.valueAt(j);
- if (js.isReady()) {
- if (DEBUG) {
- Slog.d(TAG, "Running " + js + " due to network activity.");
- }
- mStateChangedListener.onRunJobNow(js);
- }
- }
+ if (mSystemDefaultNetwork == null) {
+ Slog.wtf(TAG, "System default network is unknown but active");
+ return;
+ }
+
+ CachedNetworkMetadata cachedNetworkMetadata =
+ mAvailableNetworks.get(mSystemDefaultNetwork);
+ if (cachedNetworkMetadata == null) {
+ Slog.wtf(TAG, "System default network capabilities are unknown but active");
+ return;
}
+
+ // This method gets called on the system's main thread (not the
+ // AppSchedulingModuleThread), so shift the processing work to a handler to avoid
+ // blocking important operations on the main thread.
+ cachedNetworkMetadata.defaultNetworkActivationLastConfirmedTimeElapsed =
+ cachedNetworkMetadata.defaultNetworkActivationLastCheckTimeElapsed =
+ sElapsedRealtimeClock.millis();
+ mHandler.sendEmptyMessage(MSG_PROCESS_ACTIVE_NETWORK);
}
}
+ /** NetworkCallback to track all network changes. */
private final NetworkCallback mNetworkCallback = new NetworkCallback() {
@Override
public void onAvailable(Network network) {
@@ -1565,6 +1691,7 @@ public final class ConnectivityController extends RestrictingController implemen
CachedNetworkMetadata cnm = mAvailableNetworks.get(network);
if (cnm == null) {
cnm = new CachedNetworkMetadata();
+ cnm.capabilitiesFirstAcquiredTimeElapsed = sElapsedRealtimeClock.millis();
mAvailableNetworks.put(network, cnm);
} else {
final NetworkCapabilities oldCaps = cnm.networkCapabilities;
@@ -1700,6 +1827,29 @@ public final class ConnectivityController extends RestrictingController implemen
}
};
+ /** NetworkCallback to track only changes to the default network. */
+ private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ if (DEBUG) Slog.v(TAG, "systemDefault-onAvailable: " + network);
+ synchronized (mLock) {
+ mSystemDefaultNetwork = network;
+ }
+ }
+
+ @Override
+ public void onLost(Network network) {
+ if (DEBUG) {
+ Slog.v(TAG, "systemDefault-onLost: " + network);
+ }
+ synchronized (mLock) {
+ if (network.equals(mSystemDefaultNetwork)) {
+ mSystemDefaultNetwork = null;
+ }
+ }
+ }
+ };
+
private final INetworkPolicyListener mNetPolicyListener = new NetworkPolicyManager.Listener() {
@Override
public void onRestrictBackgroundChanged(boolean restrictBackground) {
@@ -1762,6 +1912,66 @@ public final class ConnectivityController extends RestrictingController implemen
}
}
break;
+
+ case MSG_PROCESS_ACTIVE_NETWORK:
+ removeMessages(MSG_PROCESS_ACTIVE_NETWORK);
+ synchronized (mLock) {
+ if (mSystemDefaultNetwork == null) {
+ break;
+ }
+ if (!Flags.batchConnectivityJobsPerNetwork()) {
+ break;
+ }
+ if (!isNetworkInStateForJobRunLocked(mSystemDefaultNetwork)) {
+ break;
+ }
+
+ final ArrayMap<Network, Boolean> includeInProcessing = new ArrayMap<>();
+ // Try to get the jobs to piggyback on the active network.
+ for (int u = mTrackedJobs.size() - 1; u >= 0; --u) {
+ final ArraySet<JobStatus> jobs = mTrackedJobs.valueAt(u);
+ for (int j = jobs.size() - 1; j >= 0; --j) {
+ final JobStatus js = jobs.valueAt(j);
+ if (!mSystemDefaultNetwork.equals(js.network)) {
+ final NetworkCapabilities capabilities =
+ getNetworkCapabilities(js.network);
+ if (capabilities == null
+ || !capabilities.hasTransport(
+ NetworkCapabilities.TRANSPORT_VPN)) {
+ includeInProcessing.put(js.network, Boolean.FALSE);
+ continue;
+ }
+ if (includeInProcessing.containsKey(js.network)) {
+ if (!includeInProcessing.get(js.network)) {
+ continue;
+ }
+ } else {
+ // VPNs most likely use the system default network as
+ // their underlying network. If so, process the job.
+ final List<Network> underlyingNetworks =
+ capabilities.getUnderlyingNetworks();
+ final boolean isSystemDefaultInUnderlying =
+ underlyingNetworks != null
+ && underlyingNetworks.contains(
+ mSystemDefaultNetwork);
+ includeInProcessing.put(js.network,
+ isSystemDefaultInUnderlying);
+ if (!isSystemDefaultInUnderlying) {
+ continue;
+ }
+ }
+ }
+ if (js.isReady()) {
+ if (DEBUG) {
+ Slog.d(TAG, "Potentially running " + js
+ + " due to network activity");
+ }
+ mStateChangedListener.onRunJobNow(js);
+ }
+ }
+ }
+ }
+ break;
}
}
}
@@ -1782,8 +1992,15 @@ public final class ConnectivityController extends RestrictingController implemen
@VisibleForTesting
static final String KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY =
CC_CONFIG_PREFIX + "avoid_undefined_transport_affinity";
+ private static final String KEY_NETWORK_ACTIVATION_EXPIRATION_MS =
+ CC_CONFIG_PREFIX + "network_activation_expiration_ms";
+ private static final String KEY_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS =
+ CC_CONFIG_PREFIX + "network_activation_max_wait_time_ms";
private static final boolean DEFAULT_AVOID_UNDEFINED_TRANSPORT_AFFINITY = false;
+ private static final long DEFAULT_NETWORK_ACTIVATION_EXPIRATION_MS = 10000L;
+ private static final long DEFAULT_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS =
+ 31 * MINUTE_IN_MILLIS;
/**
* If true, will avoid network transports that don't have an explicitly defined affinity.
@@ -1791,6 +2008,19 @@ public final class ConnectivityController extends RestrictingController implemen
public boolean AVOID_UNDEFINED_TRANSPORT_AFFINITY =
DEFAULT_AVOID_UNDEFINED_TRANSPORT_AFFINITY;
+ /**
+ * Amount of time that needs to pass before needing to determine if the network is still
+ * active.
+ */
+ public long NETWORK_ACTIVATION_EXPIRATION_MS = DEFAULT_NETWORK_ACTIVATION_EXPIRATION_MS;
+
+ /**
+ * Max time to wait since the network was last activated before deciding to allow jobs to
+ * run even if the network isn't active
+ */
+ public long NETWORK_ACTIVATION_MAX_WAIT_TIME_MS =
+ DEFAULT_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS;
+
@GuardedBy("mLock")
public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
@NonNull String key) {
@@ -1803,6 +2033,22 @@ public final class ConnectivityController extends RestrictingController implemen
mShouldReprocessNetworkCapabilities = true;
}
break;
+ case KEY_NETWORK_ACTIVATION_EXPIRATION_MS:
+ final long gracePeriodMs = properties.getLong(key,
+ DEFAULT_NETWORK_ACTIVATION_EXPIRATION_MS);
+ if (NETWORK_ACTIVATION_EXPIRATION_MS != gracePeriodMs) {
+ NETWORK_ACTIVATION_EXPIRATION_MS = gracePeriodMs;
+ // This doesn't need to trigger network capability reprocessing.
+ }
+ break;
+ case KEY_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS:
+ final long maxWaitMs = properties.getLong(key,
+ DEFAULT_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS);
+ if (NETWORK_ACTIVATION_MAX_WAIT_TIME_MS != maxWaitMs) {
+ NETWORK_ACTIVATION_MAX_WAIT_TIME_MS = maxWaitMs;
+ mShouldReprocessNetworkCapabilities = true;
+ }
+ break;
}
}
@@ -1814,6 +2060,10 @@ public final class ConnectivityController extends RestrictingController implemen
pw.print(KEY_AVOID_UNDEFINED_TRANSPORT_AFFINITY,
AVOID_UNDEFINED_TRANSPORT_AFFINITY).println();
+ pw.print(KEY_NETWORK_ACTIVATION_EXPIRATION_MS,
+ NETWORK_ACTIVATION_EXPIRATION_MS).println();
+ pw.print(KEY_NETWORK_ACTIVATION_MAX_WAIT_TIME_MS,
+ NETWORK_ACTIVATION_MAX_WAIT_TIME_MS).println();
pw.decreaseIndent();
}
@@ -1925,11 +2175,24 @@ public final class ConnectivityController extends RestrictingController implemen
private static class CachedNetworkMetadata {
public NetworkCapabilities networkCapabilities;
public boolean satisfiesTransportAffinities;
+ /**
+ * Track the first time ConnectivityController was informed about the capabilities of the
+ * network after it became available.
+ */
+ public long capabilitiesFirstAcquiredTimeElapsed;
+ public long defaultNetworkActivationLastCheckTimeElapsed;
+ public long defaultNetworkActivationLastConfirmedTimeElapsed;
public String toString() {
return "CNM{"
+ networkCapabilities.toString()
+ ", satisfiesTransportAffinities=" + satisfiesTransportAffinities
+ + ", capabilitiesFirstAcquiredTimeElapsed="
+ + capabilitiesFirstAcquiredTimeElapsed
+ + ", defaultNetworkActivationLastCheckTimeElapsed="
+ + defaultNetworkActivationLastCheckTimeElapsed
+ + ", defaultNetworkActivationLastConfirmedTimeElapsed="
+ + defaultNetworkActivationLastConfirmedTimeElapsed
+ "}";
}
}
@@ -2017,7 +2280,7 @@ public final class ConnectivityController extends RestrictingController implemen
pw.println("Aconfig flags:");
pw.increaseIndent();
pw.print(FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER,
- relaxPrefetchConnectivityConstraintOnlyOnCharger());
+ Flags.relaxPrefetchConnectivityConstraintOnlyOnCharger());
pw.println();
pw.decreaseIndent();
pw.println();
diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java
index 787055c8cd89..25d3a341bf6e 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Device.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Device.java
@@ -172,8 +172,10 @@ public class Device {
RuntimeException ex = new RuntimeException(
"Could not create uinput device \"" + name + "\"");
Log.e(TAG, "Couldn't create uinput device, exiting.", ex);
+ args.recycle();
throw ex;
}
+ args.recycle();
break;
case MSG_INJECT_EVENT:
if (mPtr != 0) {
diff --git a/core/api/current.txt b/core/api/current.txt
index e2c2f274e3b2..283e429c13bf 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -506,6 +506,7 @@ package android {
field public static final int autoSizeTextType = 16844085; // 0x1010535
field public static final int autoStart = 16843445; // 0x10102b5
field @Deprecated public static final int autoText = 16843114; // 0x101016a
+ field @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") public static final int autoTransact;
field public static final int autoUrlDetect = 16843404; // 0x101028c
field public static final int autoVerify = 16844014; // 0x10104ee
field public static final int autofillHints = 16844118; // 0x1010556
@@ -4379,6 +4380,7 @@ package android.app {
method public android.transition.TransitionManager getContentTransitionManager();
method @Nullable public android.view.View getCurrentFocus();
method @Deprecated public android.app.FragmentManager getFragmentManager();
+ method @FlaggedApi("android.security.content_uri_permission_apis") @NonNull public android.app.ComponentCaller getInitialCaller();
method public android.content.Intent getIntent();
method @Nullable public Object getLastNonConfigurationInstance();
method @Nullable public String getLaunchedFromPackage();
@@ -5419,6 +5421,12 @@ package android.app {
field public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; // 0x1
}
+ @FlaggedApi("android.security.content_uri_permission_apis") public final class ComponentCaller {
+ ctor public ComponentCaller(@NonNull android.os.IBinder, @Nullable android.os.IBinder);
+ method @Nullable public String getPackage();
+ method public int getUid();
+ }
+
public class DatePickerDialog extends android.app.AlertDialog implements android.widget.DatePicker.OnDateChangedListener android.content.DialogInterface.OnClickListener {
ctor public DatePickerDialog(@NonNull android.content.Context);
ctor public DatePickerDialog(@NonNull android.content.Context, @StyleRes int);
@@ -42370,6 +42378,8 @@ package android.telecom {
method public void onConference(android.telecom.Connection, android.telecom.Connection);
method public void onConnectionServiceFocusGained();
method public void onConnectionServiceFocusLost();
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public void onCreateConferenceComplete(@NonNull android.telecom.Conference);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public void onCreateConnectionComplete(@NonNull android.telecom.Connection);
method @Nullable public android.telecom.Conference onCreateIncomingConference(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest);
method public void onCreateIncomingConferenceFailed(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest);
method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
@@ -53905,6 +53915,7 @@ package android.view {
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
+ field @FlaggedApi("com.android.window.flags.untrusted_embedding_state_sharing") public static final String PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING = "android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING";
field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
field public static final String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 42b2ef6d41d5..66e03db5923b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10168,6 +10168,7 @@ package android.nfc.cardemulation {
@FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable {
ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String);
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilterToAutoTransact(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
@@ -10181,6 +10182,7 @@ package android.nfc.cardemulation {
method @FlaggedApi("android.nfc.nfc_read_polling_loop") @NonNull public java.util.List<java.lang.String> getPollingLoopFilters();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getPrefixAids();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSettingsActivityName();
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean getShouldAutoTransact(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getSubsetAids();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getUid();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean hasCategory(@NonNull String);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2103055afe50..ab9a4ecd8506 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -844,6 +844,7 @@ public class Activity extends ContextThemeWrapper
private IBinder mToken;
private IBinder mAssistToken;
private IBinder mShareableActivityToken;
+ private ComponentCaller mInitialCaller;
@UnsupportedAppUsage
private int mIdent;
@UnsupportedAppUsage
@@ -7031,6 +7032,20 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Returns the ComponentCaller instance of the app that initially launched this activity.
+ *
+ * <p>Note that calls to {@link #onNewIntent} have no effect on the returned value of this
+ * method.
+ *
+ * @return {@link ComponentCaller} instance
+ * @see ComponentCaller
+ */
+ @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
+ public @NonNull ComponentCaller getInitialCaller() {
+ return mInitialCaller;
+ }
+
+ /**
* Control whether this activity's main window is visible. This is intended
* only for the special case of an activity that is not going to show a
* UI itself, but can't just finish prior to onResume() because it needs
@@ -8647,6 +8662,19 @@ public class Activity extends ContextThemeWrapper
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
IBinder shareableActivityToken) {
+ attach(context, aThread, instr, token, ident, application, intent, info, title, parent, id,
+ lastNonConfigurationInstances, config, referrer, voiceInteractor, window,
+ activityConfigCallback, assistToken, shareableActivityToken, null);
+ }
+
+ final void attach(Context context, ActivityThread aThread,
+ Instrumentation instr, IBinder token, int ident,
+ Application application, Intent intent, ActivityInfo info,
+ CharSequence title, Activity parent, String id,
+ NonConfigurationInstances lastNonConfigurationInstances,
+ Configuration config, String referrer, IVoiceInteractor voiceInteractor,
+ Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
+ IBinder shareableActivityToken, IBinder initialCallerInfoAccessToken) {
if (sandboxActivitySdkBasedContext()) {
// Sandbox activities extract a token from the intent's extra to identify the related
// SDK as part of overriding attachBaseContext, then it wraps the passed context in an
@@ -8711,6 +8739,10 @@ public class Activity extends ContextThemeWrapper
getAutofillClientController().onActivityAttached(application);
setContentCaptureOptions(application.getContentCaptureOptions());
+
+ if (android.security.Flags.contentUriPermissionApis()) {
+ mInitialCaller = new ComponentCaller(getActivityToken(), initialCallerInfoAccessToken);
+ }
}
/** @hide */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4c54b03bd6d6..2c00c99cec91 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -580,6 +580,7 @@ public final class ActivityThread extends ClientTransactionHandler
public IBinder shareableActivityToken;
// The token of the TaskFragment that embedded this activity.
@Nullable public IBinder mTaskFragmentToken;
+ public IBinder initialCallerInfoAccessToken;
int ident;
@UnsupportedAppUsage
Intent intent;
@@ -668,7 +669,7 @@ public final class ActivityThread extends ClientTransactionHandler
List<ReferrerIntent> pendingNewIntents, SceneTransitionInfo sceneTransitionInfo,
boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble,
- IBinder taskFragmentToken) {
+ IBinder taskFragmentToken, IBinder initialCallerInfoAccessToken) {
this.token = token;
this.assistToken = assistToken;
this.shareableActivityToken = shareableActivityToken;
@@ -685,6 +686,7 @@ public final class ActivityThread extends ClientTransactionHandler
this.profilerInfo = profilerInfo;
this.overrideConfig = overrideConfig;
this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo);
+ this.initialCallerInfoAccessToken = initialCallerInfoAccessToken;
mSceneTransitionInfo = sceneTransitionInfo;
mLaunchedFromBubble = launchedFromBubble;
mTaskFragmentToken = taskFragmentToken;
@@ -3914,7 +3916,7 @@ public final class ActivityThread extends ClientTransactionHandler
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
- r.assistToken, r.shareableActivityToken);
+ r.assistToken, r.shareableActivityToken, r.initialCallerInfoAccessToken);
if (customIntent != null) {
activity.mIntent = customIntent;
diff --git a/core/java/android/app/ComponentCaller.java b/core/java/android/app/ComponentCaller.java
new file mode 100644
index 000000000000..583408ed8db9
--- /dev/null
+++ b/core/java/android/app/ComponentCaller.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024 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.app;
+
+import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.os.Process;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * Represents the app that launched the component. See below for the APIs available on the component
+ * caller.
+ *
+ * <p><b>Note</b>, that in {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} only
+ * {@link Activity} has access to {@link ComponentCaller} instances.
+ *
+ * @see Activity#getInitialCaller()
+ */
+@FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS)
+public final class ComponentCaller {
+ private final IBinder mActivityToken;
+ private final IBinder mCallerToken;
+
+ public ComponentCaller(@NonNull IBinder activityToken, @Nullable IBinder callerToken) {
+ mActivityToken = activityToken;
+ mCallerToken = callerToken;
+ }
+
+ /**
+ * Returns the uid of this component caller.
+ *
+ * <p><b>Note</b>, in {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} only
+ * {@link Activity} has access to {@link ComponentCaller} instances.
+ * <p>
+ * <h3>Requirements for {@link Activity} callers</h3>
+ *
+ * <p>In order to receive the calling app's uid, at least one of the following has to be met:
+ * <ul>
+ * <li>The calling app must call {@link ActivityOptions#setShareIdentityEnabled(boolean)}
+ * with a value of {@code true} and launch this activity with the resulting
+ * {@code ActivityOptions}.
+ * <li>The launched activity has the same uid as the calling app.
+ * <li>The launched activity is running in a package that is signed with the same key used
+ * to sign the platform (typically only system packages such as Settings will meet this
+ * requirement).
+ * </ul>
+ * These are the same requirements for {@link #getPackage()}; if any of these are met, then
+ * these methods can be used to obtain the uid and package name of the calling app. If none are
+ * met, then {@link Process#INVALID_UID} is returned.
+ *
+ * <p>Note, even if the above conditions are not met, the calling app's identity may still be
+ * available from {@link Activity#getCallingPackage()} if this activity was started with
+ * {@code Activity#startActivityForResult} to allow validation of the result's recipient.
+ *
+ * @return the uid of the calling app or {@link Process#INVALID_UID} if the current component
+ * cannot access the identity of the calling app or the caller is invalid
+ *
+ * @see ActivityOptions#setShareIdentityEnabled(boolean)
+ * @see Activity#getLaunchedFromUid()
+ */
+ public int getUid() {
+ return ActivityClient.getInstance().getLaunchedFromUid(mActivityToken);
+ }
+
+ /**
+ * Returns the package name of this component caller.
+ *
+ * <p><b>Note</b>, in {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} only
+ * {@link Activity} has access to {@link ComponentCaller} instances.
+ * <p>
+ * <h3>Requirements for {@link Activity} callers</h3>
+ *
+ * <p>In order to receive the calling app's package name, at least one of the following has to
+ * be met:
+ * <ul>
+ * <li>The calling app must call {@link ActivityOptions#setShareIdentityEnabled(boolean)}
+ * with a value of {@code true} and launch this activity with the resulting
+ * {@code ActivityOptions}.
+ * <li>The launched activity has the same uid as the calling app.
+ * <li>The launched activity is running in a package that is signed with the same key used
+ * to sign the platform (typically only system packages such as Settings will meet this
+ * meet this requirement).
+ * </ul>
+ * These are the same requirements for {@link #getUid()}; if any of these are met, then these
+ * methods can be used to obtain the uid and package name of the calling app. If none are met,
+ * then {@code null} is returned.
+ *
+ * <p>Note, even if the above conditions are not met, the calling app's identity may still be
+ * available from {@link Activity#getCallingPackage()} if this activity was started with
+ * {@code Activity#startActivityForResult} to allow validation of the result's recipient.
+ *
+ * @return the package name of the calling app or null if the current component cannot access
+ * the identity of the calling app or the caller is invalid
+ *
+ * @see ActivityOptions#setShareIdentityEnabled(boolean)
+ * @see Activity#getLaunchedFromPackage()
+ */
+ @Nullable
+ public String getPackage() {
+ return ActivityClient.getInstance().getLaunchedFromPackage(mActivityToken);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj == null || !(obj instanceof ComponentCaller other)) {
+ return false;
+ }
+ return this.mActivityToken == other.mActivityToken
+ && this.mCallerToken == other.mCallerToken;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Objects.hashCode(mActivityToken);
+ result = 31 * result + Objects.hashCode(mCallerToken);
+ return result;
+ }
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 68512b8bd771..454d60534476 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1423,8 +1423,8 @@ public class Instrumentation {
info, title, parent, id,
(Activity.NonConfigurationInstances)lastNonConfigurationInstance,
new Configuration(), null /* referrer */, null /* voiceInteractor */,
- null /* window */, null /* activityCallback */, null /*assistToken*/,
- null /*shareableActivityToken*/);
+ null /* window */, null /* activityCallback */, null /* assistToken */,
+ null /* shareableActivityToken */, null /* initialCallerInfoAccessToken */);
return activity;
}
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 5e5526802fe9..6357a20f592b 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -298,6 +298,33 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
return result;
}
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("ClientTransaction{");
+ if (mTransactionItems != null) {
+ // #addTransactionItem
+ sb.append("\n transactionItems=[");
+ final int size = mTransactionItems.size();
+ for (int i = 0; i < size; i++) {
+ sb.append("\n ").append(mTransactionItems.get(i));
+ }
+ sb.append("\n ]");
+ } else {
+ // #addCallback
+ sb.append("\n callbacks=[");
+ final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0;
+ for (int i = 0; i < size; i++) {
+ sb.append("\n ").append(mActivityCallbacks.get(i));
+ }
+ sb.append("\n ]");
+ // #setLifecycleStateRequest
+ sb.append("\n stateRequest=").append(mLifecycleStateRequest);
+ }
+ sb.append("\n}");
+ return sb.toString();
+ }
+
/** Dump transaction items callback items and final lifecycle state request. */
void dump(@NonNull String prefix, @NonNull PrintWriter pw,
@NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 4d53701772e4..95f5ad0bd38f 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -80,6 +80,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
private IBinder mShareableActivityToken;
private boolean mLaunchedFromBubble;
private IBinder mTaskFragmentToken;
+ private IBinder mInitialCallerInfoAccessToken;
/**
* It is only non-null if the process is the first time to launch activity. It is only an
* optimization for quick look up of the interface so the field is ignored for comparison.
@@ -106,7 +107,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mSceneTransitionInfo, mIsForward,
mProfilerInfo, client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
- mTaskFragmentToken);
+ mTaskFragmentToken, mInitialCallerInfoAccessToken);
client.handleLaunchActivity(r, pendingActions, mDeviceId, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -140,7 +141,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
boolean isForward, @Nullable ProfilerInfo profilerInfo, @NonNull IBinder assistToken,
@Nullable IActivityClientController activityClientController,
@NonNull IBinder shareableActivityToken, boolean launchedFromBubble,
- @Nullable IBinder taskFragmentToken) {
+ @Nullable IBinder taskFragmentToken, @NonNull IBinder initialCallerInfoAccessToken) {
LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class);
if (instance == null) {
instance = new LaunchActivityItem();
@@ -155,7 +156,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
sceneTransitionInfo, isForward,
profilerInfo != null ? new ProfilerInfo(profilerInfo) : null,
assistToken, activityClientController, shareableActivityToken,
- launchedFromBubble, taskFragmentToken);
+ launchedFromBubble, taskFragmentToken, initialCallerInfoAccessToken);
return instance;
}
@@ -170,7 +171,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
@Override
public void recycle() {
setValues(this, null, null, 0, null, null, null, 0, null, null, 0, null, null, null, null,
- null, false, null, null, null, null, false, null);
+ null, false, null, null, null, null, false, null, null);
ObjectPool.recycle(this);
}
@@ -201,6 +202,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
dest.writeStrongBinder(mShareableActivityToken);
dest.writeBoolean(mLaunchedFromBubble);
dest.writeStrongBinder(mTaskFragmentToken);
+ dest.writeStrongBinder(mInitialCallerInfoAccessToken);
}
/** Read from Parcel. */
@@ -220,6 +222,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
IActivityClientController.Stub.asInterface(in.readStrongBinder()),
in.readStrongBinder(),
in.readBoolean(),
+ in.readStrongBinder(),
in.readStrongBinder());
}
@@ -259,7 +262,9 @@ public class LaunchActivityItem extends ClientTransactionItem {
&& Objects.equals(mProfilerInfo, other.mProfilerInfo)
&& Objects.equals(mAssistToken, other.mAssistToken)
&& Objects.equals(mShareableActivityToken, other.mShareableActivityToken)
- && Objects.equals(mTaskFragmentToken, other.mTaskFragmentToken);
+ && Objects.equals(mTaskFragmentToken, other.mTaskFragmentToken)
+ && Objects.equals(mInitialCallerInfoAccessToken,
+ other.mInitialCallerInfoAccessToken);
}
@Override
@@ -283,6 +288,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
result = 31 * result + Objects.hashCode(mAssistToken);
result = 31 * result + Objects.hashCode(mShareableActivityToken);
result = 31 * result + Objects.hashCode(mTaskFragmentToken);
+ result = 31 * result + Objects.hashCode(mInitialCallerInfoAccessToken);
return result;
}
@@ -345,7 +351,7 @@ public class LaunchActivityItem extends ClientTransactionItem {
@Nullable ProfilerInfo profilerInfo, @Nullable IBinder assistToken,
@Nullable IActivityClientController activityClientController,
@Nullable IBinder shareableActivityToken, boolean launchedFromBubble,
- @Nullable IBinder taskFragmentToken) {
+ @Nullable IBinder taskFragmentToken, @Nullable IBinder initialCallerInfoAccessToken) {
instance.mActivityToken = activityToken;
instance.mIntent = intent;
instance.mIdent = ident;
@@ -368,5 +374,6 @@ public class LaunchActivityItem extends ClientTransactionItem {
instance.mShareableActivityToken = shareableActivityToken;
instance.mLaunchedFromBubble = launchedFromBubble;
instance.mTaskFragmentToken = taskFragmentToken;
+ instance.mInitialCallerInfoAccessToken = initialCallerInfoAccessToken;
}
}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index ba940770898a..406e00a84710 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -97,6 +97,10 @@ public class TransactionExecutor {
executeCallbacks(transaction);
executeLifecycleState(transaction);
}
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to execute the transaction: "
+ + transactionToString(transaction, mTransactionHandler));
+ throw e;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/core/java/android/content/pm/ProcessInfo.java b/core/java/android/content/pm/ProcessInfo.java
index 632c0f54375c..f84b46db144b 100644
--- a/core/java/android/content/pm/ProcessInfo.java
+++ b/core/java/android/content/pm/ProcessInfo.java
@@ -18,10 +18,8 @@ package android.content.pm;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.pm.ApplicationInfo;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.TextUtils;
import android.util.ArraySet;
import com.android.internal.util.DataClass;
@@ -64,6 +62,12 @@ public class ProcessInfo implements Parcelable {
*/
public @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized;
+ /**
+ * Enable use of embedded dex in the APK, rather than extracted or locally compiled variants.
+ * If false (default), the parent app's configuration determines behavior.
+ */
+ public boolean useEmbeddedDex;
+
@Deprecated
public ProcessInfo(@NonNull ProcessInfo orig) {
this.name = orig.name;
@@ -71,11 +75,12 @@ public class ProcessInfo implements Parcelable {
this.gwpAsanMode = orig.gwpAsanMode;
this.memtagMode = orig.memtagMode;
this.nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized;
+ this.useEmbeddedDex = orig.useEmbeddedDex;
}
- // Code below generated by codegen v1.0.22.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -102,6 +107,9 @@ public class ProcessInfo implements Parcelable {
* disabled, or left unspecified.
* @param nativeHeapZeroInitialized
* Enable automatic zero-initialization of native heap memory allocations.
+ * @param useEmbeddedDex
+ * Enable use of embedded dex in the APK, rather than extracted or locally compiled variants.
+ * If false (default), the parent app's configuration determines behavior.
*/
@DataClass.Generated.Member
public ProcessInfo(
@@ -109,7 +117,8 @@ public class ProcessInfo implements Parcelable {
@Nullable ArraySet<String> deniedPermissions,
@ApplicationInfo.GwpAsanMode int gwpAsanMode,
@ApplicationInfo.MemtagMode int memtagMode,
- @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) {
+ @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized,
+ boolean useEmbeddedDex) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
@@ -123,6 +132,7 @@ public class ProcessInfo implements Parcelable {
this.nativeHeapZeroInitialized = nativeHeapZeroInitialized;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+ this.useEmbeddedDex = useEmbeddedDex;
// onConstructed(); // You can define this method to get a callback
}
@@ -145,6 +155,7 @@ public class ProcessInfo implements Parcelable {
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
+ if (useEmbeddedDex) flg |= 0x20;
if (deniedPermissions != null) flg |= 0x2;
dest.writeByte(flg);
dest.writeString(name);
@@ -166,6 +177,7 @@ public class ProcessInfo implements Parcelable {
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
+ boolean _useEmbeddedDex = (flg & 0x20) != 0;
String _name = in.readString();
ArraySet<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
int _gwpAsanMode = in.readInt();
@@ -185,6 +197,7 @@ public class ProcessInfo implements Parcelable {
this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+ this.useEmbeddedDex = _useEmbeddedDex;
// onConstructed(); // You can define this method to get a callback
}
@@ -204,10 +217,10 @@ public class ProcessInfo implements Parcelable {
};
@DataClass.Generated(
- time = 1615850184524L,
- codegenVersion = "1.0.22",
+ time = 1706177470784L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/ProcessInfo.java",
- inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\npublic @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\npublic @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
+ inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\npublic @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\npublic @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic boolean useEmbeddedDex\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index d7e64b66763e..7ded7472bc1f 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -44,6 +44,20 @@ flag {
}
flag {
+ name: "start_user_before_scheduled_alarms"
+ namespace: "multiuser"
+ description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due"
+ bug: "314907186"
+}
+
+flag {
+ name: "add_ui_for_sounds_from_background_users"
+ namespace: "multiuser"
+ description: "Allow foreground user to dismiss sounds that are coming from background users"
+ bug: "314907186"
+}
+
+flag {
name: "enable_biometrics_to_unlock_private_space"
namespace: "profile_experiences"
description: "Add support to unlock the private space using biometrics"
@@ -122,3 +136,10 @@ flag {
description: "Handle listing of private space apps in settings pages with interleaved content"
bug: "323212460"
}
+
+flag {
+ name: "enable_hiding_profiles"
+ namespace: "profile_experiences"
+ description: "Allow the use of a profileApiAvailability user property to exclude HIDDEN profiles in API results"
+ bug: "316362775"
+}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 462667926053..69f9a7db6a85 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -39,6 +39,7 @@ import android.util.AttributeSet;
import android.util.Pair;
import android.util.Slog;
+import com.android.internal.pm.pkg.component.flags.Flags;
import com.android.internal.util.ArrayUtils;
import libcore.io.IoUtils;
@@ -89,6 +90,8 @@ public class ApkLiteParseUtils {
private static final String TAG_SDK_LIBRARY = "sdk-library";
private static final int SDK_VERSION = Build.VERSION.SDK_INT;
private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES;
+ private static final String TAG_PROCESSES = "processes";
+ private static final String TAG_PROCESS = "process";
/**
* Parse only lightweight details about the package at the given location.
@@ -518,6 +521,28 @@ public class ApkLiteParseUtils {
case TAG_SDK_LIBRARY:
isSdkLibrary = true;
break;
+ case TAG_PROCESSES:
+ final int processesDepth = parser.getDepth();
+ int processesType;
+ while ((processesType = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (processesType != XmlPullParser.END_TAG
+ || parser.getDepth() > processesDepth)) {
+ if (processesType == XmlPullParser.END_TAG
+ || processesType == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (parser.getDepth() != processesDepth + 1) {
+ // Search only under <processes>.
+ continue;
+ }
+
+ if (parser.getName().equals(TAG_PROCESS)
+ && Flags.enablePerProcessUseEmbeddedDexAttr()) {
+ useEmbeddedDex |= parser.getAttributeBooleanValue(
+ ANDROID_RES_NAMESPACE, "useEmbeddedDex", false);
+ }
+ }
}
}
} else if (TAG_OVERLAY.equals(parser.getName())) {
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 71698e4f4469..281ee500f741 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1256,7 +1256,9 @@ public class InputMethodService extends AbstractInputMethodService {
private void updateEditorToolTypeInternal(int toolType) {
if (Flags.useHandwritingListenerForTooltype()) {
mLastUsedToolType = toolType;
- mInputEditorInfo.setInitialToolType(toolType);
+ if (mInputEditorInfo != null) {
+ mInputEditorInfo.setInitialToolType(toolType);
+ }
}
onUpdateEditorToolType(toolType);
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index c0d1fb9c6a88..800ba6d3a542 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -150,4 +150,5 @@ interface IUserManager {
void setBootUser(int userId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS})")
int getBootUser();
+ int[] getProfileIdsExcludingHidden(int userId, boolean enabledOnly);
}
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index e6bfcd728a52..224b10d0eaca 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -313,25 +313,33 @@ public final class PerformanceHintManager {
* close to the target duration.
*
* @param workDuration the work duration of each component.
- * @throws IllegalArgumentException if work period start timestamp is not positive, or
- * actual total duration is not positive, or actual CPU duration is not positive,
- * or actual GPU duration is negative.
+ * @throws IllegalArgumentException if
+ * the work period start timestamp or the total duration are less than or equal to zero,
+ * if either the actual CPU duration or actual GPU duration is less than zero,
+ * or if both the CPU and GPU durations are zero.
*/
@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
public void reportActualWorkDuration(@NonNull WorkDuration workDuration) {
if (workDuration.mWorkPeriodStartTimestampNanos <= 0) {
throw new IllegalArgumentException(
- "the work period start timestamp should be positive.");
+ "the work period start timestamp should be greater than zero.");
}
if (workDuration.mActualTotalDurationNanos <= 0) {
- throw new IllegalArgumentException("the actual total duration should be positive.");
+ throw new IllegalArgumentException(
+ "the actual total duration should be greater than zero.");
}
- if (workDuration.mActualCpuDurationNanos <= 0) {
- throw new IllegalArgumentException("the actual CPU duration should be positive.");
+ if (workDuration.mActualCpuDurationNanos < 0) {
+ throw new IllegalArgumentException(
+ "the actual CPU duration should be greater than or equal to zero.");
}
if (workDuration.mActualGpuDurationNanos < 0) {
throw new IllegalArgumentException(
- "the actual GPU duration should be non negative.");
+ "the actual GPU duration should be greater than or equal to zero.");
+ }
+ if (workDuration.mActualCpuDurationNanos + workDuration.mActualGpuDurationNanos <= 0) {
+ throw new IllegalArgumentException(
+ "either the actual CPU duration or the actual GPU duration should be greater"
+ + "than zero.");
}
nativeReportActualWorkDuration(mNativeSessionPtr,
workDuration.mWorkPeriodStartTimestampNanos,
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ad0f940f2a0b..0da19df0b0af 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5357,6 +5357,25 @@ public class UserManager {
}
/**
+ * @return A list of ids of profiles associated with the specified user excluding those with
+ * {@link UserProperties#getProfileApiVisibility()} set to hidden. The returned list includes
+ * the user itself.
+ * @hide
+ * @see #getProfileIds(int, boolean)
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS}, conditional = true)
+ public int[] getProfileIdsExcludingHidden(@UserIdInt int userId, boolean enabled) {
+ try {
+ return mService.getProfileIdsExcludingHidden(userId, enabled);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the device credential owner id of the profile from
* which this method is called, or userId if called from a user that
* is not a profile.
diff --git a/core/java/android/os/WorkDuration.java b/core/java/android/os/WorkDuration.java
index 2ebcd830be29..5a54e90372fc 100644
--- a/core/java/android/os/WorkDuration.java
+++ b/core/java/android/os/WorkDuration.java
@@ -83,7 +83,7 @@ public final class WorkDuration implements Parcelable {
public void setWorkPeriodStartTimestampNanos(long workPeriodStartTimestampNanos) {
if (workPeriodStartTimestampNanos <= 0) {
throw new IllegalArgumentException(
- "the work period start timestamp should be positive.");
+ "the work period start timestamp should be greater than zero.");
}
mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
}
@@ -95,7 +95,8 @@ public final class WorkDuration implements Parcelable {
*/
public void setActualTotalDurationNanos(long actualTotalDurationNanos) {
if (actualTotalDurationNanos <= 0) {
- throw new IllegalArgumentException("the actual total duration should be positive.");
+ throw new IllegalArgumentException(
+ "the actual total duration should be greater than zero.");
}
mActualTotalDurationNanos = actualTotalDurationNanos;
}
@@ -106,8 +107,9 @@ public final class WorkDuration implements Parcelable {
* All timings should be in {@link SystemClock#uptimeNanos()}.
*/
public void setActualCpuDurationNanos(long actualCpuDurationNanos) {
- if (actualCpuDurationNanos <= 0) {
- throw new IllegalArgumentException("the actual CPU duration should be positive.");
+ if (actualCpuDurationNanos < 0) {
+ throw new IllegalArgumentException(
+ "the actual CPU duration should be greater than or equal to zero.");
}
mActualCpuDurationNanos = actualCpuDurationNanos;
}
@@ -119,7 +121,8 @@ public final class WorkDuration implements Parcelable {
*/
public void setActualGpuDurationNanos(long actualGpuDurationNanos) {
if (actualGpuDurationNanos < 0) {
- throw new IllegalArgumentException("the actual GPU duration should be non negative.");
+ throw new IllegalArgumentException(
+ "the actual GPU duration should be greater than or equal to zero.");
}
mActualGpuDurationNanos = actualGpuDurationNanos;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 76fda0636282..ef2d5ebd33fd 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -18161,6 +18161,16 @@ public final class Settings {
"show_notification_channel_warnings";
/**
+ * Whether to disable app and notification screen share protections.
+ *
+ * The value 1 - enable, 0 - disable
+ * @hide
+ */
+ @Readable
+ public static final String DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS =
+ "disable_screen_share_protections_for_apps_and_notifications";
+
+ /**
* Whether cell is enabled/disabled
* @hide
*/
diff --git a/core/java/android/tracing/transition/TransitionDataSource.java b/core/java/android/tracing/transition/TransitionDataSource.java
index 82559dab0e5b..744f4469bc75 100644
--- a/core/java/android/tracing/transition/TransitionDataSource.java
+++ b/core/java/android/tracing/transition/TransitionDataSource.java
@@ -16,7 +16,6 @@
package android.tracing.transition;
-import android.tracing.perfetto.CreateTlsStateArgs;
import android.tracing.perfetto.DataSource;
import android.tracing.perfetto.DataSourceInstance;
import android.tracing.perfetto.FlushCallbackArguments;
@@ -24,23 +23,17 @@ import android.tracing.perfetto.StartCallbackArguments;
import android.tracing.perfetto.StopCallbackArguments;
import android.util.proto.ProtoInputStream;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
/**
* @hide
*/
public class TransitionDataSource
- extends DataSource<DataSourceInstance, TransitionDataSource.TlsState, Void> {
+ extends DataSource<DataSourceInstance, Void, Void> {
public static String DATA_SOURCE_NAME = "com.android.wm.shell.transition";
private final Runnable mOnStartStaticCallback;
private final Runnable mOnFlushStaticCallback;
private final Runnable mOnStopStaticCallback;
- private final ConcurrentHashMap<Integer, ConcurrentHashMap<String, Integer>> mHandlerMappings =
- new ConcurrentHashMap<>();
-
public TransitionDataSource(Runnable onStart, Runnable onFlush, Runnable onStop) {
super(DATA_SOURCE_NAME);
this.mOnStartStaticCallback = onStart;
@@ -49,20 +42,6 @@ public class TransitionDataSource
}
@Override
- protected TlsState createTlsState(CreateTlsStateArgs<DataSourceInstance> args) {
- return new TlsState(args.getDataSourceInstanceLocked().getInstanceIndex());
- }
-
- public class TlsState {
- public final Map<String, Integer> handlerMapping;
-
- public TlsState(int instanceIndex) {
- handlerMapping = mHandlerMappings
- .computeIfAbsent(instanceIndex, index -> new ConcurrentHashMap<>());
- }
- }
-
- @Override
public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
return new DataSourceInstance(this, instanceIndex) {
@Override
@@ -78,7 +57,6 @@ public class TransitionDataSource
@Override
protected void onStop(StopCallbackArguments args) {
mOnStopStaticCallback.run();
- mHandlerMappings.remove(instanceIndex);
}
};
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 38cf4907cbe7..ac2a66e79d1d 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1472,6 +1472,34 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
/**
+ * Activity-level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * that declares whether this (embedded) activity allows the system to share its state with the
+ * host app when it is embedded in a different process in
+ * {@link android.R.attr#allowUntrustedActivityEmbedding untrusted mode}.
+ *
+ * <p>If this property is "true", the host app may receive event callbacks for the activity
+ * state change, including the reparent event and the component name of the activity, which are
+ * required to restore the embedding state after the embedded activity exits picture-in-picture
+ * mode. This property does not share any of the activity content with the host. Note that, for
+ * {@link android.R.attr#knownActivityEmbeddingCerts trusted embedding}, the reparent event and
+ * the component name are always shared with the host regardless of the value of this property.
+ *
+ * <p>The default value is {@code false}.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * &lt;activity&gt;
+ * &lt;property
+ * android:name="android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING"
+ * android:value="true|false"/&gt;
+ * &lt;/activity&gt;
+ * </pre>
+ */
+ @FlaggedApi(Flags.FLAG_UNTRUSTED_EMBEDDING_STATE_SHARING)
+ String PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING =
+ "android.window.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING";
+
+ /**
* Application level {@link android.content.pm.PackageManager.Property PackageManager.Property}
* that an app can specify to inform the system that the app is activity embedding split feature
* enabled.
diff --git a/core/java/android/view/accessibility/MagnificationAnimationCallback.java b/core/java/android/view/accessibility/MagnificationAnimationCallback.java
index 72518db3c60e..175549753228 100644
--- a/core/java/android/view/accessibility/MagnificationAnimationCallback.java
+++ b/core/java/android/view/accessibility/MagnificationAnimationCallback.java
@@ -16,6 +16,8 @@
package android.view.accessibility;
+import android.view.MagnificationSpec;
+
/**
* A callback for magnification animation result.
* @hide
@@ -31,4 +33,16 @@ public interface MagnificationAnimationCallback {
* change. Otherwise {@code false}
*/
void onResult(boolean success);
+
+ /**
+ * Called when the animation is finished or interrupted during animating.
+ *
+ * @param success {@code true} if animating successfully with given spec or the spec did not
+ * change. Otherwise {@code false}
+ * @param lastSpecSent the last spec that was sent to WindowManager for animation, in case you
+ * need to update the local copy
+ */
+ default void onResult(boolean success, MagnificationSpec lastSpecSent) {
+ onResult(success);
+ }
} \ No newline at end of file
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index cc875ad7ad1b..c76c7a482865 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -21,6 +21,7 @@ import static android.graphics.Color.alpha;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
@@ -93,7 +94,8 @@ public class SnapshotDrawerUtils {
| FLAG_WATCH_OUTSIDE_TOUCH
| FLAG_SPLIT_TOUCH
| FLAG_SCALED
- | FLAG_SECURE;
+ | FLAG_SECURE
+ | FLAG_DIM_BEHIND;
private static final RectF sTmpSnapshotSize = new RectF();
private static final RectF sTmpDstFrame = new RectF();
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index e32c8e58bb21..739cf0eeca0c 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -17,6 +17,7 @@
package android.window;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.view.Surface.ROTATION_0;
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -31,6 +32,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.view.Display;
+import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.InsetsState;
import android.view.WindowInsets;
@@ -157,10 +159,15 @@ public final class WindowMetricsController {
new Rect(0, 0, currentDisplayInfo.getNaturalWidth(),
currentDisplayInfo.getNaturalHeight()), isScreenRound,
ACTIVITY_TYPE_UNDEFINED);
- // Set the hardware-provided insets.
+ // Set the hardware-provided insets. Always with the ROTATION_0 result.
+ DisplayCutout cutout = currentDisplayInfo.displayCutout;
+ if (cutout != null && currentDisplayInfo.rotation != ROTATION_0) {
+ cutout = cutout.getRotated(
+ currentDisplayInfo.logicalWidth, currentDisplayInfo.logicalHeight,
+ currentDisplayInfo.rotation, ROTATION_0);
+ }
windowInsets = new WindowInsets.Builder(windowInsets).setRoundedCorners(
- currentDisplayInfo.roundedCorners)
- .setDisplayCutout(currentDisplayInfo.displayCutout).build();
+ currentDisplayInfo.roundedCorners).setDisplayCutout(cutout).build();
// Multiply default density scale because WindowMetrics provide the density value with
// the scaling factor for the Density Independent Pixel unit, which is the same unit
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 65075aea7b27..baefe7be1f54 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -226,6 +226,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
setTopOnBackInvokedCallback(null);
}
+ // We should also stop running animations since all callbacks have been removed.
+ // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+ Handler.getMain().post(mProgressAnimator::reset);
mAllCallbacks.clear();
mOnBackInvokedCallbacks.clear();
}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 2e20cceb64de..bc63881163f0 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -58,6 +58,14 @@ flag {
flag {
namespace: "windowing_sdk"
+ name: "untrusted_embedding_state_sharing"
+ description: "Feature flag to enable state sharing in untrusted embedding when apps opt in."
+ bug: "293647332"
+ is_fixed_read_only: true
+}
+
+flag {
+ namespace: "windowing_sdk"
name: "embedded_activity_back_nav_flag"
description: "Refines embedded activity back navigation behavior"
bug: "293642394"
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
index e5247f99f398..852ed1c3e2ac 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java
@@ -51,4 +51,6 @@ public interface ParsedProcess {
@ApplicationInfo.NativeHeapZeroInitialized
int getNativeHeapZeroInitialized();
+
+ boolean isUseEmbeddedDex();
}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
index 212fb867e7df..ff9b11a3d06a 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
@@ -54,6 +54,8 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable {
@ApplicationInfo.NativeHeapZeroInitialized
private int nativeHeapZeroInitialized = ApplicationInfo.ZEROINIT_DEFAULT;
+ private boolean useEmbeddedDex;
+
public ParsedProcessImpl() {
}
@@ -65,6 +67,7 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable {
gwpAsanMode = other.getGwpAsanMode();
memtagMode = other.getMemtagMode();
nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized();
+ useEmbeddedDex = other.isUseEmbeddedDex();
}
public void addStateFrom(@NonNull ParsedProcess other) {
@@ -72,6 +75,7 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable {
gwpAsanMode = other.getGwpAsanMode();
memtagMode = other.getMemtagMode();
nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized();
+ useEmbeddedDex = other.isUseEmbeddedDex();
final ArrayMap<String, String> oacn = other.getAppClassNamesByPackage();
for (int i = 0; i < oacn.size(); i++) {
@@ -115,7 +119,8 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable {
@NonNull Set<String> deniedPermissions,
@ApplicationInfo.GwpAsanMode int gwpAsanMode,
@ApplicationInfo.MemtagMode int memtagMode,
- @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized) {
+ @ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized,
+ boolean useEmbeddedDex) {
this.name = name;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, name);
@@ -134,6 +139,7 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable {
this.nativeHeapZeroInitialized = nativeHeapZeroInitialized;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+ this.useEmbeddedDex = useEmbeddedDex;
// onConstructed(); // You can define this method to get a callback
}
@@ -172,6 +178,11 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable {
}
@DataClass.Generated.Member
+ public boolean isUseEmbeddedDex() {
+ return useEmbeddedDex;
+ }
+
+ @DataClass.Generated.Member
public @NonNull ParsedProcessImpl setName(@NonNull String value) {
name = value;
com.android.internal.util.AnnotationValidations.validate(
@@ -223,6 +234,12 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable {
}
@DataClass.Generated.Member
+ public @NonNull ParsedProcessImpl setUseEmbeddedDex( boolean value) {
+ useEmbeddedDex = value;
+ return this;
+ }
+
+ @DataClass.Generated.Member
static Parcelling<Set<String>> sParcellingForDeniedPermissions =
Parcelling.Cache.get(
Parcelling.BuiltIn.ForInternedStringSet.class);
@@ -239,6 +256,9 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
+ byte flg = 0;
+ if (useEmbeddedDex) flg |= 0x40;
+ dest.writeByte(flg);
dest.writeString(name);
dest.writeMap(appClassNamesByPackage);
sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
@@ -258,6 +278,8 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
+ byte flg = in.readByte();
+ boolean _useEmbeddedDex = (flg & 0x40) != 0;
String _name = in.readString();
ArrayMap<String,String> _appClassNamesByPackage = new ArrayMap();
in.readMap(_appClassNamesByPackage, String.class.getClassLoader());
@@ -284,6 +306,7 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable {
this.nativeHeapZeroInitialized = _nativeHeapZeroInitialized;
com.android.internal.util.AnnotationValidations.validate(
ApplicationInfo.NativeHeapZeroInitialized.class, null, nativeHeapZeroInitialized);
+ this.useEmbeddedDex = _useEmbeddedDex;
// onConstructed(); // You can define this method to get a callback
}
@@ -303,10 +326,10 @@ public class ParsedProcessImpl implements ParsedProcess, Parcelable {
};
@DataClass.Generated(
- time = 1701445656489L,
+ time = 1706177189475L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java",
- inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic void addStateFrom(com.android.internal.pm.pkg.component.ParsedProcess)\npublic void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
+ inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\nprivate boolean useEmbeddedDex\npublic void addStateFrom(com.android.internal.pm.pkg.component.ParsedProcess)\npublic void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
index 3b2056e7892e..dd58815fdc5e 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
@@ -27,6 +27,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.internal.R;
+import com.android.internal.pm.pkg.component.flags.Flags;
import com.android.internal.pm.pkg.parsing.ParsingPackage;
import com.android.internal.pm.pkg.parsing.ParsingUtils;
import com.android.internal.util.CollectionUtils;
@@ -111,6 +112,12 @@ public class ParsedProcessUtils {
proc.setNativeHeapZeroInitialized(
v ? ApplicationInfo.ZEROINIT_ENABLED : ApplicationInfo.ZEROINIT_DISABLED);
}
+ if (Flags.enablePerProcessUseEmbeddedDexAttr()) {
+ proc.setUseEmbeddedDex(
+ sa.getBoolean(R.styleable.AndroidManifestProcess_useEmbeddedDex, false));
+ } else {
+ proc.setUseEmbeddedDex(false);
+ }
} finally {
sa.recycle();
}
diff --git a/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig b/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig
new file mode 100644
index 000000000000..ea9abdbc4388
--- /dev/null
+++ b/core/java/com/android/internal/pm/pkg/component/flags/flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.internal.pm.pkg.component.flags"
+
+flag {
+ name: "enable_per_process_use_embedded_dex_attr"
+ namespace: "machine_learning"
+ description: "This flag enables support for parsing per-process useEmbeddedDex attribute."
+ is_fixed_read_only: true
+ bug: "295870718"
+} \ No newline at end of file
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 323f7b63246c..b5fbb22215b4 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -73,7 +73,7 @@ private:
uint32_t mNextPublishedSeq;
const std::string getInputChannelName() {
- return mInputPublisher.getChannel()->getName();
+ return mInputPublisher.getChannel().getName();
}
int handleEvent(int receiveFd, int events, void* data) override;
@@ -102,7 +102,7 @@ NativeInputEventSender::~NativeInputEventSender() {
}
status_t NativeInputEventSender::initialize() {
- const int receiveFd = mInputPublisher.getChannel()->getFd();
+ const int receiveFd = mInputPublisher.getChannel().getFd();
mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL);
return OK;
}
@@ -112,7 +112,7 @@ void NativeInputEventSender::dispose() {
LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Disposing input event sender.";
}
- mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd());
+ mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel().getFd());
}
status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) {
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index d3f3af738273..286185864593 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -723,6 +723,7 @@ message GlobalSettingsProto {
// encoded as a key=value list separated by commas.
optional SettingProto smart_suggestions_in_notifications_flags = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto bubbles = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto disable_screen_share_protections_for_apps_and_notifications = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Notification notification = 82;
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index f154b571b3e4..e7b1d09d6ab0 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4375,9 +4375,14 @@
<!-- Specify one or more <code>polling-loop-filter</code> elements inside a
<code>host-apdu-service</code> to indicate polling loop frames that
your service can handle. -->
+ <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") -->
<declare-styleable name="PollingLoopFilter">
<!-- The polling loop frame. This attribute is mandatory. -->
<attr name="name" />
+ <!-- Whether or not the system should automatically start a transaction when this polling
+ loop filter matches. If not set, default value is false. -->
+ <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") -->
+ <attr name="autoTransact" format="boolean"/>
</declare-styleable>
<!-- Use <code>host-nfcf-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 6884fc0057d9..65c4d9fdab78 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1273,11 +1273,17 @@
null to indicate no split types are offered. -->
<attr name="splitTypes" format="string" />
- <!-- Flag to specify if this app wants to run the dex within its APK but not extracted or
- locally compiled variants. This keeps the dex code protected by the APK signature. Such
- apps will always run in JIT mode (same when they are first installed), and the system will
- never generate ahead-of-time compiled code for them. Depending on the app's workload,
- there may be some run time performance change, noteably the cold start time. -->
+ <!-- Flag to specify if this app (or process) wants to run the dex within its APK but not
+ extracted or locally compiled variants. This keeps the dex code protected by the APK
+ signature. Such apps (or processes) will always run in JIT mode (same when they are first
+ installed). If enabled at the app level, the system will never generate ahead-of-time
+ compiled code for the app. Depending on the app's workload, there may be some run time
+ performance change, noteably the cold start time.
+
+ <p>This attribute can be applied to either
+ {@link android.R.styleable#AndroidManifestProcess process} or
+ {@link android.R.styleable#AndroidManifestApplication application} tags. If enabled at the
+ app level, any process level attribute is effectively ignored. -->
<attr name="useEmbeddedDex" format="boolean" />
<!-- Extra options for an activity's UI. Applies to either the {@code <activity>} or
@@ -2793,6 +2799,7 @@
<attr name="gwpAsanMode" />
<attr name="memtagMode" />
<attr name="nativeHeapZeroInitialized" />
+ <attr name="useEmbeddedDex" />
</declare-styleable>
<!-- The <code>deny-permission</code> tag specifies that a permission is to be denied
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 72bfa8ac9113..81a8908b37df 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -145,6 +145,8 @@
<public name="fragmentSuffix"/>
<!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") -->
<public name="useBoundsForWidth"/>
+ <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") -->
+ <public name="autoTransact"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 296c45136292..262f167078e2 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -31,6 +31,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertThrows;
+import android.Manifest;
import android.app.compat.CompatChanges;
import android.graphics.Bitmap;
import android.hardware.broadcastradio.ConfigFlag;
@@ -57,6 +58,8 @@ import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import android.util.ArraySet;
+import androidx.test.platform.app.InstrumentationRegistry;
+
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
import com.android.server.broadcastradio.RadioServiceUserController;
@@ -171,6 +174,10 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase {
@Before
public void setup() throws Exception {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+ Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
+
doReturn(true).when(() -> CompatChanges.isChangeEnabled(
eq(ConversionUtils.RADIO_U_VERSION_REQUIRED), anyInt()));
doReturn(USER_ID_1).when(mUserHandleMock).getIdentifier();
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index a796a0fbb7ab..c6447be71855 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -123,6 +123,7 @@ public class ObjectPoolTests {
final IBinder shareableActivityToken = new Binder();
final int deviceId = 3;
final IBinder taskFragmentToken = new Binder();
+ final IBinder initialCallerInfoAccessToken = new Binder();
testRecycle(() -> new LaunchActivityItemBuilder(
activityToken, intent, activityInfo)
@@ -140,6 +141,7 @@ public class ObjectPoolTests {
.setShareableActivityToken(shareableActivityToken)
.setTaskFragmentToken(taskFragmentToken)
.setDeviceId(deviceId)
+ .setInitialCallerInfoAccessToken(initialCallerInfoAccessToken)
.build());
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index 3823033d48a1..d641659e6a5f 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -132,6 +132,8 @@ class TestUtils {
private boolean mLaunchedFromBubble;
@Nullable
private IBinder mTaskFragmentToken;
+ @Nullable
+ private IBinder mInitialCallerInfoAccessToken;
LaunchActivityItemBuilder(@NonNull IBinder activityToken, @NonNull Intent intent,
@NonNull ActivityInfo info) {
@@ -251,13 +253,21 @@ class TestUtils {
}
@NonNull
+ LaunchActivityItemBuilder setInitialCallerInfoAccessToken(
+ @Nullable IBinder initialCallerInfoAccessToken) {
+ mInitialCallerInfoAccessToken = initialCallerInfoAccessToken;
+ return this;
+ }
+
+ @NonNull
LaunchActivityItem build() {
return LaunchActivityItem.obtain(mActivityToken, mIntent, mIdent, mInfo,
mCurConfig, mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor,
mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
mActivityOptions != null ? mActivityOptions.getSceneTransitionInfo() : null,
mIsForward, mProfilerInfo, mAssistToken, null /* activityClientController */,
- mShareableActivityToken, mLaunchedFromBubble, mTaskFragmentToken);
+ mShareableActivityToken, mLaunchedFromBubble, mTaskFragmentToken,
+ mInitialCallerInfoAccessToken);
}
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 952cdd986614..508c6b2bcbce 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -189,6 +189,7 @@ public class TransactionParcelTests {
.setAssistToken(new Binder())
.setShareableActivityToken(new Binder())
.setTaskFragmentToken(new Binder())
+ .setInitialCallerInfoAccessToken(new Binder())
.build();
writeAndPrepareForReading(item);
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index a28bb69244eb..2bd563191134 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -219,6 +219,22 @@ public class PerformanceHintManagerTest {
workDuration.setActualGpuDurationNanos(6);
s.reportActualWorkDuration(workDuration);
}
+ {
+ WorkDuration workDuration = new WorkDuration();
+ workDuration.setWorkPeriodStartTimestampNanos(1);
+ workDuration.setActualTotalDurationNanos(14);
+ workDuration.setActualCpuDurationNanos(0);
+ workDuration.setActualGpuDurationNanos(6);
+ s.reportActualWorkDuration(workDuration);
+ }
+ {
+ WorkDuration workDuration = new WorkDuration();
+ workDuration.setWorkPeriodStartTimestampNanos(1);
+ workDuration.setActualTotalDurationNanos(14);
+ workDuration.setActualCpuDurationNanos(7);
+ workDuration.setActualGpuDurationNanos(0);
+ s.reportActualWorkDuration(workDuration);
+ }
}
@Test
@@ -242,7 +258,7 @@ public class PerformanceHintManagerTest {
s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6, 1));
});
assertThrows(IllegalArgumentException.class, () -> {
- s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6, 1));
+ s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 0, 1));
});
assertThrows(IllegalArgumentException.class, () -> {
s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1, 1));
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 52ff0d4037e8..a709d7be898b 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -20,7 +20,6 @@ import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeast;
@@ -359,7 +358,7 @@ public class WindowOnBackInvokedDispatcherTest {
}
@Test
- public void onDetachFromWindow_cancelsBackAnimation() throws RemoteException {
+ public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
@@ -369,12 +368,13 @@ public class WindowOnBackInvokedDispatcherTest {
waitForIdle();
verify(mCallback1).onBackStarted(any(BackEvent.class));
- // This should trigger mCallback1.onBackCancelled() and unset the callback in WM
+ // This should trigger mCallback1.onBackCancelled()
mDispatcher.detachFromWindow();
+ // This should be ignored by mCallback1
+ callbackInfo.getCallback().onBackInvoked();
- OnBackInvokedCallbackInfo callbackInfo1 = assertSetCallbackInfo();
- assertNull(callbackInfo1);
waitForIdle();
+ verify(mCallback1, never()).onBackInvoked();
verify(mCallback1).onBackCancelled();
}
}
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 3e0e36d6f3b8..39cb616b1ed9 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -353,7 +353,8 @@ public class ActivityThreadClientTest {
null /* pendingResults */, null /* pendingNewIntents */,
null /* activityOptions */, true /* isForward */, null /* profilerInfo */,
mThread /* client */, null /* asssitToken */, null /* shareableActivityToken */,
- false /* launchedFromBubble */, null /* taskfragmentToken */);
+ false /* launchedFromBubble */, null /* taskfragmentToken */,
+ null /* initialCallerInfoAccessToken */);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 50a58daa6363..a2a2914f969f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -19,6 +19,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.os.AsyncTask.Status.FINISHED;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.DimenRes;
import android.annotation.Hide;
@@ -47,6 +48,7 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
@@ -466,6 +468,7 @@ public class Bubble implements BubbleViewProvider {
* Call when all the views should be removed/cleaned up.
*/
public void cleanupViews() {
+ ProtoLog.d(WM_SHELL_BUBBLES, "Bubble#cleanupViews=%s", getKey());
cleanupViews(true);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index a43a951b4574..1a6e48a6c852 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -23,7 +23,6 @@ import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED;
@@ -435,6 +434,9 @@ public class BubbleController implements ConfigurationChangeListener,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
for (Bubble b : mBubbleData.getBubbles()) {
if (task.taskId == b.getTaskId()) {
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "onActivityRestartAttempt - taskId=%d selecting matching bubble=%s",
+ task.taskId, b.getKey());
mBubbleData.setSelectedBubble(b);
mBubbleData.setExpanded(true);
return;
@@ -442,6 +444,9 @@ public class BubbleController implements ConfigurationChangeListener,
}
for (Bubble b : mBubbleData.getOverflowBubbles()) {
if (task.taskId == b.getTaskId()) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onActivityRestartAttempt - taskId=%d "
+ + "selecting matching overflow bubble=%s",
+ task.taskId, b.getKey());
promoteBubbleFromOverflow(b);
mBubbleData.setExpanded(true);
return;
@@ -581,10 +586,15 @@ public class BubbleController implements ConfigurationChangeListener,
// Hide the stack temporarily if the status bar has been made invisible, and the stack
// is collapsed. An expanded stack should remain visible until collapsed.
mStackView.setTemporarilyInvisible(!visible && !isStackExpanded());
+ ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarVisibilityChanged=%b stackExpanded=%b",
+ visible, isStackExpanded());
}
}
private void onZenStateChanged() {
+ if (hasBubbles()) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onZenStateChanged");
+ }
for (Bubble b : mBubbleData.getBubbles()) {
b.setShowDot(b.showInShade());
}
@@ -593,9 +603,10 @@ public class BubbleController implements ConfigurationChangeListener,
@VisibleForTesting
public void onStatusBarStateChanged(boolean isShade) {
boolean didChange = mIsStatusBarShade != isShade;
- if (DEBUG_BUBBLE_CONTROLLER) {
- Log.d(TAG, "onStatusBarStateChanged isShade=" + isShade + " didChange=" + didChange);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarStateChanged "
+ + "isShade=%b didChange=%b mNotifEntryToExpandOnShadeUnlock=%s",
+ isShade, didChange, (mNotifEntryToExpandOnShadeUnlock != null
+ ? mNotifEntryToExpandOnShadeUnlock.getKey() : "null"));
mIsStatusBarShade = isShade;
if (!mIsStatusBarShade && didChange) {
// Only collapse stack on change
@@ -611,6 +622,8 @@ public class BubbleController implements ConfigurationChangeListener,
@VisibleForTesting
public void onBubbleMetadataFlagChanged(Bubble bubble) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onBubbleMetadataFlagChanged=%s flags=%d",
+ bubble.getKey(), bubble.getFlags());
// Make sure NoMan knows suppression state so that anyone querying it can tell.
try {
mBarService.onBubbleMetadataFlagChanged(bubble.getKey(), bubble.getFlags());
@@ -623,6 +636,8 @@ public class BubbleController implements ConfigurationChangeListener,
/** Called when the current user changes. */
@VisibleForTesting
public void onUserChanged(int newUserId) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onUserChanged currentUser=%d newUser=%d",
+ mCurrentUserId, newUserId);
saveBubbles(mCurrentUserId);
mCurrentUserId = newUserId;
@@ -825,6 +840,7 @@ public class BubbleController implements ConfigurationChangeListener,
*/
void updateWindowFlagsForBackpress(boolean interceptBack) {
if (mAddedToWindowManager) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "updateFlagsForBackPress interceptBack=%b", interceptBack);
mWmLayoutParams.flags = interceptBack
? 0
: WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -1014,8 +1030,9 @@ public class BubbleController implements ConfigurationChangeListener,
}
private void onNotificationPanelExpandedChanged(boolean expanded) {
- ProtoLog.d(WM_SHELL_BUBBLES, "onNotificationPanelExpandedChanged: expanded=%b", expanded);
if (mStackView != null && mStackView.isExpanded()) {
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "onNotificationPanelExpandedChanged expanded=%b", expanded);
if (expanded) {
mStackView.stopMonitoringSwipeUpGesture();
} else {
@@ -1096,6 +1113,7 @@ public class BubbleController implements ConfigurationChangeListener,
/** Promote the provided bubble from the overflow view. */
public void promoteBubbleFromOverflow(Bubble bubble) {
mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
+ ProtoLog.d(WM_SHELL_BUBBLES, "promoteBubbleFromOverflow=%s", bubble.getKey());
bubble.setInflateSynchronously(mInflateSynchronously);
bubble.setShouldAutoExpand(true);
bubble.markAsAccessedAt(System.currentTimeMillis());
@@ -1211,11 +1229,8 @@ public class BubbleController implements ConfigurationChangeListener,
// Skip update, but store it in user bubbles so it gets restored after user switch
mSavedUserBubbleData.get(bubbleUserId, new UserBubbleData()).add(notif.getKey(),
true /* shownInShade */);
- if (DEBUG_BUBBLE_CONTROLLER) {
- Log.d(TAG,
- "Ignore update to bubble for not active user. Bubble userId=" + bubbleUserId
- + " current userId=" + mCurrentUserId);
- }
+ Log.w(TAG, "updateBubble, ignore update for non-active user=" + bubbleUserId
+ + " currentUser=" + mCurrentUserId);
}
}
@@ -1769,18 +1784,19 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void applyUpdate(BubbleData.Update update) {
- if (DEBUG_BUBBLE_CONTROLLER) {
- Log.d(TAG, "applyUpdate:" + " bubbleAdded=" + (update.addedBubble != null)
- + " bubbleRemoved="
- + (update.removedBubbles != null && update.removedBubbles.size() > 0)
- + " bubbleUpdated=" + (update.updatedBubble != null)
- + " orderChanged=" + update.orderChanged
- + " expandedChanged=" + update.expandedChanged
- + " selectionChanged=" + update.selectionChanged
- + " suppressed=" + (update.suppressedBubble != null)
- + " unsuppressed=" + (update.unsuppressedBubble != null)
- + " shouldShowEducation=" + update.shouldShowEducation);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "mBubbleDataListener#applyUpdate:"
+ + " added=%s removed=%b updated=%s orderChanged=%b expansionChanged=%b"
+ + " expanded=%b selectionChanged=%b selected=%s"
+ + " suppressed=%s unsupressed=%s shouldShowEducation=%b",
+ update.addedBubble != null ? update.addedBubble.getKey() : "null",
+ update.removedBubbles.isEmpty(),
+ update.updatedBubble != null ? update.updatedBubble.getKey() : "null",
+ update.orderChanged, update.expandedChanged, update.expanded,
+ update.selectionChanged,
+ update.selectedBubble != null ? update.selectedBubble.getKey() : "null",
+ update.suppressedBubble != null ? update.suppressedBubble.getKey() : "null",
+ update.unsuppressedBubble != null ? update.unsuppressedBubble.getKey() : "null",
+ update.shouldShowEducation);
ensureBubbleViewsAndWindowCreated();
@@ -1974,7 +1990,8 @@ public class BubbleController implements ConfigurationChangeListener,
if (mStackView == null && mLayerView == null) {
return;
}
-
+ ProtoLog.v(WM_SHELL_BUBBLES, "updateBubbleViews mIsStatusBarShade=%s hasBubbles=%s",
+ mIsStatusBarShade, hasBubbles());
if (!mIsStatusBarShade) {
// Bubbles don't appear when the device is locked.
if (mStackView != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index dbfa2606ea06..6d3f0c32bcbb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -16,10 +16,9 @@
package com.android.wm.shell.bubbles;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
-import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.app.PendingIntent;
@@ -36,6 +35,7 @@ import android.view.View;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.R;
@@ -333,9 +333,6 @@ public class BubbleData {
}
public void setExpanded(boolean expanded) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setExpanded: " + expanded);
- }
setExpandedInternal(expanded);
dispatchPendingChanges();
}
@@ -347,9 +344,8 @@ public class BubbleData {
* updated to have the correct state.
*/
public void setSelectedBubbleFromLauncher(BubbleViewProvider bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubbleFromLauncher: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleFromLauncher=%s",
+ (bubble != null ? bubble.getKey() : "null"));
mExpanded = true;
if (Objects.equals(bubble, mSelectedBubble)) {
return;
@@ -370,9 +366,6 @@ public class BubbleData {
}
public void setSelectedBubble(BubbleViewProvider bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubble: " + bubble);
- }
setSelectedBubbleInternal(bubble);
dispatchPendingChanges();
}
@@ -430,12 +423,13 @@ public class BubbleData {
* BubbleBarLayerView, BubbleIconFactory, boolean)
*/
void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "notificationEntryUpdated: " + bubble);
- }
mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here
Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
suppressFlyout |= !bubble.isTextChanged();
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "notifEntryUpdated=%s prevBubble=%b suppressFlyout=%b showInShade=%b autoExpand=%b",
+ bubble.getKey(), (prevBubble != null), suppressFlyout, showInShade,
+ bubble.shouldAutoExpand());
if (prevBubble == null) {
// Create a new bubble
@@ -483,9 +477,6 @@ public class BubbleData {
* Dismisses the bubble with the matching key, if it exists.
*/
public void dismissBubbleWithKey(String key, @DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason);
- }
doRemove(key, reason);
dispatchPendingChanges();
}
@@ -603,9 +594,7 @@ public class BubbleData {
}
private void doAdd(Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doAdd: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doAdd=%s", bubble.getKey());
mBubbles.add(0, bubble);
mStateChange.addedBubble = bubble;
// Adding the first bubble doesn't change the order
@@ -634,9 +623,7 @@ public class BubbleData {
}
private void doUpdate(Bubble bubble, boolean reorder) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doUpdate: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "BubbleData - doUpdate=%s", bubble.getKey());
mStateChange.updatedBubble = bubble;
if (!isExpanded() && reorder) {
int prevPos = mBubbles.indexOf(bubble);
@@ -663,9 +650,6 @@ public class BubbleData {
}
private void doRemove(String key, @DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA || (key != null && key.contains(KEY_APP_BUBBLE))) {
- Log.d(TAG, "doRemove: " + key + " reason: " + reason);
- }
// If it was pending remove it
if (mPendingBubbles.containsKey(key)) {
mPendingBubbles.remove(key);
@@ -686,9 +670,7 @@ public class BubbleData {
&& shouldRemoveHiddenBubble) {
Bubble b = getOverflowBubbleWithKey(key);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Cancel overflow bubble: " + b);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel overflow bubble=%s", key);
if (b != null) {
b.stopInflation();
}
@@ -699,9 +681,7 @@ public class BubbleData {
}
if (hasSuppressedBubbleWithKey(key) && shouldRemoveHiddenBubble) {
Bubble b = getSuppressedBubbleWithKey(key);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Cancel suppressed bubble: " + b);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel suppressed bubble=%s", key);
if (b != null) {
mSuppressedBubbles.remove(b.getLocusId());
b.stopInflation();
@@ -711,6 +691,7 @@ public class BubbleData {
return;
}
Bubble bubbleToRemove = mBubbles.get(indexToRemove);
+ ProtoLog.d(WM_SHELL_BUBBLES, "doRemove=%s", bubbleToRemove.getKey());
bubbleToRemove.stopInflation();
overflowBubble(reason, bubbleToRemove);
@@ -744,17 +725,12 @@ public class BubbleData {
}
// Move selection to the new bubble at the same position.
int newIndex = Math.min(indexOfSelected, mBubbles.size() - 1);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setNewSelectedIndex: " + indexOfSelected);
- }
BubbleViewProvider newSelected = mBubbles.get(newIndex);
setSelectedBubbleInternal(newSelected);
}
private void doSuppress(Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doSuppressed: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doSuppress=%s", bubble.getKey());
mStateChange.suppressedBubble = bubble;
bubble.setSuppressBubble(true);
@@ -777,9 +753,7 @@ public class BubbleData {
}
private void doUnsuppress(Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doUnsuppressed: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "doUnsuppress=%s", bubble.getKey());
bubble.setSuppressBubble(false);
mStateChange.unsuppressedBubble = bubble;
mBubbles.add(bubble);
@@ -801,9 +775,7 @@ public class BubbleData {
|| reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
return;
}
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Overflowing: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "overflowBubble=%s", bubble.getKey());
mLogger.logOverflowAdd(bubble, reason);
mOverflowBubbles.remove(bubble);
mOverflowBubbles.add(0, bubble);
@@ -812,9 +784,7 @@ public class BubbleData {
if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
// Remove oldest bubble.
Bubble oldest = mOverflowBubbles.get(mOverflowBubbles.size() - 1);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Overflow full. Remove: " + oldest);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "overflow full, remove=%s", oldest.getKey());
mStateChange.bubbleRemoved(oldest, Bubbles.DISMISS_OVERFLOW_MAX_REACHED);
mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_MAX_REACHED);
mOverflowBubbles.remove(oldest);
@@ -823,9 +793,7 @@ public class BubbleData {
}
public void dismissAll(@DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "dismissAll: reason=" + reason);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "dismissAll reason=%d", reason);
if (mBubbles.isEmpty() && mSuppressedBubbles.isEmpty()) {
return;
}
@@ -851,9 +819,10 @@ public class BubbleData {
* @param visible whether the task with the locusId is visible or not.
*/
public void onLocusVisibilityChanged(int taskId, LocusId locusId, boolean visible) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "onLocusVisibilityChanged: " + locusId + " visible=" + visible);
- }
+ if (locusId == null) return;
+
+ ProtoLog.d(WM_SHELL_BUBBLES, "onLocusVisibilityChanged=%s visible=%b taskId=%d",
+ locusId.getId(), visible, taskId);
Bubble matchingBubble = getBubbleInStackWithLocusId(locusId);
// Don't add the locus if it's from a bubble'd activity, we only suppress for non-bubbled.
@@ -910,9 +879,8 @@ public class BubbleData {
* @param bubble the new selected bubble
*/
private void setSelectedBubbleInternal(@Nullable BubbleViewProvider bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleInternal=%s",
+ (bubble != null ? bubble.getKey() : "null"));
if (Objects.equals(bubble, mSelectedBubble)) {
return;
}
@@ -969,12 +937,10 @@ public class BubbleData {
* @param shouldExpand the new requested state
*/
private void setExpandedInternal(boolean shouldExpand) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand);
- }
if (mExpanded == shouldExpand) {
return;
}
+ ProtoLog.d(WM_SHELL_BUBBLES, "setExpandedInternal=%b", shouldExpand);
if (shouldExpand) {
if (mBubbles.isEmpty() && !mShowingOverflow) {
Log.e(TAG, "Attempt to expand stack when empty!");
@@ -1026,9 +992,6 @@ public class BubbleData {
* @return true if the position of any bubbles changed as a result
*/
private boolean repackAll() {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "repackAll()");
- }
if (mBubbles.isEmpty()) {
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index f56b1712c5c1..f1a68e246fe1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -37,17 +37,7 @@ public class BubbleDebugConfig {
// Default log tag for the Bubbles package.
public static final String TAG_BUBBLES = "Bubbles";
-
- static final boolean DEBUG_BUBBLE_CONTROLLER = false;
- static final boolean DEBUG_BUBBLE_DATA = false;
- static final boolean DEBUG_BUBBLE_STACK_VIEW = false;
- static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
- static final boolean DEBUG_EXPERIMENTS = true;
- static final boolean DEBUG_OVERFLOW = false;
public static final boolean DEBUG_USER_EDUCATION = false;
- static final boolean DEBUG_POSITIONER = false;
- public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
- public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
private static final boolean FORCE_SHOW_USER_EDUCATION = false;
private static final String FORCE_SHOW_USER_EDUCATION_SETTING =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index efc4d8b95f4f..088660e4e8dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -23,10 +23,10 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -67,6 +67,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.AlphaOptimizedButton;
import com.android.wm.shell.common.TriangleShape;
@@ -199,13 +200,9 @@ public class BubbleExpandedView extends LinearLayout {
@Override
public void onInitialized() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: destroyed=" + mDestroyed
- + " initialized=" + mInitialized
- + " bubble=" + getBubbleKey());
- }
-
if (mDestroyed || mInitialized) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: destroyed=%b initialized=%b bubble=%s",
+ mDestroyed, mInitialized, getBubbleKey());
return;
}
@@ -216,10 +213,8 @@ public class BubbleExpandedView extends LinearLayout {
// TODO: I notice inconsistencies in lifecycle
// Post to keep the lifecycle normal
post(() -> {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: calling startActivity, bubble="
- + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s",
+ getBubbleKey());
try {
Rect launchBounds = new Rect();
mTaskView.getBoundsOnScreen(launchBounds);
@@ -279,10 +274,8 @@ public class BubbleExpandedView extends LinearLayout {
@Override
public void onTaskCreated(int taskId, ComponentName name) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskCreated: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskCreated: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
// The taskId is saved to use for removeTask, preventing appearance in recent tasks.
mTaskId = taskId;
@@ -298,15 +291,15 @@ public class BubbleExpandedView extends LinearLayout {
@Override
public void onTaskVisibilityChanged(int taskId, boolean visible) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskVisibilityChanged=%b bubble=%s taskId=%d",
+ visible, getBubbleKey(), taskId);
setContentVisibility(visible);
}
@Override
public void onTaskRemovalStarted(int taskId) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
if (mBubble != null) {
mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
}
@@ -644,9 +637,6 @@ public class BubbleExpandedView extends LinearLayout {
super.onDetachedFromWindow();
mImeVisible = false;
mNeedsNewHeight = false;
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
- }
}
/**
@@ -805,10 +795,6 @@ public class BubbleExpandedView extends LinearLayout {
* and setting {@code false} actually means rendering the contents in transparent.
*/
public void setContentVisibility(boolean visibility) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "setContentVisibility: visibility=" + visibility
- + " bubble=" + getBubbleKey());
- }
mIsContentVisible = visibility;
if (mTaskView != null && !mIsAnimating) {
mTaskView.setAlpha(visibility ? 1f : 0f);
@@ -867,9 +853,6 @@ public class BubbleExpandedView extends LinearLayout {
* Sets the bubble used to populate this view.
*/
void update(Bubble bubble) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "update: bubble=" + bubble);
- }
if (mStackView == null) {
Log.w(TAG, "Stack is null for bubble: " + bubble);
return;
@@ -958,11 +941,6 @@ public class BubbleExpandedView extends LinearLayout {
}
mNeedsNewHeight = false;
}
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()
- + " height=" + height
- + " mNeedsNewHeight=" + mNeedsNewHeight);
- }
}
}
@@ -974,10 +952,6 @@ public class BubbleExpandedView extends LinearLayout {
* waiting for layout.
*/
public void updateView(int[] containerLocationOnScreen) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateView: bubble="
- + getBubbleKey());
- }
mExpandedViewContainerLocation = containerLocationOnScreen;
updateHeight();
if (mTaskView != null
@@ -1103,9 +1077,6 @@ public class BubbleExpandedView extends LinearLayout {
/** Hide the task view. */
public void cleanUpExpandedState() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
- }
if (mTaskView != null) {
mTaskView.setVisibility(GONE);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index 9655470ce914..70cdc82c47ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -16,9 +16,9 @@
package com.android.wm.shell.bubbles;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_OVERFLOW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.content.Context;
@@ -28,7 +28,6 @@ import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -43,6 +42,7 @@ import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ContrastColorUtil;
import com.android.wm.shell.R;
@@ -245,9 +245,6 @@ public class BubbleOverflowContainerView extends LinearLayout {
Bubble toRemove = update.removedOverflowBubble;
if (toRemove != null) {
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, "remove: " + toRemove);
- }
toRemove.cleanupViews();
final int indexToRemove = mOverflowBubbles.indexOf(toRemove);
mOverflowBubbles.remove(toRemove);
@@ -257,9 +254,6 @@ public class BubbleOverflowContainerView extends LinearLayout {
Bubble toAdd = update.addedOverflowBubble;
if (toAdd != null) {
final int indexToAdd = mOverflowBubbles.indexOf(toAdd);
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, "add: " + toAdd + " prevIndex: " + indexToAdd);
- }
if (indexToAdd > 0) {
mOverflowBubbles.remove(toAdd);
mOverflowBubbles.add(0, toAdd);
@@ -272,10 +266,9 @@ public class BubbleOverflowContainerView extends LinearLayout {
updateEmptyStateVisibility();
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(
- mController.getOverflowBubbles(), null));
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "Apply overflow update, added=%s removed=%s",
+ (toAdd != null ? toAdd.getKey() : "null"),
+ (toRemove != null ? toRemove.getKey() : "null"));
}
};
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index d62c86ce386f..c03b6f805cf7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -16,18 +16,20 @@
package com.android.wm.shell.bubbles;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
+
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R;
@@ -36,9 +38,6 @@ import com.android.wm.shell.R;
* placement and positioning calculations to refer to.
*/
public class BubblePositioner {
- private static final String TAG = BubbleDebugConfig.TAG_WITH_CLASS_NAME
- ? "BubblePositioner"
- : BubbleDebugConfig.TAG_BUBBLES;
/** The screen edge the bubble stack is pinned to */
public enum StackPinnedEdge {
@@ -110,16 +109,12 @@ public class BubblePositioner {
*/
public void update(DeviceConfig deviceConfig) {
mDeviceConfig = deviceConfig;
-
- if (BubbleDebugConfig.DEBUG_POSITIONER) {
- Log.w(TAG, "update positioner:"
- + " rotation: " + mRotation
- + " insets: " + deviceConfig.getInsets()
- + " isLargeScreen: " + deviceConfig.isLargeScreen()
- + " isSmallTablet: " + deviceConfig.isSmallTablet()
- + " showingInBubbleBar: " + mShowingInBubbleBar
- + " bounds: " + deviceConfig.getWindowBounds());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "update positioner: "
+ + "rotation=%d insets=%s largeScreen=%b "
+ + "smallTablet=%b isBubbleBar=%b bounds=%s",
+ mRotation, deviceConfig.getInsets(), deviceConfig.isLargeScreen(),
+ deviceConfig.isSmallTablet(), mShowingInBubbleBar,
+ deviceConfig.getWindowBounds());
updateInternal(mRotation, deviceConfig.getInsets(), deviceConfig.getWindowBounds());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 9facef36a74e..c877d4a2a019 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -21,7 +21,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
@@ -1310,13 +1309,9 @@ public class BubbleStackView extends FrameLayout
final boolean seen = getPrefBoolean(ManageEducationView.PREF_MANAGED_EDUCATION);
final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
&& mExpandedBubble != null && mExpandedBubble.getExpandedView() != null;
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Show manage edu: " + shouldShow);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "Show manage edu=%b", shouldShow);
if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) {
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Want to show manage edu, but it is forced hidden");
- }
+ Log.w(TAG, "Want to show manage edu, but it is forced hidden");
return false;
}
return shouldShow;
@@ -1360,13 +1355,9 @@ public class BubbleStackView extends FrameLayout
}
final boolean seen = getPrefBoolean(StackEducationView.PREF_STACK_EDUCATION);
final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext);
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Show stack edu: " + shouldShow);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "Show stack edu=%b", shouldShow);
if (shouldShow && BubbleDebugConfig.neverShowUserEducation(mContext)) {
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Want to show stack edu, but it is forced hidden");
- }
+ Log.w(TAG, "Want to show stack edu, but it is forced hidden");
return false;
}
return shouldShow;
@@ -1513,7 +1504,7 @@ public class BubbleStackView extends FrameLayout
mBubbleSize = mPositioner.getBubbleSize();
for (Bubble b : mBubbleData.getBubbles()) {
if (b.getIconView() == null) {
- Log.d(TAG, "Display size changed. Icon null: " + b);
+ Log.w(TAG, "Display size changed. Icon null: " + b);
continue;
}
b.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize));
@@ -1819,10 +1810,6 @@ public class BubbleStackView extends FrameLayout
// via BubbleData.Listener
@SuppressLint("ClickableViewAccessibility")
void addBubble(Bubble bubble) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "addBubble: " + bubble);
- }
-
final boolean firstBubble = getBubbleCount() == 0;
if (firstBubble && shouldShowStackEdu()) {
@@ -1868,9 +1855,6 @@ public class BubbleStackView extends FrameLayout
// via BubbleData.Listener
void removeBubble(Bubble bubble) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "removeBubble: " + bubble);
- }
if (isExpanded() && getBubbleCount() == 1) {
mRemovingLastBubbleWhileExpanded = true;
// We're expanded while the last bubble is being removed. Let the scrim animate away
@@ -1917,7 +1901,7 @@ public class BubbleStackView extends FrameLayout
bubble.cleanupViews();
logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
} else {
- Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
+ Log.w(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
}
}
@@ -1985,10 +1969,6 @@ public class BubbleStackView extends FrameLayout
*/
// via BubbleData.Listener
public void setSelectedBubble(@Nullable BubbleViewProvider bubbleToSelect) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
- }
-
if (bubbleToSelect == null) {
mBubbleData.setShowingOverflow(false);
return;
@@ -2081,10 +2061,6 @@ public class BubbleStackView extends FrameLayout
*/
// via BubbleData.Listener
public void setExpanded(boolean shouldExpand) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "setExpanded: " + shouldExpand);
- }
-
if (!shouldExpand) {
// If we're collapsing, release the animating-out surface immediately since we have no
// need for it, and this ensures it cannot remain visible as we collapse.
@@ -2126,7 +2102,6 @@ public class BubbleStackView extends FrameLayout
* Monitor for swipe up gesture that is used to collapse expanded view
*/
void startMonitoringSwipeUpGesture() {
- ProtoLog.d(WM_SHELL_BUBBLES, "startMonitoringSwipeUpGesture");
stopMonitoringSwipeUpGestureInternal();
if (isGestureNavEnabled()) {
@@ -2174,7 +2149,6 @@ public class BubbleStackView extends FrameLayout
* Stop monitoring for swipe up gesture
*/
void stopMonitoringSwipeUpGesture() {
- ProtoLog.d(WM_SHELL_BUBBLES, "stopMonitoringSwipeUpGesture");
stopMonitoringSwipeUpGestureInternal();
}
@@ -2202,9 +2176,6 @@ public class BubbleStackView extends FrameLayout
}
void setBubbleSuppressed(Bubble bubble, boolean suppressed) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "setBubbleSuppressed: suppressed=" + suppressed + " bubble=" + bubble);
- }
if (suppressed) {
int index = getBubbleIndex(bubble);
mBubbleContainer.removeViewAt(index);
@@ -2339,6 +2310,8 @@ public class BubbleStackView extends FrameLayout
}
private void animateExpansion() {
+ ProtoLog.d(WM_SHELL_BUBBLES, "animateExpansion, expandedBubble=%s",
+ mExpandedBubble != null ? mExpandedBubble.getKey() : "null");
cancelDelayedExpandCollapseSwitchAnimations();
final boolean showVertically = mPositioner.showBubblesVertically();
mIsExpanded = true;
@@ -2465,7 +2438,7 @@ public class BubbleStackView extends FrameLayout
private void animateCollapse() {
cancelDelayedExpandCollapseSwitchAnimations();
-
+ ProtoLog.d(WM_SHELL_BUBBLES, "animateCollapse");
if (isManageEduVisible()) {
mManageEduView.hide();
}
@@ -2508,11 +2481,6 @@ public class BubbleStackView extends FrameLayout
mManageEduView.hide();
}
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "animateCollapse");
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
- mExpandedBubble));
- }
updateZOrder();
updateBadges(true /* setBadgeForCollapsedStack */);
afterExpandedViewAnimation();
@@ -2894,7 +2862,7 @@ public class BubbleStackView extends FrameLayout
if (!shouldShowFlyout(bubble)) {
return;
}
-
+ ProtoLog.d(WM_SHELL_BUBBLES, "animateFlyout=%s", bubble.getKey());
mFlyoutDragDeltaX = 0f;
clearFlyoutOnHide();
mAfterFlyoutHidden = () -> {
@@ -3036,6 +3004,9 @@ public class BubbleStackView extends FrameLayout
@VisibleForTesting
public void showManageMenu(boolean show) {
if ((mManageMenu.getVisibility() == VISIBLE) == show) return;
+ ProtoLog.d(WM_SHELL_BUBBLES, "showManageMenu=%b for bubble=%s",
+ show, (mExpandedBubble != null ? mExpandedBubble.getKey() : "null"));
+
mShowingManage = show;
// This should not happen, since the manage menu is only visible when there's an expanded
@@ -3167,10 +3138,6 @@ public class BubbleStackView extends FrameLayout
}
private void updateExpandedBubble() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "updateExpandedBubble()");
- }
-
mExpandedViewContainer.removeAllViews();
if (mIsExpanded && mExpandedBubble != null
&& mExpandedBubble.getExpandedView() != null) {
@@ -3318,9 +3285,6 @@ public class BubbleStackView extends FrameLayout
}
private void updateExpandedView() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "updateExpandedView: mIsExpanded=" + mIsExpanded);
- }
boolean isOverflowExpanded = mExpandedBubble != null
&& BubbleOverflow.KEY.equals(mExpandedBubble.getKey());
int[] paddings = mPositioner.getExpandedViewContainerPadding(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index dc271331c8f9..530ec5a6c9fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -20,7 +20,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -35,6 +35,7 @@ import android.view.ViewGroup;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.taskview.TaskView;
/**
@@ -79,11 +80,8 @@ public class BubbleTaskViewHelper {
@Override
public void onInitialized() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: destroyed=" + mDestroyed
- + " initialized=" + mInitialized
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: destroyed=%b initialized=%b bubble=%s",
+ mDestroyed, mInitialized, getBubbleKey());
if (mDestroyed || mInitialized) {
return;
@@ -99,10 +97,8 @@ public class BubbleTaskViewHelper {
// TODO: I notice inconsistencies in lifecycle
// Post to keep the lifecycle normal
mParentView.post(() -> {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onInitialized: calling startActivity, bubble="
- + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onInitialized: calling startActivity, bubble=%s",
+ getBubbleKey());
try {
options.setTaskAlwaysOnTop(true);
options.setLaunchedFromBubble(true);
@@ -159,10 +155,8 @@ public class BubbleTaskViewHelper {
@Override
public void onTaskCreated(int taskId, ComponentName name) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskCreated: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskCreated: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
// The taskId is saved to use for removeTask, preventing appearance in recent tasks.
mTaskId = taskId;
@@ -178,10 +172,8 @@ public class BubbleTaskViewHelper {
@Override
public void onTaskRemovalStarted(int taskId) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "onTaskRemovalStarted: taskId=%d bubble=%s",
+ taskId, getBubbleKey());
if (mBubble != null) {
mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
index 845dca34b41f..e43609fe8ff0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
@@ -15,14 +15,11 @@
*/
package com.android.wm.shell.bubbles.animation;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_COLLAPSE_ANIMATOR;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_EXPANDED_VIEW_DRAGGING;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubbleExpandedView.BACKGROUND_ALPHA;
import static com.android.wm.shell.bubbles.BubbleExpandedView.BOTTOM_CLIP_PROPERTY;
import static com.android.wm.shell.bubbles.BubbleExpandedView.CONTENT_ALPHA;
import static com.android.wm.shell.bubbles.BubbleExpandedView.MANAGE_BUTTON_ALPHA;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -31,7 +28,6 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
-import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.ViewConfiguration;
@@ -41,6 +37,7 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.bubbles.BubbleExpandedView;
@@ -55,8 +52,6 @@ import java.util.List;
*/
public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimationController {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "ExpandedViewAnimCtrl" : TAG_BUBBLES;
-
private static final float COLLAPSE_THRESHOLD = 0.02f;
private static final int COLLAPSE_DURATION_MS = 250;
@@ -121,9 +116,6 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
@Override
public void setExpandedView(BubbleExpandedView expandedView) {
if (mExpandedView != null) {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "updating expandedView, resetting previous");
- }
if (mCollapseAnimation != null) {
mCollapseAnimation.cancel();
}
@@ -140,17 +132,14 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
if (mExpandedView != null) {
mDraggedAmount = OverScroll.dampedScroll(distance, mExpandedView.getContentHeight());
- if (DEBUG_COLLAPSE_ANIMATOR && DEBUG_EXPANDED_VIEW_DRAGGING) {
- Log.d(TAG, "updateDrag: distance=" + distance + " dragged=" + mDraggedAmount);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "updateDrag: distance=%f dragged=%d", distance, mDraggedAmount);
setCollapsedAmount(mDraggedAmount);
if (!mNotifiedAboutThreshold && isPastCollapseThreshold()) {
mNotifiedAboutThreshold = true;
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "notifying over collapse threshold");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "notifying over collapse threshold");
vibrateIfEnabled();
}
}
@@ -172,45 +161,35 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
if (mSwipeDownVelocity > mMinFlingVelocity) {
// Swipe velocity is positive and over fling velocity.
// This is a swipe down, always reset to expanded state, regardless of dragged amount.
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG,
- "not collapsing expanded view, swipe down velocity: " + mSwipeDownVelocity
- + " minV: " + mMinFlingVelocity);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "not collapsing expanded view, swipe down velocity=%f minV=%d",
+ mSwipeDownVelocity, mMinFlingVelocity);
return false;
}
if (mSwipeUpVelocity > mMinFlingVelocity) {
// Swiping up and over fling velocity, collapse the view.
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG,
- "collapse expanded view, swipe up velocity: " + mSwipeUpVelocity + " minV: "
- + mMinFlingVelocity);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "collapse expanded view, swipe up velocity=%f minV=%d",
+ mSwipeUpVelocity, mMinFlingVelocity);
return true;
}
if (isPastCollapseThreshold()) {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "collapse expanded view, past threshold, dragged: " + mDraggedAmount);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "collapse expanded view, past threshold, dragged=%d", mDraggedAmount);
return true;
}
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "not collapsing expanded view");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "not collapsing expanded view");
return false;
}
@Override
public void animateCollapse(Runnable startStackCollapse, Runnable after) {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG,
- "expandedView animate collapse swipeVel=" + mSwipeUpVelocity + " minFlingVel="
- + mMinFlingVelocity);
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate collapse swipeVel=%f minFlingVel=%d",
+ mSwipeUpVelocity, mMinFlingVelocity);
if (mExpandedView != null) {
// Mark it as animating immediately to avoid updates to the view before animation starts
mExpandedView.setAnimating(true);
@@ -243,9 +222,7 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
@Override
public void animateBackToExpanded() {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "expandedView animate back to expanded");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "expandedView animate back to expanded");
BubbleExpandedView expandedView = mExpandedView;
if (expandedView == null) {
return;
@@ -298,9 +275,7 @@ public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimatio
@Override
public void reset() {
- if (DEBUG_COLLAPSE_ANIMATOR) {
- Log.d(TAG, "reset expandedView collapsed state");
- }
+ ProtoLog.d(WM_SHELL_BUBBLES, "reset expandedView collapsed state");
if (mExpandedView == null) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index e86b62dee86d..6325c686a682 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -390,7 +390,7 @@ public class SplashScreenExitAnimationUtils {
SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
TransactionPool transactionPool, Rect firstWindowFrame,
int mainWindowShiftLength, float roundedCornerRadius) {
- mFromYDelta = fromYDelta - roundedCornerRadius;
+ mFromYDelta = fromYDelta - Math.max(firstWindowFrame.top, roundedCornerRadius);
mToYDelta = toYDelta;
mOccludeHoleView = occludeHoleView;
mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
index 0c25f27854bd..b3e8bd9bd23c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -19,16 +19,15 @@ package com.android.wm.shell.transition.tracing;
import android.internal.perfetto.protos.PerfettoTrace;
import android.os.SystemClock;
import android.os.Trace;
-import android.tracing.perfetto.DataSourceInstance;
import android.tracing.perfetto.DataSourceParams;
import android.tracing.perfetto.InitArguments;
import android.tracing.perfetto.Producer;
-import android.tracing.perfetto.TracingContext;
import android.tracing.transition.TransitionDataSource;
import android.util.proto.ProtoOutputStream;
import com.android.wm.shell.transition.Transitions;
+import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@@ -41,6 +40,7 @@ public class PerfettoTransitionTracer implements TransitionTracer {
mActiveTraces::incrementAndGet,
this::onFlush,
mActiveTraces::decrementAndGet);
+ private final Map<String, Integer> mHandlerMapping = new HashMap<>();
public PerfettoTransitionTracer() {
Producer.init(InitArguments.DEFAULTS);
@@ -69,7 +69,7 @@ public class PerfettoTransitionTracer implements TransitionTracer {
private void doLogDispatched(int transitionId, Transitions.TransitionHandler handler) {
mDataSource.trace(ctx -> {
- final int handlerId = getHandlerId(handler, ctx);
+ final int handlerId = getHandlerId(handler);
final ProtoOutputStream os = ctx.newTracePacket();
final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
@@ -81,17 +81,16 @@ public class PerfettoTransitionTracer implements TransitionTracer {
});
}
- private static int getHandlerId(Transitions.TransitionHandler handler,
- TracingContext<DataSourceInstance, TransitionDataSource.TlsState, Void> ctx) {
- final Map<String, Integer> handlerMapping =
- ctx.getCustomTlsState().handlerMapping;
+ private int getHandlerId(Transitions.TransitionHandler handler) {
final int handlerId;
- if (handlerMapping.containsKey(handler.getClass().getName())) {
- handlerId = handlerMapping.get(handler.getClass().getName());
- } else {
- // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto
- handlerId = handlerMapping.size() + 1;
- handlerMapping.put(handler.getClass().getName(), handlerId);
+ synchronized (mHandlerMapping) {
+ if (mHandlerMapping.containsKey(handler.getClass().getName())) {
+ handlerId = mHandlerMapping.get(handler.getClass().getName());
+ } else {
+ // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto
+ handlerId = mHandlerMapping.size() + 1;
+ mHandlerMapping.put(handler.getClass().getName(), handlerId);
+ }
}
return handlerId;
}
@@ -194,22 +193,14 @@ public class PerfettoTransitionTracer implements TransitionTracer {
}
private void onFlush() {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "onFlush");
- try {
- doOnFlush();
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
- private void doOnFlush() {
mDataSource.trace(ctx -> {
final ProtoOutputStream os = ctx.newTracePacket();
- final Map<String, Integer> handlerMapping = ctx.getCustomTlsState().handlerMapping;
- for (String handler : handlerMapping.keySet()) {
+ for (Map.Entry<String, Integer> entry : mHandlerMapping.entrySet()) {
+ final String handler = entry.getKey();
+ final int handlerId = entry.getValue();
final long token = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
- os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerMapping.get(handler));
+ os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerId);
os.write(PerfettoTrace.ShellHandlerMapping.NAME, handler);
os.end(token);
}
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index c5729444507e..279f9d682b9f 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -484,8 +484,9 @@ int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session,
WorkDuration& workDuration = *static_cast<WorkDuration*>(workDurationPtr);
VALIDATE_INT(workDuration.workPeriodStartTimestampNanos, > 0)
VALIDATE_INT(workDuration.actualTotalDurationNanos, > 0)
- VALIDATE_INT(workDuration.actualCpuDurationNanos, > 0)
+ VALIDATE_INT(workDuration.actualCpuDurationNanos, >= 0)
VALIDATE_INT(workDuration.actualGpuDurationNanos, >= 0)
+ VALIDATE_INT(workDuration.actualGpuDurationNanos + workDuration.actualCpuDurationNanos, > 0)
return session->reportActualWorkDuration(workDurationPtr);
}
@@ -517,7 +518,7 @@ void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* aWorkDuration,
void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration,
int64_t actualCpuDurationNanos) {
VALIDATE_PTR(aWorkDuration)
- WARN_INT(actualCpuDurationNanos, > 0)
+ WARN_INT(actualCpuDurationNanos, >= 0)
static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos;
}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 9f94ef924eae..28cf250c40be 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -73,6 +73,7 @@ package android.nfc {
method @FlaggedApi("android.nfc.enable_nfc_charging") @Nullable public android.nfc.WlcListenerDeviceInfo getWlcListenerDeviceInfo();
method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler);
method public boolean isEnabled();
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeEnabled();
method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported();
method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled();
method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported();
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 293e5d19e77c..c444740a5b1b 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -88,6 +88,7 @@ interface INfcAdapter
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
boolean enableReaderOption(boolean enable);
boolean isObserveModeSupported();
+ boolean isObserveModeEnabled();
boolean setObserveMode(boolean enabled);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 3d749801cb0a..782af5f3fe19 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1208,6 +1208,22 @@ public final class NfcAdapter {
}
/**
+ * Returns whether Observe Mode is currently enabled or not.
+ *
+ * @return true if observe mode is enabled, false otherwise.
+ */
+
+ @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+ public boolean isObserveModeEnabled() {
+ try {
+ return sService.isObserveModeEnabled();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ return false;
+ }
+ }
+
+ /**
* Controls whether the NFC adapter will allow transactions to proceed or be in observe mode
* and simply observe and notify the APDU service of polling loop frames. See
* {@link #isObserveModeSupported()} for a description of observe mode.
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index 426c5aac487b..3254a394c25a 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -105,6 +105,8 @@ public final class ApduServiceInfo implements Parcelable {
private final ArrayList<String> mPollingLoopFilters;
+ private final Map<String, Boolean> mAutoTransact;
+
/**
* Whether this service should only be started when the device is unlocked.
*/
@@ -173,6 +175,7 @@ public final class ApduServiceInfo implements Parcelable {
this.mStaticAidGroups = new HashMap<String, AidGroup>();
this.mDynamicAidGroups = new HashMap<String, AidGroup>();
this.mPollingLoopFilters = new ArrayList<String>();
+ this.mAutoTransact = new HashMap<String, Boolean>();
this.mOffHostName = offHost;
this.mStaticOffHostName = staticOffHost;
this.mOnHost = onHost;
@@ -287,6 +290,7 @@ public final class ApduServiceInfo implements Parcelable {
mStaticAidGroups = new HashMap<String, AidGroup>();
mDynamicAidGroups = new HashMap<String, AidGroup>();
mPollingLoopFilters = new ArrayList<String>();
+ mAutoTransact = new HashMap<String, Boolean>();
mOnHost = onHost;
final int depth = parser.getDepth();
@@ -377,6 +381,10 @@ public final class ApduServiceInfo implements Parcelable {
a.getString(com.android.internal.R.styleable.PollingLoopFilter_name)
.toUpperCase(Locale.ROOT);
mPollingLoopFilters.add(plf);
+ boolean autoTransact = a.getBoolean(
+ com.android.internal.R.styleable.PollingLoopFilter_autoTransact,
+ false);
+ mAutoTransact.put(plf, autoTransact);
a.recycle();
}
}
@@ -444,6 +452,17 @@ public final class ApduServiceInfo implements Parcelable {
}
/**
+ * Returns whether this service would like to automatically transact for a given plf.
+ *
+ * @param plf the polling loop filter to query.
+ * @return {@code true} indicating to auto transact, {@code false} indicating to not.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public boolean getShouldAutoTransact(@NonNull String plf) {
+ return mAutoTransact.getOrDefault(plf.toUpperCase(Locale.ROOT), false);
+ }
+
+ /**
* Returns a consolidated list of AIDs with prefixes from the AID groups
* registered by this service. Note that if a service has both
* a static (manifest-based) AID group for a category and a dynamic
@@ -630,6 +649,21 @@ public final class ApduServiceInfo implements Parcelable {
}
/**
+ * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will cause the
+ * device to exit observe mode, just as if
+ * {@link android.nfc.NfcAdapter#setTransactionAllowed(boolean)} had been called with true,
+ * allowing transactions to proceed. The matching frame will also be delivered to
+ * {@link HostApduService#processPollingFrames(List)}.
+ *
+ * @param pollingLoopFilter this polling loop filter to add.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void addPollingLoopFilterToAutoTransact(@NonNull String pollingLoopFilter) {
+ mPollingLoopFilters.add(pollingLoopFilter.toUpperCase(Locale.ROOT));
+ mAutoTransact.put(pollingLoopFilter.toUpperCase(Locale.ROOT), true);
+ }
+
+ /**
* Remove a Polling Loop Filter. Custom NFC polling frames that match this filter will no
* longer be delivered to {@link HostApduService#processPollingFrames(List)}.
* @param pollingLoopFilter this polling loop filter to add.
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 09e0d61010f7..bf69d3ba7603 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -45,11 +45,9 @@
<activity android:name=".v2.ui.InstallLaunch"
android:configChanges="orientation|keyboardHidden|screenSize"
- android:theme="@style/Theme.AlertDialogActivity"
android:exported="false"/>
<activity android:name=".InstallStart"
- android:theme="@style/Theme.AlertDialogActivity"
android:exported="true"
android:excludeFromRecents="true">
<intent-filter android:priority="1">
@@ -79,14 +77,12 @@
android:exported="false" />
<activity android:name=".DeleteStagedFileOnResult"
- android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
android:exported="false" />
<activity android:name=".PackageInstallerActivity"
android:exported="false" />
<activity android:name=".InstallInstalling"
- android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
android:exported="false" />
<receiver android:name=".common.InstallEventReceiver"
@@ -98,16 +94,13 @@
</receiver>
<activity android:name=".InstallSuccess"
- android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
android:exported="false" />
<activity android:name=".InstallFailed"
- android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
android:exported="false" />
<activity android:name=".UninstallerActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
- android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
android:excludeFromRecents="true"
android:noHistory="true"
android:exported="true">
@@ -121,7 +114,6 @@
<activity android:name=".v2.ui.UninstallLaunch"
android:configChanges="orientation|keyboardHidden|screenSize"
- android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
android:excludeFromRecents="true"
android:noHistory="true"
android:exported="false">
@@ -144,7 +136,6 @@
</receiver>
<activity android:name=".UninstallUninstalling"
- android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
android:excludeFromRecents="true"
android:exported="false" />
@@ -171,7 +162,6 @@
<activity android:name=".UnarchiveActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
- android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
android:excludeFromRecents="true"
android:noHistory="true"
android:exported="true">
@@ -183,7 +173,6 @@
<activity android:name=".UnarchiveErrorActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
- android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
android:excludeFromRecents="true"
android:noHistory="true"
android:exported="true">
diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml
index 9a062295608d..f5af510111cd 100644
--- a/packages/PackageInstaller/res/values/themes.xml
+++ b/packages/PackageInstaller/res/values/themes.xml
@@ -17,19 +17,12 @@
<resources>
- <style name="Theme.AlertDialogActivity.NoAnimation"
- parent="@style/Theme.AlertDialogActivity.NoActionBar">
- <item name="android:windowAnimationStyle">@null</item>
- </style>
-
<style name="Theme.AlertDialogActivity"
parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
<item name="alertDialogStyle">@style/AlertDialog</item>
- </style>
-
- <style name="Theme.AlertDialogActivity.NoActionBar">
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
+ <item name="android:windowAnimationStyle">@null</item>
</style>
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 1e146a51e21e..e4a762ae8118 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1113,6 +1113,10 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
GlobalSettingsProto.Notification.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS);
+ dumpSetting(s, p,
+ Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS,
+ GlobalSettingsProto.Notification
+ .DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS);
p.end(notificationToken);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 16de478d99c1..b58187d8e95e 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -185,6 +185,7 @@ public class SettingsBackupTest {
Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH,
Settings.Global.DEVICE_DEMO_MODE,
Settings.Global.DEVICE_IDLE_CONSTANTS,
+ Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS,
Settings.Global.DISABLE_WINDOW_BLURS,
Settings.Global.BATTERY_SAVER_CONSTANTS,
Settings.Global.BATTERY_TIP_CONSTANTS,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0050676ace84..f877d7a89b92 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -475,6 +475,9 @@
<service android:name=".screenrecord.RecordingService"
android:foregroundServiceType="systemExempted"/>
+ <service android:name=".recordissue.IssueRecordingService"
+ android:foregroundServiceType="systemExempted"/>
+
<receiver android:name=".SysuiRestartReceiver"
android:exported="false">
<intent-filter>
@@ -992,7 +995,6 @@
android:theme="@style/Theme.EditWidgetsActivity"
android:excludeFromRecents="true"
android:autoRemoveFromRecents="true"
- android:showOnLockScreen="true"
android:launchMode="singleTop"
android:exported="false">
</activity>
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 6e4a4ea70959..2ad7192a3248 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -168,6 +168,9 @@ flag {
"Note that, even after this callback is called, we're waiting for all windows to finish "
" drawing."
bug: "295873557"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/painter/DrawablePainter.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/painter/DrawablePainter.kt
new file mode 100644
index 000000000000..846abf7e30bd
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/painter/DrawablePainter.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2022 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.compose.ui.graphics.painter
+
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.RememberObserver
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.asAndroidColorFilter
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.withSave
+import androidx.compose.ui.unit.LayoutDirection
+import kotlin.math.roundToInt
+
+/**
+ * *************************************************************************************************
+ * This file was forked from
+ * https://github.com/google/accompanist/blob/main/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt
+ */
+private val MAIN_HANDLER by lazy(LazyThreadSafetyMode.NONE) { Handler(Looper.getMainLooper()) }
+
+/**
+ * A [Painter] which draws an Android [Drawable] and supports [Animatable] drawables. Instances
+ * should be remembered to be able to start and stop [Animatable] animations.
+ *
+ * Instances are usually retrieved from [rememberDrawablePainter].
+ */
+public class DrawablePainter(public val drawable: Drawable) : Painter(), RememberObserver {
+ private var drawInvalidateTick by mutableStateOf(0)
+ private var drawableIntrinsicSize by mutableStateOf(drawable.intrinsicSize)
+
+ private val callback: Drawable.Callback by lazy {
+ object : Drawable.Callback {
+ override fun invalidateDrawable(d: Drawable) {
+ // Update the tick so that we get re-drawn
+ drawInvalidateTick++
+ // Update our intrinsic size too
+ drawableIntrinsicSize = drawable.intrinsicSize
+ }
+
+ override fun scheduleDrawable(d: Drawable, what: Runnable, time: Long) {
+ MAIN_HANDLER.postAtTime(what, time)
+ }
+
+ override fun unscheduleDrawable(d: Drawable, what: Runnable) {
+ MAIN_HANDLER.removeCallbacks(what)
+ }
+ }
+ }
+
+ init {
+ if (drawable.intrinsicWidth >= 0 && drawable.intrinsicHeight >= 0) {
+ // Update the drawable's bounds to match the intrinsic size
+ drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
+ }
+ }
+
+ override fun onRemembered() {
+ drawable.callback = callback
+ drawable.setVisible(true, true)
+ if (drawable is Animatable) drawable.start()
+ }
+
+ override fun onAbandoned(): Unit = onForgotten()
+
+ override fun onForgotten() {
+ if (drawable is Animatable) drawable.stop()
+ drawable.setVisible(false, false)
+ drawable.callback = null
+ }
+
+ override fun applyAlpha(alpha: Float): Boolean {
+ drawable.alpha = (alpha * 255).roundToInt().coerceIn(0, 255)
+ return true
+ }
+
+ override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
+ drawable.colorFilter = colorFilter?.asAndroidColorFilter()
+ return true
+ }
+
+ override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
+ if (Build.VERSION.SDK_INT >= 23) {
+ return drawable.setLayoutDirection(
+ when (layoutDirection) {
+ LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
+ LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
+ }
+ )
+ }
+ return false
+ }
+
+ override val intrinsicSize: Size
+ get() = drawableIntrinsicSize
+
+ override fun DrawScope.onDraw() {
+ drawIntoCanvas { canvas ->
+ // Reading this ensures that we invalidate when invalidateDrawable() is called
+ drawInvalidateTick
+
+ // Update the Drawable's bounds
+ drawable.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt())
+
+ canvas.withSave { drawable.draw(canvas.nativeCanvas) }
+ }
+ }
+}
+
+/**
+ * Remembers [Drawable] wrapped up as a [Painter]. This function attempts to un-wrap the drawable
+ * contents and use Compose primitives where possible.
+ *
+ * If the provided [drawable] is `null`, an empty no-op painter is returned.
+ *
+ * This function tries to dispatch lifecycle events to [drawable] as much as possible from within
+ * Compose.
+ *
+ * @sample com.google.accompanist.sample.drawablepainter.BasicSample
+ */
+@Composable
+public fun rememberDrawablePainter(drawable: Drawable?): Painter =
+ remember(drawable) {
+ when (drawable) {
+ null -> EmptyPainter
+ is ColorDrawable -> ColorPainter(Color(drawable.color))
+ // Since the DrawablePainter will be remembered and it implements RememberObserver, it
+ // will receive the necessary events
+ else -> DrawablePainter(drawable.mutate())
+ }
+ }
+
+private val Drawable.intrinsicSize: Size
+ get() =
+ when {
+ // Only return a finite size if the drawable has an intrinsic size
+ intrinsicWidth >= 0 && intrinsicHeight >= 0 -> {
+ Size(width = intrinsicWidth.toFloat(), height = intrinsicHeight.toFloat())
+ }
+ else -> Size.Unspecified
+ }
+
+internal object EmptyPainter : Painter() {
+ override val intrinsicSize: Size
+ get() = Size.Unspecified
+ override fun DrawScope.onDraw() {}
+}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 4cc7332aa8a9..51d2a03342b7 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -30,12 +30,13 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner
import com.android.compose.theme.PlatformTheme
import com.android.compose.ui.platform.DensityAwareComposeView
+import com.android.internal.policy.ScreenDecorationsUtils
import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.composable.BouncerContent
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
-import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
+import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@@ -129,8 +130,9 @@ object ComposeFacade : BaseComposeFacade {
return ComposeView(context).apply {
setContent {
PlatformTheme {
- DisplayCutoutProvider(
- displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets)
+ ScreenDecorProvider(
+ displayCutout = displayCutoutFromWindowInsets(scope, context, windowInsets),
+ screenCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
) {
SceneContainer(
viewModel = viewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutoutProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
index ed393c0a9cb5..76bd4ec2778a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutoutProvider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt
@@ -21,17 +21,28 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
import kotlinx.coroutines.flow.StateFlow
/** The bounds and [CutoutLocation] of the current display. */
val LocalDisplayCutout = staticCompositionLocalOf { DisplayCutout() }
+/** The corner radius in px of the current display. */
+val LocalScreenCornerRadius = staticCompositionLocalOf { 0.dp }
+
@Composable
-fun DisplayCutoutProvider(
+fun ScreenDecorProvider(
displayCutout: StateFlow<DisplayCutout>,
+ screenCornerRadius: Float,
content: @Composable () -> Unit,
) {
val cutout by displayCutout.collectAsState()
-
- CompositionLocalProvider(LocalDisplayCutout provides cutout) { content() }
+ val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() }
+ CompositionLocalProvider(
+ LocalScreenCornerRadius provides screenCornerRadiusDp,
+ LocalDisplayCutout provides cutout
+ ) {
+ content()
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 0e08a198c71e..3fb825471c57 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -51,11 +51,13 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
@@ -63,10 +65,15 @@ import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.NestedScrollBehavior
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.height
+import com.android.compose.ui.util.lerp
+import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.notifications.ui.composable.Notifications.Form
+import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS
+import com.android.systemui.notifications.ui.composable.Notifications.TransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
import com.android.systemui.scene.ui.composable.Gone
import com.android.systemui.scene.ui.composable.Shade
import com.android.systemui.shade.ui.composable.ShadeHeader
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder.SCRIM_CORNER_RADIUS
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import kotlin.math.roundToInt
@@ -77,6 +84,13 @@ object Notifications {
val ShelfSpace = ElementKey("ShelfSpace")
}
+ // Expansion fraction thresholds (between 0-1f) at which the corresponding value should be
+ // at its maximum, given they are at their minimum value at expansion = 0f.
+ object TransitionThresholds {
+ const val EXPANSION_FOR_MAX_CORNER_RADIUS = 0.1f
+ const val EXPANSION_FOR_MAX_SCRIM_ALPHA = 0.3f
+ }
+
enum class Form {
HunFromTop,
Stack,
@@ -125,19 +139,19 @@ fun SceneScope.NotificationScrollingStack(
modifier: Modifier = Modifier,
) {
val density = LocalDensity.current
- val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
+ val screenCornerRadius = LocalScreenCornerRadius.current
val expansionFraction by viewModel.expandFraction.collectAsState(0f)
val navBarHeight =
with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() }
- val statusBarHeight =
- with(density) { WindowInsets.systemBars.asPaddingValues().calculateTopPadding().toPx() }
- val displayCutoutHeight =
- with(density) { WindowInsets.displayCutout.asPaddingValues().calculateTopPadding().toPx() }
+ val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
+ val displayCutoutHeight = WindowInsets.displayCutout.asPaddingValues().calculateTopPadding()
val screenHeight =
- with(density) { LocalConfiguration.current.screenHeightDp.dp.toPx() } +
- navBarHeight +
- maxOf(statusBarHeight, displayCutoutHeight)
+ with(density) {
+ (LocalConfiguration.current.screenHeightDp.dp +
+ maxOf(statusBarHeight, displayCutoutHeight))
+ .toPx()
+ } + navBarHeight
val contentHeight = viewModel.intrinsicContentHeight.collectAsState()
@@ -171,26 +185,53 @@ fun SceneScope.NotificationScrollingStack(
.collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f }
}
- Box(modifier = modifier.element(Notifications.Elements.NotificationScrim)) {
+ Box(
+ modifier =
+ modifier
+ .element(Notifications.Elements.NotificationScrim)
+ .offset {
+ // if scrim is expanded while transitioning to Gone scene, increase the offset
+ // in step with the transition so that it is 0 when it completes.
+ if (
+ scrimOffset.value < 0 &&
+ layoutState.isTransitioning(from = Shade, to = Gone)
+ ) {
+ IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
+ } else {
+ IntOffset(x = 0, y = scrimOffset.value.roundToInt())
+ }
+ }
+ .graphicsLayer {
+ shape =
+ calculateCornerRadius(
+ screenCornerRadius,
+ { expansionFraction },
+ layoutState.isTransitioningBetween(Gone, Shade)
+ )
+ .let {
+ RoundedCornerShape(
+ topStart = it,
+ topEnd = it,
+ )
+ }
+ clip = true
+ }
+ ) {
+ // Creates a cutout in the background scrim in the shape of the notifications scrim.
+ // Only visible when notif scrim alpha < 1, during shade expansion.
Spacer(
modifier =
- Modifier.fillMaxSize()
- .graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
- }
- .drawBehind { drawRect(Color.Black, blendMode = BlendMode.DstOut) }
+ Modifier.fillMaxSize().drawBehind {
+ drawRect(Color.Black, blendMode = BlendMode.DstOut)
+ }
)
Box(
modifier =
Modifier.fillMaxSize()
- .offset { IntOffset(0, scrimOffset.value.roundToInt()) }
.graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
alpha =
if (layoutState.isTransitioningBetween(Gone, Shade)) {
- (expansionFraction / 0.3f).coerceAtMost(1f)
+ (expansionFraction / EXPANSION_FOR_MAX_SCRIM_ALPHA).coerceAtMost(1f)
} else 1f
}
.background(MaterialTheme.colorScheme.surface)
@@ -278,10 +319,10 @@ private fun SceneScope.NotificationPlaceholder(
.onSizeChanged { size: IntSize ->
debugLog(viewModel) { "STACK onSizeChanged: size=$size" }
}
- .onPlaced { coordinates: LayoutCoordinates ->
+ .onGloballyPositioned { coordinates: LayoutCoordinates ->
viewModel.onContentTopChanged(coordinates.positionInWindow().y)
debugLog(viewModel) {
- "STACK onPlaced:" +
+ "STACK onGloballyPositioned:" +
" size=${coordinates.size}" +
" position=${coordinates.positionInWindow()}" +
" bounds=${coordinates.boundsInWindow()}"
@@ -310,6 +351,23 @@ private fun SceneScope.NotificationPlaceholder(
}
}
+private fun calculateCornerRadius(
+ screenCornerRadius: Dp,
+ expansionFraction: () -> Float,
+ transitioning: Boolean,
+): Dp {
+ return if (transitioning) {
+ lerp(
+ start = screenCornerRadius.value,
+ stop = SCRIM_CORNER_RADIUS,
+ fraction = (expansionFraction() / EXPANSION_FOR_MAX_CORNER_RADIUS).coerceAtMost(1f),
+ )
+ .dp
+ } else {
+ SCRIM_CORNER_RADIUS.dp
+ }
+}
+
private inline fun debugLog(
viewModel: NotificationsPlaceholderViewModel,
msg: () -> Any,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 5531f9cc5589..969dec31971c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -36,7 +36,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
@@ -47,11 +46,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.boundsInWindow
-import androidx.compose.ui.layout.onPlaced
-import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
@@ -62,7 +56,6 @@ import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.SceneKey
@@ -122,8 +115,6 @@ private fun SceneScope.QuickSettingsScene(
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
- val cornerRadius by viewModel.notifications.cornerRadiusDp.collectAsState()
-
// TODO(b/280887232): implement the real UI.
Box(modifier = modifier.fillMaxSize()) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
@@ -155,9 +146,9 @@ private fun SceneScope.QuickSettingsScene(
// a background that extends to the edges.
Spacer(
modifier =
- Modifier.element(Shade.Elements.ScrimBackground)
+ Modifier.element(Shade.Elements.BackgroundScrim)
.fillMaxSize()
- .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
+ .background(MaterialTheme.colorScheme.scrim)
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@@ -242,32 +233,5 @@ private fun SceneScope.QuickSettingsScene(
}
}
}
- // Scrim with height 0 aligned to bottom of the screen to facilitate shared element
- // transition from Shade scene.
- Box(
- modifier =
- Modifier.element(Notifications.Elements.NotificationScrim)
- .fillMaxWidth()
- .height(0.dp)
- .graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
- alpha = 1f
- }
- .background(MaterialTheme.colorScheme.surface)
- .align(Alignment.BottomCenter)
- .onPlaced { coordinates: LayoutCoordinates ->
- viewModel.notifications.onContentTopChanged(
- coordinates.positionInWindow().y
- )
- val boundsInWindow = coordinates.boundsInWindow()
- viewModel.notifications.onBoundsChanged(
- left = boundsInWindow.left,
- top = boundsInWindow.top,
- right = boundsInWindow.right,
- bottom = boundsInWindow.bottom,
- )
- }
- )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 770d654a4c88..736ee1f235c6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -16,18 +16,18 @@
package com.android.systemui.scene.ui.composable
-import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.shade.ui.composable.Shade
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -63,6 +63,6 @@ constructor(
override fun SceneScope.Content(
modifier: Modifier,
) {
- Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
+ Spacer(modifier.fillMaxSize())
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 0c2c5195becc..1223ace6957b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -3,6 +3,7 @@ package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.shade.ui.composable.ShadeHeader
@@ -10,5 +11,6 @@ fun TransitionBuilder.goneToShadeTransition() {
spec = tween(durationMillis = 500)
fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContent) }
- translate(QuickSettings.Elements.Content, Edge.Top, true)
+ translate(QuickSettings.Elements.Content, y = -ShadeHeader.Dimensions.CollapsedHeight * .66f)
+ translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
index ebc343dc6d76..2d5cf5c752bf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -10,9 +10,8 @@ import com.android.systemui.shade.ui.composable.Shade
fun TransitionBuilder.lockscreenToShadeTransition() {
spec = tween(durationMillis = 500)
- translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
fractionRange(end = 0.5f) {
- fade(Shade.Elements.ScrimBackground)
+ fade(Shade.Elements.BackgroundScrim)
translate(
QuickSettings.Elements.CollapsedGrid,
Edge.Top,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 497fe873e87d..677df7ead2da 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -38,6 +38,7 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
@@ -74,8 +75,8 @@ object Shade {
object Elements {
val QuickSettings = ElementKey("ShadeQuickSettings")
val MediaCarousel = ElementKey("ShadeMediaCarousel")
- val Scrim = ElementKey("ShadeScrim")
- val ScrimBackground = ElementKey("ShadeScrimBackground")
+ val BackgroundScrim =
+ ElementKey("ShadeBackgroundScrim", scenePicker = LowestZIndexScenePicker)
}
object Dimensions {
@@ -162,7 +163,9 @@ private fun SceneScope.ShadeScene(
Box(
modifier =
- modifier.element(Shade.Elements.Scrim).background(MaterialTheme.colorScheme.scrim),
+ modifier
+ .element(Shade.Elements.BackgroundScrim)
+ .background(MaterialTheme.colorScheme.scrim),
)
Box {
Layout(
@@ -236,17 +239,7 @@ private fun SceneScope.ShadeScene(
check(measurables[1].size == 1)
val quickSettingsPlaceable = measurables[0][0].measure(constraints)
-
- val notificationsMeasurable = measurables[1][0]
- val notificationsScrimMaxHeight =
- constraints.maxHeight - ShadeHeader.Dimensions.CollapsedHeight.roundToPx()
- val notificationsPlaceable =
- notificationsMeasurable.measure(
- constraints.copy(
- minHeight = notificationsScrimMaxHeight,
- maxHeight = notificationsScrimMaxHeight
- )
- )
+ val notificationsPlaceable = measurables[1][0].measure(constraints)
maxNotifScrimTop.value = quickSettingsPlaceable.height.toFloat()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 84b2c4bfed34..53b262bd29a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -21,7 +21,11 @@ import android.content.res.Configuration
import android.graphics.Rect
import android.view.Display
import android.view.DisplayCutout
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.CameraProtectionInfo
+import com.android.systemui.SysUICutoutInformation
+import com.android.systemui.SysUICutoutProvider
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -38,30 +42,30 @@ import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
+@RunWith(AndroidJUnit4::class)
@SmallTest
class StatusBarContentInsetsProviderTest : SysuiTestCase() {
- @Mock private lateinit var dc: DisplayCutout
- @Mock private lateinit var contextMock: Context
- @Mock private lateinit var display: Display
- private lateinit var configurationController: ConfigurationController
-
+ private val sysUICutout = mock<SysUICutoutInformation>()
+ private val dc = mock<DisplayCutout>()
+ private val contextMock = mock<Context>()
+ private val display = mock<Display>()
private val configuration = Configuration()
+ private lateinit var configurationController: ConfigurationController
+
@Before
fun setup() {
- MockitoAnnotations.initMocks(this)
- `when`(contextMock.display).thenReturn(display)
+ whenever(sysUICutout.cutout).thenReturn(dc)
+ whenever(contextMock.display).thenReturn(display)
context.ensureTestableResources()
- `when`(contextMock.resources).thenReturn(context.resources)
- `when`(contextMock.resources.configuration).thenReturn(configuration)
- `when`(contextMock.createConfigurationContext(any())).thenAnswer {
+ whenever(contextMock.resources).thenReturn(context.resources)
+ whenever(contextMock.resources.configuration).thenReturn(configuration)
+ whenever(contextMock.createConfigurationContext(any())).thenAnswer {
context.createConfigurationContext(it.arguments[0] as Configuration)
}
configurationController = ConfigurationControllerImpl(contextMock)
@@ -117,7 +121,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -161,7 +165,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
}
@Test
- fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout() {
+ fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout_noCameraProtection() {
// GIVEN a device in portrait mode with width < height and a display cutout in the top-left
val screenBounds = Rect(0, 0, 1080, 2160)
val dcBounds = Rect(0, 0, 100, 100)
@@ -174,7 +178,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val dotWidth = 10
val statusBarContentHeight = 15
- `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN rotations which share a short side should use the greater value between rounded
// corner padding and the display cutout's size
@@ -187,7 +191,224 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
var bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ targetRotation = ROTATION_LANDSCAPE
+ expectedBounds = Rect(dcBounds.height(),
+ 0,
+ screenBounds.height() - minRightPadding,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // THEN the side that does NOT share a short side with the display cutout ignores the
+ // display cutout bounds
+ targetRotation = ROTATION_UPSIDE_DOWN
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.width() - minRightPadding,
+ sbHeightPortrait)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // Phone in portrait, seascape (rot_270) bounds
+ targetRotation = ROTATION_SEASCAPE
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.height() - dcBounds.height() - dotWidth,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+ }
+
+ @Test
+ fun testCalculateInsetsForRotationWithRotatedResources_topLeftCutout_withCameraProtection() {
+ // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+ val screenBounds = Rect(0, 0, 1080, 2160)
+ val dcBounds = Rect(0, 0, 100, 100)
+ val protectionBounds = Rect(10, 10, 110, 110)
+ val minLeftPadding = 20
+ val minRightPadding = 20
+ val sbHeightPortrait = 100
+ val sbHeightLandscape = 60
+ val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
+ val statusBarContentHeight = 15
+
+ val protectionInfo = mock<CameraProtectionInfo> {
+ whenever(this.cutoutBounds).thenReturn(protectionBounds)
+ }
+ whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+ // THEN rotations which share a short side should use the greater value between rounded
+ // corner padding, the display cutout's size, and the camera protections' size.
+ var targetRotation = ROTATION_NONE
+ var expectedBounds = Rect(protectionBounds.right,
+ 0,
+ screenBounds.right - minRightPadding,
+ sbHeightPortrait)
+
+ var bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ targetRotation = ROTATION_LANDSCAPE
+ expectedBounds = Rect(protectionBounds.bottom,
+ 0,
+ screenBounds.height() - minRightPadding,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // THEN the side that does NOT share a short side with the display cutout ignores the
+ // display cutout bounds
+ targetRotation = ROTATION_UPSIDE_DOWN
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.width() - minRightPadding,
+ sbHeightPortrait)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // Phone in portrait, seascape (rot_270) bounds
+ targetRotation = ROTATION_SEASCAPE
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.height() - protectionBounds.bottom - dotWidth,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+ }
+
+ @Test
+ fun testCalculateInsetsForRotationWithRotatedResources_topRightCutout_noCameraProtection() {
+ // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+ val screenBounds = Rect(0, 0, 1000, 2000)
+ val dcBounds = Rect(900, 0, 1000, 100)
+ val minLeftPadding = 20
+ val minRightPadding = 20
+ val sbHeightPortrait = 100
+ val sbHeightLandscape = 60
+ val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
+ val statusBarContentHeight = 15
+
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+ // THEN rotations which share a short side should use the greater value between rounded
+ // corner padding and the display cutout's size
+ var targetRotation = ROTATION_NONE
+ var expectedBounds = Rect(minLeftPadding,
+ 0,
+ dcBounds.left - dotWidth,
+ sbHeightPortrait)
+
+ var bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -208,7 +429,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -231,7 +452,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -253,7 +474,118 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+ }
+
+ @Test
+ fun testCalculateInsetsForRotationWithRotatedResources_topRightCutout_withCameraProtection() {
+ // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+ val screenBounds = Rect(0, 0, 1000, 2000)
+ val dcBounds = Rect(900, 0, 1000, 100)
+ val protectionBounds = Rect(890, 10, 990, 110)
+ val minLeftPadding = 20
+ val minRightPadding = 20
+ val sbHeightPortrait = 100
+ val sbHeightLandscape = 60
+ val currentRotation = ROTATION_NONE
+ val isRtl = false
+ val dotWidth = 10
+ val statusBarContentHeight = 15
+
+ val protectionInfo = mock<CameraProtectionInfo> {
+ whenever(this.cutoutBounds).thenReturn(protectionBounds)
+ }
+ whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+ // THEN rotations which share a short side should use the greater value between rounded
+ // corner padding, the display cutout's size, and the camera protections' size.
+ var targetRotation = ROTATION_NONE
+ var expectedBounds = Rect(minLeftPadding,
+ 0,
+ protectionBounds.left - dotWidth,
+ sbHeightPortrait)
+
+ var bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ targetRotation = ROTATION_LANDSCAPE
+ expectedBounds = Rect(protectionBounds.bottom,
+ 0,
+ screenBounds.height() - minRightPadding,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightLandscape,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // THEN the side that does NOT share a short side with the display cutout ignores the
+ // display cutout bounds
+ targetRotation = ROTATION_UPSIDE_DOWN
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.width() - minRightPadding,
+ sbHeightPortrait)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
+ screenBounds,
+ sbHeightPortrait,
+ minLeftPadding,
+ minRightPadding,
+ isRtl,
+ dotWidth,
+ BOTTOM_ALIGNED_MARGIN_NONE,
+ statusBarContentHeight)
+
+ assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+
+ // Phone in portrait, seascape (rot_270) bounds
+ targetRotation = ROTATION_SEASCAPE
+ expectedBounds = Rect(minLeftPadding,
+ 0,
+ screenBounds.height() - protectionBounds.bottom - dotWidth,
+ sbHeightLandscape)
+
+ bounds = calculateInsetsForRotationWithRotatedResources(
+ currentRotation,
+ targetRotation,
+ sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -273,7 +605,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation = ROTATION_NONE,
targetRotation = ROTATION_NONE,
- displayCutout = dc,
+ sysUICutout = sysUICutout,
maxBounds = Rect(0, 0, 1080, 2160),
statusBarHeight = 100,
minLeft = 0,
@@ -293,7 +625,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation = ROTATION_NONE,
targetRotation = ROTATION_NONE,
- displayCutout = dc,
+ sysUICutout = sysUICutout,
maxBounds = Rect(0, 0, 1080, 2160),
statusBarHeight = 100,
minLeft = 0,
@@ -321,6 +653,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val screenBounds = Rect(0, 0, 1080, 2160)
// cutout centered at the top
val dcBounds = Rect(490, 0, 590, 100)
+ val protectionBounds = Rect(480, 10, 600, 90)
val minLeftPadding = 20
val minRightPadding = 20
val sbHeightPortrait = 100
@@ -330,7 +663,11 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val dotWidth = 10
val statusBarContentHeight = 15
- `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+ val protectionInfo = mock<CameraProtectionInfo> {
+ whenever(this.cutoutBounds).thenReturn(protectionBounds)
+ }
+ whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN only the landscape/seascape rotations should avoid the cutout area because of the
// potential letterboxing
@@ -343,7 +680,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
var bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -364,7 +701,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -385,7 +722,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -406,7 +743,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout = sysUICutout,
screenBounds,
sbHeightLandscape,
minLeftPadding,
@@ -528,7 +865,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val dotWidth = 10
val statusBarContentHeight = 15
- `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+ whenever(dc.boundingRects).thenReturn(listOf(dcBounds))
// THEN left should be set to the display cutout width, and right should use the minRight
val targetRotation = ROTATION_NONE
@@ -540,7 +877,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val bounds = calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- dc,
+ sysUICutout,
screenBounds,
sbHeightPortrait,
minLeftPadding,
@@ -557,7 +894,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
fun testDisplayChanged_returnsUpdatedInsets() {
// GIVEN: get insets on the first display and switch to the second display
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -576,7 +913,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
// GIVEN: get insets on the first display, switch to the second display,
// get insets and switch back
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
val firstDisplayInsetsFirstCall = provider
@@ -602,7 +939,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
configurationController.onConfigurationChanged(configuration)
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
val listener = object : StatusBarContentInsetsChangedListener {
var triggered = false
@@ -624,7 +961,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
fun onDensityOrFontScaleChanged_listenerNotified() {
configuration.densityDpi = 12
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
val listener = object : StatusBarContentInsetsChangedListener {
var triggered = false
@@ -645,7 +982,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
@Test
fun onThemeChanged_listenerNotified() {
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
- mock<DumpManager>(), mock<CommandRegistry>())
+ mock<DumpManager>(), mock<CommandRegistry>(), mock<SysUICutoutProvider>())
val listener = object : StatusBarContentInsetsChangedListener {
var triggered = false
diff --git a/packages/SystemUI/res/color/brightness_slider_track.xml b/packages/SystemUI/res/color/brightness_slider_track.xml
new file mode 100644
index 000000000000..6028769f3789
--- /dev/null
+++ b/packages/SystemUI/res/color/brightness_slider_track.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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:color="@android:color/system_neutral2_500" android:lStar="40" />
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
index 95c7778c0e76..cae9d6b0513e 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml
@@ -24,7 +24,7 @@
<shape>
<size android:height="@dimen/rounded_slider_track_width" />
<corners android:radius="@dimen/rounded_slider_track_corner_radius" />
- <solid android:color="?attr/shadeInactive" />
+ <solid android:color="@color/brightness_slider_track" />
</shape>
</inset>
</item>
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index 6e541a7a5f32..ce09385eaf45 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -49,7 +49,7 @@
android:layout_centerVertical="true"
android:paddingTop="1dp"
android:importantForAccessibility="yes"
- android:tint="#9E9E9E" />
+ android:tint="?android:attr/textColorPrimary"/>
<TextView
android:id="@+id/undo"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7a83070d1806..65c69f78e9d0 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -542,6 +542,9 @@
<string translatable="false" name="config_protectedCameraId"></string>
<!-- Physical ID for the camera of outer display that needs extra protection -->
<string translatable="false" name="config_protectedPhysicalCameraId"></string>
+ <!-- Unique ID of the outer display that contains the camera that needs protection. -->
+ <string translatable="false" name="config_protectedScreenUniqueId"></string>
+
<!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
<string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
@@ -550,6 +553,8 @@
<string translatable="false" name="config_protectedInnerCameraId"></string>
<!-- Physical ID for the camera of inner display that needs extra protection -->
<string translatable="false" name="config_protectedInnerPhysicalCameraId"></string>
+ <!-- Unique ID of the inner display that contains the camera that needs protection. -->
+ <string translatable="false" name="config_protectedInnerScreenUniqueId"></string>
<!-- Comma-separated list of packages to exclude from camera protection e.g.
"com.android.systemui,com.android.xyz" -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 33bdca38074e..51012a4f3a21 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -318,9 +318,6 @@
notification panel collapses -->
<dimen name="shelf_appear_translation">42dp</dimen>
- <!-- Vertical translation of pulsing notification animations -->
- <dimen name="pulsing_notification_appear_translation">10dp</dimen>
-
<!-- The amount the content shifts upwards when transforming into the shelf -->
<dimen name="shelf_transform_content_shift">32dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8971859256e5..f0cbe7af4a89 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -305,6 +305,27 @@
<!-- A toast message shown when the screen recording cannot be started due to a generic error [CHAR LIMIT=NONE] -->
<string name="screenrecord_start_error">Error starting screen recording</string>
+ <!-- Notification title displayed for issue recording [CHAR LIMIT=50]-->
+ <string name="issuerecord_title">Issue Recorder</string>
+ <!-- Processing issue recoding data in the background [CHAR LIMIT=30]-->
+ <string name="issuerecord_background_processing_label">Processing issue recording</string>
+ <!-- Description of the screen recording notification channel [CHAR LIMIT=NONE]-->
+ <string name="issuerecord_channel_description">Ongoing notification for an issue collection session</string>
+
+ <!-- Notification text displayed when we are recording the screen [CHAR LIMIT=100]-->
+ <string name="issuerecord_ongoing_screen_only">Recording issue</string>
+ <!-- Label for notification action to share issue recording [CHAR LIMIT=35] -->
+ <string name="issuerecord_share_label">Share</string>
+ <!-- A toast message shown after successfully canceling a issue recording [CHAR LIMIT=NONE] -->
+ <!-- Notification text shown after saving a issue recording [CHAR LIMIT=100] -->
+ <string name="issuerecord_save_title">Issue recording saved</string>
+ <!-- Subtext for a notification shown after saving a issue recording to prompt the user to view it [CHAR_LIMIT=100] -->
+ <string name="issuerecord_save_text">Tap to view</string>
+ <!-- A toast message shown when there is an error saving a issue recording [CHAR LIMIT=NONE] -->
+ <string name="issuerecord_save_error">Error saving issue recording</string>
+ <!-- A toast message shown when the issue recording cannot be started due to a generic error [CHAR LIMIT=NONE] -->
+ <string name="issuerecord_start_error">Error starting issue recording</string>
+
<!-- Cling help message title when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
<string name="immersive_cling_title">Viewing full screen</string>
<!-- Cling help message description when hiding the navigation bar entering immersive mode [CHAR LIMIT=none] -->
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
index bbab4ded3fa2..6314bd9a5615 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
@@ -24,4 +24,5 @@ data class CameraProtectionInfo(
val physicalCameraId: String?,
val cutoutProtectionPath: Path,
val cutoutBounds: Rect,
+ val displayUniqueId: String?,
)
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
index 8fe9389b7b1d..6cee28be78f1 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
@@ -25,15 +25,21 @@ import com.android.systemui.res.R
import javax.inject.Inject
import kotlin.math.roundToInt
-class CameraProtectionLoader @Inject constructor(private val context: Context) {
+interface CameraProtectionLoader {
+ fun loadCameraProtectionInfoList(): List<CameraProtectionInfo>
+}
+
+class CameraProtectionLoaderImpl @Inject constructor(private val context: Context) :
+ CameraProtectionLoader {
- fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> {
+ override fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> {
val list = mutableListOf<CameraProtectionInfo>()
val front =
loadCameraProtectionInfo(
R.string.config_protectedCameraId,
R.string.config_protectedPhysicalCameraId,
- R.string.config_frontBuiltInDisplayCutoutProtection
+ R.string.config_frontBuiltInDisplayCutoutProtection,
+ R.string.config_protectedScreenUniqueId,
)
if (front != null) {
list.add(front)
@@ -42,7 +48,8 @@ class CameraProtectionLoader @Inject constructor(private val context: Context) {
loadCameraProtectionInfo(
R.string.config_protectedInnerCameraId,
R.string.config_protectedInnerPhysicalCameraId,
- R.string.config_innerBuiltInDisplayCutoutProtection
+ R.string.config_innerBuiltInDisplayCutoutProtection,
+ R.string.config_protectedInnerScreenUniqueId,
)
if (inner != null) {
list.add(inner)
@@ -53,7 +60,8 @@ class CameraProtectionLoader @Inject constructor(private val context: Context) {
private fun loadCameraProtectionInfo(
cameraIdRes: Int,
physicalCameraIdRes: Int,
- pathRes: Int
+ pathRes: Int,
+ displayUniqueIdRes: Int,
): CameraProtectionInfo? {
val logicalCameraId = context.getString(cameraIdRes)
if (logicalCameraId.isNullOrEmpty()) {
@@ -70,11 +78,13 @@ class CameraProtectionLoader @Inject constructor(private val context: Context) {
computed.right.roundToInt(),
computed.bottom.roundToInt()
)
+ val displayUniqueId = context.getString(displayUniqueIdRes)
return CameraProtectionInfo(
logicalCameraId,
physicalCameraId,
protectionPath,
- protectionBounds
+ protectionBounds,
+ displayUniqueId
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt
new file mode 100644
index 000000000000..58680a88d670
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionModule.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 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
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface CameraProtectionModule {
+
+ @Binds fun cameraProtectionLoaderImpl(impl: CameraProtectionLoaderImpl): CameraProtectionLoader
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt
new file mode 100644
index 000000000000..fc0b97ea7013
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutInformation.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.view.DisplayCutout
+
+data class SysUICutoutInformation(
+ val cutout: DisplayCutout,
+ val cameraProtection: CameraProtectionInfo?
+)
diff --git a/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
new file mode 100644
index 000000000000..aad934124dfb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.content.Context
+import android.view.DisplayCutout
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class SysUICutoutProvider
+@Inject
+constructor(
+ private val context: Context,
+ private val cameraProtectionLoader: CameraProtectionLoader,
+) {
+
+ private val cameraProtectionList by lazy {
+ cameraProtectionLoader.loadCameraProtectionInfoList()
+ }
+
+ fun cutoutInfoForCurrentDisplay(): SysUICutoutInformation? {
+ val display = context.display
+ val displayCutout: DisplayCutout = display.cutout ?: return null
+ val displayUniqueId: String? = display.uniqueId
+ if (displayUniqueId.isNullOrEmpty()) {
+ return SysUICutoutInformation(displayCutout, cameraProtection = null)
+ }
+ val cameraProtection: CameraProtectionInfo? =
+ cameraProtectionList.firstOrNull { it.displayUniqueId == displayUniqueId }
+ return SysUICutoutInformation(displayCutout, cameraProtection)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index 92eeace648ad..904d5898fcf8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -23,6 +23,7 @@ import com.android.systemui.doze.DozeService;
import com.android.systemui.dreams.DreamOverlayService;
import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
import com.android.systemui.keyguard.KeyguardService;
+import com.android.systemui.recordissue.IssueRecordingService;
import com.android.systemui.screenrecord.RecordingService;
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
import com.android.systemui.wallpapers.ImageWallpaper;
@@ -85,4 +86,11 @@ public abstract class DefaultServiceBinder {
@IntoMap
@ClassKey(RecordingService.class)
public abstract Service bindRecordingService(RecordingService service);
+
+ /** Inject into IssueRecordingService */
+ @Binds
+ @IntoMap
+ @ClassKey(IssueRecordingService.class)
+ public abstract Service bindIssueRecordingService(IssueRecordingService service);
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index efcbd47b67b4..28fd9a994f8a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -27,6 +27,7 @@ import com.android.keyguard.dagger.ClockRegistryModule;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.BootCompleteCache;
import com.android.systemui.BootCompleteCacheImpl;
+import com.android.systemui.CameraProtectionModule;
import com.android.systemui.accessibility.AccessibilityModule;
import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
import com.android.systemui.appops.dagger.AppOpsModule;
@@ -177,6 +178,7 @@ import javax.inject.Named;
BouncerInteractorModule.class,
BouncerRepositoryModule.class,
BouncerViewModule.class,
+ CameraProtectionModule.class,
ClipboardOverlayModule.class,
ClockRegistryModule.class,
CommunalModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
index 16dfc2130402..47df021eaf83 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
@@ -32,6 +32,7 @@ import android.util.MathUtils.lerpInv
import android.util.MathUtils.lerpInvSat
import androidx.annotation.VisibleForTesting
import com.android.app.animation.Interpolators
+import com.android.app.tracing.traceSection
import com.android.internal.graphics.ColorUtils
import kotlin.math.abs
import kotlin.math.cos
@@ -127,6 +128,10 @@ class SquigglyProgress : Drawable() {
}
override fun draw(canvas: Canvas) {
+ traceSection("SquigglyProgress#draw") { drawTraced(canvas) }
+ }
+
+ private fun drawTraced(canvas: Canvas) {
if (animate) {
invalidateSelf()
val now = SystemClock.uptimeMillis()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 53f287b81be9..720120b630d5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -47,6 +47,8 @@ public class QSIconViewImpl extends QSIconView {
public static final long QS_ANIM_LENGTH = 350;
+ private static final long ICON_APPLIED_TRANSACTION_ID = -1;
+
protected final View mIcon;
protected int mIconSizePx;
private boolean mAnimationEnabled = true;
@@ -57,7 +59,8 @@ public class QSIconViewImpl extends QSIconView {
@VisibleForTesting
QSTile.Icon mLastIcon;
- private boolean mIconChangeScheduled;
+ private long mScheduledIconChangeTransactionId = ICON_APPLIED_TRANSACTION_ID;
+ private long mHighestScheduledIconChangeTransactionId = ICON_APPLIED_TRANSACTION_ID;
private ValueAnimator mColorAnimator = new ValueAnimator();
@@ -117,7 +120,7 @@ public class QSIconViewImpl extends QSIconView {
}
protected void updateIcon(ImageView iv, State state, boolean allowAnimations) {
- mIconChangeScheduled = false;
+ mScheduledIconChangeTransactionId = ICON_APPLIED_TRANSACTION_ID;
final QSTile.Icon icon = state.iconSupplier != null ? state.iconSupplier.get() : state.icon;
if (!Objects.equals(icon, iv.getTag(R.id.qs_icon_tag))) {
boolean shouldAnimate = allowAnimations && shouldAnimate(iv);
@@ -173,9 +176,10 @@ public class QSIconViewImpl extends QSIconView {
mState = state.state;
mDisabledByPolicy = state.disabledByPolicy;
if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
- mIconChangeScheduled = true;
+ final long iconTransactionId = getNextIconTransactionId();
+ mScheduledIconChangeTransactionId = iconTransactionId;
animateGrayScale(mTint, color, iv, () -> {
- if (mIconChangeScheduled) {
+ if (mScheduledIconChangeTransactionId == iconTransactionId) {
updateIcon(iv, state, allowAnimations);
}
});
@@ -237,6 +241,11 @@ public class QSIconViewImpl extends QSIconView {
child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
}
+ private long getNextIconTransactionId() {
+ mHighestScheduledIconChangeTransactionId++;
+ return mHighestScheduledIconChangeTransactionId;
+ }
+
/**
* Color to tint the tile icon based on state
*/
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index 88863cbad1ee..a4748686a784 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -42,6 +42,7 @@ import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.recordissue.IssueRecordingService
import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingService
@@ -107,7 +108,7 @@ constructor(
PendingIntent.getService(
userContextProvider.userContext,
RecordingService.REQUEST_CODE,
- RecordingService.getStopIntent(userContextProvider.userContext),
+ IssueRecordingService.getStopIntent(userContextProvider.userContext),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
.send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
new file mode 100644
index 000000000000..f4872589b3bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2024 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.recordissue
+
+import android.app.NotificationManager
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.os.Handler
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.qualifiers.LongRunning
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.screenrecord.RecordingController
+import com.android.systemui.screenrecord.RecordingService
+import com.android.systemui.screenrecord.RecordingServiceStrings
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+class IssueRecordingService
+@Inject
+constructor(
+ controller: RecordingController,
+ @LongRunning executor: Executor,
+ @Main handler: Handler,
+ uiEventLogger: UiEventLogger,
+ notificationManager: NotificationManager,
+ userContextProvider: UserContextProvider,
+ keyguardDismissUtil: KeyguardDismissUtil
+) :
+ RecordingService(
+ controller,
+ executor,
+ handler,
+ uiEventLogger,
+ notificationManager,
+ userContextProvider,
+ keyguardDismissUtil
+ ) {
+
+ override fun getTag(): String = TAG
+
+ override fun getChannelId(): String = CHANNEL_ID
+
+ override fun provideRecordingServiceStrings(): RecordingServiceStrings = IrsStrings(resources)
+
+ companion object {
+ private const val TAG = "IssueRecordingService"
+ private const val CHANNEL_ID = "issue_record"
+
+ /**
+ * Get an intent to stop the issue recording service.
+ *
+ * @param context Context from the requesting activity
+ * @return
+ */
+ fun getStopIntent(context: Context): Intent =
+ Intent(context, RecordingService::class.java)
+ .setAction(ACTION_STOP)
+ .putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
+
+ /**
+ * Get an intent to start the issue recording service.
+ *
+ * @param context Context from the requesting activity
+ */
+ fun getStartIntent(context: Context): Intent =
+ Intent(context, RecordingService::class.java).setAction(ACTION_START)
+ }
+}
+
+private class IrsStrings(private val res: Resources) : RecordingServiceStrings(res) {
+ override val title
+ get() = res.getString(R.string.issuerecord_title)
+ override val notificationChannelDescription
+ get() = res.getString(R.string.issuerecord_channel_description)
+ override val startErrorResId
+ get() = R.string.issuerecord_start_error
+ override val startError
+ get() = res.getString(R.string.issuerecord_start_error)
+ override val saveErrorResId
+ get() = R.string.issuerecord_save_error
+ override val saveError
+ get() = res.getString(R.string.issuerecord_save_error)
+ override val ongoingRecording
+ get() = res.getString(R.string.issuerecord_ongoing_screen_only)
+ override val backgroundProcessingLabel
+ get() = res.getString(R.string.issuerecord_background_processing_label)
+ override val saveTitle
+ get() = res.getString(R.string.issuerecord_save_title)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index e051df4e6c7b..80f11f1e1874 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -17,7 +17,6 @@
package com.android.systemui.recordissue
import android.annotation.SuppressLint
-import android.app.Activity
import android.app.BroadcastOptions
import android.app.PendingIntent
import android.content.Context
@@ -45,7 +44,6 @@ import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDi
import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingService
-import com.android.systemui.screenrecord.ScreenRecordingAudioSource
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
@@ -183,13 +181,7 @@ constructor(
PendingIntent.getForegroundService(
userContextProvider.userContext,
RecordingService.REQUEST_CODE,
- RecordingService.getStartIntent(
- userContextProvider.userContext,
- Activity.RESULT_OK,
- ScreenRecordingAudioSource.NONE.ordinal,
- false,
- null
- ),
+ IssueRecordingService.getStartIntent(userContextProvider.userContext),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
.send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index b5a13138a278..b355d2d6b4f8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -24,7 +24,6 @@ import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.media.MediaRecorder;
import android.net.Uri;
@@ -71,8 +70,8 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
private static final String EXTRA_CAPTURE_TARGET = "extra_captureTarget";
- private static final String ACTION_START = "com.android.systemui.screenrecord.START";
- private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
+ protected static final String ACTION_START = "com.android.systemui.screenrecord.START";
+ protected static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
private static final String ACTION_STOP_NOTIF =
"com.android.systemui.screenrecord.STOP_FROM_NOTIF";
private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
@@ -90,6 +89,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
private final NotificationManager mNotificationManager;
private final UserContextProvider mUserContextTracker;
private int mNotificationId = NOTIF_BASE_ID;
+ private RecordingServiceStrings mStrings;
@Inject
public RecordingService(RecordingController controller, @LongRunning Executor executor,
@@ -134,9 +134,9 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
return Service.START_NOT_STICKY;
}
String action = intent.getAction();
- Log.d(TAG, "onStartCommand " + action);
+ Log.d(getTag(), "onStartCommand " + action);
NotificationChannel channel = new NotificationChannel(
- CHANNEL_ID,
+ getChannelId(),
getString(R.string.screenrecord_title),
NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription(getString(R.string.screenrecord_channel_description));
@@ -152,7 +152,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
mNotificationId = NOTIF_BASE_ID + (int) SystemClock.uptimeMillis();
mAudioSource = ScreenRecordingAudioSource
.values()[intent.getIntExtra(EXTRA_AUDIO_SOURCE, 0)];
- Log.d(TAG, "recording with audio source " + mAudioSource);
+ Log.d(getTag(), "recording with audio source " + mAudioSource);
mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false);
MediaProjectionCaptureTarget captureTarget =
intent.getParcelableExtra(EXTRA_CAPTURE_TARGET,
@@ -207,7 +207,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
.setType("video/mp4")
.putExtra(Intent.EXTRA_STREAM, shareUri);
mKeyguardDismissUtil.executeWhenUnlocked(() -> {
- String shareLabel = getResources().getString(R.string.screenrecord_share_label);
+ String shareLabel = strings().getShareLabel();
startActivity(Intent.createChooser(shareIntent, shareLabel)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
// Remove notification
@@ -270,13 +270,11 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
*/
@VisibleForTesting
protected void createErrorNotification() {
- Resources res = getResources();
Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- res.getString(R.string.screenrecord_title));
- String notificationTitle = res.getString(R.string.screenrecord_start_error);
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
+ String notificationTitle = strings().getStartError();
- Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
+ Notification.Builder builder = new Notification.Builder(this, getChannelId())
.setSmallIcon(R.drawable.ic_screenrecord)
.setContentTitle(notificationTitle)
.addExtras(extras);
@@ -290,14 +288,12 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
@VisibleForTesting
protected void createRecordingNotification() {
- Resources res = getResources();
Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- res.getString(R.string.screenrecord_title));
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE
- ? res.getString(R.string.screenrecord_ongoing_screen_only)
- : res.getString(R.string.screenrecord_ongoing_screen_and_audio);
+ ? strings().getOngoingRecording()
+ : strings().getOngoingRecordingWithAudio();
PendingIntent pendingIntent = PendingIntent.getService(
this,
@@ -306,9 +302,9 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
Notification.Action stopAction = new Notification.Action.Builder(
Icon.createWithResource(this, R.drawable.ic_android),
- getResources().getString(R.string.screenrecord_stop_label),
+ strings().getStopLabel(),
pendingIntent).build();
- Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
+ Notification.Builder builder = new Notification.Builder(this, getChannelId())
.setSmallIcon(R.drawable.ic_screenrecord)
.setContentTitle(notificationTitle)
.setUsesChronometer(true)
@@ -323,19 +319,17 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
@VisibleForTesting
protected Notification createProcessingNotification() {
- Resources res = getApplicationContext().getResources();
String notificationTitle = mAudioSource == ScreenRecordingAudioSource.NONE
- ? res.getString(R.string.screenrecord_ongoing_screen_only)
- : res.getString(R.string.screenrecord_ongoing_screen_and_audio);
+ ? strings().getOngoingRecording()
+ : strings().getOngoingRecordingWithAudio();
Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- res.getString(R.string.screenrecord_title));
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
- Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
+ Notification.Builder builder = new Notification.Builder(this, getChannelId())
.setContentTitle(notificationTitle)
.setContentText(
- getResources().getString(R.string.screenrecord_background_processing_label))
+ strings().getBackgroundProcessingLabel())
.setSmallIcon(R.drawable.ic_screenrecord)
.setGroup(GROUP_KEY)
.addExtras(extras);
@@ -351,7 +345,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
Notification.Action shareAction = new Notification.Action.Builder(
Icon.createWithResource(this, R.drawable.ic_screenrecord),
- getResources().getString(R.string.screenrecord_share_label),
+ strings().getShareLabel(),
PendingIntent.getService(
this,
REQUEST_CODE,
@@ -360,13 +354,12 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
.build();
Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- getResources().getString(R.string.screenrecord_title));
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, strings().getTitle());
- Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID)
+ Notification.Builder builder = new Notification.Builder(this, getChannelId())
.setSmallIcon(R.drawable.ic_screenrecord)
- .setContentTitle(getResources().getString(R.string.screenrecord_save_title))
- .setContentText(getResources().getString(R.string.screenrecord_save_text))
+ .setContentTitle(strings().getSaveTitle())
+ .setContentText(strings().getSaveText())
.setContentIntent(PendingIntent.getActivity(
this,
REQUEST_CODE,
@@ -394,15 +387,15 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
private void postGroupNotification(UserHandle currentUser) {
Bundle extras = new Bundle();
extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- getResources().getString(R.string.screenrecord_title));
- Notification groupNotif = new Notification.Builder(this, CHANNEL_ID)
+ strings().getTitle());
+ Notification groupNotif = new Notification.Builder(this, getChannelId())
.setSmallIcon(R.drawable.ic_screenrecord)
- .setContentTitle(getResources().getString(R.string.screenrecord_save_title))
+ .setContentTitle(strings().getSaveTitle())
.setGroup(GROUP_KEY)
.setGroupSummary(true)
.setExtras(extras)
.build();
- mNotificationManager.notifyAsUser(TAG, NOTIF_BASE_ID, groupNotif, currentUser);
+ mNotificationManager.notifyAsUser(getTag(), NOTIF_BASE_ID, groupNotif, currentUser);
}
private void stopService() {
@@ -413,7 +406,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
if (userId == USER_ID_NOT_SPECIFIED) {
userId = mUserContextTracker.getUserContext().getUserId();
}
- Log.d(TAG, "notifying for user " + userId);
+ Log.d(getTag(), "notifying for user " + userId);
setTapsVisible(mOriginalShowTaps);
if (getRecorder() != null) {
try {
@@ -424,7 +417,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
// let's release the recorder and delete all temporary files in this case
getRecorder().release();
showErrorToast(R.string.screenrecord_start_error);
- Log.e(TAG, "stopRecording called, but there was an error when ending"
+ Log.e(getTag(), "stopRecording called, but there was an error when ending"
+ "recording");
exception.printStackTrace();
createErrorNotification();
@@ -435,7 +428,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
throw new RuntimeException(throwable);
}
} else {
- Log.e(TAG, "stopRecording called, but recorder was null");
+ Log.e(getTag(), "stopRecording called, but recorder was null");
}
updateState(false);
stopForeground(STOP_FOREGROUND_DETACH);
@@ -449,13 +442,13 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
mLongExecutor.execute(() -> {
try {
- Log.d(TAG, "saving recording");
+ Log.d(getTag(), "saving recording");
Notification notification = createSaveNotification(getRecorder().save());
postGroupNotification(currentUser);
mNotificationManager.notifyAsUser(null, mNotificationId, notification,
currentUser);
} catch (IOException | IllegalStateException e) {
- Log.e(TAG, "Error saving screen recording: " + e.getMessage());
+ Log.e(getTag(), "Error saving screen recording: " + e.getMessage());
e.printStackTrace();
showErrorToast(R.string.screenrecord_save_error);
mNotificationManager.cancelAsUser(null, mNotificationId, currentUser);
@@ -468,6 +461,26 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
Settings.System.putInt(getContentResolver(), Settings.System.SHOW_TOUCHES, value);
}
+ protected String getTag() {
+ return TAG;
+ }
+
+ protected String getChannelId() {
+ return CHANNEL_ID;
+ }
+
+ private RecordingServiceStrings strings() {
+ if (mStrings == null) {
+ mStrings = provideRecordingServiceStrings();
+ }
+ return mStrings;
+ }
+
+ protected RecordingServiceStrings provideRecordingServiceStrings() {
+ return new RecordingServiceStrings(getResources());
+ }
+
+
/**
* Get an intent to stop the recording service.
* @param context Context from the requesting activity
@@ -484,25 +497,25 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
* @param context
* @return
*/
- protected static Intent getNotificationIntent(Context context) {
- return new Intent(context, RecordingService.class).setAction(ACTION_STOP_NOTIF);
+ protected Intent getNotificationIntent(Context context) {
+ return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF);
}
- private static Intent getShareIntent(Context context, String path) {
- return new Intent(context, RecordingService.class).setAction(ACTION_SHARE)
+ private Intent getShareIntent(Context context, String path) {
+ return new Intent(context, this.getClass()).setAction(ACTION_SHARE)
.putExtra(EXTRA_PATH, path);
}
@Override
public void onInfo(MediaRecorder mr, int what, int extra) {
- Log.d(TAG, "Media recorder info: " + what);
+ Log.d(getTag(), "Media recorder info: " + what);
onStartCommand(getStopIntent(this), 0, 0);
}
@Override
public void onStopped() {
if (mController.isRecording()) {
- Log.d(TAG, "Stopping recording because the system requested the stop");
+ Log.d(getTag(), "Stopping recording because the system requested the stop");
stopService();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingServiceStrings.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingServiceStrings.kt
new file mode 100644
index 000000000000..fdb1eb6af298
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingServiceStrings.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 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.screenrecord
+
+import android.content.res.Resources
+import com.android.systemui.res.R
+
+open class RecordingServiceStrings(private val res: Resources) {
+ open val title
+ get() = res.getString(R.string.screenrecord_title)
+ open val notificationChannelDescription
+ get() = res.getString(R.string.screenrecord_channel_description)
+ open val startErrorResId
+ get() = R.string.screenrecord_start_error
+ open val startError
+ get() = res.getString(R.string.screenrecord_start_error)
+ open val saveErrorResId
+ get() = R.string.screenrecord_save_error
+ open val saveError
+ get() = res.getString(R.string.screenrecord_save_error)
+ open val ongoingRecording
+ get() = res.getString(R.string.screenrecord_ongoing_screen_only)
+ open val backgroundProcessingLabel
+ get() = res.getString(R.string.screenrecord_background_processing_label)
+ open val saveTitle
+ get() = res.getString(R.string.screenrecord_save_title)
+
+ val saveText
+ get() = res.getString(R.string.screenrecord_save_text)
+ val ongoingRecordingWithAudio
+ get() = res.getString(R.string.screenrecord_ongoing_screen_and_audio)
+ val stopLabel
+ get() = res.getString(R.string.screenrecord_stop_label)
+ val shareLabel
+ get() = res.getString(R.string.screenrecord_share_label)
+}
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 5e0110b8af03..0a11eb26cc07 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
@@ -1445,12 +1445,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
}
- final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
// TODO(b/322228881): Clean up scene container vs legacy behavior in NSSL
if (SceneContainerFlag.isEnabled()) {
// stackY should be driven by scene container, not NSSL
mAmbientState.setStackY(mTopPadding);
} else {
+ final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
mAmbientState.setStackY(stackY);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index a3e09417b34c..b38d619a4434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -59,9 +59,6 @@ public class StackStateAnimator {
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
public static final int ANIMATION_DURATION_FOLD_TO_AOD =
AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD;
- public static final int ANIMATION_DURATION_PULSE_APPEAR =
- KeyguardSliceView.DEFAULT_ANIM_DURATION;
- public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
public static final int ANIMATION_DURATION_PRIORITY_CHANGE = 500;
public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
@@ -70,7 +67,6 @@ public class StackStateAnimator {
private static final int MAX_STAGGER_COUNT = 5;
private final int mGoToFullShadeAppearingTranslation;
- private final int mPulsingAppearingTranslation;
@VisibleForTesting
float mHeadsUpAppearStartAboveScreen;
private final ExpandableViewState mTmpState = new ExpandableViewState();
@@ -102,9 +98,6 @@ public class StackStateAnimator {
mGoToFullShadeAppearingTranslation =
hostLayout.getContext().getResources().getDimensionPixelSize(
R.dimen.go_to_full_shade_appearing_translation);
- mPulsingAppearingTranslation =
- hostLayout.getContext().getResources().getDimensionPixelSize(
- R.dimen.pulsing_notification_appear_translation);
mHeadsUpAppearStartAboveScreen = hostLayout.getContext().getResources()
.getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen);
mAnimationProperties = new AnimationProperties() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index 01972646f394..311ba83e85f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -28,9 +28,6 @@ class NotificationStackAppearanceRepository @Inject constructor() {
/** The bounds of the notification stack in the current scene. */
val stackBounds = MutableStateFlow(NotificationContainerBounds())
- /** The corner radius of the notification stack, in dp. */
- val cornerRadiusDp = MutableStateFlow(32f)
-
/**
* The height in px of the contents of notification stack. Depending on the number of
* notifications, this can exceed the space available on screen to show notifications, at which
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 8307397c57da..9984ba9c32ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -34,9 +34,6 @@ constructor(
/** The bounds of the notification stack in the current scene. */
val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow()
- /** The corner radius of the notification stack, in dp. */
- val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
-
/**
* The height in px of the contents of notification stack. Depending on the number of
* notifications, this can exceed the space available on screen to show notifications, at which
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index 50b08b8bb361..814146c329a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -31,6 +31,7 @@ import kotlinx.coroutines.launch
/** Binds the shared notification container to its view-model. */
object NotificationStackAppearanceViewBinder {
+ const val SCRIM_CORNER_RADIUS = 32f
@JvmStatic
fun bind(
@@ -49,8 +50,8 @@ object NotificationStackAppearanceViewBinder {
bounds.top.roundToInt(),
bounds.right.roundToInt(),
bounds.bottom.roundToInt(),
- viewModel.cornerRadiusDp.value.dpToPx(context),
- viewModel.cornerRadiusDp.value.dpToPx(context),
+ SCRIM_CORNER_RADIUS.dpToPx(context),
+ 0,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index fe5bdd41a94f..f3d0d2c2e9fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -136,8 +136,12 @@ object SharedNotificationContainerBinder {
.collect { y -> controller.setTranslationY(y) }
}
- launch {
- viewModel.expansionAlpha.collect { controller.setMaxAlphaForExpansion(it) }
+ if (!sceneContainerFlags.isEnabled()) {
+ launch {
+ viewModel.expansionAlpha.collect {
+ controller.setMaxAlphaForExpansion(it)
+ }
+ }
}
launch {
viewModel.glanceableHubAlpha.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index 56ff7f9e50df..bdf1a6431549 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -74,9 +74,6 @@ constructor(
/** The bounds of the notification stack in the current scene. */
val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds
- /** The corner radius of the notification stack, in dp. */
- val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp
-
/** The y-coordinate in px of top of the contents of the notification stack. */
val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 8b723da65162..65d9c9f523f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -28,7 +28,6 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.Notif
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
/**
* ViewModel used by the Notification placeholders inside the scene container to update the
@@ -74,9 +73,6 @@ constructor(
interactor.setStackBounds(notificationContainerBounds)
}
- /** The corner radius of the placeholder, in dp. */
- val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp
-
/**
* The height in px of the contents of notification stack. Depending on the number of
* notifications, this can exceed the space available on screen to show notifications, at which
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 877bd7c11e95..e84b7a077b21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -44,6 +44,8 @@ import com.android.systemui.util.leak.RotationUtils.getResourcesForRotation
import com.android.app.tracing.traceSection
import com.android.systemui.BottomMarginCommand
import com.android.systemui.StatusBarInsetsCommand
+import com.android.systemui.SysUICutoutInformation
+import com.android.systemui.SysUICutoutProvider
import com.android.systemui.statusbar.commandline.CommandRegistry
import java.io.PrintWriter
import java.lang.Math.max
@@ -69,6 +71,7 @@ class StatusBarContentInsetsProvider @Inject constructor(
val configurationController: ConfigurationController,
val dumpManager: DumpManager,
val commandRegistry: CommandRegistry,
+ val sysUICutoutProvider: SysUICutoutProvider,
) : CallbackController<StatusBarContentInsetsChangedListener>,
ConfigurationController.ConfigurationListener,
Dumpable {
@@ -176,7 +179,8 @@ class StatusBarContentInsetsProvider @Inject constructor(
*/
fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets =
traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
- val displayCutout = checkNotNull(context.display).cutout
+ val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+ val displayCutout = sysUICutout?.cutout
val key = getCacheKey(rotation, displayCutout)
val screenBounds = context.resources.configuration.windowConfiguration.maxBounds
@@ -187,7 +191,7 @@ class StatusBarContentInsetsProvider @Inject constructor(
val width = point.logicalWidth(rotation)
val area = insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
- rotation, displayCutout, getResourcesForRotation(rotation, context), key)
+ rotation, sysUICutout, getResourcesForRotation(rotation, context), key)
Insets.of(area.left, area.top, /* right= */ width - area.right, /* bottom= */ 0)
}
@@ -212,10 +216,11 @@ class StatusBarContentInsetsProvider @Inject constructor(
fun getStatusBarContentAreaForRotation(
@Rotation rotation: Int
): Rect {
- val displayCutout = checkNotNull(context.display).cutout
+ val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+ val displayCutout = sysUICutout?.cutout
val key = getCacheKey(rotation, displayCutout)
return insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
- rotation, displayCutout, getResourcesForRotation(rotation, context), key)
+ rotation, sysUICutout, getResourcesForRotation(rotation, context), key)
}
/**
@@ -228,18 +233,18 @@ class StatusBarContentInsetsProvider @Inject constructor(
private fun getAndSetCalculatedAreaForRotation(
@Rotation targetRotation: Int,
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
rotatedResources: Resources,
key: CacheKey
): Rect {
- return getCalculatedAreaForRotation(displayCutout, targetRotation, rotatedResources)
+ return getCalculatedAreaForRotation(sysUICutout, targetRotation, rotatedResources)
.also {
insetsCache.put(key, it)
}
}
private fun getCalculatedAreaForRotation(
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
@Rotation targetRotation: Int,
rotatedResources: Resources
): Rect {
@@ -271,7 +276,7 @@ class StatusBarContentInsetsProvider @Inject constructor(
return calculateInsetsForRotationWithRotatedResources(
currentRotation,
targetRotation,
- displayCutout,
+ sysUICutout,
context.resources.configuration.windowConfiguration.maxBounds,
SystemBarUtils.getStatusBarHeightForRotation(context, targetRotation),
minLeft,
@@ -415,7 +420,7 @@ fun getPrivacyChipBoundingRectForInsets(
fun calculateInsetsForRotationWithRotatedResources(
@Rotation currentRotation: Int,
@Rotation targetRotation: Int,
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
maxBounds: Rect,
statusBarHeight: Int,
minLeft: Int,
@@ -434,7 +439,7 @@ fun calculateInsetsForRotationWithRotatedResources(
val rotZeroBounds = getRotationZeroDisplayBounds(maxBounds, currentRotation)
return getStatusBarContentBounds(
- displayCutout,
+ sysUICutout,
statusBarHeight,
rotZeroBounds.right,
rotZeroBounds.bottom,
@@ -470,7 +475,7 @@ fun calculateInsetsForRotationWithRotatedResources(
* rotation
*/
private fun getStatusBarContentBounds(
- displayCutout: DisplayCutout?,
+ sysUICutout: SysUICutoutInformation?,
sbHeight: Int,
width: Int,
height: Int,
@@ -489,19 +494,17 @@ private fun getStatusBarContentBounds(
val logicalDisplayWidth = if (targetRotation.isHorizontal()) height else width
- val cutoutRects = displayCutout?.boundingRects
- if (cutoutRects == null || cutoutRects.isEmpty()) {
- return Rect(minLeft,
- insetTop,
- logicalDisplayWidth - minRight,
- sbHeight)
+ val cutoutRects = sysUICutout?.cutout?.boundingRects
+ if (cutoutRects.isNullOrEmpty()) {
+ return Rect(minLeft, insetTop, logicalDisplayWidth - minRight, sbHeight)
}
- val relativeRotation = if (currentRotation - targetRotation < 0) {
- currentRotation - targetRotation + 4
- } else {
- currentRotation - targetRotation
- }
+ val relativeRotation =
+ if (currentRotation - targetRotation < 0) {
+ currentRotation - targetRotation + 4
+ } else {
+ currentRotation - targetRotation
+ }
// Size of the status bar window for the given rotation relative to our exact rotation
val sbRect = sbRect(relativeRotation, sbHeight, Pair(cWidth, cHeight))
@@ -509,19 +512,26 @@ private fun getStatusBarContentBounds(
var leftMargin = minLeft
var rightMargin = minRight
for (cutoutRect in cutoutRects) {
+ val protectionRect = sysUICutout.cameraProtection?.cutoutBounds
+ val actualCutoutRect =
+ if (protectionRect?.intersects(cutoutRect) == true) {
+ rectUnion(cutoutRect, protectionRect)
+ } else {
+ cutoutRect
+ }
// There is at most one non-functional area per short edge of the device. So if the status
// bar doesn't share a short edge with the cutout, we can ignore its insets because there
// will be no letter-boxing to worry about
- if (!shareShortEdge(sbRect, cutoutRect, cWidth, cHeight)) {
+ if (!shareShortEdge(sbRect, actualCutoutRect, cWidth, cHeight)) {
continue
}
- if (cutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) {
- var logicalWidth = cutoutRect.logicalWidth(relativeRotation)
+ if (actualCutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) {
+ var logicalWidth = actualCutoutRect.logicalWidth(relativeRotation)
if (isRtl) logicalWidth += dotWidth
leftMargin = max(logicalWidth, leftMargin)
- } else if (cutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) {
- var logicalWidth = cutoutRect.logicalWidth(relativeRotation)
+ } else if (actualCutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) {
+ var logicalWidth = actualCutoutRect.logicalWidth(relativeRotation)
if (!isRtl) logicalWidth += dotWidth
rightMargin = max(rightMargin, logicalWidth)
}
@@ -532,6 +542,11 @@ private fun getStatusBarContentBounds(
return Rect(leftMargin, insetTop, logicalDisplayWidth - rightMargin, sbHeight)
}
+private fun rectUnion(first: Rect, second: Rect) = Rect(first).apply { union(second) }
+
+private fun Rect.intersects(other: Rect): Boolean =
+ intersects(other.left, other.top, other.right, other.bottom)
+
/*
* Returns the inset top of the status bar.
*
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
index ceebcb77fde2..e5179dd96742 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt
@@ -37,19 +37,13 @@ class PendingTasksContainer {
*/
fun registerTask(name: String): Runnable {
pendingTasksCount.incrementAndGet()
-
- if (ENABLE_TRACE) {
- Trace.beginAsyncSection("PendingTasksContainer#$name", 0)
- }
+ Trace.beginAsyncSection("PendingTasksContainer#$name", 0)
return Runnable {
+ Trace.endAsyncSection("PendingTasksContainer#$name", 0)
if (pendingTasksCount.decrementAndGet() == 0) {
val onComplete = completionCallback.getAndSet(null)
onComplete?.run()
-
- if (ENABLE_TRACE) {
- Trace.endAsyncSection("PendingTasksContainer#$name", 0)
- }
}
}
}
@@ -82,4 +76,3 @@ class PendingTasksContainer {
fun getPendingCount(): Int = pendingTasksCount.get()
}
-private const val ENABLE_TRACE = false
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 2acd4b92c5a2..139d190ae63c 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -111,6 +111,7 @@ public class BubblesManager {
// TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
private final List<NotifCallback> mCallbacks = new ArrayList<>();
private final StatusBarWindowCallback mStatusBarWindowCallback;
+ private boolean mPanelExpanded;
/**
* Creates {@link BubblesManager}, returns {@code null} if Optional {@link Bubbles} not present
@@ -242,8 +243,12 @@ public class BubblesManager {
// Store callback in a field so it won't get GC'd
mStatusBarWindowCallback =
(keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, isDozing,
- panelExpanded, isDreaming) ->
+ panelExpanded, isDreaming) -> {
+ if (panelExpanded != mPanelExpanded) {
+ mPanelExpanded = panelExpanded;
mBubbles.onNotificationPanelExpandedChanged(panelExpanded);
+ }
+ };
notificationShadeWindowController.registerCallback(mStatusBarWindowCallback);
mSysuiProxy = new Bubbles.SysuiProxy() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
index 64cd5262eade..f776a63c54ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
@@ -347,8 +347,8 @@ class CameraAvailabilityListenerTest : SysuiTestCase() {
return CameraAvailabilityListener.build(
context,
context.mainExecutor,
- CameraProtectionLoader((context))
- )
+ CameraProtectionLoaderImpl((context))
+ )
.also {
it.addTransitionCallback(cameraTransitionCallback)
it.startListening()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
index 238e5e9197a3..a19a0c7d12a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
@@ -27,9 +27,9 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
-class CameraProtectionLoaderTest : SysuiTestCase() {
+class CameraProtectionLoaderImplTest : SysuiTestCase() {
- private val loader = CameraProtectionLoader(context)
+ private val loader = CameraProtectionLoaderImpl(context)
@Before
fun setUp() {
@@ -39,19 +39,21 @@ class CameraProtectionLoaderTest : SysuiTestCase() {
R.string.config_frontBuiltInDisplayCutoutProtection,
OUTER_CAMERA_PROTECTION_PATH
)
+ overrideResource(R.string.config_protectedScreenUniqueId, OUTER_SCREEN_UNIQUE_ID)
overrideResource(R.string.config_protectedInnerCameraId, INNER_CAMERA_LOGICAL_ID)
overrideResource(R.string.config_protectedInnerPhysicalCameraId, INNER_CAMERA_PHYSICAL_ID)
overrideResource(
R.string.config_innerBuiltInDisplayCutoutProtection,
INNER_CAMERA_PROTECTION_PATH
)
+ overrideResource(R.string.config_protectedInnerScreenUniqueId, INNER_SCREEN_UNIQUE_ID)
}
@Test
fun loadCameraProtectionInfoList() {
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos)
+ assertThat(protectionList)
.containsExactly(OUTER_CAMERA_PROTECTION_INFO, INNER_CAMERA_PROTECTION_INFO)
}
@@ -59,18 +61,18 @@ class CameraProtectionLoaderTest : SysuiTestCase() {
fun loadCameraProtectionInfoList_outerCameraIdEmpty_onlyReturnsInnerInfo() {
overrideResource(R.string.config_protectedCameraId, "")
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos).containsExactly(INNER_CAMERA_PROTECTION_INFO)
+ assertThat(protectionList).containsExactly(INNER_CAMERA_PROTECTION_INFO)
}
@Test
fun loadCameraProtectionInfoList_innerCameraIdEmpty_onlyReturnsOuterInfo() {
overrideResource(R.string.config_protectedInnerCameraId, "")
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos).containsExactly(OUTER_CAMERA_PROTECTION_INFO)
+ assertThat(protectionList).containsExactly(OUTER_CAMERA_PROTECTION_INFO)
}
@Test
@@ -78,13 +80,16 @@ class CameraProtectionLoaderTest : SysuiTestCase() {
overrideResource(R.string.config_protectedCameraId, "")
overrideResource(R.string.config_protectedInnerCameraId, "")
- val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+ val protectionList = loadProtectionList()
- assertThat(protectionInfos).isEmpty()
+ assertThat(protectionList).isEmpty()
}
+ private fun loadProtectionList() =
+ loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+
private fun CameraProtectionInfo.toTestableVersion() =
- TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds)
+ TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds, displayUniqueId)
/**
* "Testable" version, because the original version contains a Path property, which doesn't
@@ -94,6 +99,7 @@ class CameraProtectionLoaderTest : SysuiTestCase() {
val logicalCameraId: String,
val physicalCameraId: String?,
val cutoutBounds: Rect,
+ val displayUniqueId: String?,
)
companion object {
@@ -102,11 +108,13 @@ class CameraProtectionLoaderTest : SysuiTestCase() {
private const val OUTER_CAMERA_PROTECTION_PATH = "M 0,0 H 10,10 V 10,10 H 0,10 Z"
private val OUTER_CAMERA_PROTECTION_BOUNDS =
Rect(/* left = */ 0, /* top = */ 0, /* right = */ 10, /* bottom = */ 10)
+ private const val OUTER_SCREEN_UNIQUE_ID = "111"
private val OUTER_CAMERA_PROTECTION_INFO =
TestableProtectionInfo(
OUTER_CAMERA_LOGICAL_ID,
OUTER_CAMERA_PHYSICAL_ID,
- OUTER_CAMERA_PROTECTION_BOUNDS
+ OUTER_CAMERA_PROTECTION_BOUNDS,
+ OUTER_SCREEN_UNIQUE_ID,
)
private const val INNER_CAMERA_LOGICAL_ID = "2"
@@ -114,11 +122,13 @@ class CameraProtectionLoaderTest : SysuiTestCase() {
private const val INNER_CAMERA_PROTECTION_PATH = "M 0,0 H 20,20 V 20,20 H 0,20 Z"
private val INNER_CAMERA_PROTECTION_BOUNDS =
Rect(/* left = */ 0, /* top = */ 0, /* right = */ 20, /* bottom = */ 20)
+ private const val INNER_SCREEN_UNIQUE_ID = "222"
private val INNER_CAMERA_PROTECTION_INFO =
TestableProtectionInfo(
INNER_CAMERA_LOGICAL_ID,
INNER_CAMERA_PHYSICAL_ID,
- INNER_CAMERA_PROTECTION_BOUNDS
+ INNER_CAMERA_PROTECTION_BOUNDS,
+ INNER_SCREEN_UNIQUE_ID,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
new file mode 100644
index 000000000000..f769b4e5f2d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 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
+
+import com.android.systemui.res.R
+
+class FakeCameraProtectionLoader(private val context: SysuiTestableContext) :
+ CameraProtectionLoader {
+
+ private val realLoader = CameraProtectionLoaderImpl(context)
+
+ override fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> =
+ realLoader.loadCameraProtectionInfoList()
+
+ fun clearProtectionInfoList() {
+ context.orCreateTestableResources.addOverride(R.string.config_protectedCameraId, "")
+ context.orCreateTestableResources.addOverride(R.string.config_protectedInnerCameraId, "")
+ }
+
+ fun addAllProtections() {
+ addOuterCameraProtection()
+ addInnerCameraProtection()
+ }
+
+ fun addOuterCameraProtection(displayUniqueId: String = "111") {
+ context.orCreateTestableResources.addOverride(R.string.config_protectedCameraId, "1")
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedPhysicalCameraId,
+ "11"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_frontBuiltInDisplayCutoutProtection,
+ "M 0,0 H 10,10 V 10,10 H 0,10 Z"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedScreenUniqueId,
+ displayUniqueId
+ )
+ }
+
+ fun addInnerCameraProtection(displayUniqueId: String = "222") {
+ context.orCreateTestableResources.addOverride(R.string.config_protectedInnerCameraId, "2")
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedInnerPhysicalCameraId,
+ "22"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_innerBuiltInDisplayCutoutProtection,
+ "M 0,0 H 20,20 V 20,20 H 0,20 Z"
+ )
+ context.orCreateTestableResources.addOverride(
+ R.string.config_protectedInnerScreenUniqueId,
+ displayUniqueId
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 1f1fa7259cd5..c20367efa5da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -177,7 +177,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
new FakeFacePropertyRepository();
private List<DecorProvider> mMockCutoutList;
private final CameraProtectionLoader mCameraProtectionLoader =
- new CameraProtectionLoader(mContext);
+ new CameraProtectionLoaderImpl(mContext);
@Before
public void setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
new file mode 100644
index 000000000000..f37c4ae613ff
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayCutout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysUICutoutProviderTest : SysuiTestCase() {
+
+ private val fakeProtectionLoader = FakeCameraProtectionLoader(context)
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_noCutout_returnsNull() {
+ val noCutoutDisplay = createDisplay(cutout = null)
+ val noCutoutDisplayContext = context.createDisplayContext(noCutoutDisplay)
+ val provider = SysUICutoutProvider(noCutoutDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()
+
+ assertThat(sysUICutout).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_returnsCutout() {
+ val cutoutDisplay = createDisplay()
+ val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
+ val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cutout).isEqualTo(cutoutDisplay.cutout)
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_noAssociatedProtection_returnsNoProtection() {
+ val cutoutDisplay = createDisplay()
+ val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
+ val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_outerDisplay_protectionAssociated_returnsProtection() {
+ fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = OUTER_DISPLAY_UNIQUE_ID)
+ val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
+ val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNotNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_outerDisplay_protectionNotAvailable_returnsNullProtection() {
+ fakeProtectionLoader.clearProtectionInfoList()
+ val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
+ val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_displayWithNullId_protectionsWithNoId_returnsNullProtection() {
+ fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = "")
+ val displayContext = context.createDisplayContext(createDisplay(uniqueId = null))
+ val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ @Test
+ fun cutoutInfoForCurrentDisplay_displayWithEmptyId_protectionsWithNoId_returnsNullProtection() {
+ fakeProtectionLoader.addOuterCameraProtection(displayUniqueId = "")
+ val displayContext = context.createDisplayContext(createDisplay(uniqueId = ""))
+ val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+
+ assertThat(sysUICutout.cameraProtection).isNull()
+ }
+
+ companion object {
+ private const val OUTER_DISPLAY_UNIQUE_ID = "outer"
+ private val OUTER_DISPLAY = createDisplay(uniqueId = OUTER_DISPLAY_UNIQUE_ID)
+
+ private fun createDisplay(
+ uniqueId: String? = "uniqueId",
+ cutout: DisplayCutout? = mock<DisplayCutout>()
+ ) =
+ mock<Display> {
+ whenever(this.displayAdjustments).thenReturn(DisplayAdjustments())
+ whenever(this.cutout).thenReturn(cutout)
+ whenever(this.uniqueId).thenReturn(uniqueId)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
index e8aa8f0bdc5d..bbae0c90170a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
@@ -34,7 +34,7 @@ import kotlin.test.Test
import org.junit.Rule
import org.junit.runner.RunWith
-/** Test for regression b/311121830 */
+/** Test for regression b/311121830 and b/323125376 */
@RunWith(AndroidTestingRunner::class)
@UiThreadTest
@SmallTest
@@ -82,6 +82,55 @@ class QSIconViewImplTest_311121830 : SysuiTestCase() {
assertThat(iconView.mLastIcon).isEqualTo(secondState.icon)
}
+ @Test
+ fun alwaysLastIcon_twoStateChanges() {
+ // Need to inflate with the correct theme so the colors can be retrieved and the animations
+ // are run
+ val iconView =
+ AnimateQSIconViewImpl(
+ ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+ )
+
+ val initialState =
+ QSTile.State().apply {
+ state = Tile.STATE_ACTIVE
+ icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_FULL_ICONS[4])
+ }
+ val firstState =
+ QSTile.State().apply {
+ state = Tile.STATE_INACTIVE
+ icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4])
+ }
+ val secondState =
+ QSTile.State().apply {
+ state = Tile.STATE_ACTIVE
+ icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_FULL_ICONS[3])
+ }
+ val thirdState =
+ QSTile.State().apply {
+ state = Tile.STATE_INACTIVE
+ icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_NO_NETWORK)
+ }
+
+ // Start with the initial state
+ iconView.setIcon(initialState, /* allowAnimations= */ false)
+
+ // Set the first state to animate, and advance time to one third of the animation
+ iconView.setIcon(firstState, /* allowAnimations= */ true)
+ animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH / 3)
+
+ // Set the second state to animate and advance time by another third of animations length
+ iconView.setIcon(secondState, /* allowAnimations= */ true)
+ animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH / 3)
+
+ // Set the third state to animate and advance time by two times the animation length
+ // to guarantee that all animations are done
+ iconView.setIcon(thirdState, /* allowAnimations= */ true)
+ animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH * 2)
+
+ assertThat(iconView.mLastIcon).isEqualTo(thirdState.icon)
+ }
+
private class AnimateQSIconViewImpl(context: Context) : QSIconViewImpl(context) {
override fun createIcon(): View {
return object : ImageView(context) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 9ce77e58a5f2..deecc5bb5a03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -166,7 +166,7 @@ public class RecordingServiceTest extends SysuiTestCase {
@Test
public void testLogStopFromNotificationIntent() {
- Intent stopIntent = RecordingService.getNotificationIntent(mContext);
+ Intent stopIntent = mRecordingService.getNotificationIntent(mContext);
mRecordingService.onStartCommand(stopIntent, 0, 0);
// Verify that we log the correct event
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 44682e2088f4..f902439c20e5 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -52,6 +52,13 @@ flag {
}
flag {
+ name: "fullscreen_fling_gesture"
+ namespace: "accessibility"
+ description: "When true, adds a fling gesture animation for fullscreen magnification"
+ bug: "319175022"
+}
+
+flag {
name: "pinch_zoom_zero_min_span"
namespace: "accessibility"
description: "Whether to set min span of ScaleGestureDetector to zero."
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 805f6e390fd4..351760bd8865 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -25,7 +25,9 @@ import static com.android.server.accessibility.AccessibilityManagerService.INVAL
import android.accessibilityservice.MagnificationConfig;
import android.animation.Animator;
+import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -51,6 +53,7 @@ import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.MagnificationAnimationCallback;
import android.view.animation.DecelerateInterpolator;
+import android.widget.Scroller;
import com.android.internal.R;
import com.android.internal.accessibility.common.MagnificationConstants;
@@ -60,6 +63,7 @@ import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.accessibility.Flags;
import com.android.server.wm.WindowManagerInternal;
import java.util.ArrayList;
@@ -86,6 +90,8 @@ public class FullScreenMagnificationController implements
private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
private final Object mLock;
+ private final Supplier<Scroller> mScrollerSupplier;
+ private final Supplier<TimeAnimator> mTimeAnimatorSupplier;
private final ControllerContext mControllerCtx;
@@ -149,7 +155,13 @@ public class FullScreenMagnificationController implements
DisplayMagnification(int displayId) {
mDisplayId = displayId;
- mSpecAnimationBridge = new SpecAnimationBridge(mControllerCtx, mLock, mDisplayId);
+ mSpecAnimationBridge =
+ new SpecAnimationBridge(
+ mControllerCtx,
+ mLock,
+ mDisplayId,
+ mScrollerSupplier,
+ mTimeAnimatorSupplier);
}
/**
@@ -406,6 +418,52 @@ public class FullScreenMagnificationController implements
}
}
+ void startFlingAnimation(
+ float xPixelsPerSecond,
+ float yPixelsPerSecond,
+ MagnificationAnimationCallback animationCallback
+ ) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG,
+ "startFlingAnimation(spec = " + xPixelsPerSecond + ", animationCallback = "
+ + animationCallback + ")");
+ }
+ if (Thread.currentThread().getId() == mMainThreadId) {
+ mSpecAnimationBridge.startFlingAnimation(
+ xPixelsPerSecond,
+ yPixelsPerSecond,
+ getMinOffsetXLocked(),
+ getMaxOffsetXLocked(),
+ getMinOffsetYLocked(),
+ getMaxOffsetYLocked(),
+ animationCallback);
+ } else {
+ final Message m =
+ PooledLambda.obtainMessage(
+ SpecAnimationBridge::startFlingAnimation,
+ mSpecAnimationBridge,
+ xPixelsPerSecond,
+ yPixelsPerSecond,
+ getMinOffsetXLocked(),
+ getMaxOffsetXLocked(),
+ getMinOffsetYLocked(),
+ getMaxOffsetYLocked(),
+ animationCallback);
+ mControllerCtx.getHandler().sendMessage(m);
+ }
+ }
+
+ void cancelFlingAnimation() {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "cancelFlingAnimation()");
+ }
+ if (Thread.currentThread().getId() == mMainThreadId) {
+ mSpecAnimationBridge.cancelFlingAnimation();
+ } else {
+ mControllerCtx.getHandler().post(mSpecAnimationBridge::cancelFlingAnimation);
+ }
+ }
+
/**
* Get the ID of the last service that changed the magnification spec.
*
@@ -759,6 +817,63 @@ public class FullScreenMagnificationController implements
sendSpecToAnimation(mCurrentMagnificationSpec, null);
}
+ @GuardedBy("mLock")
+ void startFling(float xPixelsPerSecond, float yPixelsPerSecond, int id) {
+ if (!mRegistered) {
+ return;
+ }
+ if (!isActivated()) {
+ return;
+ }
+
+ if (id != INVALID_SERVICE_ID) {
+ mIdOfLastServiceToMagnify = id;
+ }
+
+ startFlingAnimation(
+ xPixelsPerSecond,
+ yPixelsPerSecond,
+ new MagnificationAnimationCallback() {
+ @Override
+ public void onResult(boolean success) {
+ // never called
+ }
+
+ @Override
+ public void onResult(boolean success, MagnificationSpec lastSpecSent) {
+ if (DEBUG) {
+ Slog.i(
+ LOG_TAG,
+ "startFlingAnimation finished( "
+ + success
+ + " = "
+ + lastSpecSent.offsetX
+ + ", "
+ + lastSpecSent.offsetY
+ + ")");
+ }
+ synchronized (mLock) {
+ mCurrentMagnificationSpec.setTo(lastSpecSent);
+ onMagnificationChangedLocked();
+ }
+ }
+ });
+ }
+
+
+ @GuardedBy("mLock")
+ void cancelFling(int id) {
+ if (!mRegistered) {
+ return;
+ }
+
+ if (id != INVALID_SERVICE_ID) {
+ mIdOfLastServiceToMagnify = id;
+ }
+
+ cancelFlingAnimation();
+ }
+
boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
if (DEBUG) {
Slog.i(LOG_TAG,
@@ -838,7 +953,9 @@ public class FullScreenMagnificationController implements
magnificationInfoChangedCallback,
scaleProvider,
/* thumbnailSupplier= */ null,
- backgroundExecutor);
+ backgroundExecutor,
+ () -> new Scroller(context),
+ TimeAnimator::new);
}
/** Constructor for tests */
@@ -849,9 +966,13 @@ public class FullScreenMagnificationController implements
@NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback,
@NonNull MagnificationScaleProvider scaleProvider,
Supplier<MagnificationThumbnail> thumbnailSupplier,
- @NonNull Executor backgroundExecutor) {
+ @NonNull Executor backgroundExecutor,
+ Supplier<Scroller> scrollerSupplier,
+ Supplier<TimeAnimator> timeAnimatorSupplier) {
mControllerCtx = ctx;
mLock = lock;
+ mScrollerSupplier = scrollerSupplier;
+ mTimeAnimatorSupplier = timeAnimatorSupplier;
mMainThreadId = mControllerCtx.getContext().getMainLooper().getThread().getId();
mScreenStateObserver = new ScreenStateObserver(mControllerCtx.getContext(), this);
addInfoChangedCallback(magnificationInfoChangedCallback);
@@ -1437,6 +1558,42 @@ public class FullScreenMagnificationController implements
}
/**
+ * Call after a pan ends, if the velocity has passed the threshold, to start a fling animation.
+ *
+ * @param displayId The logical display id.
+ * @param xPixelsPerSecond the velocity of the last pan gesture in the X direction, in current
+ * screen pixels per second.
+ * @param yPixelsPerSecond the velocity of the last pan gesture in the Y direction, in current
+ * screen pixels per second.
+ * @param id the ID of the service requesting the change
+ */
+ public void startFling(int displayId, float xPixelsPerSecond, float yPixelsPerSecond, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.startFling(xPixelsPerSecond, yPixelsPerSecond, id);
+ }
+ }
+
+ /**
+ * Call to cancel the fling animation if it is running. Call this on any ACTION_DOWN event.
+ *
+ * @param displayId The logical display id.
+ * @param id the ID of the service requesting the change
+ */
+ public void cancelFling(int displayId, int id) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.cancelFling(id);
+ }
+ }
+
+ /**
* Get the ID of the last service that changed the magnification spec.
*
* @param displayId The logical display id.
@@ -1698,7 +1855,15 @@ public class FullScreenMagnificationController implements
@GuardedBy("mLock")
private boolean mEnabled = false;
- private SpecAnimationBridge(ControllerContext ctx, Object lock, int displayId) {
+ private final Scroller mScroller;
+ private final TimeAnimator mScrollAnimator;
+
+ private SpecAnimationBridge(
+ ControllerContext ctx,
+ Object lock,
+ int displayId,
+ Supplier<Scroller> scrollerSupplier,
+ Supplier<TimeAnimator> timeAnimatorSupplier) {
mControllerCtx = ctx;
mLock = lock;
mDisplayId = displayId;
@@ -1709,6 +1874,37 @@ public class FullScreenMagnificationController implements
mValueAnimator.setFloatValues(0.0f, 1.0f);
mValueAnimator.addUpdateListener(this);
mValueAnimator.addListener(this);
+
+ if (Flags.fullscreenFlingGesture()) {
+ mScroller = scrollerSupplier.get();
+ mScrollAnimator = timeAnimatorSupplier.get();
+ mScrollAnimator.addListener(this);
+ mScrollAnimator.setTimeListener(
+ (animation, totalTime, deltaTime) -> {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.v(
+ LOG_TAG,
+ "onScrollAnimationUpdate: "
+ + mEnabled + " : " + totalTime);
+ }
+
+ if (mEnabled) {
+ if (!mScroller.computeScrollOffset()) {
+ animation.end();
+ return;
+ }
+
+ mEndMagnificationSpec.offsetX = mScroller.getCurrX();
+ mEndMagnificationSpec.offsetY = mScroller.getCurrY();
+ setMagnificationSpecLocked(mEndMagnificationSpec);
+ }
+ }
+ });
+ } else {
+ mScroller = null;
+ mScrollAnimator = null;
+ }
}
/**
@@ -1735,16 +1931,20 @@ public class FullScreenMagnificationController implements
}
}
- void updateSentSpecMainThread(MagnificationSpec spec,
- MagnificationAnimationCallback animationCallback) {
- if (mValueAnimator.isRunning()) {
- mValueAnimator.cancel();
- }
+ @MainThread
+ void updateSentSpecMainThread(
+ MagnificationSpec spec, MagnificationAnimationCallback animationCallback) {
+ cancelAnimations();
mAnimationCallback = animationCallback;
// If the current and sent specs don't match, update the sent spec.
synchronized (mLock) {
final boolean changed = !mSentMagnificationSpec.equals(spec);
+ if (DEBUG_SET_MAGNIFICATION_SPEC) {
+ Slog.d(
+ LOG_TAG,
+ "updateSentSpecMainThread: " + mEnabled + " : changed: " + changed);
+ }
if (changed) {
if (mAnimationCallback != null) {
animateMagnificationSpecLocked(spec);
@@ -1757,12 +1957,13 @@ public class FullScreenMagnificationController implements
}
}
+ @MainThread
private void sendEndCallbackMainThread(boolean success) {
if (mAnimationCallback != null) {
if (DEBUG) {
Slog.d(LOG_TAG, "sendEndCallbackMainThread: " + success);
}
- mAnimationCallback.onResult(success);
+ mAnimationCallback.onResult(success, mSentMagnificationSpec);
mAnimationCallback = null;
}
}
@@ -1830,6 +2031,77 @@ public class FullScreenMagnificationController implements
public void onAnimationRepeat(Animator animation) {
}
+
+ /**
+ * Call after a pan ends, if the velocity has passed the threshold, to start a fling
+ * animation.
+ */
+ @MainThread
+ public void startFlingAnimation(
+ float xPixelsPerSecond,
+ float yPixelsPerSecond,
+ float minX,
+ float maxX,
+ float minY,
+ float maxY,
+ MagnificationAnimationCallback animationCallback
+ ) {
+ if (!Flags.fullscreenFlingGesture()) {
+ return;
+ }
+ cancelAnimations();
+
+ mAnimationCallback = animationCallback;
+
+ // We use this as a temp object to send updates every animation frame, so make sure it
+ // matches the current spec before we start.
+ mEndMagnificationSpec.setTo(mSentMagnificationSpec);
+
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "startFlingAnimation: "
+ + "offsetX " + mSentMagnificationSpec.offsetX
+ + "offsetY " + mSentMagnificationSpec.offsetY
+ + "xPixelsPerSecond " + xPixelsPerSecond
+ + "yPixelsPerSecond " + yPixelsPerSecond
+ + "minX " + minX
+ + "maxX " + maxX
+ + "minY " + minY
+ + "maxY " + maxY
+ );
+ }
+
+ mScroller.fling(
+ (int) mSentMagnificationSpec.offsetX,
+ (int) mSentMagnificationSpec.offsetY,
+ (int) xPixelsPerSecond,
+ (int) yPixelsPerSecond,
+ (int) minX,
+ (int) maxX,
+ (int) minY,
+ (int) maxY);
+
+ mScrollAnimator.start();
+ }
+
+ @MainThread
+ void cancelAnimations() {
+ if (mValueAnimator.isRunning()) {
+ mValueAnimator.cancel();
+ }
+
+ cancelFlingAnimation();
+ }
+
+ @MainThread
+ void cancelFlingAnimation() {
+ if (!Flags.fullscreenFlingGesture()) {
+ return;
+ }
+ if (mScrollAnimator.isRunning()) {
+ mScrollAnimator.cancel();
+ }
+ mScroller.forceFinished(true);
+ }
}
private static class ScreenStateObserver extends BroadcastReceiver {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index baae1d934e8c..f4ea754c21ba 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -62,6 +62,7 @@ import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
+import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import com.android.internal.R;
@@ -174,6 +175,10 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
private final boolean mIsWatch;
+ @Nullable private VelocityTracker mVelocityTracker;
+ private final int mMinimumVelocity;
+ private final int mMaximumVelocity;
+
public FullScreenMagnificationGestureHandler(@UiContext Context context,
FullScreenMagnificationController fullScreenMagnificationController,
AccessibilityTraceManager trace,
@@ -184,15 +189,25 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
@NonNull WindowMagnificationPromptController promptController,
int displayId,
FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper) {
- this(context, fullScreenMagnificationController, trace, callback,
- detectSingleFingerTripleTap, detectTwoFingerTripleTap,
- detectShortcutTrigger, promptController, displayId,
- fullScreenMagnificationVibrationHelper, /* magnificationLogger= */ null);
+ this(
+ context,
+ fullScreenMagnificationController,
+ trace,
+ callback,
+ detectSingleFingerTripleTap,
+ detectTwoFingerTripleTap,
+ detectShortcutTrigger,
+ promptController,
+ displayId,
+ fullScreenMagnificationVibrationHelper,
+ /* magnificationLogger= */ null,
+ ViewConfiguration.get(context));
}
/** Constructor for tests. */
@VisibleForTesting
- FullScreenMagnificationGestureHandler(@UiContext Context context,
+ FullScreenMagnificationGestureHandler(
+ @UiContext Context context,
FullScreenMagnificationController fullScreenMagnificationController,
AccessibilityTraceManager trace,
Callback callback,
@@ -202,7 +217,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
@NonNull WindowMagnificationPromptController promptController,
int displayId,
FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper,
- MagnificationLogger magnificationLogger) {
+ MagnificationLogger magnificationLogger,
+ ViewConfiguration viewConfiguration) {
super(displayId, detectSingleFingerTripleTap, detectTwoFingerTripleTap,
detectShortcutTrigger, trace, callback);
if (DEBUG_ALL) {
@@ -212,6 +228,15 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
+ ", detectTwoFingerTripleTap = " + detectTwoFingerTripleTap
+ ", detectShortcutTrigger = " + detectShortcutTrigger + ")");
}
+
+ if (Flags.fullscreenFlingGesture()) {
+ mMinimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
+ mMaximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
+ } else {
+ mMinimumVelocity = 0;
+ mMaximumVelocity = 0;
+ }
+
mFullScreenMagnificationController = fullScreenMagnificationController;
mMagnificationInfoChangedCallback =
new FullScreenMagnificationController.MagnificationInfoChangedCallback() {
@@ -299,6 +324,10 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
@Override
void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (event.getActionMasked() == ACTION_DOWN) {
+ cancelFling();
+ }
+
handleEventWith(mCurrentState, event, rawEvent, policyFlags);
}
@@ -501,6 +530,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
}
persistScaleAndTransitionTo(mViewportDraggingState);
} else if (action == ACTION_UP || action == ACTION_CANCEL) {
+ onPanningFinished(event);
// if feature flag is enabled, currently only true on watches
if (mIsSinglePanningEnabled) {
mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
@@ -578,6 +608,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
Slog.i(mLogTag, "Panned content by scrollX: " + distanceX
+ " scrollY: " + distanceY);
}
+ onPan(second);
mFullScreenMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
if (mIsSinglePanningEnabled) {
@@ -973,7 +1004,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
&& overscrollState(event, mFirstPointerDownLocation)
== OVERSCROLL_VERTICAL_EDGE) {
transitionToDelegatingStateAndClear();
- }
+ } // TODO(b/319537921): should there be an else here?
//Primary pointer is swiping, so transit to PanningScalingState
transitToPanningScalingStateAndClear();
} else if (mIsSinglePanningEnabled
@@ -982,7 +1013,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
if (overscrollState(event, mFirstPointerDownLocation)
== OVERSCROLL_VERTICAL_EDGE) {
transitionToDelegatingStateAndClear();
- }
+ } // TODO(b/319537921): should there be an else here?
transitToSinglePanningStateAndClear();
} else if (!mIsTwoFingerCountReached) {
// If it is a two-finger gesture, do not transition to the
@@ -1742,6 +1773,71 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
}
}
+ /** Call during MOVE events for a panning gesture. */
+ private void onPan(MotionEvent event) {
+ if (!Flags.fullscreenFlingGesture()) {
+ return;
+ }
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(event);
+ }
+
+ /**
+ * Call during UP events for a panning gesture, so we can detect a fling and play a physics-
+ * based fling animation.
+ */
+ private void onPanningFinished(MotionEvent event) {
+ if (!Flags.fullscreenFlingGesture()) {
+ return;
+ }
+
+ if (mVelocityTracker == null) {
+ Log.e(mLogTag, "onPanningFinished: mVelocityTracker is null");
+ return;
+ }
+ mVelocityTracker.addMovement(event);
+ mVelocityTracker.computeCurrentVelocity(/* units= */ 1000, mMaximumVelocity);
+
+ float xPixelsPerSecond = mVelocityTracker.getXVelocity();
+ float yPixelsPerSecond = mVelocityTracker.getYVelocity();
+
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+
+ if (DEBUG_PANNING_SCALING) {
+ Slog.v(
+ mLogTag,
+ "onPanningFinished: pixelsPerSecond: "
+ + xPixelsPerSecond
+ + ", "
+ + yPixelsPerSecond
+ + " mMinimumVelocity: "
+ + mMinimumVelocity);
+ }
+
+ if ((Math.abs(yPixelsPerSecond) > mMinimumVelocity)
+ || (Math.abs(xPixelsPerSecond) > mMinimumVelocity)) {
+ mFullScreenMagnificationController.startFling(
+ mDisplayId,
+ xPixelsPerSecond,
+ yPixelsPerSecond,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ }
+ }
+
+ private void cancelFling() {
+ if (!Flags.fullscreenFlingGesture()) {
+ return;
+ }
+
+ mFullScreenMagnificationController.cancelFling(
+ mDisplayId,
+ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
+ }
+
final class SinglePanningState extends SimpleOnGestureListener implements State {
@@ -1756,6 +1852,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
int action = event.getActionMasked();
switch (action) {
case ACTION_UP:
+ onPanningFinished(event);
+ // fall-through!
case ACTION_CANCEL:
mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
mOverscrollHandler.clearEdgeState();
@@ -1770,6 +1868,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
if (mCurrentState != mSinglePanningState) {
return true;
}
+ onPan(second);
mFullScreenMagnificationController.offsetMagnifiedRegion(
mDisplayId,
distanceX,
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index 586aa8aaae98..af0777c74605 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -16,6 +16,8 @@
package com.android.server.companion;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -25,8 +27,10 @@ import android.companion.CompanionDeviceService;
import android.companion.DevicePresenceEvent;
import android.content.ComponentName;
import android.content.Context;
+import android.hardware.power.Mode;
import android.os.Handler;
import android.os.ParcelUuid;
+import android.os.PowerManagerInternal;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -79,6 +83,8 @@ public class CompanionApplicationController {
private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final @NonNull CompanionServicesRegister mCompanionServicesRegister;
+ private final PowerManagerInternal mPowerManagerInternal;
+
@GuardedBy("mBoundCompanionApplications")
private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>>
mBoundCompanionApplications;
@@ -87,11 +93,13 @@ public class CompanionApplicationController {
CompanionApplicationController(Context context, AssociationStore associationStore,
ObservableUuidStore observableUuidStore,
- CompanionDevicePresenceMonitor companionDevicePresenceMonitor) {
+ CompanionDevicePresenceMonitor companionDevicePresenceMonitor,
+ PowerManagerInternal powerManagerInternal) {
mContext = context;
mAssociationStore = associationStore;
mObservableUuidStore = observableUuidStore;
mDevicePresenceMonitor = companionDevicePresenceMonitor;
+ mPowerManagerInternal = powerManagerInternal;
mCompanionServicesRegister = new CompanionServicesRegister();
mBoundCompanionApplications = new AndroidPackageMap<>();
mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>();
@@ -364,9 +372,21 @@ public class CompanionApplicationController {
boolean isPrimary = serviceConnector.isPrimary();
Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary);
- // First: Only mark not BOUND for primary service.
- synchronized (mBoundCompanionApplications) {
- if (serviceConnector.isPrimary()) {
+ // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY.
+ if (isPrimary) {
+ final List<AssociationInfo> associations =
+ mAssociationStore.getAssociationsForPackage(userId, packageName);
+
+ for (AssociationInfo association : associations) {
+ final String deviceProfile = association.getDeviceProfile();
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
+ mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
+ break;
+ }
+ }
+
+ synchronized (mBoundCompanionApplications) {
mBoundCompanionApplications.removePackage(userId, packageName);
}
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 5019428c5323..0054bc87af77 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -20,7 +20,6 @@ package com.android.server.companion;
import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
-import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
@@ -270,7 +269,8 @@ public class CompanionDeviceManagerService extends SystemService {
mAssociationStore, mObservableUuidStore, mDevicePresenceCallback);
mCompanionAppController = new CompanionApplicationController(
- context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor);
+ context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor,
+ mPowerManagerInternal);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
@@ -1128,7 +1128,9 @@ public class CompanionDeviceManagerService extends SystemService {
mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId);
- if (association.getDeviceProfile() == REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) {
+ final String deviceProfile = association.getDeviceProfile();
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ Slog.i(TAG, "Enable hint mode for device device profile: " + deviceProfile);
mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, true);
}
}
@@ -1146,7 +1148,9 @@ public class CompanionDeviceManagerService extends SystemService {
mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId);
- if (association.getDeviceProfile() == REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) {
+ final String deviceProfile = association.getDeviceProfile();
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
}
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 10cd6e5bb136..d110349c9fc1 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1950,7 +1950,8 @@ public final class ProcessList {
mService.mNativeDebuggingApp = null;
}
- if (app.info.isEmbeddedDexUsed()) {
+ if (app.info.isEmbeddedDexUsed()
+ || (app.processInfo != null && app.processInfo.useEmbeddedDex)) {
runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e930627b55af..7ebc311b5ee8 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -695,14 +695,14 @@ public final class DisplayManagerService extends SystemService {
logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(),
userSerial);
dpc.setBrightnessConfiguration(config, /* shouldResetShortTermModel= */ true);
- // change the brightness value according to the selected user.
- final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked();
- if (device != null) {
- dpc.setBrightness(
- mPersistentDataStore.getBrightness(device, userSerial), userSerial);
- }
}
- dpc.onSwitchUser(newUserId);
+ final DisplayDevice device = logicalDisplay.getPrimaryDisplayDeviceLocked();
+ float newBrightness = device == null ? PowerManager.BRIGHTNESS_INVALID_FLOAT
+ : mPersistentDataStore.getBrightness(device, userSerial);
+ if (Float.isNaN(newBrightness)) {
+ newBrightness = logicalDisplay.getDisplayInfoLocked().brightnessDefault;
+ }
+ dpc.onSwitchUser(newUserId, userSerial, newBrightness);
});
handleSettingsChange();
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 087cacf9a570..1ca3923b325c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -685,17 +685,27 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
@Override
- public void onSwitchUser(@UserIdInt int newUserId) {
- Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId);
- mHandler.sendMessage(msg);
+ public void onSwitchUser(@UserIdInt int newUserId, int userSerial, float newBrightness) {
+ Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, newUserId, userSerial, newBrightness);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
- private void handleOnSwitchUser(@UserIdInt int newUserId) {
- handleSettingsChange(true /* userSwitch */);
+ private void handleOnSwitchUser(@UserIdInt int newUserId, int userSerial, float newBrightness) {
+ Slog.i(mTag, "Switching user newUserId=" + newUserId + " userSerial=" + userSerial
+ + " newBrightness=" + newBrightness);
handleBrightnessModeChange();
if (mBrightnessTracker != null) {
mBrightnessTracker.onSwitchUser(newUserId);
}
+ setBrightness(newBrightness, userSerial);
+
+ // Don't treat user switches as user initiated change.
+ mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(newBrightness);
+
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.resetShortTermModel();
+ }
+ sendUpdatePowerState();
}
@Nullable
@@ -2394,20 +2404,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
MetricsLogger.action(log);
}
- private void handleSettingsChange(boolean userSwitch) {
+ private void handleSettingsChange() {
mDisplayBrightnessController
.setPendingScreenBrightness(mDisplayBrightnessController
.getScreenBrightnessSetting());
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(userSwitch);
- if (userSwitch) {
- // Don't treat user switches as user initiated change.
- mDisplayBrightnessController
- .setAndNotifyCurrentScreenBrightness(mDisplayBrightnessController
- .getPendingScreenBrightness());
- if (mAutomaticBrightnessController != null) {
- mAutomaticBrightnessController.resetShortTermModel();
- }
- }
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
sendUpdatePowerState();
}
@@ -2416,11 +2417,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
- mHandler.postAtTime(() -> {
- mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
- == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- updatePowerState();
- }, mClock.uptimeMillis());
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
+ == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
}
@@ -2430,9 +2428,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
@Override
- public void setBrightness(float brightnessValue, int userSerial) {
- mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightnessValue),
- userSerial);
+ public void setBrightness(float brightness) {
+ mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness));
+ }
+
+ @Override
+ public void setBrightness(float brightness, int userSerial) {
+ mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness), userSerial);
}
@Override
@@ -2966,7 +2968,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
if (mStopped) {
return;
}
- handleSettingsChange(false /*userSwitch*/);
+ handleSettingsChange();
break;
case MSG_UPDATE_RBC:
@@ -2985,7 +2987,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
break;
case MSG_SWITCH_USER:
- handleOnSwitchUser(msg.arg1);
+ float newBrightness = msg.obj instanceof Float ? (float) msg.obj
+ : PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ handleOnSwitchUser(msg.arg1, msg.arg2, newBrightness);
break;
case MSG_BOOT_COMPLETED:
@@ -3023,7 +3027,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
@Override
public void onChange(boolean selfChange, Uri uri) {
if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
- handleBrightnessModeChange();
+ mHandler.postAtTime(() -> {
+ handleBrightnessModeChange();
+ updatePowerState();
+ }, mClock.uptimeMillis());
} else if (uri.equals(Settings.System.getUriFor(
Settings.System.SCREEN_BRIGHTNESS_FOR_ALS))) {
int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
@@ -3035,7 +3042,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
setUpAutoBrightness(mContext, mHandler);
sendUpdatePowerState();
} else {
- handleSettingsChange(false /* userSwitch */);
+ handleSettingsChange();
}
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index 13acb3f79c67..ecf1635a0cd2 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -30,7 +30,6 @@ import java.io.PrintWriter;
* An interface to manage the display's power state and brightness
*/
public interface DisplayPowerControllerInterface {
- int DEFAULT_USER_SERIAL = -1;
/**
* Notified when the display is changed.
*
@@ -100,15 +99,12 @@ public interface DisplayPowerControllerInterface {
* Set the screen brightness of the associated display
* @param brightness The value to which the brightness is to be set
*/
- default void setBrightness(float brightness) {
- setBrightness(brightness, DEFAULT_USER_SERIAL);
- }
+ void setBrightness(float brightness);
/**
* Set the screen brightness of the associated display
* @param brightness The value to which the brightness is to be set
- * @param userSerial The user for which the brightness value is to be set. Use userSerial = -1,
- * if brightness needs to be updated for the current user.
+ * @param userSerial The user for which the brightness value is to be set.
*/
void setBrightness(float brightness, int userSerial);
@@ -188,8 +184,10 @@ public interface DisplayPowerControllerInterface {
/**
* Handles the changes to be done to update the brightness when the user is changed
* @param newUserId The new userId
+ * @param userSerial The serial number of the new user
+ * @param newBrightness The brightness for the new user
*/
- void onSwitchUser(int newUserId);
+ void onSwitchUser(int newUserId, int userSerial, float newBrightness);
/**
* Get the ID of the display associated with this DPC.
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 3bb798689ea6..f6d02dbc46df 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -41,7 +41,6 @@ import java.io.PrintWriter;
* display. Applies the chosen brightness.
*/
public final class DisplayBrightnessController {
- private static final int DEFAULT_USER_SERIAL = -1;
// The ID of the display tied to this DisplayBrightnessController
private final int mDisplayId;
@@ -302,16 +301,8 @@ public final class DisplayBrightnessController {
* Notifies the brightnessSetting to persist the supplied brightness value.
*/
public void setBrightness(float brightnessValue) {
- setBrightness(brightnessValue, DEFAULT_USER_SERIAL);
- }
-
- /**
- * Notifies the brightnessSetting to persist the supplied brightness value for a user.
- */
- public void setBrightness(float brightnessValue, int userSerial) {
// Update the setting, which will eventually call back into DPC to have us actually
// update the display with the new value.
- mBrightnessSetting.setUserSerial(userSerial);
mBrightnessSetting.setBrightness(brightnessValue);
if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
float nits = convertToNits(brightnessValue);
@@ -322,6 +313,14 @@ public final class DisplayBrightnessController {
}
/**
+ * Notifies the brightnessSetting to persist the supplied brightness value for a user.
+ */
+ public void setBrightness(float brightnessValue, int userSerial) {
+ mBrightnessSetting.setUserSerial(userSerial);
+ setBrightness(brightnessValue);
+ }
+
+ /**
* Sets the current screen brightness, and notifies the BrightnessSetting about the change.
*/
public void updateScreenBrightnessSetting(float brightnessValue) {
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 3c23b5c10671..3e6e09da9753 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -203,14 +203,11 @@ public class AutomaticBrightnessStrategy {
* Sets the pending auto-brightness adjustments in the system settings. Executed
* when there is a change in the brightness system setting, or when there is a user switch.
*/
- public void updatePendingAutoBrightnessAdjustments(boolean userSwitch) {
+ public void updatePendingAutoBrightnessAdjustments() {
final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
mPendingAutoBrightnessAdjustment = Float.isNaN(adj) ? Float.NaN
: BrightnessUtils.clampBrightnessAdjustment(adj);
- if (userSwitch) {
- processPendingAutoBrightnessAdjustments();
- }
}
/**
diff --git a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
index 82786001ab68..202c8941c154 100644
--- a/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/AbsoluteVolumeAudioStatusAction.java
@@ -98,18 +98,21 @@ final class AbsoluteVolumeAudioStatusAction extends HdmiCecFeatureAction {
localDevice().getService().enableAbsoluteVolumeBehavior(audioStatus);
mState = STATE_MONITOR_AUDIO_STATUS;
} else if (mState == STATE_MONITOR_AUDIO_STATUS) {
- // On TV panels, we notify AudioService even if neither volume nor mute state changed.
- // This ensures that the user sees volume UI if they tried to adjust the AVR's volume,
- // even if the new volume level is the same as the previous one.
- boolean notifyAvbVolumeToShowUi = localDevice().getService().isTvDevice()
- && audioStatus.equals(mLastAudioStatus);
-
- if (audioStatus.getVolume() != mLastAudioStatus.getVolume()
- || notifyAvbVolumeToShowUi) {
+ // Update volume in AudioService if it has changed since the last <Report Audio Status>
+ boolean updateVolume = audioStatus.getVolume() != mLastAudioStatus.getVolume();
+ if (updateVolume) {
localDevice().getService().notifyAvbVolumeChange(audioStatus.getVolume());
}
- if (audioStatus.getMute() != mLastAudioStatus.getMute()) {
+ // Update mute in AudioService if any of the following conditions are met:
+ // - The mute status changed
+ // - The volume changed - we need to make sure mute is set correctly afterwards, since
+ // setting volume can affect mute status as well as a side effect.
+ // - We're a TV panel - we want to trigger volume UI on TV panels, so that the user
+ // always gets visual feedback when they attempt to adjust the AVR's volume/mute.
+ if ((audioStatus.getMute() != mLastAudioStatus.getMute())
+ || updateVolume
+ || localDevice().getService().isTvDevice()) {
localDevice().getService().notifyAvbMuteChange(audioStatus.getMute());
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index a61199ab908c..7726609e7075 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -170,9 +170,6 @@ public class InputManagerService extends IInputManager.Stub
private InputMethodManagerInternal mInputMethodManagerInternal;
- // Context cache used for loading pointer resources.
- private Context mPointerIconDisplayContext;
-
private final File mDoubleTouchGestureEnableFile;
private WindowManagerCallbacks mWindowManagerCallbacks;
@@ -416,6 +413,8 @@ public class InputManagerService extends IInputManager.Stub
new SparseArray<>();
@GuardedBy("mLoadedPointerIconsByDisplayAndType")
boolean mUseLargePointerIcons = false;
+ @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+ final SparseArray<Context> mDisplayContexts = new SparseArray<>();
final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
@Override
@@ -427,6 +426,7 @@ public class InputManagerService extends IInputManager.Stub
public void onDisplayRemoved(int displayId) {
synchronized (mLoadedPointerIconsByDisplayAndType) {
mLoadedPointerIconsByDisplayAndType.remove(displayId);
+ mDisplayContexts.remove(displayId);
}
}
@@ -440,6 +440,7 @@ public class InputManagerService extends IInputManager.Stub
return;
}
iconsByType.clear();
+ mDisplayContexts.remove(displayId);
}
mNative.reloadPointerIcons();
}
@@ -1323,11 +1324,6 @@ public class InputManagerService extends IInputManager.Stub
/** Clean up input window handles of the given display. */
public void onDisplayRemoved(int displayId) {
- if (mPointerIconDisplayContext != null
- && mPointerIconDisplayContext.getDisplay().getDisplayId() == displayId) {
- mPointerIconDisplayContext = null;
- }
-
updateAdditionalDisplayInputProperties(displayId, AdditionalDisplayInputProperties::reset);
// TODO(b/320763728): Rely on WindowInfosListener to determine when a display has been
@@ -2379,6 +2375,7 @@ public class InputManagerService extends IInputManager.Stub
synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
synchronized (mAdditionalDisplayInputPropertiesLock) { /* Test if blocked by props lock */ }
+ synchronized (mLoadedPointerIconsByDisplayAndType) { /* Test if blocked by pointer lock */}
mBatteryController.monitor();
mNative.monitor();
}
@@ -2782,7 +2779,7 @@ public class InputManagerService extends IInputManager.Stub
}
PointerIcon icon = iconsByType.get(type);
if (icon == null) {
- icon = PointerIcon.getLoadedSystemIcon(getContextForPointerIcon(displayId), type,
+ icon = PointerIcon.getLoadedSystemIcon(getContextForDisplay(displayId), type,
mUseLargePointerIcons);
iconsByType.put(type, icon);
}
@@ -2800,40 +2797,31 @@ public class InputManagerService extends IInputManager.Stub
return sc.mNativeObject;
}
+ @GuardedBy("mLoadedPointerIconsByDisplayAndType")
@NonNull
- private Context getContextForPointerIcon(int displayId) {
- if (mPointerIconDisplayContext != null
- && mPointerIconDisplayContext.getDisplay().getDisplayId() == displayId) {
- return mPointerIconDisplayContext;
- }
-
- // Create and cache context for non-default display.
- mPointerIconDisplayContext = getContextForDisplay(displayId);
-
- // Fall back to default display if the requested displayId does not exist.
- if (mPointerIconDisplayContext == null) {
- mPointerIconDisplayContext = getContextForDisplay(Display.DEFAULT_DISPLAY);
- }
- return mPointerIconDisplayContext;
- }
-
- @Nullable
private Context getContextForDisplay(int displayId) {
if (displayId == Display.INVALID_DISPLAY) {
- return null;
+ // Fallback to using the default context.
+ return mContext;
}
- if (mContext.getDisplay().getDisplayId() == displayId) {
+ if (displayId == mContext.getDisplay().getDisplayId()) {
return mContext;
}
- final DisplayManager displayManager = Objects.requireNonNull(
- mContext.getSystemService(DisplayManager.class));
- final Display display = displayManager.getDisplay(displayId);
- if (display == null) {
- return null;
- }
+ Context displayContext = mDisplayContexts.get(displayId);
+ if (displayContext == null) {
+ final DisplayManager displayManager = Objects.requireNonNull(
+ mContext.getSystemService(DisplayManager.class));
+ final Display display = displayManager.getDisplay(displayId);
+ if (display == null) {
+ // Fallback to using the default context.
+ return mContext;
+ }
- return mContext.createDisplayContext(display);
+ displayContext = mContext.createDisplayContext(display);
+ mDisplayContexts.put(displayId, displayContext);
+ }
+ return displayContext;
}
// Native callback.
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index f852b8173f30..097daf2e51e6 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -97,7 +97,7 @@ public final class NotificationAttentionHelper {
private static final float DEFAULT_VOLUME = 1.0f;
// TODO (b/291899544): remove for release
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED = 1;
- private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 0;
+ private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 1;
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0;
@@ -1405,6 +1405,7 @@ public final class NotificationAttentionHelper {
long timestampMillis) {
super.setLastNotificationUpdateTimeMs(record, timestampMillis);
mLastNotificationTimestamp = timestampMillis;
+ mAppStrategy.setLastNotificationUpdateTimeMs(record, timestampMillis);
}
long getLastNotificationUpdateTimeMs(final NotificationRecord record) {
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index f1dc2849a391..b9aa28e58879 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -49,6 +49,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
+import android.content.pm.UserProperties;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -268,7 +269,8 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
private boolean canRequestInteractAcrossProfilesUnchecked(String packageName) {
final int callingUserId = mInjector.getCallingUserId();
final int[] enabledProfileIds =
- mInjector.getUserManager().getEnabledProfileIds(callingUserId);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(
+ callingUserId, /* enabled= */ true);
if (enabledProfileIds.length < 2) {
return false;
}
@@ -350,7 +352,8 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
String packageName, @UserIdInt int userId) {
return mInjector.withCleanCallingIdentity(() -> {
final int[] enabledProfileIds =
- mInjector.getUserManager().getEnabledProfileIds(userId);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+ true);
List<UserHandle> targetProfiles = new ArrayList<>();
for (final int profileId : enabledProfileIds) {
@@ -466,7 +469,8 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
return;
}
final int[] profileIds =
- mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+ false);
for (int profileId : profileIds) {
if (!isPackageInstalled(packageName, profileId)) {
continue;
@@ -632,7 +636,8 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
private boolean canUserAttemptToConfigureInteractAcrossProfiles(
String packageName, @UserIdInt int userId) {
final int[] profileIds =
- mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+ false);
if (profileIds.length < 2) {
return false;
}
@@ -676,7 +681,8 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
private boolean hasOtherProfileWithPackageInstalled(String packageName, @UserIdInt int userId) {
return mInjector.withCleanCallingIdentity(() -> {
final int[] profileIds =
- mInjector.getUserManager().getProfileIds(userId, /* enabledOnly= */ false);
+ mInjector.getUserManager().getProfileIdsExcludingHidden(userId, /* enabled= */
+ false);
for (int profileId : profileIds) {
if (profileId != userId && isPackageInstalled(packageName, profileId)) {
return true;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c0596bb10823..796edde7d9bd 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1385,7 +1385,7 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
- return getProfileIds(userId, null, enabledOnly);
+ return getProfileIds(userId, null, enabledOnly, /* excludeHidden */ false);
}
// TODO(b/142482943): Probably @Override and make this accessible in UserManager.
@@ -1397,14 +1397,14 @@ public class UserManagerService extends IUserManager.Stub {
* If enabledOnly, only returns users that are not {@link UserInfo#FLAG_DISABLED}.
*/
public int[] getProfileIds(@UserIdInt int userId, @Nullable String userType,
- boolean enabledOnly) {
+ boolean enabledOnly, boolean excludeHidden) {
if (userId != UserHandle.getCallingUserId()) {
checkQueryOrCreateUsersPermission("getting profiles related to user " + userId);
}
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mUsersLock) {
- return getProfileIdsLU(userId, userType, enabledOnly).toArray();
+ return getProfileIdsLU(userId, userType, enabledOnly, excludeHidden).toArray();
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -1415,7 +1415,8 @@ public class UserManagerService extends IUserManager.Stub {
@GuardedBy("mUsersLock")
private List<UserInfo> getProfilesLU(@UserIdInt int userId, @Nullable String userType,
boolean enabledOnly, boolean fullInfo) {
- IntArray profileIds = getProfileIdsLU(userId, userType, enabledOnly);
+ IntArray profileIds = getProfileIdsLU(userId, userType, enabledOnly, /* excludeHidden */
+ false);
ArrayList<UserInfo> users = new ArrayList<>(profileIds.size());
for (int i = 0; i < profileIds.size(); i++) {
int profileId = profileIds.get(i);
@@ -1440,7 +1441,7 @@ public class UserManagerService extends IUserManager.Stub {
*/
@GuardedBy("mUsersLock")
private IntArray getProfileIdsLU(@UserIdInt int userId, @Nullable String userType,
- boolean enabledOnly) {
+ boolean enabledOnly, boolean excludeHidden) {
UserInfo user = getUserInfoLU(userId);
IntArray result = new IntArray(mUsers.size());
if (user == null) {
@@ -1465,11 +1466,36 @@ public class UserManagerService extends IUserManager.Stub {
if (userType != null && !userType.equals(profile.userType)) {
continue;
}
+ if (excludeHidden && isProfileHidden(userId)) {
+ continue;
+ }
result.add(profile.id);
}
return result;
}
+ /*
+ * Returns all the users that are in the same profile group as userId excluding those with
+ * {@link UserProperties#getProfileApiVisibility()} set to hidden. The returned list includes
+ * the user itself.
+ */
+ // TODO (b/323011770): Add a permission check to make an exception for App stores if we end
+ // up supporting Private Space on COPE devices
+ @Override
+ public int[] getProfileIdsExcludingHidden(@UserIdInt int userId, boolean enabledOnly) {
+ return getProfileIds(userId, null, enabledOnly, /* excludeHidden */ true);
+ }
+
+ private boolean isProfileHidden(int userId) {
+ UserProperties userProperties = getUserPropertiesCopy(userId);
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enableHidingProfiles()) {
+ return userProperties.getProfileApiVisibility()
+ == UserProperties.PROFILE_API_VISIBILITY_HIDDEN;
+ }
+ return false;
+ }
+
@Override
public int getCredentialOwnerProfile(@UserIdInt int userId) {
checkManageUsersPermission("get the credential owner");
@@ -3630,7 +3656,8 @@ public class UserManagerService extends IUserManager.Stub {
return 0;
}
- final int userTypeCount = getProfileIds(userId, userType, false).length;
+ final int userTypeCount = getProfileIds(userId, userType, false, /* excludeHidden */
+ false).length;
final int profilesRemovedCount = userTypeCount > 0 && allowedToRemoveOne ? 1 : 0;
final int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU()
- profilesRemovedCount;
@@ -5931,7 +5958,8 @@ public class UserManagerService extends IUserManager.Stub {
}
userData = mUsers.get(userId);
isProfile = userData.info.isProfile();
- profileIds = isProfile ? null : getProfileIdsLU(userId, null, false);
+ profileIds = isProfile ? null : getProfileIdsLU(userId, null, false, /* excludeHidden */
+ false);
}
if (!isProfile) {
@@ -7458,7 +7486,8 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public @NonNull int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
synchronized (mUsersLock) {
- return getProfileIdsLU(userId, null /* userType */, enabledOnly).toArray();
+ return getProfileIdsLU(userId, null /* userType */, enabledOnly, /* excludeHidden */
+ false).toArray();
}
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 6ed2d3126455..a9e5a5434189 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -831,7 +831,7 @@ public class PackageInfoUtils {
retProcs.put(proc.getName(),
new ProcessInfo(proc.getName(), new ArraySet<>(proc.getDeniedPermissions()),
proc.getGwpAsanMode(), proc.getMemtagMode(),
- proc.getNativeHeapZeroInitialized()));
+ proc.getNativeHeapZeroInitialized(), proc.isUseEmbeddedDex()));
}
return retProcs;
}
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 6f754391f1ca..35717af962ef 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -691,16 +691,26 @@ public final class HintManagerService extends SystemService {
TextUtils.formatSimple("Actual total duration (%d) should be greater than 0",
workDuration.getActualTotalDurationNanos()));
}
- if (workDuration.getActualCpuDurationNanos() <= 0) {
+ if (workDuration.getActualCpuDurationNanos() < 0) {
throw new IllegalArgumentException(
- TextUtils.formatSimple("Actual CPU duration (%d) should be greater than 0",
+ TextUtils.formatSimple(
+ "Actual CPU duration (%d) should be greater than or equal to 0",
workDuration.getActualCpuDurationNanos()));
}
if (workDuration.getActualGpuDurationNanos() < 0) {
throw new IllegalArgumentException(
- TextUtils.formatSimple("Actual GPU duration (%d) should be non negative",
+ TextUtils.formatSimple(
+ "Actual GPU duration (%d) should greater than or equal to 0",
workDuration.getActualGpuDurationNanos()));
}
+ if (workDuration.getActualCpuDurationNanos()
+ + workDuration.getActualGpuDurationNanos() <= 0) {
+ throw new IllegalArgumentException(
+ TextUtils.formatSimple(
+ "The actual CPU duration (%d) and the actual GPU duration (%d)"
+ + " should not both be 0", workDuration.getActualCpuDurationNanos(),
+ workDuration.getActualGpuDurationNanos()));
+ }
}
private void onProcStateChanged(boolean updateAllowed) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 8c27bb80d337..118985a729aa 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -145,7 +145,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -613,8 +612,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
private final Context mContext;
- private final AtomicBoolean mIsInitialBinding = new AtomicBoolean(true);
- private final ServiceThread mHandlerThread;
+ private boolean mInitialUserSwitch = true;
+ private ServiceThread mHandlerThread;
private final WindowManagerInternal mWindowManagerInternal;
private final PackageManagerInternal mPackageManagerInternal;
private final IPackageManager mIPackageManager;
@@ -1474,12 +1473,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
public WallpaperManagerService(Context context) {
if (DEBUG) Slog.v(TAG, "WallpaperService startup");
mContext = context;
- if (Flags.bindWallpaperServiceOnItsOwnThreadDuringAUserSwitch()) {
- mHandlerThread = new ServiceThread(TAG, THREAD_PRIORITY_FOREGROUND, true /*allowIo*/);
- mHandlerThread.start();
- } else {
- mHandlerThread = null;
- }
mShuttingDown = false;
mImageWallpaper = ComponentName.unflattenFromString(
context.getResources().getString(R.string.image_wallpaper_component));
@@ -1803,6 +1796,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
switchWallpaper(lockWallpaper, null);
}
switchWallpaper(systemWallpaper, reply);
+ mInitialUserSwitch = false;
}
// Offload color extraction to another thread since switchUser will be called
@@ -3326,11 +3320,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
com.android.internal.R.bool.config_wallpaperTopApp)) {
bindFlags |= Context.BIND_SCHEDULE_LIKE_TOP_APP;
}
- Handler handler = Flags.bindWallpaperServiceOnItsOwnThreadDuringAUserSwitch()
- && !mIsInitialBinding.compareAndSet(true, false)
- ? mHandlerThread.getThreadHandler() : mContext.getMainThreadHandler();
- boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn, bindFlags, handler,
- new UserHandle(serviceUserId));
+ boolean bindSuccess = mContext.bindServiceAsUser(intent, newConn, bindFlags,
+ getHandlerForBindingWallpaperLocked(), new UserHandle(serviceUserId));
if (!bindSuccess) {
String msg = "Unable to bind service: " + componentName;
if (fromUser) {
@@ -3358,6 +3349,20 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return true;
}
+ private Handler getHandlerForBindingWallpaperLocked() {
+ if (!Flags.bindWallpaperServiceOnItsOwnThreadDuringAUserSwitch()) {
+ return mContext.getMainThreadHandler();
+ }
+ if (mInitialUserSwitch) {
+ return mContext.getMainThreadHandler();
+ }
+ if (mHandlerThread == null) {
+ mHandlerThread = new ServiceThread(TAG, THREAD_PRIORITY_FOREGROUND, true /*allowIo*/);
+ mHandlerThread.start();
+ }
+ return mHandlerThread.getThreadHandler();
+ }
+
// Updates tracking of the currently bound wallpapers.
private void updateCurrentWallpapers(WallpaperData newWallpaper) {
if (newWallpaper.userId != mCurrentUserId || newWallpaper.equals(mFallbackWallpaper)) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b1d04c9ddb16..7b5975928308 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -122,6 +122,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED;
+import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
@@ -386,6 +387,7 @@ import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import com.android.server.wm.WindowManagerService.H;
import com.android.server.wm.utils.InsetUtils;
+import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
@@ -960,6 +962,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// without security checks
final Binder shareableActivityToken = new Binder();
+ // Token for accessing the initial caller who started the activity.
+ final IBinder initialCallerInfoAccessToken = new Binder();
+
// Tracking cookie for the launch of this activity and it's task.
IBinder mLaunchCookie;
@@ -986,6 +991,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Whether the ActivityEmbedding is enabled on the app.
private final boolean mAppActivityEmbeddingSplitsEnabled;
+ // Whether the Activity allows state sharing in untrusted embedding
+ private final boolean mAllowUntrustedEmbeddingStateSharing;
+
// Records whether client has overridden the WindowAnimation_(Open/Close)(Enter/Exit)Animation.
private CustomAppTransition mCustomOpenTransition;
private CustomAppTransition mCustomCloseTransition;
@@ -2223,6 +2231,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// No such property name.
}
mAppActivityEmbeddingSplitsEnabled = appActivityEmbeddingEnabled;
+ mAllowUntrustedEmbeddingStateSharing = getAllowUntrustedEmbeddingStateSharingProperty();
mOptInOnBackInvoked = WindowOnBackInvokedDispatcher
.isOnBackInvokedCallbackEnabled(info, info.applicationInfo,
@@ -3078,6 +3087,32 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return parent != null && parent.isEmbedded();
}
+ /**
+ * Returns {@code true} if the system is allowed to share this activity's state with the host
+ * app when this activity is embedded in untrusted mode.
+ */
+ boolean isUntrustedEmbeddingStateSharingAllowed() {
+ if (!Flags.untrustedEmbeddingStateSharing()) {
+ return false;
+ }
+ return mAllowUntrustedEmbeddingStateSharing;
+ }
+
+ private boolean getAllowUntrustedEmbeddingStateSharingProperty() {
+ if (!Flags.untrustedEmbeddingStateSharing()) {
+ return false;
+ }
+ try {
+ return mAtmService.mContext.getPackageManager()
+ .getProperty(PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING,
+ mActivityComponent)
+ .getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ // No such property name.
+ return false;
+ }
+ }
+
@Override
@Nullable
TaskDisplayArea getDisplayArea() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index ec0e3e7f178f..49df39664b1c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -930,7 +930,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
results, newIntents, r.takeSceneTransitionInfo(), isTransitionForward,
proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
- r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken);
+ r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken,
+ r.initialCallerInfoAccessToken);
// Set desired final state.
final ActivityLifecycleItem lifecycleItem;
@@ -943,7 +944,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// Schedule transaction.
mService.getLifecycleManager().scheduleTransactionAndLifecycleItems(
- proc.getThread(), launchActivityItem, lifecycleItem);
+ proc.getThread(), launchActivityItem, lifecycleItem,
+ // Immediately dispatch the transaction, so that if it fails, the server can
+ // restart the process and retry now.
+ true /* shouldDispatchImmediately */);
if (procConfig.seq > mRootWindowContainer.getConfiguration().seq) {
// If the seq is increased, there should be something changed (e.g. registered
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index c7df83a9ccf9..5b4fb3e6971f 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -64,6 +64,10 @@ class ClientLifecycleManager {
final IApplicationThread client = transaction.getClient();
try {
transaction.schedule();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to deliver transaction for " + client
+ + "\ntransaction=" + transaction);
+ throw e;
} finally {
if (!(client instanceof Binder)) {
// If client is not an instance of Binder - it's a remote call and at this point it
@@ -106,7 +110,8 @@ class ClientLifecycleManager {
final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
clientTransaction.addTransactionItem(transactionItem);
- onClientTransactionItemScheduled(clientTransaction);
+ onClientTransactionItemScheduled(clientTransaction,
+ false /* shouldDispatchImmediately */);
} else {
// TODO(b/260873529): cleanup after launch.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
@@ -119,14 +124,30 @@ class ClientLifecycleManager {
}
}
+ void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client,
+ @NonNull ClientTransactionItem transactionItem,
+ @NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException {
+ scheduleTransactionAndLifecycleItems(client, transactionItem, lifecycleItem,
+ false /* shouldDispatchImmediately */);
+ }
+
/**
* Schedules a single transaction item with a lifecycle request, delivery to client application.
+ *
+ * @param shouldDispatchImmediately whether or not to dispatch the transaction immediately. This
+ * should only be {@code true} when it is important to know the
+ * result of dispatching immediately. For example, when cold
+ * launches an app, the server needs to know if the transaction
+ * is dispatched successfully, and may restart the process if
+ * not.
+ *
* @throws RemoteException
* @see ClientTransactionItem
*/
void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem,
- @NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException {
+ @NonNull ActivityLifecycleItem lifecycleItem,
+ boolean shouldDispatchImmediately) throws RemoteException {
// The behavior is different depending on the flag.
// When flag is on, we wait until RootWindowContainer#performSurfacePlacementNoTrace to
// dispatch all pending transactions at once.
@@ -135,7 +156,7 @@ class ClientLifecycleManager {
clientTransaction.addTransactionItem(transactionItem);
clientTransaction.addTransactionItem(lifecycleItem);
- onClientTransactionItemScheduled(clientTransaction);
+ onClientTransactionItemScheduled(clientTransaction, shouldDispatchImmediately);
} else {
// TODO(b/260873529): cleanup after launch.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
@@ -157,7 +178,8 @@ class ClientLifecycleManager {
try {
scheduleTransaction(transaction);
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to deliver transaction for " + transaction.getClient());
+ Slog.e(TAG, "Failed to deliver pending transaction", e);
+ // TODO(b/323801078): apply cleanup for individual transaction item if needed.
}
}
mPendingTransactions.clear();
@@ -194,8 +216,9 @@ class ClientLifecycleManager {
/** Must only be called with WM lock. */
private void onClientTransactionItemScheduled(
- @NonNull ClientTransaction clientTransaction) throws RemoteException {
- if (shouldDispatchPendingTransactionsImmediately()) {
+ @NonNull ClientTransaction clientTransaction,
+ boolean shouldDispatchImmediately) throws RemoteException {
+ if (shouldDispatchImmediately || shouldDispatchPendingTransactionsImmediately()) {
// Dispatch the pending transaction immediately.
mPendingTransactions.remove(clientTransaction.getClient().asBinder());
scheduleTransaction(clientTransaction);
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index d9dda4aeb96a..cd968067e289 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -178,6 +178,7 @@ class InsetsSourceProvider {
mWindowContainer.cancelAnimation();
mWindowContainer.getInsetsSourceProviders().remove(mSource.getId());
mSeamlessRotating = false;
+ mHasPendingPosition = false;
}
ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s",
windowContainer, WindowInsets.Type.toString(mSource.getType()));
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 0e2d3d151db0..edf9da1e0bf5 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -58,6 +58,8 @@ import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH;
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
@@ -927,8 +929,7 @@ final class LetterboxUiController {
}
void updateLetterboxSurface(WindowState winHint, Transaction t) {
- final WindowState w = mActivityRecord.findMainWindow();
- if (w != winHint && winHint != null && w != null) {
+ if (shouldNotLayoutLetterbox(winHint)) {
return;
}
layoutLetterbox(winHint);
@@ -937,20 +938,11 @@ final class LetterboxUiController {
}
}
- void layoutLetterbox(WindowState winHint) {
- final WindowState w = mActivityRecord.findMainWindow();
- if (w == null || winHint != null && w != winHint) {
+ void layoutLetterbox(WindowState w) {
+ if (shouldNotLayoutLetterbox(w)) {
return;
}
updateRoundedCornersIfNeeded(w);
- // If there is another main window that is not an application-starting window, we should
- // update rounded corners for it as well, to avoid flickering rounded corners.
- final WindowState nonStartingAppW = mActivityRecord.findMainWindow(
- /* includeStartingApp= */ false);
- if (nonStartingAppW != null && nonStartingAppW != w) {
- updateRoundedCornersIfNeeded(nonStartingAppW);
- }
-
updateWallpaperForLetterbox(w);
if (shouldShowLetterboxUi(w)) {
if (mLetterbox == null) {
@@ -1023,6 +1015,18 @@ final class LetterboxUiController {
return mActivityRecord.getSurfaceControl();
}
+ private static boolean shouldNotLayoutLetterbox(WindowState w) {
+ if (w == null) {
+ return true;
+ }
+ final int type = w.mAttrs.type;
+ // Allow letterbox to be displayed early for base application or application starting
+ // windows even if it is not on the top z order to prevent flickering when the
+ // letterboxed window is brought to the top
+ return (type != TYPE_BASE_APPLICATION && type != TYPE_APPLICATION_STARTING)
+ || w.mAnimatingExit;
+ }
+
private boolean shouldLetterboxHaveRoundedCorners() {
// TODO(b/214030873): remove once background is drawn for transparent activities
// Letterbox shouldn't have rounded corners if the activity is transparent
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 81fe453cc692..8d054db8994a 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -376,10 +376,15 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
+ " is not in a task belong to the organizer app.");
return null;
}
- if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED
- || !task.isAllowedToEmbedActivityInTrustedMode(activity, mOrganizerUid)) {
+ if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) {
Slog.d(TAG, "Reparent activity=" + activity.token
- + " is not allowed to be embedded in trusted mode.");
+ + " is not allowed to be embedded.");
+ return null;
+ }
+ if (!task.isAllowedToEmbedActivityInTrustedMode(activity, mOrganizerUid)
+ && !activity.isUntrustedEmbeddingStateSharingAllowed()) {
+ Slog.d(TAG, "Reparent activity=" + activity.token
+ + " is not allowed to be shared with untrusted host.");
return null;
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 44cd23d037c6..09c4f7ca3c6c 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -499,6 +499,10 @@ class WindowStateAnimator {
}
void applyEnterAnimationLocked() {
+ if (mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow()) {
+ // It's unnecessary to play enter animation below starting window.
+ return;
+ }
final int transit;
if (mEnterAnimationPending) {
mEnterAnimationPending = false;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index 129efc630be6..005cad125115 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -46,6 +46,7 @@ import android.os.UserManager;
import android.permission.PermissionCheckerManager;
import android.permission.PermissionManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.SparseArray;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
@@ -55,6 +56,7 @@ import com.android.server.pm.permission.PermissionManagerService;
import com.android.server.wm.ActivityTaskManagerInternal;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -109,6 +111,8 @@ public class CrossProfileAppsServiceImplTest {
private IApplicationThread mIApplicationThread;
private SparseArray<Boolean> mUserEnabled = new SparseArray<>();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void initCrossProfileAppsServiceImpl() {
@@ -123,8 +127,9 @@ public class CrossProfileAppsServiceImplTest {
mUserEnabled.put(PRIMARY_USER, true);
mUserEnabled.put(PROFILE_OF_PRIMARY_USER, true);
mUserEnabled.put(SECONDARY_USER, true);
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_ENABLE_HIDING_PROFILES);
- when(mUserManager.getEnabledProfileIds(anyInt())).thenAnswer(
+ when(mUserManager.getProfileIdsExcludingHidden(anyInt(), eq(true))).thenAnswer(
invocation -> {
List<Integer> users = new ArrayList<>();
final int targetUser = invocation.getArgument(0);
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
index 93bdeaeb90e3..d538f255c5aa 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
@@ -40,6 +40,7 @@ class ParsedProcessTest : ParcelableComponentTest(ParsedProcess::class, ParsedPr
ParsedProcess::getGwpAsanMode,
ParsedProcess::getMemtagMode,
ParsedProcess::getNativeHeapZeroInitialized,
+ ParsedProcess::isUseEmbeddedDex,
)
override fun extraParams() = listOf(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 42bcb3367933..b142334db9e9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -72,6 +72,7 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -102,7 +103,9 @@ import android.os.MessageQueue;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.os.UserManager;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
import android.util.SparseArray;
import android.view.ContentRecordingSession;
import android.view.Display;
@@ -210,6 +213,10 @@ public class DisplayManagerServiceTest {
private int mPreferredHdrOutputType;
+ private Handler mPowerHandler;
+
+ private UserManager mUserManager;
+
private final DisplayManagerService.Injector mShortMockedInjector =
new DisplayManagerService.Injector() {
@Override
@@ -370,11 +377,14 @@ public class DisplayManagerServiceTest {
mContext = spy(new ContextWrapper(
ApplicationProvider.getApplicationContext().createDisplayContext(display)));
mResources = Mockito.spy(mContext.getResources());
+ mPowerHandler = new Handler(Looper.getMainLooper());
manageDisplaysPermission(/* granted= */ false);
when(mContext.getResources()).thenReturn(mResources);
+ mUserManager = Mockito.spy(mContext.getSystemService(UserManager.class));
VirtualDeviceManager vdm = new VirtualDeviceManager(mIVirtualDeviceManager, mContext);
when(mContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+ when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
// Disable binder caches in this process.
PropertyInvalidatedCache.disableForTestMode();
setUpDisplay();
@@ -2789,6 +2799,85 @@ public class DisplayManagerServiceTest {
assertThat(display.getDisplayOffloadSessionLocked()).isNull();
}
+ @Test
+ public void testOnUserSwitching_UpdatesBrightness() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ float brightness1 = 0.3f;
+ float brightness2 = 0.45f;
+
+ int userId1 = 123;
+ int userId2 = 456;
+ UserInfo userInfo1 = new UserInfo();
+ userInfo1.id = userId1;
+ UserInfo userInfo2 = new UserInfo();
+ userInfo2.id = userId2;
+ when(mUserManager.getUserSerialNumber(userId1)).thenReturn(12345);
+ when(mUserManager.getUserSerialNumber(userId2)).thenReturn(45678);
+ final SystemService.TargetUser from = new SystemService.TargetUser(userInfo1);
+ final SystemService.TargetUser to = new SystemService.TargetUser(userInfo2);
+
+ // The same brightness will be restored for a user only if auto-brightness is off,
+ // otherwise the current lux will be used to determine the brightness.
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+ displayManager.onUserSwitching(to, from);
+ waitForIdleHandler(mPowerHandler);
+ displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightness1);
+ displayManager.onUserSwitching(from, to);
+ waitForIdleHandler(mPowerHandler);
+ displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightness2);
+
+ displayManager.onUserSwitching(to, from);
+ waitForIdleHandler(mPowerHandler);
+ assertEquals(brightness1,
+ displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+ FLOAT_TOLERANCE);
+
+ displayManager.onUserSwitching(from, to);
+ waitForIdleHandler(mPowerHandler);
+ assertEquals(brightness2,
+ displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+ FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testOnUserSwitching_brightnessForNewUserIsDefault() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ initDisplayPowerController(localService);
+
+ int userId1 = 123;
+ int userId2 = 456;
+ UserInfo userInfo1 = new UserInfo();
+ userInfo1.id = userId1;
+ UserInfo userInfo2 = new UserInfo();
+ userInfo2.id = userId2;
+ when(mUserManager.getUserSerialNumber(userId1)).thenReturn(12345);
+ when(mUserManager.getUserSerialNumber(userId2)).thenReturn(45678);
+ final SystemService.TargetUser from = new SystemService.TargetUser(userInfo1);
+ final SystemService.TargetUser to = new SystemService.TargetUser(userInfo2);
+
+ displayManager.onUserSwitching(from, to);
+ waitForIdleHandler(mPowerHandler);
+ assertEquals(displayManagerBinderService.getDisplayInfo(Display.DEFAULT_DISPLAY)
+ .brightnessDefault,
+ displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY),
+ FLOAT_TOLERANCE);
+ }
+
private void initDisplayPowerController(DisplayManagerInternal localService) {
localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
@Override
@@ -2820,7 +2909,7 @@ public class DisplayManagerServiceTest {
public void releaseSuspendBlocker(String id) {
}
- }, new Handler(Looper.getMainLooper()), mSensorManager);
+ }, mPowerHandler, mSensorManager);
}
private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 88a9758991f4..57b86324e171 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -227,18 +227,14 @@ public final class DisplayPowerControllerTest {
advanceTime(1);
// two times, one for unfinished business and one for proximity
- verify(mHolder.wakelockController, times(2)).acquireWakelock(
+ verify(mHolder.wakelockController).acquireWakelock(
WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
verify(mHolder.wakelockController).acquireWakelock(
WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
mHolder.dpc.stop();
advanceTime(1);
- // two times, one for unfinished business and one for proximity
- verify(mHolder.wakelockController, times(2)).acquireWakelock(
- WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
- verify(mHolder.wakelockController).acquireWakelock(
- WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+ verify(mHolder.wakelockController).releaseAll();
}
@Test
@@ -515,9 +511,8 @@ public final class DisplayPowerControllerTest {
verify(mHolder.animator).animateTo(eq(leadBrightness), anyFloat(),
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
- // One triggered by handleBrightnessModeChange, another triggered by setBrightnessToFollow
- verify(followerDpc.hbmController, times(2)).onAmbientLuxChange(ambientLux);
- verify(followerDpc.animator, times(2)).animateTo(eq(followerBrightness), anyFloat(),
+ verify(followerDpc.hbmController).onAmbientLuxChange(ambientLux);
+ verify(followerDpc.animator).animateTo(eq(followerBrightness), anyFloat(),
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(leadBrightness);
@@ -811,7 +806,7 @@ public final class DisplayPowerControllerTest {
DisplayPowerRequest dpr = new DisplayPowerRequest();
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
+ verify(mHolder.displayPowerState).setScreenState(anyInt());
mHolder = createDisplayPowerController(42, UNIQUE_ID);
@@ -1024,15 +1019,14 @@ public final class DisplayPowerControllerTest {
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by the test, the other by handleBrightnessModeChange
- verify(mHolder.automaticBrightnessController, times(2)).configure(
+ verify(mHolder.automaticBrightnessController).configure(
AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
/* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
/* userChangedBrightness= */ false, /* adjustment= */ 0,
/* userChangedAutoBrightnessAdjustment= */ false, DisplayPowerRequest.POLICY_BRIGHT,
/* shouldResetShortTermModel= */ false
);
- verify(mHolder.hbmController, times(2))
+ verify(mHolder.hbmController)
.setAutoBrightnessEnabled(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
}
@@ -1098,8 +1092,7 @@ public final class DisplayPowerControllerTest {
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by the test, the other by handleBrightnessModeChange
- verify(mHolder.automaticBrightnessController, times(2)).configure(
+ verify(mHolder.automaticBrightnessController).configure(
AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
/* configuration= */ null, PowerManager.BRIGHTNESS_INVALID_FLOAT,
/* userChangedBrightness= */ false, /* adjustment= */ 0,
@@ -1136,8 +1129,7 @@ public final class DisplayPowerControllerTest {
DisplayPowerRequest dpr = new DisplayPowerRequest();
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
- verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat(),
+ verify(mHolder.animator).animateTo(eq(newBrightness), anyFloat(), anyFloat(),
eq(false));
}
@@ -1210,8 +1202,9 @@ public final class DisplayPowerControllerTest {
when(mHolder.hbmController.getCurrentBrightnessMax()).thenReturn(clampedBrightness);
mHolder.dpc.setBrightness(PowerManager.BRIGHTNESS_MAX);
+ mHolder.dpc.setBrightness(0.8f, /* userSerial= */ 123);
- verify(mHolder.brightnessSetting).setBrightness(clampedBrightness);
+ verify(mHolder.brightnessSetting, times(2)).setBrightness(clampedBrightness);
}
@Test
@@ -1564,9 +1557,7 @@ public final class DisplayPowerControllerTest {
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by handleBrightnessModeChange, another triggered by
- // setBrightnessFromOffload
- verify(mHolder.animator, times(2)).animateTo(eq(brightness), anyFloat(),
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
}
@@ -1580,9 +1571,7 @@ public final class DisplayPowerControllerTest {
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- // One triggered by handleBrightnessModeChange, another triggered by requestPowerState
- verify(mHolder.automaticBrightnessController, times(2))
- .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+ verify(mHolder.automaticBrightnessController).switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
// Back to default mode
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
@@ -1620,6 +1609,62 @@ public final class DisplayPowerControllerTest {
.switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
}
+ @Test
+ public void testOnSwitchUserUpdatesBrightness() {
+ int userSerial = 12345;
+ float brightness = 0.65f;
+
+ mHolder.dpc.onSwitchUser(/* newUserId= */ 15, userSerial, brightness);
+ advanceTime(1);
+
+ verify(mHolder.brightnessSetting).setUserSerial(userSerial);
+ verify(mHolder.brightnessSetting).setBrightness(brightness);
+ }
+
+ @Test
+ public void testOnSwitchUserDoesNotAddUserDataPoint() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ int userSerial = 12345;
+ float brightness = 0.65f;
+ when(mHolder.automaticBrightnessController.hasValidAmbientLux()).thenReturn(true);
+ when(mHolder.automaticBrightnessController.convertToAdjustedNits(brightness))
+ .thenReturn(500f);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+
+ mHolder.dpc.onSwitchUser(/* newUserId= */ 15, userSerial, brightness);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+
+ verify(mHolder.automaticBrightnessController, never()).configure(
+ /* state= */ anyInt(),
+ /* configuration= */ any(),
+ eq(brightness),
+ /* userChangedBrightness= */ eq(true),
+ /* adjustment= */ anyFloat(),
+ /* userChangedAutoBrightnessAdjustment= */ anyBoolean(),
+ /* displayPolicy= */ anyInt(),
+ /* shouldResetShortTermModel= */ anyBoolean());
+ verify(mBrightnessTrackerMock, never()).notifyBrightnessChanged(
+ /* brightness= */ anyFloat(),
+ /* userInitiated= */ eq(true),
+ /* powerBrightnessFactor= */ anyFloat(),
+ /* wasShortTermModelActive= */ anyBoolean(),
+ /* isDefaultBrightnessConfig= */ anyBoolean(),
+ /* uniqueDisplayId= */ any(),
+ /* luxValues */ any(),
+ /* luxTimestamps= */ any());
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 2d0c3fdb6352..289d54b86c7d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -19,7 +19,6 @@ package com.android.server.display.brightness;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -252,7 +251,6 @@ public final class DisplayBrightnessControllerTest {
0.0f);
verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
verify(mBrightnessSetting).setBrightness(brightnessValue);
- verify(mBrightnessSetting).setUserSerial(anyInt());
// Does nothing if the value is invalid
mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index 78ec2ff31161..5408e1143330 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -104,7 +104,7 @@ public class AutomaticBrightnessStrategyTest {
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -127,7 +127,7 @@ public class AutomaticBrightnessStrategyTest {
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -150,7 +150,7 @@ public class AutomaticBrightnessStrategyTest {
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -173,7 +173,7 @@ public class AutomaticBrightnessStrategyTest {
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -196,7 +196,7 @@ public class AutomaticBrightnessStrategyTest {
int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
float lastUserSetBrightness = 0.2f;
boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -221,7 +221,7 @@ public class AutomaticBrightnessStrategyTest {
boolean userSetBrightnessChanged = true;
Settings.System.putFloat(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -247,7 +247,7 @@ public class AutomaticBrightnessStrategyTest {
float pendingBrightnessAdjustment = 0.1f;
Settings.System.putFloat(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
userSetBrightnessChanged);
@@ -411,7 +411,7 @@ public class AutomaticBrightnessStrategyTest {
private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
Settings.System.putFloat(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
}
private void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index eaf0ffdc3cbd..3355a6cff2ad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -29,6 +29,8 @@ import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
+import static com.android.server.job.Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS;
+import static com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -60,12 +62,15 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
import android.os.BatteryManagerInternal;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.platform.test.flag.junit.SetFlagsRule;
import com.android.server.AppStateTracker;
import com.android.server.AppStateTrackerImpl;
@@ -82,6 +87,7 @@ import com.android.server.usage.AppStandbyInternal;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoSession;
@@ -105,6 +111,9 @@ public class JobSchedulerServiceTest {
@Mock
private PackageManagerInternal mPackageManagerInternal;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private class TestJobSchedulerService extends JobSchedulerService {
TestJobSchedulerService(Context context) {
super(context);
@@ -1711,29 +1720,283 @@ public class JobSchedulerServiceTest {
/** Tests that rare job batching works as expected. */
@Test
- public void testRareJobBatching() {
+ public void testConnectivityJobBatching() {
+ mSetFlagsRule.enableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
spyOn(mService);
doReturn(false).when(mService).evaluateControllerStatesLocked(any());
doNothing().when(mService).noteJobsPending(any());
doReturn(true).when(mService).isReadyToBeExecutedLocked(any(), anyBoolean());
+ ConnectivityController connectivityController = mService.getConnectivityController();
+ spyOn(connectivityController);
advanceElapsedClock(24 * HOUR_IN_MILLIS);
JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor =
mService.new MaybeReadyJobQueueFunctor();
- mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
- mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
+ mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD.clear();
+ mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD
+ .put(NetworkCapabilities.TRANSPORT_CELLULAR, 5);
+ mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD
+ .put(NetworkCapabilities.TRANSPORT_WIFI, 2);
+ mService.mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
+
+ final Network network = mock(Network.class);
+
+ // Not enough connectivity jobs to run.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ NetworkCapabilities capabilities = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+ doReturn(false).when(connectivityController).isNetworkInStateForJobRunLocked(any());
+ for (int i = 0; i < 4; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertNull(maybeQueueFunctor.mBatches.get(null));
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(0, mService.getPendingJobQueue().size());
+
+ // Not enough connectivity jobs to run, but the network is already active
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+ doReturn(true).when(connectivityController).isNetworkInStateForJobRunLocked(any());
+ for (int i = 0; i < 4; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertNull(maybeQueueFunctor.mBatches.get(null));
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(0, job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(4, mService.getPendingJobQueue().size());
+
+ // Enough connectivity jobs to run.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ capabilities = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+ doReturn(false).when(connectivityController).isNetworkInStateForJobRunLocked(any());
+ for (int i = 0; i < 3; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+
+ // Not enough connectivity jobs to run, but a non-batched job saves the day.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ JobStatus runningJob = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ runningJob.network = network;
+ doReturn(true).when(mService).isCurrentlyRunningLocked(runningJob);
+ capabilities = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
+ for (int i = 0; i < 3; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.accept(runningJob);
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+
+ // Not enough connectivity jobs to run, but an old connectivity job saves the day.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ JobStatus oldConnJob = createJobStatus("testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ oldConnJob.network = network;
+ final long oldBatchTime = sElapsedRealtimeClock.millis()
+ - 2 * mService.mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS;
+ oldConnJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
+ for (int i = 0; i < 2; ++i) {
+ JobStatus job = createJobStatus(
+ "testConnectivityJobBatching",
+ createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.accept(oldConnJob);
+ assertEquals(oldBatchTime, oldConnJob.getFirstForceBatchedTimeElapsed());
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+ // Transport type doesn't have a set threshold. One job should be the default threshold.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ capabilities = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .build();
+ doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
JobStatus job = createJobStatus(
- "testRareJobBatching",
+ "testConnectivityJobBatching",
createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
- job.setStandbyBucket(RARE_INDEX);
+ job.setStandbyBucket(ACTIVE_INDEX);
+ job.network = network;
+ maybeQueueFunctor.accept(job);
+ assertEquals(1, maybeQueueFunctor.mBatches.get(network).size());
+ assertEquals(1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(1, mService.getPendingJobQueue().size());
+ }
+
+ /** Tests that active job batching works as expected. */
+ @Test
+ public void testActiveJobBatching_activeBatchingEnabled() {
+ mSetFlagsRule.enableFlags(FLAG_BATCH_ACTIVE_BUCKET_JOBS);
+
+ spyOn(mService);
+ doReturn(false).when(mService).evaluateControllerStatesLocked(any());
+ doNothing().when(mService).noteJobsPending(any());
+ doReturn(true).when(mService).isReadyToBeExecutedLocked(any(), anyBoolean());
+ advanceElapsedClock(24 * HOUR_IN_MILLIS);
+
+ JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor =
+ mService.new MaybeReadyJobQueueFunctor();
+ mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT = 5;
+ mService.mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
+
+ // Not enough ACTIVE jobs to run.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+ job.setStandbyBucket(ACTIVE_INDEX);
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(0, mService.getPendingJobQueue().size());
+
+ // Enough ACTIVE jobs to run.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT; ++i) {
+ JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+ job.setStandbyBucket(ACTIVE_INDEX);
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(5, mService.getPendingJobQueue().size());
+
+ // Not enough ACTIVE jobs to run, but a non-batched job saves the day.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ JobStatus expeditedJob = createJobStatus("testActiveJobBatching",
+ createJobInfo().setExpedited(true));
+ spyOn(expeditedJob);
+ when(expeditedJob.shouldTreatAsExpeditedJob()).thenReturn(true);
+ expeditedJob.setStandbyBucket(RARE_INDEX);
+ for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+ job.setStandbyBucket(ACTIVE_INDEX);
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.accept(expeditedJob);
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+
+ // Not enough ACTIVE jobs to run, but an old ACTIVE job saves the day.
+ mService.getPendingJobQueue().clear();
+ maybeQueueFunctor.reset();
+ JobStatus oldActiveJob = createJobStatus("testActiveJobBatching", createJobInfo());
+ oldActiveJob.setStandbyBucket(ACTIVE_INDEX);
+ final long oldBatchTime = sElapsedRealtimeClock.millis()
+ - 2 * mService.mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS;
+ oldActiveJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
+ for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
+ job.setStandbyBucket(ACTIVE_INDEX);
+
+ maybeQueueFunctor.accept(job);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
+ assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
+ assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
+ }
+ maybeQueueFunctor.accept(oldActiveJob);
+ assertEquals(oldBatchTime, oldActiveJob.getFirstForceBatchedTimeElapsed());
+ maybeQueueFunctor.postProcessLocked();
+ assertEquals(3, mService.getPendingJobQueue().size());
+ }
+
+ /** Tests that rare job batching works as expected. */
+ @Test
+ public void testRareJobBatching() {
+ spyOn(mService);
+ doReturn(false).when(mService).evaluateControllerStatesLocked(any());
+ doNothing().when(mService).noteJobsPending(any());
+ doReturn(true).when(mService).isReadyToBeExecutedLocked(any(), anyBoolean());
+ advanceElapsedClock(24 * HOUR_IN_MILLIS);
+
+ JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor =
+ mService.new MaybeReadyJobQueueFunctor();
+ mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
+ mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
// Not enough RARE jobs to run.
mService.getPendingJobQueue().clear();
maybeQueueFunctor.reset();
for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+ job.setStandbyBucket(RARE_INDEX);
+
maybeQueueFunctor.accept(job);
- assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
}
@@ -1744,8 +2007,11 @@ public class JobSchedulerServiceTest {
mService.getPendingJobQueue().clear();
maybeQueueFunctor.reset();
for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT; ++i) {
+ JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+ job.setStandbyBucket(RARE_INDEX);
+
maybeQueueFunctor.accept(job);
- assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
}
@@ -1753,15 +2019,17 @@ public class JobSchedulerServiceTest {
assertEquals(5, mService.getPendingJobQueue().size());
// Not enough RARE jobs to run, but a non-batched job saves the day.
+ mSetFlagsRule.disableFlags(FLAG_BATCH_ACTIVE_BUCKET_JOBS);
mService.getPendingJobQueue().clear();
maybeQueueFunctor.reset();
- JobStatus activeJob = createJobStatus(
- "testRareJobBatching",
- createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus activeJob = createJobStatus("testRareJobBatching", createJobInfo());
activeJob.setStandbyBucket(ACTIVE_INDEX);
for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+ job.setStandbyBucket(RARE_INDEX);
+
maybeQueueFunctor.accept(job);
- assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
}
@@ -1778,8 +2046,11 @@ public class JobSchedulerServiceTest {
- 2 * mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
oldRareJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
+ JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
+ job.setStandbyBucket(RARE_INDEX);
+
maybeQueueFunctor.accept(job);
- assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
+ assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 4958f1c1c214..f27d0c2ce5a9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -36,6 +36,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK;
import static com.android.server.job.Flags.FLAG_RELAX_PREFETCH_CONNECTIVITY_CONSTRAINT_ONLY_ON_CHARGER;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
@@ -69,6 +70,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.ConnectivityManager.OnNetworkActiveListener;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
@@ -102,6 +104,7 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import java.time.Clock;
+import java.time.Duration;
import java.time.ZoneOffset;
import java.util.Set;
@@ -1650,6 +1653,141 @@ public class ConnectivityControllerTest {
assertEquals((81920 + 4096) * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
}
+ @Test
+ public void testIsNetworkInStateForJobRunLocked_JobStatus() {
+ mSetFlagsRule.enableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
+
+ // Null network
+ final JobStatus expeditedJob =
+ spy(createJobStatus(createJob().setExpedited(true), UID_RED));
+ doReturn(true).when(expeditedJob).shouldTreatAsExpeditedJob();
+ final JobStatus highProcJob = spy(createJobStatus(createJob(), UID_BLUE));
+ doReturn(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE)
+ .when(mService).getUidProcState(eq(UID_BLUE));
+ doReturn(ActivityManager.PROCESS_STATE_CACHED_EMPTY)
+ .when(mService).getUidProcState(eq(UID_RED));
+ final JobStatus regJob = createJobStatus(createJob(), UID_RED);
+ final JobStatus uiJob = spy(createJobStatus(
+ createJob().setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY),
+ UID_RED));
+ doReturn(true).when(uiJob).shouldTreatAsUserInitiatedJob();
+ assertFalse(controller.isNetworkInStateForJobRunLocked(expeditedJob));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(highProcJob));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(regJob));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(uiJob));
+
+ // Privileged jobs are exempted
+ expeditedJob.network = mock(Network.class);
+ highProcJob.network = mock(Network.class);
+ regJob.network = mock(Network.class);
+ uiJob.network = mock(Network.class);
+ assertTrue(controller.isNetworkInStateForJobRunLocked(expeditedJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(highProcJob));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(regJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(uiJob));
+
+ mSetFlagsRule.disableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+ assertTrue(controller.isNetworkInStateForJobRunLocked(expeditedJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(highProcJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(regJob));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(uiJob));
+ }
+
+ @Test
+ public void testIsNetworkInStateForJobRunLocked_Network() {
+ mSetFlagsRule.disableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+ final ArgumentCaptor<NetworkCallback> allNetworkCallbackCaptor =
+ ArgumentCaptor.forClass(NetworkCallback.class);
+ doNothing().when(mConnManager)
+ .registerNetworkCallback(any(), allNetworkCallbackCaptor.capture());
+ final ArgumentCaptor<OnNetworkActiveListener> onNetworkActiveListenerCaptor =
+ ArgumentCaptor.forClass(OnNetworkActiveListener.class);
+ doNothing().when(mConnManager).addDefaultNetworkActiveListener(
+ onNetworkActiveListenerCaptor.capture());
+ final ArgumentCaptor<NetworkCallback> systemDefaultNetworkCallbackCaptor =
+ ArgumentCaptor.forClass(NetworkCallback.class);
+ doNothing().when(mConnManager).registerSystemDefaultNetworkCallback(
+ systemDefaultNetworkCallbackCaptor.capture(), any());
+
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
+
+ assertTrue(controller.isNetworkInStateForJobRunLocked(mock(Network.class)));
+
+ mSetFlagsRule.enableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
+
+ // Unknown network
+ assertFalse(controller.isNetworkInStateForJobRunLocked(mock(Network.class)));
+
+ final Network systemDefaultNetwork = mock(Network.class);
+ final Network otherNetwork = mock(Network.class);
+
+ controller.startTrackingLocked();
+
+ final NetworkCallback allNetworkCallback = allNetworkCallbackCaptor.getValue();
+ final OnNetworkActiveListener onNetworkActiveListener =
+ onNetworkActiveListenerCaptor.getValue();
+ final NetworkCallback systemDefaultNetworkCallback =
+ systemDefaultNetworkCallbackCaptor.getValue();
+
+ // No capabilities set
+ allNetworkCallback.onAvailable(systemDefaultNetwork);
+ allNetworkCallback.onAvailable(otherNetwork);
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Capabilities set, but never active.
+ allNetworkCallback.onCapabilitiesChanged(
+ systemDefaultNetwork, mock(NetworkCapabilities.class));
+ allNetworkCallback.onCapabilitiesChanged(otherNetwork, mock(NetworkCapabilities.class));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Mark system default network as active before identifying system default network.
+ onNetworkActiveListener.onNetworkActive();
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Identify system default network and mark as active.
+ systemDefaultNetworkCallback.onAvailable(systemDefaultNetwork);
+ onNetworkActiveListener.onNetworkActive();
+ assertTrue(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ advanceElapsedClock(controller.getCcConfig().NETWORK_ACTIVATION_EXPIRATION_MS - 1);
+ assertTrue(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Network stays active beyond expected timeout.
+ advanceElapsedClock(2);
+ doReturn(true).when(mConnManager).isDefaultNetworkActive();
+ assertTrue(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Network becomes inactive after expected timeout.
+ advanceElapsedClock(controller.getCcConfig().NETWORK_ACTIVATION_EXPIRATION_MS);
+ doReturn(false).when(mConnManager).isDefaultNetworkActive();
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertFalse(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+
+ // Other network hasn't received a signal for a long time. System default network has
+ // been active within the max wait time.
+ advanceElapsedClock(controller.getCcConfig().NETWORK_ACTIVATION_MAX_WAIT_TIME_MS
+ - controller.getCcConfig().NETWORK_ACTIVATION_EXPIRATION_MS);
+ doReturn(false).when(mConnManager).isDefaultNetworkActive();
+ assertFalse(controller.isNetworkInStateForJobRunLocked(systemDefaultNetwork));
+ assertTrue(controller.isNetworkInStateForJobRunLocked(otherNetwork));
+ }
+
+ private void advanceElapsedClock(long incrementMs) {
+ JobSchedulerService.sElapsedRealtimeClock = Clock.offset(
+ JobSchedulerService.sElapsedRealtimeClock, Duration.ofMillis(incrementMs));
+ }
+
private void answerNetwork(@NonNull NetworkCallback generalCallback,
@Nullable NetworkCallback uidCallback, @Nullable Network lastNetwork,
@Nullable Network net, @Nullable NetworkCapabilities caps) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 52726caba2bf..b224773a9eb1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -42,6 +42,7 @@ import static org.mockito.Mockito.when;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
import android.accessibilityservice.MagnificationConfig;
+import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -52,11 +53,15 @@ import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
import android.os.Looper;
import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.view.DisplayInfo;
import android.view.MagnificationSpec;
import android.view.accessibility.MagnificationAnimationCallback;
+import android.widget.Scroller;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -65,6 +70,7 @@ import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.accessibility.Flags;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
@@ -74,6 +80,7 @@ import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -104,6 +111,9 @@ public class FullScreenMagnificationControllerTest {
static final int INVALID_DISPLAY = 2;
private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
final FullScreenMagnificationController.ControllerContext mMockControllerCtx =
mock(FullScreenMagnificationController.ControllerContext.class);
final Context mMockContext = mock(Context.class);
@@ -118,6 +128,7 @@ public class FullScreenMagnificationControllerTest {
private MagnificationScaleProvider mScaleProvider;
private MockContentResolver mResolver;
private final MagnificationThumbnail mMockThumbnail = mock(MagnificationThumbnail.class);
+ private final Scroller mMockScroller = mock(Scroller.class);
private final ArgumentCaptor<MagnificationConfig> mConfigCaptor = ArgumentCaptor.forClass(
MagnificationConfig.class);
@@ -126,6 +137,8 @@ public class FullScreenMagnificationControllerTest {
ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
ValueAnimator.AnimatorListener mStateListener;
+ private final TimeAnimator mMockTimeAnimator = mock(TimeAnimator.class);
+
FullScreenMagnificationController mFullScreenMagnificationController;
public DisplayManagerInternal mDisplayManagerInternalMock = mock(DisplayManagerInternal.class);
@@ -134,7 +147,8 @@ public class FullScreenMagnificationControllerTest {
@Before
public void setUp() {
- Looper looper = InstrumentationRegistry.getContext().getMainLooper();
+ Context realContext = InstrumentationRegistry.getContext();
+ Looper looper = realContext.getMainLooper();
// Pretending ID of the Thread associated with looper as main thread ID in controller
when(mMockContext.getMainLooper()).thenReturn(looper);
when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
@@ -168,7 +182,9 @@ public class FullScreenMagnificationControllerTest {
mRequestObserver,
mScaleProvider,
() -> mMockThumbnail,
- ConcurrentUtils.DIRECT_EXECUTOR);
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ () -> mMockScroller,
+ () -> mMockTimeAnimator);
}
@After
@@ -428,7 +444,7 @@ public class FullScreenMagnificationControllerTest {
mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
mStateListener.onAnimationEnd(mMockValueAnimator);
verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec)));
- verify(mAnimationCallback).onResult(true);
+ verify(mAnimationCallback).onResult(eq(true), any());
}
@Test
@@ -451,7 +467,7 @@ public class FullScreenMagnificationControllerTest {
mMessageCapturingHandler.sendAllMessages();
verify(mMockValueAnimator, never()).start();
- verify(mAnimationCallback).onResult(true);
+ verify(mAnimationCallback).onResult(eq(true), any());
}
@Test
@@ -653,6 +669,85 @@ public class FullScreenMagnificationControllerTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+ public void testStartFling_whileMagnifying_flings() throws InterruptedException {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ startFling_whileMagnifying_flings(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void startFling_whileMagnifying_flings(int displayId) throws InterruptedException {
+ register(displayId);
+ PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ float scale = 2.0f;
+ // First zoom in
+ assertTrue(mFullScreenMagnificationController
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1));
+ mMessageCapturingHandler.sendAllMessages();
+
+ PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+ PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+ mFullScreenMagnificationController.startFling(displayId,
+ /* xPixelsPerSecond= */ 400f,
+ /* yPixelsPerSecond= */ 100f,
+ SERVICE_ID_1
+ );
+ mMessageCapturingHandler.sendAllMessages();
+
+ verify(mMockTimeAnimator).start();
+ verify(mMockScroller).fling(
+ /* startX= */ eq((int) newOffsets.x / 2),
+ /* startY= */ eq((int) newOffsets.y / 2),
+ /* velocityX= */ eq(400),
+ /* velocityY= */ eq(100),
+ /* minX= */ anyInt(),
+ /* minY= */ anyInt(),
+ /* maxX= */ anyInt(),
+ /* maxY= */ anyInt()
+ );
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+ public void testStopFling_whileMagnifyingAndFlinging_stops() throws InterruptedException {
+ for (int i = 0; i < DISPLAY_COUNT; i++) {
+ stopFling_whileMagnifyingAndFlinging_stops(i);
+ resetMockWindowManager();
+ }
+ }
+
+ private void stopFling_whileMagnifyingAndFlinging_stops(int displayId)
+ throws InterruptedException {
+ register(displayId);
+ PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+ float scale = 2.0f;
+ PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
+ // First zoom in
+ assertTrue(mFullScreenMagnificationController
+ .setScaleAndCenter(displayId, scale, startCenter.x, startCenter.y, false,
+ SERVICE_ID_1));
+ mMessageCapturingHandler.sendAllMessages();
+
+ mFullScreenMagnificationController.startFling(displayId,
+ /* xPixelsPerSecond= */ 400f,
+ /* yPixelsPerSecond= */ 100f,
+ SERVICE_ID_1
+ );
+ mMessageCapturingHandler.sendAllMessages();
+
+ when(mMockTimeAnimator.isRunning()).thenReturn(true);
+
+ mFullScreenMagnificationController.cancelFling(displayId, SERVICE_ID_1);
+ mMessageCapturingHandler.sendAllMessages();
+
+ verify(mMockTimeAnimator).cancel();
+ // Can't verify forceFinished() because it's final
+// verify(mMockScroller).forceFinished(eq(true));
+ }
+
+ @Test
public void testGetIdOfLastServiceToChange_returnsCorrectValue() {
for (int i = 0; i < DISPLAY_COUNT; i++) {
getIdOfLastServiceToChange_returnsCorrectValue(i);
@@ -736,7 +831,7 @@ public class FullScreenMagnificationControllerTest {
verify(mRequestObserver, never()).onFullScreenMagnificationChanged(eq(displayId),
any(Region.class), any(MagnificationConfig.class));
- verify(mAnimationCallback).onResult(true);
+ verify(mAnimationCallback).onResult(eq(true), any());
}
@Test
@@ -772,7 +867,7 @@ public class FullScreenMagnificationControllerTest {
mMessageCapturingHandler.sendAllMessages();
// Verify expected actions.
- verify(mAnimationCallback).onResult(false);
+ verify(mAnimationCallback).onResult(eq(false), any());
verify(mMockValueAnimator).start();
verify(mMockValueAnimator).cancel();
@@ -782,7 +877,7 @@ public class FullScreenMagnificationControllerTest {
mStateListener.onAnimationEnd(mMockValueAnimator);
checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId);
- verify(lastAnimationCallback).onResult(true);
+ verify(lastAnimationCallback).onResult(eq(true), any());
}
@Test
@@ -1379,6 +1474,8 @@ public class FullScreenMagnificationControllerTest {
private void resetMockWindowManager() {
Mockito.reset(mMockWindowManager);
Mockito.reset(mMockThumbnail);
+ Mockito.reset(mMockScroller);
+ Mockito.reset(mMockTimeAnimator);
initMockWindowManager();
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 71d64cf4c8d4..8c0d44c46814 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -31,6 +31,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
+import static org.mockito.AdditionalMatchers.gt;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
@@ -40,10 +41,13 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.pm.PackageManager;
@@ -65,6 +69,7 @@ import android.util.DebugUtils;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import android.widget.Scroller;
import androidx.test.filters.FlakyTest;
import androidx.test.runner.AndroidJUnit4;
@@ -79,6 +84,8 @@ import com.android.server.testutils.OffsettableClock;
import com.android.server.testutils.TestHandler;
import com.android.server.wm.WindowManagerInternal;
+import com.google.common.truth.Truth;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
@@ -186,6 +193,8 @@ public class FullScreenMagnificationGestureHandlerTest {
@Rule
public final TestableContext mContext = new TestableContext(getInstrumentation().getContext());
+ private final Scroller mMockScroller = spy(new Scroller(mContext));
+
private OffsettableClock mClock;
private FullScreenMagnificationGestureHandler mMgh;
private TestHandler mHandler;
@@ -218,18 +227,21 @@ public class FullScreenMagnificationGestureHandlerTest {
Settings.Secure.putFloatForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 2.0f,
UserHandle.USER_SYSTEM);
- mFullScreenMagnificationController = new FullScreenMagnificationController(
- mockController,
- new Object(),
- mMagnificationInfoChangedCallback,
- new MagnificationScaleProvider(mContext),
- () -> null,
- ConcurrentUtils.DIRECT_EXECUTOR) {
- @Override
- public boolean magnificationRegionContains(int displayId, float x, float y) {
- return true;
- }
- };
+ mFullScreenMagnificationController =
+ new FullScreenMagnificationController(
+ mockController,
+ new Object(),
+ mMagnificationInfoChangedCallback,
+ new MagnificationScaleProvider(mContext),
+ () -> null,
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ () -> mMockScroller,
+ TimeAnimator::new) {
+ @Override
+ public boolean magnificationRegionContains(int displayId, float x, float y) {
+ return true;
+ }
+ };
doAnswer((Answer<Void>) invocationOnMock -> {
Object[] args = invocationOnMock.getArguments();
@@ -263,11 +275,20 @@ public class FullScreenMagnificationGestureHandlerTest {
@NonNull
private FullScreenMagnificationGestureHandler newInstance(boolean detectSingleFingerTripleTap,
boolean detectTwoFingerTripleTap, boolean detectShortcutTrigger) {
- FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler(
- mContext, mFullScreenMagnificationController, mMockTraceManager, mMockCallback,
- detectSingleFingerTripleTap, detectTwoFingerTripleTap, detectShortcutTrigger,
- mWindowMagnificationPromptController, DISPLAY_0,
- mMockFullScreenMagnificationVibrationHelper, mMockMagnificationLogger);
+ FullScreenMagnificationGestureHandler h =
+ new FullScreenMagnificationGestureHandler(
+ mContext,
+ mFullScreenMagnificationController,
+ mMockTraceManager,
+ mMockCallback,
+ detectSingleFingerTripleTap,
+ detectTwoFingerTripleTap,
+ detectShortcutTrigger,
+ mWindowMagnificationPromptController,
+ DISPLAY_0,
+ mMockFullScreenMagnificationVibrationHelper,
+ mMockMagnificationLogger,
+ ViewConfiguration.get(mContext));
if (isWatch()) {
h.setSinglePanningEnabled(true);
} else {
@@ -724,7 +745,7 @@ public class FullScreenMagnificationGestureHandlerTest {
//The minimum movement to transit to panningState.
final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
pointer2.offset(sWipeMinDistance + 1, 0);
- send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
fastForward(ViewConfiguration.getTapTimeout());
assertIn(STATE_PANNING);
@@ -743,7 +764,7 @@ public class FullScreenMagnificationGestureHandlerTest {
//The minimum movement to transit to panningState.
final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
pointer2.offset(sWipeMinDistance + 1, 0);
- send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
fastForward(ViewConfiguration.getTapTimeout());
assertIn(STATE_PANNING);
@@ -762,7 +783,7 @@ public class FullScreenMagnificationGestureHandlerTest {
//The minimum movement to transit to panningState.
final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
pointer2.offset(sWipeMinDistance + 1, 0);
- send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
assertIn(STATE_PANNING);
returnToNormalFrom(STATE_PANNING);
@@ -780,7 +801,7 @@ public class FullScreenMagnificationGestureHandlerTest {
//The minimum movement to transit to panningState.
final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop();
pointer2.offset(sWipeMinDistance + 1, 0);
- send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1));
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
assertIn(STATE_PANNING);
returnToNormalFrom(STATE_PANNING);
@@ -972,6 +993,198 @@ public class FullScreenMagnificationGestureHandlerTest {
}
@Test
+ public void singleFinger_testScrollAfterMagnified_startsFling() {
+ assumeTrue(mMgh.mIsSinglePanningEnabled);
+ goFromStateIdleTo(STATE_ACTIVATED);
+
+ swipeAndHold();
+ fastForward(20);
+ swipe(DEFAULT_POINT, new PointF(DEFAULT_X * 2, DEFAULT_Y * 2), /* durationMs= */ 20);
+
+ verify(mMockScroller).fling(
+ /* startX= */ anyInt(),
+ /* startY= */ anyInt(),
+ // The system fling velocity is configurable and hard to test across devices, so as
+ // long as there is some fling velocity, we are happy.
+ /* velocityX= */ gt(1000),
+ /* velocityY= */ gt(1000),
+ /* minX= */ anyInt(),
+ /* minY= */ anyInt(),
+ /* maxX= */ anyInt(),
+ /* maxY= */ anyInt()
+ );
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+ public void testTwoFingerPanDiagonalAfterMagnified_doesNotFlingXY()
+ throws InterruptedException {
+ goFromStateIdleTo(STATE_ACTIVATED);
+ PointF pointer1 = DEFAULT_POINT;
+ PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+ send(downEvent());
+ send(pointerEvent(ACTION_POINTER_DOWN, new PointF[]{pointer1, pointer2}, 1));
+
+ // first move triggers the panning state
+ pointer1.offset(100, 100);
+ pointer2.offset(100, 100);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[]{pointer1, pointer2}, 0));
+
+ // second move actually pans
+ pointer1.offset(100, 100);
+ pointer2.offset(100, 100);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[]{pointer1, pointer2}, 0));
+ pointer1.offset(100, 100);
+ pointer2.offset(100, 100);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[]{pointer1, pointer2}, 0));
+
+ assertIn(STATE_PANNING);
+ mHandler.timeAdvance();
+ returnToNormalFrom(STATE_PANNING);
+
+ mHandler.timeAdvance();
+
+ verifyNoMoreInteractions(mMockScroller);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+ public void testTwoFingerPanDiagonalAfterMagnified_startsFlingXY()
+ throws InterruptedException {
+ goFromStateIdleTo(STATE_ACTIVATED);
+ PointF pointer1 = DEFAULT_POINT;
+ PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+ send(downEvent());
+ send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
+
+ // first move triggers the panning state
+ pointer1.offset(100, 100);
+ pointer2.offset(100, 100);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+ // second move actually pans
+ pointer1.offset(100, 100);
+ pointer2.offset(100, 100);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+ pointer1.offset(100, 100);
+ pointer2.offset(100, 100);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+ assertIn(STATE_PANNING);
+ mHandler.timeAdvance();
+ returnToNormalFrom(STATE_PANNING);
+
+ mHandler.timeAdvance();
+
+ verify(mMockScroller).fling(
+ /* startX= */ anyInt(),
+ /* startY= */ anyInt(),
+ // The system fling velocity is configurable and hard to test across devices, so as
+ // long as there is some fling velocity, we are happy.
+ /* velocityX= */ gt(1000),
+ /* velocityY= */ gt(1000),
+ /* minX= */ anyInt(),
+ /* minY= */ anyInt(),
+ /* maxX= */ anyInt(),
+ /* maxY= */ anyInt()
+ );
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+ public void testTwoFingerPanRightAfterMagnified_startsFlingXOnly()
+ throws InterruptedException {
+ goFromStateIdleTo(STATE_ACTIVATED);
+ PointF pointer1 = DEFAULT_POINT;
+ PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+ send(downEvent());
+ send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
+
+ // first move triggers the panning state
+ pointer1.offset(100, 0);
+ pointer2.offset(100, 0);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+ // second move actually pans
+ pointer1.offset(100, 0);
+ pointer2.offset(100, 0);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+ pointer1.offset(100, 0);
+ pointer2.offset(100, 0);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+ assertIn(STATE_PANNING);
+ mHandler.timeAdvance();
+ returnToNormalFrom(STATE_PANNING);
+
+ mHandler.timeAdvance();
+
+ verify(mMockScroller).fling(
+ /* startX= */ anyInt(),
+ /* startY= */ anyInt(),
+ // The system fling velocity is configurable and hard to test across devices, so as
+ // long as there is some fling velocity, we are happy.
+ /* velocityX= */ gt(100),
+ /* velocityY= */ eq(0),
+ /* minX= */ anyInt(),
+ /* minY= */ anyInt(),
+ /* maxX= */ anyInt(),
+ /* maxY= */ anyInt()
+ );
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
+ public void testDownEvent_cancelsFling()
+ throws InterruptedException {
+ goFromStateIdleTo(STATE_ACTIVATED);
+ PointF pointer1 = DEFAULT_POINT;
+ PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y);
+
+ send(downEvent());
+ send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1));
+
+ // first move triggers the panning state
+ pointer1.offset(100, 0);
+ pointer2.offset(100, 0);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+ // second move actually pans
+ pointer1.offset(100, 0);
+ pointer2.offset(100, 0);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+ pointer1.offset(100, 0);
+ pointer2.offset(100, 0);
+ fastForward(20);
+ send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 0));
+
+ assertIn(STATE_PANNING);
+ mHandler.timeAdvance();
+ returnToNormalFrom(STATE_PANNING);
+
+ mHandler.timeAdvance();
+
+ send(downEvent());
+ mHandler.timeAdvance();
+
+ verify(mMockScroller).forceFinished(eq(true));
+ }
+
+ @Test
public void testShortcutTriggered_invokeShowWindowPromptAction() {
goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED);
@@ -1397,8 +1610,11 @@ public class FullScreenMagnificationGestureHandlerTest {
send(upEvent());
}
- private void swipe(PointF start, PointF end) {
- swipeAndHold(start, end);
+ private void swipe(PointF start, PointF end, int durationMs) {
+ var mid = new PointF(start.x + (end.x - start.x) / 2f, start.y + (end.y - start.y) / 2f);
+ swipeAndHold(start, mid);
+ fastForward(durationMs);
+ send(moveEvent(end.x - start.x / 10f, end.y - start.y / 10f));
send(upEvent(end.x, end.y));
}
@@ -1491,9 +1707,18 @@ public class FullScreenMagnificationGestureHandlerTest {
private MotionEvent pointerEvent(int action, float x, float y) {
- return pointerEvent(action, new PointF[] {DEFAULT_POINT, new PointF(x, y)}, 1);
+ return pointerEvent(action, new PointF[] {DEFAULT_POINT, new PointF(x, y)},
+ (action == ACTION_POINTER_UP || action == ACTION_POINTER_DOWN) ? 1 : 0);
}
+ /**
+ * Create a pointer event simulating the given pointer positions.
+ *
+ * @param action the action
+ * @param pointersPosition positions of the pointers
+ * @param changedIndex the index of the pointer associated with the ACTION_POINTER_UP or
+ * ACTION_POINTER_DOWN action. Must be 0 for all other actions.
+ */
private MotionEvent pointerEvent(int action, PointF[] pointersPosition, int changedIndex) {
final MotionEvent.PointerProperties[] PointerPropertiesArray =
new MotionEvent.PointerProperties[pointersPosition.length];
@@ -1513,12 +1738,12 @@ public class FullScreenMagnificationGestureHandlerTest {
pointerCoordsArray[i] = pointerCoords;
}
- action += (changedIndex << ACTION_POINTER_INDEX_SHIFT);
+ var actionWithPointer = action | (changedIndex << ACTION_POINTER_INDEX_SHIFT);
- return MotionEvent.obtain(
+ var event = MotionEvent.obtain(
/* downTime */ mClock.now(),
/* eventTime */ mClock.now(),
- /* action */ action,
+ /* action */ actionWithPointer,
/* pointerCount */ pointersPosition.length,
/* pointerProperties */ PointerPropertiesArray,
/* pointerCoords */ pointerCoordsArray,
@@ -1530,6 +1755,14 @@ public class FullScreenMagnificationGestureHandlerTest {
/* edgeFlags */ 0,
/* source */ InputDevice.SOURCE_TOUCHSCREEN,
/* flags */ 0);
+
+ Truth.assertThat(event.getActionIndex()).isEqualTo(changedIndex);
+ Truth.assertThat(event.getActionMasked()).isEqualTo(action);
+ if (action == ACTION_DOWN) {
+ Truth.assertThat(changedIndex).isEqualTo(0);
+ }
+
+ return event;
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 28d07f995ad3..cd904eb562de 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -42,6 +42,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.accessibilityservice.MagnificationConfig;
+import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -59,6 +60,7 @@ import android.view.Display;
import android.view.DisplayInfo;
import android.view.accessibility.IRemoteMagnificationAnimationCallback;
import android.view.accessibility.MagnificationAnimationCallback;
+import android.widget.Scroller;
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
@@ -119,6 +121,8 @@ public class MagnificationControllerTest {
@Mock
private ValueAnimator mValueAnimator;
@Mock
+ private TimeAnimator mTimeAnimator;
+ @Mock
private MessageCapturingHandler mMessageCapturingHandler;
private FullScreenMagnificationController mScreenMagnificationController;
@@ -195,14 +199,17 @@ public class MagnificationControllerTest {
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal);
- mScreenMagnificationController = spy(new FullScreenMagnificationController(
- mControllerCtx,
- new Object(),
- mScreenMagnificationInfoChangedCallbackDelegate,
- mScaleProvider,
- () -> null,
- ConcurrentUtils.DIRECT_EXECUTOR
- ));
+ mScreenMagnificationController =
+ spy(
+ new FullScreenMagnificationController(
+ mControllerCtx,
+ new Object(),
+ mScreenMagnificationInfoChangedCallbackDelegate,
+ mScaleProvider,
+ () -> null,
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ () -> new Scroller(mContext),
+ () -> mTimeAnimator));
mScreenMagnificationController.register(TEST_DISPLAY);
mMagnificationConnectionManager = spy(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index e7da26ea38b1..98789ac96e98 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -519,11 +519,12 @@ public abstract class BaseAbsoluteVolumeBehaviorTest {
eq(AudioManager.ADJUST_MUTE), anyInt());
clearInvocations(mAudioManager);
- // New volume only: sets volume only
+ // New volume only: sets both volume and mute.
+ // Volume changes can affect mute status; we need to set mute afterwards to undo this.
receiveReportAudioStatus(32, true);
verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
anyInt());
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
eq(AudioManager.ADJUST_MUTE), anyInt());
clearInvocations(mAudioManager);
@@ -536,17 +537,17 @@ public abstract class BaseAbsoluteVolumeBehaviorTest {
clearInvocations(mAudioManager);
// Repeat of earlier message: sets neither volume nor mute
- // Exception: On TV, volume is set to ensure that UI is shown
+ // Exception: On TV, mute is set to ensure that UI is shown
receiveReportAudioStatus(32, false);
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(32), anyInt());
if (getDeviceType() == HdmiDeviceInfo.DEVICE_TV) {
- verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
- anyInt());
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE), anyInt());
} else {
- verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
- anyInt());
+ verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE), anyInt());
}
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
- eq(AudioManager.ADJUST_UNMUTE), anyInt());
clearInvocations(mAudioManager);
// Volume not within range [0, 100]: sets neither volume nor mute
@@ -570,7 +571,8 @@ public abstract class BaseAbsoluteVolumeBehaviorTest {
receiveReportAudioStatus(32, false);
verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
anyInt());
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ // Update mute status because we updated volume
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
eq(AudioManager.ADJUST_UNMUTE), anyInt());
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
index a410702e202d..9ad2652e9c63 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
@@ -174,11 +174,12 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav
eq(AudioManager.ADJUST_MUTE), anyInt());
clearInvocations(mAudioManager);
- // New volume only: sets volume only
+ // New volume only: sets both volume and mute.
+ // Volume changes can affect mute status; we need to set mute afterwards to undo this.
receiveReportAudioStatus(32, true);
verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
anyInt());
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
eq(AudioManager.ADJUST_MUTE), anyInt());
clearInvocations(mAudioManager);
@@ -190,11 +191,11 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav
eq(AudioManager.ADJUST_UNMUTE), anyInt());
clearInvocations(mAudioManager);
- // Repeat of earlier message: sets volume only (to ensure volume UI is shown)
+ // Repeat of earlier message: sets mute only (to ensure volume UI is shown)
receiveReportAudioStatus(32, false);
- verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
+ verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(8),
anyInt());
- verify(mAudioManager, never()).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ verify(mAudioManager).adjustStreamVolume(eq(AudioManager.STREAM_MUSIC),
eq(AudioManager.ADJUST_UNMUTE), anyInt());
clearInvocations(mAudioManager);
@@ -392,18 +393,18 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav
INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getMute()));
mTestLooper.dispatchAll();
- // HdmiControlService calls setStreamVolume to trigger volume UI
- verify(mAudioManager).setStreamVolume(
+ // HdmiControlService calls adjustStreamVolume to trigger volume UI
+ verify(mAudioManager).adjustStreamVolume(
+ eq(AudioManager.STREAM_MUSIC),
+ eq(AudioManager.ADJUST_UNMUTE),
+ eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
+ // setStreamVolume is not called because volume didn't change,
+ // and adjustStreamVolume is sufficient to show volume UI
+ verify(mAudioManager, never()).setStreamVolume(
eq(AudioManager.STREAM_MUSIC),
// Volume level is rescaled to the max volume of STREAM_MUSIC
eq(INITIAL_SYSTEM_AUDIO_DEVICE_STATUS.getVolume()
* STREAM_MUSIC_MAX_VOLUME / AudioStatus.MAX_VOLUME),
eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
- // adjustStreamVolume is not called because mute status didn't change,
- // and setStreamVolume is sufficient to show volume UI
- verify(mAudioManager, never()).adjustStreamVolume(
- eq(AudioManager.STREAM_MUSIC),
- eq(AudioManager.ADJUST_UNMUTE),
- eq(AudioManager.FLAG_ABSOLUTE_VOLUME | AudioManager.FLAG_SHOW_UI));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
index 213e05eb788e..2b07d33ea541 100644
--- a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
@@ -18,6 +18,7 @@ package com.android.server.job;
import static android.app.job.JobInfo.NETWORK_TYPE_ANY;
import static android.app.job.JobInfo.NETWORK_TYPE_NONE;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -39,8 +40,6 @@ import com.android.server.job.controllers.JobStatus;
import org.junit.Test;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Random;
public class PendingJobQueueTest {
@@ -68,7 +67,7 @@ public class PendingJobQueueTest {
@Test
public void testAdd() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testAdd", createJobInfo(1), 1));
jobs.add(createJobStatus("testAdd", createJobInfo(2), 2));
jobs.add(createJobStatus("testAdd", createJobInfo(3).setExpedited(true), 3));
@@ -77,7 +76,7 @@ public class PendingJobQueueTest {
PendingJobQueue jobQueue = new PendingJobQueue();
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.add(jobs.get(i));
+ jobQueue.add(jobs.valueAt(i));
assertEquals(i + 1, jobQueue.size());
}
@@ -90,7 +89,7 @@ public class PendingJobQueueTest {
@Test
public void testAddAll() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testAddAll", createJobInfo(1), 1));
jobs.add(createJobStatus("testAddAll", createJobInfo(2), 2));
jobs.add(createJobStatus("testAddAll", createJobInfo(3).setExpedited(true), 3));
@@ -110,7 +109,7 @@ public class PendingJobQueueTest {
@Test
public void testClear() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testClear", createJobInfo(1), 1));
jobs.add(createJobStatus("testClear", createJobInfo(2), 2));
jobs.add(createJobStatus("testClear", createJobInfo(3).setExpedited(true), 3));
@@ -179,7 +178,7 @@ public class PendingJobQueueTest {
@Test
public void testRemove() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testRemove", createJobInfo(1), 1));
jobs.add(createJobStatus("testRemove", createJobInfo(2), 2));
jobs.add(createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 3));
@@ -192,8 +191,8 @@ public class PendingJobQueueTest {
ArraySet<JobStatus> removed = new ArraySet<>();
JobStatus job;
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.remove(jobs.get(i));
- removed.add(jobs.get(i));
+ jobQueue.remove(jobs.valueAt(i));
+ removed.add(jobs.valueAt(i));
assertEquals(jobs.size() - i - 1, jobQueue.size());
@@ -209,7 +208,7 @@ public class PendingJobQueueTest {
@Test
public void testRemove_duringIteration() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
jobs.add(createJobStatus("testRemove", createJobInfo(1), 1));
jobs.add(createJobStatus("testRemove", createJobInfo(2), 2));
jobs.add(createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 3));
@@ -234,7 +233,7 @@ public class PendingJobQueueTest {
@Test
public void testRemove_outOfOrder() {
- List<JobStatus> jobs = new ArrayList<>();
+ ArraySet<JobStatus> jobs = new ArraySet<>();
JobStatus job1 = createJobStatus("testRemove", createJobInfo(1), 1);
JobStatus job2 = createJobStatus("testRemove", createJobInfo(2), 1);
JobStatus job3 = createJobStatus("testRemove", createJobInfo(3).setExpedited(true), 1);
@@ -269,8 +268,8 @@ public class PendingJobQueueTest {
Log.d(TAG, testJobToString(job));
}
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.remove(jobs.get(i));
- removed.add(jobs.get(i));
+ jobQueue.remove(jobs.valueAt(i));
+ removed.add(jobs.valueAt(i));
assertEquals(jobs.size() - i - 1, jobQueue.size());
@@ -294,8 +293,8 @@ public class PendingJobQueueTest {
removed.clear();
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.remove(jobs.get(i));
- removed.add(jobs.get(i));
+ jobQueue.remove(jobs.valueAt(i));
+ removed.add(jobs.valueAt(i));
assertEquals(jobs.size() - i - 1, jobQueue.size());
@@ -319,8 +318,8 @@ public class PendingJobQueueTest {
removed.clear();
for (int i = 0; i < jobs.size(); ++i) {
- jobQueue.remove(jobs.get(i));
- removed.add(jobs.get(i));
+ jobQueue.remove(jobs.valueAt(i));
+ removed.add(jobs.valueAt(i));
assertEquals(jobs.size() - i - 1, jobQueue.size());
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
index c81fbb443dce..cee738711473 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -46,6 +46,7 @@ import android.os.UserManager;
import android.permission.PermissionCheckerManager;
import android.permission.PermissionManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.SparseArray;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
@@ -55,6 +56,7 @@ import com.android.server.pm.permission.PermissionManagerService;
import com.android.server.wm.ActivityTaskManagerInternal;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -109,6 +111,8 @@ public class CrossProfileAppsServiceImplTest {
private IApplicationThread mIApplicationThread;
private SparseArray<Boolean> mUserEnabled = new SparseArray<>();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void initCrossProfileAppsServiceImpl() {
@@ -123,8 +127,9 @@ public class CrossProfileAppsServiceImplTest {
mUserEnabled.put(PRIMARY_USER, true);
mUserEnabled.put(PROFILE_OF_PRIMARY_USER, true);
mUserEnabled.put(SECONDARY_USER, true);
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_ENABLE_HIDING_PROFILES);
- when(mUserManager.getEnabledProfileIds(anyInt())).thenAnswer(
+ when(mUserManager.getProfileIdsExcludingHidden(anyInt(), eq(true))).thenAnswer(
invocation -> {
List<Integer> users = new ArrayList<>();
final int targetUser = invocation.getArgument(0);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
index 39cc6537c759..6decf438f95b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
@@ -114,16 +114,19 @@ public class UserManagerServiceCreateProfileTest {
final String userType1 = USER_TYPE_PROFILE_MANAGED;
// System user should still have no userType1 profile so getProfileIds should be empty.
- int[] users = mUserManagerService.getProfileIds(UserHandle.USER_SYSTEM, userType1, false);
+ int[] users = mUserManagerService.getProfileIds(UserHandle.USER_SYSTEM, userType1,
+ false, /* excludeHidden */ false);
assertEquals("System user should have no managed profiles", 0, users.length);
// Secondary user should have one userType1 profile, so return just that.
- users = mUserManagerService.getProfileIds(secondaryUser.id, userType1, false);
+ users = mUserManagerService.getProfileIds(secondaryUser.id, userType1,
+ false, /* excludeHidden */ false);
assertEquals("Wrong number of profiles", 1, users.length);
assertEquals("Wrong profile id", profile.id, users[0]);
// The profile itself is a userType1 profile, so it should return just itself.
- users = mUserManagerService.getProfileIds(profile.id, userType1, false);
+ users = mUserManagerService.getProfileIds(profile.id, userType1, false, /* excludeHidden */
+ false);
assertEquals("Wrong number of profiles", 1, users.length);
assertEquals("Wrong profile id", profile.id, users[0]);
}
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index ffb3bce33a27..4ab9d3ecf624 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -90,10 +90,12 @@ public class HintManagerServiceTest {
private static final long[] DURATIONS_ZERO = new long[] {};
private static final long[] TIMESTAMPS_ZERO = new long[] {};
private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L};
- private static final WorkDuration[] WORK_DURATIONS_THREE = new WorkDuration[] {
+ private static final WorkDuration[] WORK_DURATIONS_FIVE = new WorkDuration[] {
new WorkDuration(1L, 11L, 8L, 4L, 1L),
new WorkDuration(2L, 13L, 8L, 6L, 2L),
new WorkDuration(3L, 333333333L, 8L, 333333333L, 3L),
+ new WorkDuration(2L, 13L, 0L, 6L, 2L),
+ new WorkDuration(2L, 13L, 8L, 0L, 2L),
};
@Mock private Context mContext;
@@ -609,9 +611,9 @@ public class HintManagerServiceTest {
.createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
a.updateTargetWorkDuration(100L);
- a.reportActualWorkDuration2(WORK_DURATIONS_THREE);
+ a.reportActualWorkDuration2(WORK_DURATIONS_FIVE);
verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(),
- eq(WORK_DURATIONS_THREE));
+ eq(WORK_DURATIONS_FIVE));
assertThrows(IllegalArgumentException.class, () -> {
a.reportActualWorkDuration2(new WorkDuration[] {});
@@ -627,7 +629,7 @@ public class HintManagerServiceTest {
});
assertThrows(IllegalArgumentException.class, () -> {
- a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 4L, 1L)});
+ a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 0L, 1L)});
});
assertThrows(IllegalArgumentException.class, () -> {
@@ -648,7 +650,7 @@ public class HintManagerServiceTest {
latch.await();
assertFalse(service.mUidObserver.isUidForeground(a.mUid));
- a.reportActualWorkDuration2(WORK_DURATIONS_THREE);
+ a.reportActualWorkDuration2(WORK_DURATIONS_FIVE);
verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index 03cdbbd8397b..1c2dab867de2 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -39,6 +39,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.server.SystemConfig;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -710,6 +711,7 @@ public class SystemConfigTest {
@Test
@RequiresFlagsEnabled(
android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @Ignore("b/323603510")
public void getEnhancedConfirmationTrustedInstallers_returnsTrustedInstallers()
throws IOException {
String pkgName = "com.example.app";
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index 46e14d51b5fd..5f18f848df75 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -208,6 +208,21 @@ public class ClientLifecycleManagerTests extends SystemServiceTestsBase {
}
@Test
+ public void testScheduleTransactionAndLifecycleItems_shouldDispatchImmediately()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+ spyOn(mWms.mWindowPlacerLocked);
+ doReturn(true).when(mWms.mWindowPlacerLocked).isTraversalScheduled();
+
+ // Use non binder client to get non-recycled ClientTransaction.
+ mLifecycleManager.scheduleTransactionAndLifecycleItems(mNonBinderClient, mTransactionItem,
+ mLifecycleItem, true /* shouldDispatchImmediately */);
+
+ verify(mLifecycleManager).scheduleTransaction(any());
+ assertTrue(mLifecycleManager.mPendingTransactions.isEmpty());
+ }
+
+ @Test
public void testDispatchPendingTransactions() throws RemoteException {
mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 0c1a9c3ab5b9..f42cdb88021e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -845,6 +845,30 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ public void testLetterboxDisplayedForWindowBelow() {
+ setUpDisplaySizeWithApp(1000, 2500);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+ // Prepare two windows, one base app window below the splash screen
+ final WindowState appWindow = addWindowToActivity(mActivity);
+ final WindowState startWindow = addWindowToActivity(mActivity, TYPE_APPLICATION_STARTING);
+ spyOn(appWindow);
+ // Base app window is letterboxed for display cutout and splash screen is fullscreen
+ doReturn(true).when(appWindow).isLetterboxedForDisplayCutout();
+
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ assertEquals(2, mActivity.mChildren.size());
+ // Splash screen is still the activity's main window
+ assertEquals(startWindow, mActivity.findMainWindow());
+ assertFalse(startWindow.isLetterboxedForDisplayCutout());
+
+ final Rect letterboxInnerBounds = new Rect();
+ mActivity.getLetterboxInnerBounds(letterboxInnerBounds);
+ // Letterboxed is still displayed for app window below splash screen
+ assertFalse(letterboxInnerBounds.isEmpty());
+ }
+
+ @Test
public void testLetterboxFullscreenBoundsAndNotImeAttachable() {
final int displayWidth = 2500;
setUpDisplaySizeWithApp(displayWidth, 1000);
@@ -4773,8 +4797,12 @@ public class SizeCompatTests extends WindowTestsBase {
}
private WindowState addWindowToActivity(ActivityRecord activity) {
+ return addWindowToActivity(activity, WindowManager.LayoutParams.TYPE_BASE_APPLICATION);
+ }
+
+ private WindowState addWindowToActivity(ActivityRecord activity, int type) {
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+ params.type = type;
params.setFitInsetsSides(0);
params.setFitInsetsTypes(0);
final TestWindowState w = new TestWindowState(
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index a88285ac4c8f..897a3da07473 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -527,29 +527,11 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testOnActivityReparentedToTask_untrustedEmbed_notReported() {
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
- DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
- mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
- final Task task = createTask(mDisplayContent);
- task.addChild(mTaskFragment, POSITION_TOP);
- final ActivityRecord activity = createActivityRecord(task);
- // Flush EVENT_APPEARED.
- mController.dispatchPendingEvents();
-
- // Make sure the activity is embedded in untrusted mode.
- activity.info.applicationInfo.uid = uid + 1;
- doReturn(pid + 1).when(activity).getPid();
- task.effectiveUid = uid;
- doReturn(EMBEDDING_ALLOWED).when(task).isAllowedToEmbedActivity(activity, uid);
- doReturn(false).when(task).isAllowedToEmbedActivityInTrustedMode(activity, uid);
- doReturn(true).when(task).isAllowedToEmbedActivityInUntrustedMode(activity);
+ final ActivityRecord activity = setupUntrustedEmbeddingPipReparent();
+ doReturn(false).when(activity).isUntrustedEmbeddingStateSharingAllowed();
// Notify organizer if it was embedded before entered Pip.
// Create a temporary token since the activity doesn't belong to the same process.
- clearInvocations(mOrganizer);
- activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
@@ -558,6 +540,30 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
}
@Test
+ public void testOnActivityReparentedToTask_untrustedEmbed_reportedWhenAppOptIn() {
+ final ActivityRecord activity = setupUntrustedEmbeddingPipReparent();
+ doReturn(true).when(activity).isUntrustedEmbeddingStateSharingAllowed();
+
+ // Notify organizer if it was embedded before entered Pip.
+ // Create a temporary token since the activity doesn't belong to the same process.
+ mController.onActivityReparentedToTask(activity);
+ mController.dispatchPendingEvents();
+
+ // Allow organizer to reparent activity in other process using the temporary token.
+ verify(mOrganizer).onTransactionReady(mTransactionCaptor.capture());
+ final TaskFragmentTransaction transaction = mTransactionCaptor.getValue();
+ final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
+ assertFalse(changes.isEmpty());
+ final TaskFragmentTransaction.Change change = changes.get(0);
+ assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
+ assertEquals(activity.getTask().mTaskId, change.getTaskId());
+ assertIntentsEqualForOrganizer(activity.intent, change.getActivityIntent());
+ assertNotEquals(activity.token, change.getActivityToken());
+ mTransaction.reparentActivityToTaskFragment(mFragmentToken, change.getActivityToken());
+ assertApplyTransactionAllowed(mTransaction);
+ }
+
+ @Test
public void testOnActivityReparentedToTask_trimReportedIntent() {
// Make sure the activity pid/uid is the same as the organizer caller.
final int pid = Binder.getCallingPid();
@@ -1868,6 +1874,34 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
OP_TYPE_REORDER_TO_TOP_OF_TASK);
}
+ @NonNull
+ private ActivityRecord setupUntrustedEmbeddingPipReparent() {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
+ DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ final Task task = createTask(mDisplayContent);
+ task.addChild(mTaskFragment, POSITION_TOP);
+ final ActivityRecord activity = createActivityRecord(task);
+
+ // Flush EVENT_APPEARED.
+ mController.dispatchPendingEvents();
+
+ // Make sure the activity is embedded in untrusted mode.
+ activity.info.applicationInfo.uid = uid + 1;
+ doReturn(pid + 1).when(activity).getPid();
+ task.effectiveUid = uid;
+ doReturn(EMBEDDING_ALLOWED).when(task).isAllowedToEmbedActivity(activity, uid);
+ doReturn(false).when(task).isAllowedToEmbedActivityInTrustedMode(activity, uid);
+ doReturn(true).when(task).isAllowedToEmbedActivityInUntrustedMode(activity);
+
+ clearInvocations(mOrganizer);
+ activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
+
+ return activity;
+ }
+
private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
@TaskFragmentOperation.OperationType int opType) {
final Task task = createTask(mDisplayContent);
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 536e458159d1..01448c3bcd9e 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -17,6 +17,7 @@
package android.telecom;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -43,6 +44,7 @@ import com.android.internal.os.SomeArgs;
import com.android.internal.telecom.IConnectionService;
import com.android.internal.telecom.IConnectionServiceAdapter;
import com.android.internal.telecom.RemoteServiceCallback;
+import com.android.server.telecom.flags.Flags;
import java.util.ArrayList;
import java.util.Collection;
@@ -3235,27 +3237,27 @@ public abstract class ConnectionService extends Service {
}
/**
- * Called after the {@link Connection} returned by
+ * Called by Telecom after the {@link Connection} returned by
* {@link #onCreateIncomingConnection(PhoneAccountHandle, ConnectionRequest)}
* or {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)} has been
* added to the {@link ConnectionService} and sent to Telecom.
*
- * @param connection the {@link Connection}.
- * @hide
+ * @param connection the {@link Connection} which was added to Telecom.
*/
- public void onCreateConnectionComplete(Connection connection) {
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public void onCreateConnectionComplete(@NonNull Connection connection) {
}
/**
- * Called after the {@link Conference} returned by
+ * Called by Telecom after the {@link Conference} returned by
* {@link #onCreateIncomingConference(PhoneAccountHandle, ConnectionRequest)}
* or {@link #onCreateOutgoingConference(PhoneAccountHandle, ConnectionRequest)} has been
* added to the {@link ConnectionService} and sent to Telecom.
*
- * @param conference the {@link Conference}.
- * @hide
+ * @param conference the {@link Conference} which was added to Telecom.
*/
- public void onCreateConferenceComplete(Conference conference) {
+ @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public void onCreateConferenceComplete(@NonNull Conference conference) {
}
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index 2b0d6261886f..d5db61233230 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -174,6 +174,7 @@ public final class CarrierRestrictionRules implements Parcelable {
private int mMultiSimPolicy;
@CarrierRestrictionStatus
private int mCarrierRestrictionStatus;
+ private boolean mUseCarrierLockInfo;
private CarrierRestrictionRules() {
mAllowedCarriers = new ArrayList<CarrierIdentifier>();
@@ -183,6 +184,7 @@ public final class CarrierRestrictionRules implements Parcelable {
mCarrierRestrictionDefault = CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED;
mMultiSimPolicy = MULTISIM_POLICY_NONE;
mCarrierRestrictionStatus = TelephonyManager.CARRIER_RESTRICTION_STATUS_UNKNOWN;
+ mUseCarrierLockInfo = false;
}
private CarrierRestrictionRules(Parcel in) {
@@ -198,6 +200,7 @@ public final class CarrierRestrictionRules implements Parcelable {
if (Flags.carrierRestrictionRulesEnhancement()) {
in.readTypedList(mAllowedCarrierInfo, CarrierInfo.CREATOR);
in.readTypedList(mExcludedCarrierInfo, CarrierInfo.CREATOR);
+ mUseCarrierLockInfo = in.readBoolean();
}
}
@@ -213,6 +216,14 @@ public final class CarrierRestrictionRules implements Parcelable {
* Indicates if all carriers are allowed
*/
public boolean isAllCarriersAllowed() {
+ if (Flags.carrierRestrictionStatus() && mCarrierRestrictionStatus
+ == TelephonyManager.CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED) {
+ return true;
+ }
+ if (Flags.carrierRestrictionRulesEnhancement() && mUseCarrierLockInfo) {
+ return (mAllowedCarrierInfo.isEmpty() && mExcludedCarrierInfo.isEmpty()
+ && mCarrierRestrictionDefault == CARRIER_RESTRICTION_DEFAULT_ALLOWED);
+ }
return (mAllowedCarriers.isEmpty() && mExcludedCarriers.isEmpty()
&& mCarrierRestrictionDefault == CARRIER_RESTRICTION_DEFAULT_ALLOWED);
}
@@ -419,6 +430,7 @@ public final class CarrierRestrictionRules implements Parcelable {
if (Flags.carrierRestrictionRulesEnhancement()) {
out.writeTypedList(mAllowedCarrierInfo);
out.writeTypedList(mExcludedCarrierInfo);
+ out.writeBoolean(mUseCarrierLockInfo);
}
}
@@ -451,7 +463,8 @@ public final class CarrierRestrictionRules implements Parcelable {
public String toString() {
return "CarrierRestrictionRules(allowed:" + mAllowedCarriers + ", excluded:"
+ mExcludedCarriers + ", default:" + mCarrierRestrictionDefault
- + ", multisim policy:" + mMultiSimPolicy + getCarrierInfoList() + ")";
+ + ", MultiSim policy:" + mMultiSimPolicy + getCarrierInfoList() +
+ " mIsCarrierLockInfoSupported = " + mUseCarrierLockInfo + ")";
}
private String getCarrierInfoList() {
@@ -490,6 +503,7 @@ public final class CarrierRestrictionRules implements Parcelable {
TelephonyManager.CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED;
mRules.mAllowedCarrierInfo.clear();
mRules.mExcludedCarrierInfo.clear();
+ mRules.mUseCarrierLockInfo = false;
}
return this;
}
@@ -572,5 +586,16 @@ public final class CarrierRestrictionRules implements Parcelable {
mRules.mExcludedCarrierInfo = new ArrayList<CarrierInfo>(excludedCarrierInfo);
return this;
}
+
+ /**
+ * set whether the HAL radio supports the advanced carrier lock features or not.
+ *
+ * @param carrierLockInfoSupported advanced carrierInfo changes supported or not
+ * @hide
+ */
+ public @NonNull Builder setCarrierLockInfoFeature(boolean carrierLockInfoSupported) {
+ mRules.mUseCarrierLockInfo = carrierLockInfoSupported;
+ return this;
+ }
}
}