summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityThread.java21
-rw-r--r--core/java/android/app/Notification.java23
-rw-r--r--core/java/android/app/SystemServiceRegistry.java36
-rw-r--r--core/java/android/app/notification.aconfig10
-rw-r--r--core/java/android/app/servertransaction/ClientTransactionListenerController.java2
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java8
-rw-r--r--core/java/android/companion/ICompanionDeviceManager.aidl24
-rw-r--r--core/java/android/os/vibrator/flags.aconfig8
-rw-r--r--core/java/android/view/ViewRootImpl.java4
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig5
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java16
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java176
-rw-r--r--core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java6
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java6
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java528
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java4
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java46
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java63
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java32
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java28
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java195
-rw-r--r--libs/hwui/Android.bp12
-rw-r--r--libs/hwui/Properties.h2
-rw-r--r--libs/hwui/hwui/AnimatedImageDrawable.cpp14
-rw-r--r--libs/hwui/hwui/AnimatedImageThread.cpp4
-rw-r--r--libs/hwui/jni/AnimatedImageDrawable.cpp36
-rw-r--r--libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp122
-rw-r--r--libs/hwui/pipeline/skia/SkiaCpuPipeline.h77
-rw-r--r--libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp185
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp16
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp234
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.h24
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp13
-rw-r--r--libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h59
-rw-r--r--libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h (renamed from libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h)4
-rw-r--r--libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h (renamed from libs/hwui/pipeline/skia/SkiaVulkanPipeline.h)4
l---------libs/hwui/platform/host/android/api-level.h1
-rw-r--r--libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h83
-rw-r--r--libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h35
-rw-r--r--libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h35
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp4
-rw-r--r--libs/hwui/renderthread/IRenderPipeline.h3
-rw-r--r--media/java/android/media/IMediaRouterService.aidl5
-rw-r--r--media/java/android/media/MediaRouter2.java20
-rw-r--r--media/java/android/media/MediaRouter2Manager.java15
-rw-r--r--nfc/api/current.txt8
-rw-r--r--nfc/java/android/nfc/cardemulation/CardEmulation.java50
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt21
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt2
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt31
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt42
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt8
-rw-r--r--packages/SettingsLib/Spa/build.gradle.kts2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt (renamed from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt)10
-rw-r--r--packages/SettingsLib/Spa/gradle/libs.versions.toml6
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle.kts4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt44
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt7
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/LoadingBar.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt38
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt63
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt73
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt)64
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt9
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt84
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt4
-rw-r--r--services/accessibility/accessibility.aconfig5
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java6
-rw-r--r--services/companion/java/com/android/server/companion/CompanionApplicationController.java567
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java566
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java3
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java41
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java41
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationStore.java53
-rw-r--r--services/companion/java/com/android/server/companion/association/DisassociationProcessor.java86
-rw-r--r--services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java6
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java70
-rw-r--r--services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java2
-rw-r--r--services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java2
-rw-r--r--services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java321
-rw-r--r--services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java620
-rw-r--r--services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java (renamed from services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java)63
-rw-r--r--services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java1042
-rw-r--r--services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java14
-rw-r--r--services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java29
-rw-r--r--services/companion/java/com/android/server/companion/utils/PermissionsUtils.java20
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java16
-rw-r--r--services/core/java/com/android/server/am/AppProfiler.java131
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java3
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java11
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java46
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java8
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java23
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java74
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java6
-rw-r--r--services/core/java/com/android/server/display/config/LowBrightnessData.java39
-rw-r--r--services/core/java/com/android/server/inputmethod/LocaleUtils.java28
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java61
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java25
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java174
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java24
-rw-r--r--services/core/java/com/android/server/notification/NotificationAttentionHelper.java6
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java911
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java19
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java18
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java44
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java19
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java32
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java13
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java6
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java2
-rw-r--r--services/core/java/com/android/server/wm/Task.java16
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java4
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd3
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt2
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java23
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java11
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt179
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java1969
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java31
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java12
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java257
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java2
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java6
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java54
149 files changed, 5100 insertions, 5871 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index fa9346e89a9f..4bf887962dfa 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -607,9 +607,9 @@ public final class ActivityThread extends ClientTransactionHandler
Configuration createdConfig;
Configuration overrideConfig;
@NonNull
- private ActivityWindowInfo mActivityWindowInfo;
- @Nullable
- private ActivityWindowInfo mLastReportedActivityWindowInfo;
+ private final ActivityWindowInfo mActivityWindowInfo = new ActivityWindowInfo();
+ @NonNull
+ private final ActivityWindowInfo mLastReportedActivityWindowInfo = new ActivityWindowInfo();
// Used for consolidating configs before sending on to Activity.
private final Configuration tmpConfig = new Configuration();
@@ -700,7 +700,7 @@ public final class ActivityThread extends ClientTransactionHandler
mSceneTransitionInfo = sceneTransitionInfo;
mLaunchedFromBubble = launchedFromBubble;
mTaskFragmentToken = taskFragmentToken;
- mActivityWindowInfo = activityWindowInfo;
+ mActivityWindowInfo.set(activityWindowInfo);
init();
}
@@ -720,7 +720,7 @@ public final class ActivityThread extends ClientTransactionHandler
throw new IllegalStateException(
"Received config update for non-existing activity");
}
- if (activityWindowInfoFlag() && activityWindowInfo == null) {
+ if (activityWindowInfo == null) {
Log.w(TAG, "Received empty ActivityWindowInfo update for r=" + activity);
activityWindowInfo = mActivityWindowInfo;
}
@@ -6063,7 +6063,7 @@ public final class ActivityThread extends ClientTransactionHandler
target.createdConfig = config.getGlobalConfiguration();
target.overrideConfig = config.getOverrideConfiguration();
target.pendingConfigChanges |= configChanges;
- target.mActivityWindowInfo = activityWindowInfo;
+ target.mActivityWindowInfo.set(activityWindowInfo);
}
return scheduleRelaunch ? target : null;
@@ -6257,7 +6257,7 @@ public final class ActivityThread extends ClientTransactionHandler
}
r.startsNotResumed = startsNotResumed;
r.overrideConfig = overrideConfig;
- r.mActivityWindowInfo = activityWindowInfo;
+ r.mActivityWindowInfo.set(activityWindowInfo);
handleLaunchActivity(r, pendingActions, mLastReportedDeviceId, customIntent);
}
@@ -6759,7 +6759,7 @@ public final class ActivityThread extends ClientTransactionHandler
// Perform updates.
r.overrideConfig = overrideConfig;
- r.mActivityWindowInfo = activityWindowInfo;
+ r.mActivityWindowInfo.set(activityWindowInfo);
final ViewRootImpl viewRoot = r.activity.mDecor != null
? r.activity.mDecor.getViewRootImpl() : null;
@@ -6792,11 +6792,10 @@ public final class ActivityThread extends ClientTransactionHandler
if (!activityWindowInfoFlag()) {
return;
}
- if (r.mActivityWindowInfo == null
- || r.mActivityWindowInfo.equals(r.mLastReportedActivityWindowInfo)) {
+ if (r.mActivityWindowInfo.equals(r.mLastReportedActivityWindowInfo)) {
return;
}
- r.mLastReportedActivityWindowInfo = r.mActivityWindowInfo;
+ r.mLastReportedActivityWindowInfo.set(r.mActivityWindowInfo);
ClientTransactionListenerController.getInstance().onActivityWindowInfoChanged(r.token,
r.mActivityWindowInfo);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7337a7c3b2c3..d7b9a2c46c9b 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -24,6 +24,7 @@ import static android.app.admin.DevicePolicyResources.UNDEFINED;
import static android.graphics.drawable.Icon.TYPE_URI;
import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
import static android.app.Flags.evenlyDividedCallStyleActionLayout;
+import static android.app.Flags.updateRankingTime;
import static java.util.Objects.requireNonNull;
@@ -339,8 +340,9 @@ public class Notification implements Parcelable
/**
* The creation time of the notification
+ * @hide
*/
- private long creationTime;
+ public long creationTime;
/**
* The resource id of a drawable to use as the icon in the status bar.
@@ -2578,7 +2580,11 @@ public class Notification implements Parcelable
public Notification()
{
this.when = System.currentTimeMillis();
- this.creationTime = System.currentTimeMillis();
+ if (updateRankingTime()) {
+ creationTime = when;
+ } else {
+ this.creationTime = System.currentTimeMillis();
+ }
this.priority = PRIORITY_DEFAULT;
}
@@ -2589,6 +2595,9 @@ public class Notification implements Parcelable
public Notification(Context context, int icon, CharSequence tickerText, long when,
CharSequence contentTitle, CharSequence contentText, Intent contentIntent)
{
+ if (updateRankingTime()) {
+ creationTime = when;
+ }
new Builder(context)
.setWhen(when)
.setSmallIcon(icon)
@@ -2618,7 +2627,11 @@ public class Notification implements Parcelable
this.icon = icon;
this.tickerText = tickerText;
this.when = when;
- this.creationTime = System.currentTimeMillis();
+ if (updateRankingTime()) {
+ creationTime = when;
+ } else {
+ this.creationTime = System.currentTimeMillis();
+ }
}
/**
@@ -6843,7 +6856,9 @@ public class Notification implements Parcelable
}
}
- mN.creationTime = System.currentTimeMillis();
+ if (!updateRankingTime()) {
+ mN.creationTime = System.currentTimeMillis();
+ }
// lazy stuff from mContext; see comment in Builder(Context, Notification)
Notification.addFieldsFromContext(mContext, mN);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 66ec865092f7..103af4bfa760 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -66,6 +66,8 @@ import android.companion.ICompanionDeviceManager;
import android.companion.virtual.IVirtualDeviceManager;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.content.ClipboardManager;
import android.content.ContentCaptureOptions;
import android.content.Context;
@@ -196,6 +198,7 @@ import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.StatsFrameworkInitializer;
import android.os.SystemConfigManager;
+import android.os.SystemProperties;
import android.os.SystemUpdateManager;
import android.os.SystemVibrator;
import android.os.SystemVibratorManager;
@@ -285,6 +288,18 @@ public final class SystemServiceRegistry {
/** @hide */
public static boolean sEnableServiceNotFoundWtf = false;
+ /**
+ * Starting with {@link VANILLA_ICE_CREAM}, Telephony feature flags
+ * (e.g. {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}) are being checked before
+ * returning managers that depend on them. If the feature is missing,
+ * {@link Context#getSystemService} will return null.
+ *
+ * This change is specific to VcnManager.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ static final long ENABLE_CHECKING_TELEPHONY_FEATURES_FOR_VCN = 330902016;
+
// Service registry information.
// This information is never changed once static initialization has completed.
private static final Map<Class<?>, String> SYSTEM_SERVICE_NAMES =
@@ -450,8 +465,9 @@ public final class SystemServiceRegistry {
new CachedServiceFetcher<VcnManager>() {
@Override
public VcnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
- if (!ctx.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) {
+ if (shouldCheckTelephonyFeatures()
+ && !ctx.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) {
return null;
}
@@ -1748,6 +1764,22 @@ public final class SystemServiceRegistry {
return manager.hasSystemFeature(featureName);
}
+ // Suppressing AndroidFrameworkCompatChange because we're querying vendor
+ // partition SDK level, not application's target SDK version (which BTW we
+ // also check through Compatibility framework a few lines below).
+ @SuppressWarnings("AndroidFrameworkCompatChange")
+ private static boolean shouldCheckTelephonyFeatures() {
+ // Check SDK version of the vendor partition. Pre-V devices might have
+ // incorrectly under-declared telephony features.
+ final int vendorApiLevel = SystemProperties.getInt(
+ "ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
+ if (vendorApiLevel < Build.VERSION_CODES.VANILLA_ICE_CREAM) return false;
+
+ // Check SDK version of the client app. Apps targeting pre-V SDK might
+ // have not checked for existence of these features.
+ return Compatibility.isChangeEnabled(ENABLE_CHECKING_TELEPHONY_FEATURES_FOR_VCN);
+ }
+
/**
* Gets a system service from a given context.
* @hide
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index e9a746022a75..29ffdc5f4a55 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -71,4 +71,14 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "update_ranking_time"
+ namespace: "systemui"
+ description: "Updates notification sorting criteria to highlight new content while maintaining stability"
+ bug: "326016985"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 7383d07c82e9..c55b0f110b3b 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -122,7 +122,7 @@ public class ClientTransactionListenerController {
}
for (Object activityWindowInfoChangedListener : activityWindowInfoChangedListeners) {
((BiConsumer<IBinder, ActivityWindowInfo>) activityWindowInfoChangedListener)
- .accept(activityToken, activityWindowInfo);
+ .accept(activityToken, new ActivityWindowInfo(activityWindowInfo));
}
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 5e00b7a798d8..2c26389071ce 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1086,7 +1086,7 @@ public final class CompanionDeviceManager {
}
Objects.requireNonNull(deviceAddress, "address cannot be null");
try {
- mService.registerDevicePresenceListenerService(deviceAddress,
+ mService.legacyStartObservingDevicePresence(deviceAddress,
mContext.getOpPackageName(), mContext.getUserId());
} catch (RemoteException e) {
ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
@@ -1128,7 +1128,7 @@ public final class CompanionDeviceManager {
}
Objects.requireNonNull(deviceAddress, "address cannot be null");
try {
- mService.unregisterDevicePresenceListenerService(deviceAddress,
+ mService.legacyStopObservingDevicePresence(deviceAddress,
mContext.getPackageName(), mContext.getUserId());
} catch (RemoteException e) {
ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
@@ -1328,7 +1328,7 @@ public final class CompanionDeviceManager {
@RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
public void notifyDeviceAppeared(int associationId) {
try {
- mService.notifyDeviceAppeared(associationId);
+ mService.notifySelfManagedDeviceAppeared(associationId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1350,7 +1350,7 @@ public final class CompanionDeviceManager {
@RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
public void notifyDeviceDisappeared(int associationId) {
try {
- mService.notifyDeviceDisappeared(associationId);
+ mService.notifySelfManagedDeviceDisappeared(associationId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 57d59e5e5bf0..1b00f90e1fb3 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -59,12 +59,16 @@ interface ICompanionDeviceManager {
int userId);
@EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void registerDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
- int userId);
+ void legacyStartObservingDevicePresence(in String deviceAddress, in String callingPackage, int userId);
@EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void unregisterDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
- int userId);
+ void legacyStopObservingDevicePresence(in String deviceAddress, in String callingPackage, int userId);
+
+ @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
+ void startObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
+
+ @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
+ void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId);
@@ -93,9 +97,11 @@ interface ICompanionDeviceManager {
@EnforcePermission("USE_COMPANION_TRANSPORTS")
void removeOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener);
- void notifyDeviceAppeared(int associationId);
+ @EnforcePermission("REQUEST_COMPANION_SELF_MANAGED")
+ void notifySelfManagedDeviceAppeared(int associationId);
- void notifyDeviceDisappeared(int associationId);
+ @EnforcePermission("REQUEST_COMPANION_SELF_MANAGED")
+ void notifySelfManagedDeviceDisappeared(int associationId);
PendingIntent buildPermissionTransferUserConsentIntent(String callingPackage, int userId,
int associationId);
@@ -135,10 +141,4 @@ interface ICompanionDeviceManager {
byte[] getBackupPayload(int userId);
void applyRestoredPayload(in byte[] payload, int userId);
-
- @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void startObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
-
- @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
}
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index bb0498ed6a78..229d1195f6b7 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -9,14 +9,6 @@ flag {
flag {
namespace: "haptics"
- name: "haptics_customization_enabled"
- is_exported: true
- description: "Enables the haptics customization feature"
- bug: "241918098"
-}
-
-flag {
- namespace: "haptics"
name: "haptic_feedback_vibration_oem_customization_enabled"
description: "Enables OEMs/devices to customize vibrations for haptic feedback"
# Make read only. This is because the flag is used only once, and this could happen before
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3c61854c89f0..85d3688a9a1e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -104,7 +104,7 @@ import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_B
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
-import static android.view.accessibility.Flags.fixMergedContentChangeEvent;
+import static android.view.accessibility.Flags.fixMergedContentChangeEventV2;
import static android.view.accessibility.Flags.forceInvertColor;
import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly;
@@ -11796,7 +11796,7 @@ public final class ViewRootImpl implements ViewParent,
}
if (mSource != null) {
- if (fixMergedContentChangeEvent()) {
+ if (fixMergedContentChangeEventV2()) {
View newSource = getCommonPredecessor(mSource, source);
if (newSource != null) {
newSource = newSource.getSelfOrParentImportantForA11y();
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 91bd4ea0bc87..eefc72b82c24 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -56,9 +56,12 @@ flag {
flag {
namespace: "accessibility"
- name: "fix_merged_content_change_event"
+ name: "fix_merged_content_change_event_v2"
description: "Fixes event type and source of content change event merged in ViewRootImpl"
bug: "277305460"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index b6066ba5560f..9c63d0dd746a 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -1297,6 +1297,17 @@ public class ConversationLayout extends FrameLayout
*/
@Nullable
private Drawable resolveAvatarImageForOneToOne(Icon conversationIcon) {
+ final Drawable conversationIconDrawable =
+ tryLoadingSizeRestrictedIconForOneToOne(conversationIcon);
+ if (conversationIconDrawable != null) {
+ return conversationIconDrawable;
+ }
+ // when size restricted icon loading fails, we fallback to icons load drawable.
+ return loadDrawableFromIcon(conversationIcon);
+ }
+
+ @Nullable
+ private Drawable tryLoadingSizeRestrictedIconForOneToOne(Icon conversationIcon) {
try {
return mConversationIconView.loadSizeRestrictedIcon(conversationIcon);
} catch (Exception ex) {
@@ -1309,6 +1320,11 @@ public class ConversationLayout extends FrameLayout
*/
@Nullable
private Drawable resolveAvatarImageForFacePile(Icon conversationIcon) {
+ return loadDrawableFromIcon(conversationIcon);
+ }
+
+ @Nullable
+ private Drawable loadDrawableFromIcon(Icon conversationIcon) {
try {
return conversationIcon.loadDrawable(getContext());
} catch (Exception ex) {
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 927c67cb1d36..be0669c42d44 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -34,9 +34,11 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
@@ -67,7 +69,6 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.DisplayMetrics;
@@ -90,7 +91,6 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@@ -123,9 +123,7 @@ public class ActivityThreadTest {
@Rule(order = 1)
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
- @Mock
- private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
-
+ private ActivityWindowInfoListener mActivityWindowInfoListener;
private WindowTokenClientController mOriginalWindowTokenClientController;
private Configuration mOriginalAppConfig;
@@ -140,6 +138,7 @@ public class ActivityThreadTest {
mOriginalWindowTokenClientController = WindowTokenClientController.getInstance();
mOriginalAppConfig = new Configuration(ActivityThread.currentActivityThread()
.getConfiguration());
+ mActivityWindowInfoListener = spy(new ActivityWindowInfoListener());
}
@After
@@ -808,96 +807,107 @@ public class ActivityThreadTest {
@Test
public void testActivityWindowInfoChanged_activityLaunch() {
mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
mActivityWindowInfoListener);
final Activity activity = mActivityTestRule.launchActivity(new Intent());
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ mActivityWindowInfoListener.await();
final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);
- verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+ // In case the system change the window after launch, there can be more than one callback.
+ verify(mActivityWindowInfoListener, atLeastOnce()).accept(activityClientRecord.token,
activityClientRecord.getActivityWindowInfo());
}
@Test
- public void testActivityWindowInfoChanged_activityRelaunch() throws RemoteException {
+ public void testActivityWindowInfoChanged_activityRelaunch() {
mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
mActivityWindowInfoListener);
final Activity activity = mActivityTestRule.launchActivity(new Intent());
- final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
- appThread.scheduleTransaction(newRelaunchResumeTransaction(activity));
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ mActivityWindowInfoListener.await();
final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);
- // The same ActivityWindowInfo won't trigger duplicated callback.
- verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
- activityClientRecord.getActivityWindowInfo());
-
- final Configuration currentConfig = activity.getResources().getConfiguration();
- final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
- activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
- new Rect(0, 0, 1000, 1000));
- final ActivityRelaunchItem relaunchItem = ActivityRelaunchItem.obtain(
- activity.getActivityToken(), null, null, 0,
- new MergedConfiguration(currentConfig, currentConfig),
- false /* preserveWindow */, activityWindowInfo);
- final ClientTransaction transaction = newTransaction(activity);
- transaction.addTransactionItem(relaunchItem);
- appThread.scheduleTransaction(transaction);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-
- verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
- activityWindowInfo);
+ // Run on main thread to avoid racing from updating from window relayout.
+ final ActivityThread activityThread = activity.getActivityThread();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // Try relaunch with the same ActivityWindowInfo
+ clearInvocations(mActivityWindowInfoListener);
+ activityThread.executeTransaction(newRelaunchResumeTransaction(activity));
+
+ // The same ActivityWindowInfo won't trigger duplicated callback.
+ verify(mActivityWindowInfoListener, never()).accept(activityClientRecord.token,
+ activityClientRecord.getActivityWindowInfo());
+
+ // Try relaunch with different ActivityWindowInfo
+ final Configuration currentConfig = activity.getResources().getConfiguration();
+ final ActivityWindowInfo newInfo = new ActivityWindowInfo();
+ newInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
+ new Rect(0, 0, 1000, 1000));
+ final ActivityRelaunchItem relaunchItem = ActivityRelaunchItem.obtain(
+ activity.getActivityToken(), null, null, 0,
+ new MergedConfiguration(currentConfig, currentConfig),
+ false /* preserveWindow */, newInfo);
+ final ClientTransaction transaction = newTransaction(activity);
+ transaction.addTransactionItem(relaunchItem);
+
+ clearInvocations(mActivityWindowInfoListener);
+ activityThread.executeTransaction(transaction);
+
+ // Trigger callback with a different ActivityWindowInfo
+ verify(mActivityWindowInfoListener).accept(activityClientRecord.token, newInfo);
+ });
}
@Test
- public void testActivityWindowInfoChanged_activityConfigurationChanged()
- throws RemoteException {
+ public void testActivityWindowInfoChanged_activityConfigurationChanged() {
mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
mActivityWindowInfoListener);
final Activity activity = mActivityTestRule.launchActivity(new Intent());
- final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ mActivityWindowInfoListener.await();
- clearInvocations(mActivityWindowInfoListener);
- final Configuration config = new Configuration(activity.getResources().getConfiguration());
- config.seq++;
- final Rect taskBounds = new Rect(0, 0, 1000, 2000);
- final Rect taskFragmentBounds = new Rect(0, 0, 1000, 1000);
- final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
- activityWindowInfo.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
- final ActivityConfigurationChangeItem activityConfigurationChangeItem =
- ActivityConfigurationChangeItem.obtain(
- activity.getActivityToken(), config, activityWindowInfo);
- final ClientTransaction transaction = newTransaction(activity);
- transaction.addTransactionItem(activityConfigurationChangeItem);
- appThread.scheduleTransaction(transaction);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-
- verify(mActivityWindowInfoListener).accept(activity.getActivityToken(),
- activityWindowInfo);
-
- clearInvocations(mActivityWindowInfoListener);
- final ActivityWindowInfo activityWindowInfo2 = new ActivityWindowInfo();
- activityWindowInfo2.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
- config.seq++;
- final ActivityConfigurationChangeItem activityConfigurationChangeItem2 =
- ActivityConfigurationChangeItem.obtain(
- activity.getActivityToken(), config, activityWindowInfo2);
- final ClientTransaction transaction2 = newTransaction(activity);
- transaction2.addTransactionItem(activityConfigurationChangeItem2);
- appThread.scheduleTransaction(transaction);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-
- // The same ActivityWindowInfo won't trigger duplicated callback.
- verify(mActivityWindowInfoListener, never()).accept(any(), any());
+ final ActivityThread activityThread = activity.getActivityThread();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // Trigger callback with different ActivityWindowInfo
+ final Configuration config = new Configuration(activity.getResources()
+ .getConfiguration());
+ config.seq++;
+ final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+ final Rect taskFragmentBounds = new Rect(0, 0, 1000, 1000);
+ final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+ activityWindowInfo.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
+ final ActivityConfigurationChangeItem activityConfigurationChangeItem =
+ ActivityConfigurationChangeItem.obtain(
+ activity.getActivityToken(), config, activityWindowInfo);
+ final ClientTransaction transaction = newTransaction(activity);
+ transaction.addTransactionItem(activityConfigurationChangeItem);
+
+ clearInvocations(mActivityWindowInfoListener);
+ activityThread.executeTransaction(transaction);
+
+ // Trigger callback with a different ActivityWindowInfo
+ verify(mActivityWindowInfoListener).accept(activity.getActivityToken(),
+ activityWindowInfo);
+
+ // Try callback with the same ActivityWindowInfo
+ final ActivityWindowInfo activityWindowInfo2 =
+ new ActivityWindowInfo(activityWindowInfo);
+ config.seq++;
+ final ActivityConfigurationChangeItem activityConfigurationChangeItem2 =
+ ActivityConfigurationChangeItem.obtain(
+ activity.getActivityToken(), config, activityWindowInfo2);
+ final ClientTransaction transaction2 = newTransaction(activity);
+ transaction2.addTransactionItem(activityConfigurationChangeItem2);
+
+ clearInvocations(mActivityWindowInfoListener);
+ activityThread.executeTransaction(transaction);
+
+ // The same ActivityWindowInfo won't trigger duplicated callback.
+ verify(mActivityWindowInfoListener, never()).accept(any(), any());
+ });
}
/**
@@ -958,10 +968,12 @@ public class ActivityThreadTest {
@NonNull
private static ClientTransaction newRelaunchResumeTransaction(@NonNull Activity activity) {
final Configuration currentConfig = activity.getResources().getConfiguration();
+ final ActivityWindowInfo activityWindowInfo = getActivityClientRecord(activity)
+ .getActivityWindowInfo();
final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(
activity.getActivityToken(), null, null, 0,
new MergedConfiguration(currentConfig, currentConfig),
- false /* preserveWindow */, new ActivityWindowInfo());
+ false /* preserveWindow */, activityWindowInfo);
final ResumeActivityItem resumeStateRequest =
ResumeActivityItem.obtain(activity.getActivityToken(), true /* isForward */,
false /* shouldSendCompatFakeFocus*/);
@@ -1127,4 +1139,28 @@ public class ActivityThreadTest {
return mPipEnterSkipped;
}
}
+
+ public static class ActivityWindowInfoListener implements
+ BiConsumer<IBinder, ActivityWindowInfo> {
+
+ CountDownLatch mCallbackLatch = new CountDownLatch(1);
+
+ @Override
+ public void accept(@NonNull IBinder activityToken,
+ @NonNull ActivityWindowInfo activityWindowInfo) {
+ mCallbackLatch.countDown();
+ }
+
+ /**
+ * When the test is expecting to receive a callback, waits until the callback is triggered.
+ */
+ void await() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ try {
+ mCallbackLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index de7244d49834..71c068d7ad46 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -149,7 +149,7 @@ public class DateIntervalFormatTest {
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
assertEquals("19.–22.01.2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("19.01. – 22.04.2009",
+ assertEquals("19.01.\u2009–\u200922.04.2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
assertEquals("19.01.2009\u2009\u2013\u200909.02.2012",
@@ -220,10 +220,10 @@ public class DateIntervalFormatTest {
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, 0));
assertEquals("19.–22. Jan. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Mo., 19. – Do., 22. Jan. 2009",
+ assertEquals("Mo., 19.\u2009–\u2009Do., 22. Jan. 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
- assertEquals("Montag, 19. – Donnerstag, 22. Januar 2009",
+ assertEquals("Montag, 19.\u2009–\u2009Donnerstag, 22. Januar 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
assertEquals("19. Januar\u2009\u2013\u200922. April 2009",
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 488f017872b1..ff280555a3a1 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -1258,7 +1258,8 @@ public class ResolverActivityTest {
@Test
public void testTriggerFromMainProfile_inSingleUserMode_withWorkProfilePresent() {
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
markWorkProfileUserAvailable();
setTabOwnerUserHandleForLaunch(PERSONAL_USER_HANDLE);
Intent sendIntent = createSendImageIntent();
@@ -1281,7 +1282,8 @@ public class ResolverActivityTest {
@Test
public void testTriggerFromWorkProfile_inSingleUserMode() {
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
markWorkProfileUserAvailable();
setTabOwnerUserHandleForLaunch(sOverrides.workProfileUserHandle);
Intent sendIntent = createSendImageIntent();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index cae232e54f3c..b8ac19189f60 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -23,9 +23,11 @@ import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE;
import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
import static androidx.window.extensions.embedding.DividerAttributes.RATIO_UNSET;
import static androidx.window.extensions.embedding.DividerAttributes.WIDTH_UNSET;
+import static androidx.window.extensions.embedding.SplitAttributesHelper.isReversedLayout;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT;
@@ -45,8 +47,10 @@ import android.hardware.display.DisplayManager;
import android.os.IBinder;
import android.util.TypedValue;
import android.view.Gravity;
+import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
+import android.view.View;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.widget.FrameLayout;
@@ -56,23 +60,30 @@ import android.window.TaskFragmentOperation;
import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.GuardedBy;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
+import androidx.window.extensions.core.util.function.Consumer;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.window.flags.Flags;
import java.util.Objects;
+import java.util.concurrent.Executor;
/**
* Manages the rendering and interaction of the divider.
*/
-class DividerPresenter {
+class DividerPresenter implements View.OnTouchListener {
private static final String WINDOW_NAME = "AE Divider";
+ private static final int VEIL_LAYER = 0;
+ private static final int DIVIDER_LAYER = 1;
// TODO(b/327067596) Update based on UX guidance.
private static final Color DEFAULT_DIVIDER_COLOR = Color.valueOf(Color.BLACK);
+ private static final Color DEFAULT_PRIMARY_VEIL_COLOR = Color.valueOf(Color.BLACK);
+ private static final Color DEFAULT_SECONDARY_VEIL_COLOR = Color.valueOf(Color.GRAY);
@VisibleForTesting
static final float DEFAULT_MIN_RATIO = 0.35f;
@VisibleForTesting
@@ -80,11 +91,23 @@ class DividerPresenter {
@VisibleForTesting
static final int DEFAULT_DIVIDER_WIDTH_DP = 24;
+ private final int mTaskId;
+
+ @NonNull
+ private final Object mLock = new Object();
+
+ @NonNull
+ private final DragEventCallback mDragEventCallback;
+
+ @NonNull
+ private final Executor mCallbackExecutor;
+
/**
* The {@link Properties} of the divider. This field is {@code null} when no divider should be
* drawn, e.g. when the split doesn't have {@link DividerAttributes} or when the decor surface
* is not available.
*/
+ @GuardedBy("mLock")
@Nullable
@VisibleForTesting
Properties mProperties;
@@ -94,6 +117,7 @@ class DividerPresenter {
* drawn, i.e. when {@link #mProperties} is {@code null}. The {@link Renderer} is recreated or
* updated when {@link #mProperties} is changed.
*/
+ @GuardedBy("mLock")
@Nullable
@VisibleForTesting
Renderer mRenderer;
@@ -102,10 +126,26 @@ class DividerPresenter {
* The owner TaskFragment token of the decor surface. The decor surface is placed right above
* the owner TaskFragment surface and is removed if the owner TaskFragment is destroyed.
*/
+ @GuardedBy("mLock")
@Nullable
@VisibleForTesting
IBinder mDecorSurfaceOwner;
+ /**
+ * The current divider position relative to the Task bounds. For vertical split (left-to-right
+ * or right-to-left), it is the x coordinate in the task window, and for horizontal split
+ * (top-to-bottom or bottom-to-top), it is the y coordinate in the task window.
+ */
+ @GuardedBy("mLock")
+ private int mDividerPosition;
+
+ DividerPresenter(int taskId, @NonNull DragEventCallback dragEventCallback,
+ @NonNull Executor callbackExecutor) {
+ mTaskId = taskId;
+ mDragEventCallback = dragEventCallback;
+ mCallbackExecutor = callbackExecutor;
+ }
+
/** Updates the divider when external conditions are changed. */
void updateDivider(
@NonNull WindowContainerTransaction wct,
@@ -115,58 +155,65 @@ class DividerPresenter {
return;
}
- // Clean up the decor surface if top SplitContainer is null.
- if (topSplitContainer == null) {
- removeDecorSurfaceAndDivider(wct);
- return;
- }
+ synchronized (mLock) {
+ // Clean up the decor surface if top SplitContainer is null.
+ if (topSplitContainer == null) {
+ removeDecorSurfaceAndDivider(wct);
+ return;
+ }
- // Clean up the decor surface if DividerAttributes is null.
- final DividerAttributes dividerAttributes =
- topSplitContainer.getCurrentSplitAttributes().getDividerAttributes();
- if (dividerAttributes == null) {
- removeDecorSurfaceAndDivider(wct);
- return;
- }
+ // Clean up the decor surface if DividerAttributes is null.
+ final DividerAttributes dividerAttributes =
+ topSplitContainer.getCurrentSplitAttributes().getDividerAttributes();
+ if (dividerAttributes == null) {
+ removeDecorSurfaceAndDivider(wct);
+ return;
+ }
- if (topSplitContainer.getCurrentSplitAttributes().getSplitType()
- instanceof SplitAttributes.SplitType.ExpandContainersSplitType) {
- // No divider is needed for ExpandContainersSplitType.
- removeDivider();
- return;
- }
+ if (topSplitContainer.getCurrentSplitAttributes().getSplitType()
+ instanceof SplitAttributes.SplitType.ExpandContainersSplitType) {
+ // No divider is needed for ExpandContainersSplitType.
+ removeDivider();
+ return;
+ }
- // Skip updating when the TFs have not been updated to match the SplitAttributes.
- if (topSplitContainer.getPrimaryContainer().getLastRequestedBounds().isEmpty()
- || topSplitContainer.getSecondaryContainer().getLastRequestedBounds().isEmpty()) {
- return;
- }
+ // Skip updating when the TFs have not been updated to match the SplitAttributes.
+ if (topSplitContainer.getPrimaryContainer().getLastRequestedBounds().isEmpty()
+ || topSplitContainer.getSecondaryContainer().getLastRequestedBounds()
+ .isEmpty()) {
+ return;
+ }
- final SurfaceControl decorSurface = parentInfo.getDecorSurface();
- if (decorSurface == null) {
- // Clean up when the decor surface is currently unavailable.
- removeDivider();
- // Request to create the decor surface
- createOrMoveDecorSurface(wct, topSplitContainer.getPrimaryContainer());
- return;
- }
+ final SurfaceControl decorSurface = parentInfo.getDecorSurface();
+ if (decorSurface == null) {
+ // Clean up when the decor surface is currently unavailable.
+ removeDivider();
+ // Request to create the decor surface
+ createOrMoveDecorSurface(wct, topSplitContainer.getPrimaryContainer());
+ return;
+ }
- // make the top primary container the owner of the decor surface.
- if (!Objects.equals(mDecorSurfaceOwner,
- topSplitContainer.getPrimaryContainer().getTaskFragmentToken())) {
- createOrMoveDecorSurface(wct, topSplitContainer.getPrimaryContainer());
- }
+ // make the top primary container the owner of the decor surface.
+ if (!Objects.equals(mDecorSurfaceOwner,
+ topSplitContainer.getPrimaryContainer().getTaskFragmentToken())) {
+ createOrMoveDecorSurface(wct, topSplitContainer.getPrimaryContainer());
+ }
- updateProperties(
- new Properties(
- parentInfo.getConfiguration(),
- dividerAttributes,
- decorSurface,
- getInitialDividerPosition(topSplitContainer),
- isVerticalSplit(topSplitContainer),
- parentInfo.getDisplayId()));
+ updateProperties(
+ new Properties(
+ parentInfo.getConfiguration(),
+ dividerAttributes,
+ decorSurface,
+ getInitialDividerPosition(topSplitContainer),
+ isVerticalSplit(topSplitContainer),
+ isReversedLayout(
+ topSplitContainer.getCurrentSplitAttributes(),
+ parentInfo.getConfiguration()),
+ parentInfo.getDisplayId()));
+ }
}
+ @GuardedBy("mLock")
private void updateProperties(@NonNull Properties properties) {
if (Properties.equalsForDivider(mProperties, properties)) {
return;
@@ -176,16 +223,16 @@ class DividerPresenter {
if (mRenderer == null) {
// Create a new renderer when a renderer doesn't exist yet.
- mRenderer = new Renderer();
+ mRenderer = new Renderer(mProperties, this);
} else if (!Properties.areSameSurfaces(
previousProperties.mDecorSurface, mProperties.mDecorSurface)
|| previousProperties.mDisplayId != mProperties.mDisplayId) {
// Release and recreate the renderer if the decor surface or the display has changed.
mRenderer.release();
- mRenderer = new Renderer();
+ mRenderer = new Renderer(mProperties, this);
} else {
// Otherwise, update the renderer for the new properties.
- mRenderer.update();
+ mRenderer.update(mProperties);
}
}
@@ -195,6 +242,7 @@ class DividerPresenter {
*
* See {@link TaskFragmentOperation#OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE}.
*/
+ @GuardedBy("mLock")
private void createOrMoveDecorSurface(
@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) {
final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
@@ -204,6 +252,7 @@ class DividerPresenter {
mDecorSurfaceOwner = container.getTaskFragmentToken();
}
+ @GuardedBy("mLock")
private void removeDecorSurfaceAndDivider(@NonNull WindowContainerTransaction wct) {
if (mDecorSurfaceOwner != null) {
final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
@@ -215,6 +264,7 @@ class DividerPresenter {
removeDivider();
}
+ @GuardedBy("mLock")
private void removeDivider() {
if (mRenderer != null) {
mRenderer.release();
@@ -238,7 +288,7 @@ class DividerPresenter {
private static boolean isVerticalSplit(@NonNull SplitContainer splitContainer) {
final int layoutDirection = splitContainer.getCurrentSplitAttributes().getLayoutDirection();
- switch(layoutDirection) {
+ switch (layoutDirection) {
case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT:
case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT:
case SplitAttributes.LayoutDirection.LOCALE:
@@ -251,12 +301,6 @@ class DividerPresenter {
}
}
- private static void safeReleaseSurfaceControl(@Nullable SurfaceControl sc) {
- if (sc != null) {
- sc.release();
- }
- }
-
private static int getDividerWidthPx(@NonNull DividerAttributes dividerAttributes) {
int dividerWidthDp = dividerAttributes.getWidthDp();
return convertDpToPixel(dividerWidthDp);
@@ -388,6 +432,227 @@ class DividerPresenter {
.build();
}
+ @Override
+ public boolean onTouch(@NonNull View view, @NonNull MotionEvent event) {
+ synchronized (mLock) {
+ final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
+ mDividerPosition = calculateDividerPosition(
+ event, taskBounds, mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
+ mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
+ mRenderer.setDividerPosition(mDividerPosition);
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ onStartDragging();
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ onFinishDragging();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ onDrag();
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Returns false so that the default button click callback is still triggered, i.e. the
+ // button UI transitions into the "pressed" state.
+ return false;
+ }
+
+ @GuardedBy("mLock")
+ private void onStartDragging() {
+ mRenderer.mIsDragging = true;
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ mRenderer.updateSurface(t);
+ mRenderer.showVeils(t);
+ final IBinder decorSurfaceOwner = mDecorSurfaceOwner;
+
+ // Callbacks must be executed on the executor to release mLock and prevent deadlocks.
+ mCallbackExecutor.execute(() -> {
+ mDragEventCallback.onStartDragging(
+ wct -> setDecorSurfaceBoosted(wct, decorSurfaceOwner, true /* boosted */, t));
+ });
+ }
+
+ @GuardedBy("mLock")
+ private void onDrag() {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ mRenderer.updateSurface(t);
+ t.apply();
+ }
+
+ @GuardedBy("mLock")
+ private void onFinishDragging() {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ mRenderer.updateSurface(t);
+ mRenderer.hideVeils(t);
+ final IBinder decorSurfaceOwner = mDecorSurfaceOwner;
+
+ // Callbacks must be executed on the executor to release mLock and prevent deadlocks.
+ mCallbackExecutor.execute(() -> {
+ mDragEventCallback.onFinishDragging(
+ mTaskId,
+ wct -> setDecorSurfaceBoosted(wct, decorSurfaceOwner, false /* boosted */, t));
+ });
+ mRenderer.mIsDragging = false;
+ }
+
+ private static void setDecorSurfaceBoosted(
+ @NonNull WindowContainerTransaction wct,
+ @Nullable IBinder decorSurfaceOwner,
+ boolean boosted,
+ @NonNull SurfaceControl.Transaction clientTransaction) {
+ if (decorSurfaceOwner == null) {
+ return;
+ }
+ wct.addTaskFragmentOperation(
+ decorSurfaceOwner,
+ new TaskFragmentOperation.Builder(OP_TYPE_SET_DECOR_SURFACE_BOOSTED)
+ .setBooleanValue(boosted)
+ .setSurfaceTransaction(clientTransaction)
+ .build()
+ );
+ }
+
+ /** Calculates the new divider position based on the touch event and divider attributes. */
+ @VisibleForTesting
+ static int calculateDividerPosition(@NonNull MotionEvent event, @NonNull Rect taskBounds,
+ int dividerWidthPx, @NonNull DividerAttributes dividerAttributes,
+ boolean isVerticalSplit, boolean isReversedLayout) {
+ // The touch event is in display space. Converting it into the task window space.
+ final int touchPositionInTaskSpace = isVerticalSplit
+ ? (int) (event.getRawX()) - taskBounds.left
+ : (int) (event.getRawY()) - taskBounds.top;
+
+ // Assuming that the touch position is at the center of the divider bar, so the divider
+ // position is offset by half of the divider width.
+ int dividerPosition = touchPositionInTaskSpace - dividerWidthPx / 2;
+
+ // Limit the divider position to the min and max ratios set in DividerAttributes.
+ // TODO(b/327536303) Handle when the divider is dragged to the edge.
+ dividerPosition = Math.max(dividerPosition, calculateMinPosition(
+ taskBounds, dividerWidthPx, dividerAttributes, isVerticalSplit, isReversedLayout));
+ dividerPosition = Math.min(dividerPosition, calculateMaxPosition(
+ taskBounds, dividerWidthPx, dividerAttributes, isVerticalSplit, isReversedLayout));
+ return dividerPosition;
+ }
+
+ /** Calculates the min position of the divider that the user is allowed to drag to. */
+ @VisibleForTesting
+ static int calculateMinPosition(@NonNull Rect taskBounds, int dividerWidthPx,
+ @NonNull DividerAttributes dividerAttributes, boolean isVerticalSplit,
+ boolean isReversedLayout) {
+ // The usable size is the task window size minus the divider bar width. This is shared
+ // between the primary and secondary containers based on the split ratio.
+ final int usableSize = isVerticalSplit
+ ? taskBounds.width() - dividerWidthPx
+ : taskBounds.height() - dividerWidthPx;
+ return (int) (isReversedLayout
+ ? usableSize - usableSize * dividerAttributes.getPrimaryMaxRatio()
+ : usableSize * dividerAttributes.getPrimaryMinRatio());
+ }
+
+ /** Calculates the max position of the divider that the user is allowed to drag to. */
+ @VisibleForTesting
+ static int calculateMaxPosition(@NonNull Rect taskBounds, int dividerWidthPx,
+ @NonNull DividerAttributes dividerAttributes, boolean isVerticalSplit,
+ boolean isReversedLayout) {
+ // The usable size is the task window size minus the divider bar width. This is shared
+ // between the primary and secondary containers based on the split ratio.
+ final int usableSize = isVerticalSplit
+ ? taskBounds.width() - dividerWidthPx
+ : taskBounds.height() - dividerWidthPx;
+ return (int) (isReversedLayout
+ ? usableSize - usableSize * dividerAttributes.getPrimaryMinRatio()
+ : usableSize * dividerAttributes.getPrimaryMaxRatio());
+ }
+
+ /**
+ * Returns the new split ratio of the {@link SplitContainer} based on the current divider
+ * position.
+ */
+ float calculateNewSplitRatio(@NonNull SplitContainer topSplitContainer) {
+ synchronized (mLock) {
+ return calculateNewSplitRatio(
+ topSplitContainer,
+ mDividerPosition,
+ mProperties.mConfiguration.windowConfiguration.getBounds(),
+ mRenderer.mDividerWidthPx,
+ mProperties.mIsVerticalSplit,
+ mProperties.mIsReversedLayout);
+ }
+ }
+
+ /**
+ * Returns the new split ratio of the {@link SplitContainer} based on the current divider
+ * position.
+ * @param topSplitContainer the {@link SplitContainer} for which to compute the split ratio.
+ * @param dividerPosition the divider position. See {@link #mDividerPosition}.
+ * @param taskBounds the task bounds
+ * @param dividerWidthPx the width of the divider in pixels.
+ * @param isVerticalSplit if {@code true}, the split is a vertical split. If {@code false}, the
+ * split is a horizontal split. See
+ * {@link #isVerticalSplit(SplitContainer)}.
+ * @param isReversedLayout if {@code true}, the split layout is reversed, i.e. right-to-left or
+ * bottom-to-top. If {@code false}, the split is not reversed, i.e.
+ * left-to-right or top-to-bottom. See
+ * {@link SplitAttributesHelper#isReversedLayout}
+ * @return the computed split ratio of the primary container.
+ */
+ @VisibleForTesting
+ static float calculateNewSplitRatio(
+ @NonNull SplitContainer topSplitContainer,
+ int dividerPosition,
+ @NonNull Rect taskBounds,
+ int dividerWidthPx,
+ boolean isVerticalSplit,
+ boolean isReversedLayout) {
+ final int usableSize = isVerticalSplit
+ ? taskBounds.width() - dividerWidthPx
+ : taskBounds.height() - dividerWidthPx;
+
+ final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
+ final Rect origPrimaryBounds = primaryContainer.getLastRequestedBounds();
+
+ float newRatio;
+ if (isVerticalSplit) {
+ final int newPrimaryWidth = isReversedLayout
+ ? (origPrimaryBounds.right - (dividerPosition + dividerWidthPx))
+ : (dividerPosition - origPrimaryBounds.left);
+ newRatio = 1.0f * newPrimaryWidth / usableSize;
+ } else {
+ final int newPrimaryHeight = isReversedLayout
+ ? (origPrimaryBounds.bottom - (dividerPosition + dividerWidthPx))
+ : (dividerPosition - origPrimaryBounds.top);
+ newRatio = 1.0f * newPrimaryHeight / usableSize;
+ }
+ return newRatio;
+ }
+
+ /** Callbacks for drag events */
+ interface DragEventCallback {
+ /**
+ * Called when the user starts dragging the divider. Callbacks are executed on
+ * {@link #mCallbackExecutor}.
+ *
+ * @param action additional action that should be applied to the
+ * {@link WindowContainerTransaction}
+ */
+ void onStartDragging(@NonNull Consumer<WindowContainerTransaction> action);
+
+ /**
+ * Called when the user finishes dragging the divider. Callbacks are executed on
+ * {@link #mCallbackExecutor}.
+ *
+ * @param taskId the Task id of the {@link TaskContainer} that this divider belongs to.
+ * @param action additional action that should be applied to the
+ * {@link WindowContainerTransaction}
+ */
+ void onFinishDragging(int taskId, @NonNull Consumer<WindowContainerTransaction> action);
+ }
+
/**
* Properties for the {@link DividerPresenter}. The rendering of the divider solely depends on
* these properties. When any value is updated, the divider is re-rendered. The Properties
@@ -411,6 +676,7 @@ class DividerPresenter {
private final boolean mIsVerticalSplit;
private final int mDisplayId;
+ private final boolean mIsReversedLayout;
@VisibleForTesting
Properties(
@@ -419,12 +685,14 @@ class DividerPresenter {
@NonNull SurfaceControl decorSurface,
int initialDividerPosition,
boolean isVerticalSplit,
+ boolean isReversedLayout,
int displayId) {
mConfiguration = configuration;
mDividerAttributes = dividerAttributes;
mDecorSurface = decorSurface;
mInitialDividerPosition = initialDividerPosition;
mIsVerticalSplit = isVerticalSplit;
+ mIsReversedLayout = isReversedLayout;
mDisplayId = displayId;
}
@@ -445,7 +713,8 @@ class DividerPresenter {
&& areConfigurationsEqualForDivider(a.mConfiguration, b.mConfiguration)
&& a.mInitialDividerPosition == b.mInitialDividerPosition
&& a.mIsVerticalSplit == b.mIsVerticalSplit
- && a.mDisplayId == b.mDisplayId;
+ && a.mDisplayId == b.mDisplayId
+ && a.mIsReversedLayout == b.mIsReversedLayout;
}
private static boolean areSameSurfaces(
@@ -472,7 +741,7 @@ class DividerPresenter {
* recreated. When other fields in the Properties are changed, the renderer is updated.
*/
@VisibleForTesting
- class Renderer {
+ static class Renderer {
@NonNull
private final SurfaceControl mDividerSurface;
@NonNull
@@ -481,10 +750,21 @@ class DividerPresenter {
private final SurfaceControlViewHost mViewHost;
@NonNull
private final FrameLayout mDividerLayout;
- private final int mDividerWidthPx;
-
- private Renderer() {
- mDividerWidthPx = getDividerWidthPx(mProperties.mDividerAttributes);
+ @NonNull
+ private final View.OnTouchListener mListener;
+ @NonNull
+ private Properties mProperties;
+ private int mDividerWidthPx;
+ @Nullable
+ private SurfaceControl mPrimaryVeil;
+ @Nullable
+ private SurfaceControl mSecondaryVeil;
+ private boolean mIsDragging;
+ private int mDividerPosition;
+
+ private Renderer(@NonNull Properties properties, @NonNull View.OnTouchListener listener) {
+ mProperties = properties;
+ mListener = listener;
mDividerSurface = createChildSurface("DividerSurface", true /* visible */);
mWindowlessWindowManager = new WindowlessWindowManager(
@@ -503,36 +783,63 @@ class DividerPresenter {
}
/** Updates the divider when properties are changed */
+ private void update(@NonNull Properties newProperties) {
+ mProperties = newProperties;
+ update();
+ }
+
+ /** Updates the divider when initializing or when properties are changed */
@VisibleForTesting
void update() {
+ mDividerWidthPx = getDividerWidthPx(mProperties.mDividerAttributes);
+ mDividerPosition = mProperties.mInitialDividerPosition;
mWindowlessWindowManager.setConfiguration(mProperties.mConfiguration);
- updateSurface();
+ // TODO handle synchronization between surface transactions and WCT.
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ updateSurface(t);
updateLayout();
- updateDivider();
+ updateDivider(t);
+ t.apply();
}
@VisibleForTesting
void release() {
mViewHost.release();
// TODO handle synchronization between surface transactions and WCT.
- new SurfaceControl.Transaction().remove(mDividerSurface).apply();
- safeReleaseSurfaceControl(mDividerSurface);
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.remove(mDividerSurface);
+ removeVeils(t);
+ t.apply();
}
- private void updateSurface() {
+ private void setDividerPosition(int dividerPosition) {
+ mDividerPosition = dividerPosition;
+ }
+
+ /**
+ * Updates the positions and crops of the divider surface and veil surfaces. This method
+ * should be called when {@link #mProperties} is changed or while dragging to update the
+ * position of the divider surface and the veil surfaces.
+ */
+ private void updateSurface(@NonNull SurfaceControl.Transaction t) {
final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
- // TODO handle synchronization between surface transactions and WCT.
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
if (mProperties.mIsVerticalSplit) {
- t.setPosition(mDividerSurface, mProperties.mInitialDividerPosition, 0.0f);
+ t.setPosition(mDividerSurface, mDividerPosition, 0.0f);
t.setWindowCrop(mDividerSurface, mDividerWidthPx, taskBounds.height());
} else {
- t.setPosition(mDividerSurface, 0.0f, mProperties.mInitialDividerPosition);
+ t.setPosition(mDividerSurface, 0.0f, mDividerPosition);
t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerWidthPx);
}
- t.apply();
+ if (mIsDragging) {
+ updateVeils(t);
+ }
}
+ /**
+ * Updates the layout parameters of the layout used to host the divider. This method should
+ * be called only when {@link #mProperties} is changed. This should not be called while
+ * dragging, because the layout parameters are not changed during dragging.
+ */
private void updateLayout() {
final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
final WindowManager.LayoutParams lp = mProperties.mIsVerticalSplit
@@ -552,12 +859,21 @@ class DividerPresenter {
mViewHost.setView(mDividerLayout, lp);
}
- private void updateDivider() {
+ /**
+ * Updates the UI component of the divider, including the drag handle and the veils. This
+ * method should be called only when {@link #mProperties} is changed. This should not be
+ * called while dragging, because the UI components are not changed during dragging and
+ * only their surface positions are changed.
+ */
+ private void updateDivider(@NonNull SurfaceControl.Transaction t) {
mDividerLayout.removeAllViews();
mDividerLayout.setBackgroundColor(DEFAULT_DIVIDER_COLOR.toArgb());
if (mProperties.mDividerAttributes.getDividerType()
== DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
+ createVeils();
drawDragHandle();
+ } else {
+ removeVeils(t);
}
mViewHost.getView().invalidate();
}
@@ -580,7 +896,7 @@ class DividerPresenter {
button.setLayoutParams(params);
button.setBackgroundColor(R.color.transparent);
- final Drawable handle = context.getResources().getDrawable(
+ final Drawable handle = context.getResources().getDrawable(
R.drawable.activity_embedding_divider_handle, context.getTheme());
if (mProperties.mIsVerticalSplit) {
button.setImageDrawable(handle);
@@ -598,6 +914,8 @@ class DividerPresenter {
button.setImageDrawable(rotatedHandle);
}
+
+ button.setOnTouchListener(mListener);
mDividerLayout.addView(button);
}
@@ -613,5 +931,69 @@ class DividerPresenter {
.setColorLayer()
.build();
}
+
+ private void createVeils() {
+ if (mPrimaryVeil == null) {
+ mPrimaryVeil = createChildSurface("DividerPrimaryVeil", false /* visible */);
+ }
+ if (mSecondaryVeil == null) {
+ mSecondaryVeil = createChildSurface("DividerSecondaryVeil", false /* visible */);
+ }
+ }
+
+ private void removeVeils(@NonNull SurfaceControl.Transaction t) {
+ if (mPrimaryVeil != null) {
+ t.remove(mPrimaryVeil);
+ }
+ if (mSecondaryVeil != null) {
+ t.remove(mSecondaryVeil);
+ }
+ mPrimaryVeil = null;
+ mSecondaryVeil = null;
+ }
+
+ private void showVeils(@NonNull SurfaceControl.Transaction t) {
+ t.setColor(mPrimaryVeil, colorToFloatArray(DEFAULT_PRIMARY_VEIL_COLOR))
+ .setColor(mSecondaryVeil, colorToFloatArray(DEFAULT_SECONDARY_VEIL_COLOR))
+ .setLayer(mDividerSurface, DIVIDER_LAYER)
+ .setLayer(mPrimaryVeil, VEIL_LAYER)
+ .setLayer(mSecondaryVeil, VEIL_LAYER)
+ .setVisibility(mPrimaryVeil, true)
+ .setVisibility(mSecondaryVeil, true);
+ updateVeils(t);
+ }
+
+ private void hideVeils(@NonNull SurfaceControl.Transaction t) {
+ t.setVisibility(mPrimaryVeil, false).setVisibility(mSecondaryVeil, false);
+ }
+
+ private void updateVeils(@NonNull SurfaceControl.Transaction t) {
+ final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
+
+ // Relative bounds of the primary and secondary containers in the Task.
+ Rect primaryBounds;
+ Rect secondaryBounds;
+ if (mProperties.mIsVerticalSplit) {
+ final Rect boundsLeft = new Rect(0, 0, mDividerPosition, taskBounds.height());
+ final Rect boundsRight = new Rect(mDividerPosition + mDividerWidthPx, 0,
+ taskBounds.width(), taskBounds.height());
+ primaryBounds = mProperties.mIsReversedLayout ? boundsRight : boundsLeft;
+ secondaryBounds = mProperties.mIsReversedLayout ? boundsLeft : boundsRight;
+ } else {
+ final Rect boundsTop = new Rect(0, 0, taskBounds.width(), mDividerPosition);
+ final Rect boundsBottom = new Rect(0, mDividerPosition + mDividerWidthPx,
+ taskBounds.width(), taskBounds.height());
+ primaryBounds = mProperties.mIsReversedLayout ? boundsBottom : boundsTop;
+ secondaryBounds = mProperties.mIsReversedLayout ? boundsTop : boundsBottom;
+ }
+ t.setWindowCrop(mPrimaryVeil, primaryBounds.width(), primaryBounds.height());
+ t.setWindowCrop(mSecondaryVeil, secondaryBounds.width(), secondaryBounds.height());
+ t.setPosition(mPrimaryVeil, primaryBounds.left, primaryBounds.top);
+ t.setPosition(mSecondaryVeil, secondaryBounds.left, secondaryBounds.top);
+ }
+
+ private static float[] colorToFloatArray(@NonNull Color color) {
+ return new float[]{color.red(), color.green(), color.blue()};
+ }
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 3f4dddf0cc81..32f2d67888ae 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -165,7 +165,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
/**
* Expands an existing TaskFragment to fill parent.
* @param wct WindowContainerTransaction in which the task fragment should be resized.
- * @param fragmentToken token of an existing TaskFragment.
+ * @param container the {@link TaskFragmentContainer} to be expanded.
*/
void expandTaskFragment(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
@@ -174,8 +174,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
clearAdjacentTaskFragments(wct, fragmentToken);
updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
-
- container.getTaskContainer().updateDivider(wct);
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java
new file mode 100644
index 000000000000..042a68a684c0
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java
@@ -0,0 +1,46 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import android.content.res.Configuration;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+/** Helper functions for {@link SplitAttributes} */
+class SplitAttributesHelper {
+ /**
+ * Returns whether the split layout direction is reversed. Right-to-left and bottom-to-top are
+ * considered reversed.
+ */
+ static boolean isReversedLayout(
+ @NonNull SplitAttributes splitAttributes, @NonNull Configuration configuration) {
+ switch (splitAttributes.getLayoutDirection()) {
+ case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT:
+ case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM:
+ return false;
+ case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT:
+ case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP:
+ return true;
+ case SplitAttributes.LayoutDirection.LOCALE:
+ return configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid layout direction:" + splitAttributes.getLayoutDirection());
+ }
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 1bc8264d8e7e..b9b86f015606 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -110,7 +110,7 @@ import java.util.function.BiConsumer;
* Main controller class that manages split states and presentation.
*/
public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
- ActivityEmbeddingComponent {
+ ActivityEmbeddingComponent, DividerPresenter.DragEventCallback {
static final String TAG = "SplitController";
static final boolean ENABLE_SHELL_TRANSITIONS =
SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
@@ -163,6 +163,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>();
+ /** Map from Task id to {@link DividerPresenter} which manages the divider in the Task. */
+ @GuardedBy("mLock")
+ private final SparseArray<DividerPresenter> mDividerPresenters = new SparseArray<>();
+
/** Callback to Jetpack to notify about changes to split states. */
@GuardedBy("mLock")
@Nullable
@@ -195,15 +199,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
: null;
private final Handler mHandler;
+ private final MainThreadExecutor mExecutor;
final Object mLock = new Object();
private final ActivityStartMonitor mActivityStartMonitor;
public SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent,
@NonNull DeviceStateManagerFoldingFeatureProducer foldingFeatureProducer) {
Log.i(TAG, "Initializing Activity Embedding Controller.");
- final MainThreadExecutor executor = new MainThreadExecutor();
- mHandler = executor.mHandler;
- mPresenter = new SplitPresenter(executor, windowLayoutComponent, this);
+ mExecutor = new MainThreadExecutor();
+ mHandler = mExecutor.mHandler;
+ mPresenter = new SplitPresenter(mExecutor, windowLayoutComponent, this);
mTransactionManager = new TransactionManager(mPresenter);
final ActivityThread activityThread = ActivityThread.currentActivityThread();
final Application application = activityThread.getApplication();
@@ -844,7 +849,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Checks if container should be updated before apply new parentInfo.
final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo);
taskContainer.updateTaskFragmentParentInfo(parentInfo);
- taskContainer.updateDivider(wct);
+
+ // The divider need to be updated even if shouldUpdateContainer is false, because the decor
+ // surface may change in TaskFragmentParentInfo, which requires divider update but not
+ // container update.
+ updateDivider(wct, taskContainer);
// If the last direct activity of the host task is dismissed and the overlay container is
// the only taskFragment, the overlay container should also be dismissed.
@@ -1007,6 +1016,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (taskContainer.isEmpty()) {
// Cleanup the TaskContainer if it becomes empty.
mTaskContainers.remove(taskContainer.getTaskId());
+ mDividerPresenters.remove(taskContainer.getTaskId());
}
return;
}
@@ -1759,6 +1769,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
if (!mTaskContainers.contains(taskId)) {
mTaskContainers.put(taskId, new TaskContainer(taskId, activityInTask));
+ mDividerPresenters.put(taskId, new DividerPresenter(taskId, this, mExecutor));
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
@@ -3065,4 +3076,46 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return configuration != null
&& configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
}
+
+ @GuardedBy("mLock")
+ void updateDivider(
+ @NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) {
+ final DividerPresenter dividerPresenter = mDividerPresenters.get(taskContainer.getTaskId());
+ final TaskFragmentParentInfo parentInfo = taskContainer.getTaskFragmentParentInfo();
+ if (parentInfo != null) {
+ dividerPresenter.updateDivider(
+ wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer());
+ }
+ }
+
+ @Override
+ public void onStartDragging(@NonNull Consumer<WindowContainerTransaction> action) {
+ synchronized (mLock) {
+ final TransactionRecord transactionRecord =
+ mTransactionManager.startNewTransaction();
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
+ action.accept(wct);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ }
+ }
+
+ @Override
+ public void onFinishDragging(
+ int taskId,
+ @NonNull Consumer<WindowContainerTransaction> action) {
+ synchronized (mLock) {
+ final TransactionRecord transactionRecord =
+ mTransactionManager.startNewTransaction();
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
+ final TaskContainer taskContainer = mTaskContainers.get(taskId);
+ if (taskContainer != null) {
+ final DividerPresenter dividerPresenter =
+ mDividerPresenters.get(taskContainer.getTaskId());
+ taskContainer.updateTopSplitContainerForDivider(dividerPresenter);
+ updateContainersInTask(wct, taskContainer);
+ }
+ action.accept(wct);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ }
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 20bc82002339..0d31266d771b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -19,6 +19,7 @@ package androidx.window.extensions.embedding;
import static android.content.pm.PackageManager.MATCH_ALL;
import static androidx.window.extensions.embedding.DividerPresenter.getBoundsOffsetForDivider;
+import static androidx.window.extensions.embedding.SplitAttributesHelper.isReversedLayout;
import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
import android.app.Activity;
@@ -33,7 +34,6 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
-import android.util.LayoutDirection;
import android.util.Pair;
import android.util.Size;
import android.view.View;
@@ -368,7 +368,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
updateAnimationParams(wct, primaryContainer.getTaskFragmentToken(), splitAttributes);
updateAnimationParams(wct, secondaryContainer.getTaskFragmentToken(), splitAttributes);
- taskContainer.updateDivider(wct);
+ mController.updateDivider(wct, taskContainer);
}
private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@@ -697,6 +697,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
return RESULT_NOT_EXPANDED;
}
+ /**
+ * Expands an existing TaskFragment to fill parent.
+ * @param wct WindowContainerTransaction in which the task fragment should be resized.
+ * @param container the {@link TaskFragmentContainer} to be expanded.
+ */
+ void expandTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container) {
+ super.expandTaskFragment(wct, container);
+ mController.updateDivider(wct, container.getTaskContainer());
+ }
+
static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) {
return shouldShowSplit(splitContainer.getCurrentSplitAttributes());
}
@@ -1108,7 +1119,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
*/
private SplitType computeSplitType(@NonNull SplitAttributes splitAttributes,
@NonNull Configuration taskConfiguration, @Nullable FoldingFeature foldingFeature) {
- final int layoutDirection = splitAttributes.getLayoutDirection();
final SplitType splitType = splitAttributes.getSplitType();
if (splitType instanceof ExpandContainersSplitType) {
return splitType;
@@ -1117,19 +1127,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// Reverse the ratio for RIGHT_TO_LEFT and BOTTOM_TO_TOP to make the boundary
// computation have the same direction, which is from (top, left) to (bottom, right).
final SplitType reversedSplitType = new RatioSplitType(1 - splitRatio.getRatio());
- switch (layoutDirection) {
- case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT:
- case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM:
- return splitType;
- case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT:
- case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP:
- return reversedSplitType;
- case LayoutDirection.LOCALE: {
- boolean isLtr = taskConfiguration.getLayoutDirection()
- == View.LAYOUT_DIRECTION_LTR;
- return isLtr ? splitType : reversedSplitType;
- }
- }
+ return isReversedLayout(splitAttributes, taskConfiguration)
+ ? reversedSplitType
+ : splitType;
} else if (splitType instanceof HingeSplitType) {
final HingeSplitType hinge = (HingeSplitType) splitType;
@WindowingMode
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index e75a317cc3b3..a215bdf4b566 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -88,10 +88,6 @@ class TaskContainer {
*/
final Set<IBinder> mFinishedContainer = new ArraySet<>();
- // TODO(b/293654166): move DividerPresenter to SplitController.
- @NonNull
- final DividerPresenter mDividerPresenter;
-
/**
* The {@link TaskContainer} constructor
*
@@ -113,7 +109,6 @@ class TaskContainer {
// the host task is visible and has an activity in the task.
mIsVisible = true;
mHasDirectActivity = true;
- mDividerPresenter = new DividerPresenter();
}
int getTaskId() {
@@ -151,6 +146,11 @@ class TaskContainer {
mTaskFragmentParentInfo = info;
}
+ @Nullable
+ TaskFragmentParentInfo getTaskFragmentParentInfo() {
+ return mTaskFragmentParentInfo;
+ }
+
/**
* Returns {@code true} if the container should be updated with {@code info}.
*/
@@ -398,16 +398,22 @@ class TaskContainer {
return mContainers;
}
- void updateDivider(@NonNull WindowContainerTransaction wct) {
- if (mTaskFragmentParentInfo != null) {
- // Update divider only if TaskFragmentParentInfo is available.
- mDividerPresenter.updateDivider(
- wct, mTaskFragmentParentInfo, getTopNonFinishingSplitContainer());
+ void updateTopSplitContainerForDivider(@NonNull DividerPresenter dividerPresenter) {
+ final SplitContainer topSplitContainer = getTopNonFinishingSplitContainer();
+ if (topSplitContainer == null) {
+ return;
}
+
+ final float newRatio = dividerPresenter.calculateNewSplitRatio(topSplitContainer);
+ topSplitContainer.updateDefaultSplitAttributes(
+ new SplitAttributes.Builder(topSplitContainer.getDefaultSplitAttributes())
+ .setSplitType(new SplitAttributes.SplitType.RatioSplitType(newRatio))
+ .build()
+ );
}
@Nullable
- private SplitContainer getTopNonFinishingSplitContainer() {
+ SplitContainer getTopNonFinishingSplitContainer() {
for (int i = mSplitContainers.size() - 1; i >= 0; i--) {
final SplitContainer splitContainer = mSplitContainers.get(i);
if (!splitContainer.getPrimaryContainer().isFinished()
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
index 4d1d807038eb..47d01da1c8b5 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
@@ -42,6 +42,7 @@ import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.Display;
+import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.window.TaskFragmentOperation;
import android.window.TaskFragmentParentInfo;
@@ -60,6 +61,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.Executor;
+
/**
* Test class for {@link DividerPresenter}.
*
@@ -73,6 +76,8 @@ public class DividerPresenterTest {
@Rule
public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
+ private static final int MOCK_TASK_ID = 1234;
+
@Mock
private DividerPresenter.Renderer mRenderer;
@@ -83,6 +88,12 @@ public class DividerPresenterTest {
private TaskFragmentParentInfo mParentInfo;
@Mock
+ private TaskContainer mTaskContainer;
+
+ @Mock
+ private DividerPresenter.DragEventCallback mDragEventCallback;
+
+ @Mock
private SplitContainer mSplitContainer;
@Mock
@@ -110,6 +121,8 @@ public class DividerPresenterTest {
MockitoAnnotations.initMocks(this);
mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_INTERACTIVE_DIVIDER_FLAG);
+ when(mTaskContainer.getTaskId()).thenReturn(MOCK_TASK_ID);
+
when(mParentInfo.getDisplayId()).thenReturn(Display.DEFAULT_DISPLAY);
when(mParentInfo.getConfiguration()).thenReturn(new Configuration());
when(mParentInfo.getDecorSurface()).thenReturn(mSurfaceControl);
@@ -133,9 +146,11 @@ public class DividerPresenterTest {
mSurfaceControl,
getInitialDividerPosition(mSplitContainer),
true /* isVerticalSplit */,
+ false /* isReversedLayout */,
Display.DEFAULT_DISPLAY);
- mDividerPresenter = new DividerPresenter();
+ mDividerPresenter = new DividerPresenter(
+ MOCK_TASK_ID, mDragEventCallback, mock(Executor.class));
mDividerPresenter.mProperties = mProperties;
mDividerPresenter.mRenderer = mRenderer;
mDividerPresenter.mDecorSurfaceOwner = mPrimaryContainerToken;
@@ -311,6 +326,184 @@ public class DividerPresenterTest {
dividerWidthPx, splitType, expectedTopLeftOffset, expectedBottomRightOffset);
}
+ @Test
+ public void testCalculateDividerPosition() {
+ final MotionEvent event = mock(MotionEvent.class);
+ final Rect taskBounds = new Rect(100, 200, 1000, 2000);
+ final int dividerWidthPx = 50;
+ final DividerAttributes dividerAttributes =
+ new DividerAttributes.Builder(DividerAttributes.DIVIDER_TYPE_DRAGGABLE)
+ .setPrimaryMinRatio(0.3f)
+ .setPrimaryMaxRatio(0.8f)
+ .build();
+
+ // Left-to-right split
+ when(event.getRawX()).thenReturn(500f); // Touch event is in display space
+ assertEquals(
+ // Touch position is in task space is 400, then minus half of divider width.
+ 375,
+ DividerPresenter.calculateDividerPosition(
+ event,
+ taskBounds,
+ dividerWidthPx,
+ dividerAttributes,
+ true /* isVerticalSplit */,
+ false /* isReversedLayout */));
+
+ // Top-to-bottom split
+ when(event.getRawY()).thenReturn(1000f); // Touch event is in display space
+ assertEquals(
+ // Touch position is in task space is 800, then minus half of divider width.
+ 775,
+ DividerPresenter.calculateDividerPosition(
+ event,
+ taskBounds,
+ dividerWidthPx,
+ dividerAttributes,
+ false /* isVerticalSplit */,
+ false /* isReversedLayout */));
+ }
+
+ @Test
+ public void testCalculateMinPosition() {
+ final Rect taskBounds = new Rect(100, 200, 1000, 2000);
+ final int dividerWidthPx = 50;
+ final DividerAttributes dividerAttributes =
+ new DividerAttributes.Builder(DividerAttributes.DIVIDER_TYPE_DRAGGABLE)
+ .setPrimaryMinRatio(0.3f)
+ .setPrimaryMaxRatio(0.8f)
+ .build();
+
+ // Left-to-right split
+ assertEquals(
+ 255, /* (1000 - 100 - 50) * 0.3 */
+ DividerPresenter.calculateMinPosition(
+ taskBounds,
+ dividerWidthPx,
+ dividerAttributes,
+ true /* isVerticalSplit */,
+ false /* isReversedLayout */));
+
+ // Top-to-bottom split
+ assertEquals(
+ 525, /* (2000 - 200 - 50) * 0.3 */
+ DividerPresenter.calculateMinPosition(
+ taskBounds,
+ dividerWidthPx,
+ dividerAttributes,
+ false /* isVerticalSplit */,
+ false /* isReversedLayout */));
+
+ // Right-to-left split
+ assertEquals(
+ 170, /* (1000 - 100 - 50) * (1 - 0.8) */
+ DividerPresenter.calculateMinPosition(
+ taskBounds,
+ dividerWidthPx,
+ dividerAttributes,
+ true /* isVerticalSplit */,
+ true /* isReversedLayout */));
+ }
+
+ @Test
+ public void testCalculateMaxPosition() {
+ final Rect taskBounds = new Rect(100, 200, 1000, 2000);
+ final int dividerWidthPx = 50;
+ final DividerAttributes dividerAttributes =
+ new DividerAttributes.Builder(DividerAttributes.DIVIDER_TYPE_DRAGGABLE)
+ .setPrimaryMinRatio(0.3f)
+ .setPrimaryMaxRatio(0.8f)
+ .build();
+
+ // Left-to-right split
+ assertEquals(
+ 680, /* (1000 - 100 - 50) * 0.8 */
+ DividerPresenter.calculateMaxPosition(
+ taskBounds,
+ dividerWidthPx,
+ dividerAttributes,
+ true /* isVerticalSplit */,
+ false /* isReversedLayout */));
+
+ // Top-to-bottom split
+ assertEquals(
+ 1400, /* (2000 - 200 - 50) * 0.8 */
+ DividerPresenter.calculateMaxPosition(
+ taskBounds,
+ dividerWidthPx,
+ dividerAttributes,
+ false /* isVerticalSplit */,
+ false /* isReversedLayout */));
+
+ // Right-to-left split
+ assertEquals(
+ 595, /* (1000 - 100 - 50) * (1 - 0.3) */
+ DividerPresenter.calculateMaxPosition(
+ taskBounds,
+ dividerWidthPx,
+ dividerAttributes,
+ true /* isVerticalSplit */,
+ true /* isReversedLayout */));
+ }
+
+ @Test
+ public void testCalculateNewSplitRatio_leftToRight() {
+ // primary=500px; secondary=500px; divider=100px; total=1100px.
+ final Rect taskBounds = new Rect(0, 0, 1100, 2000);
+ final Rect primaryBounds = new Rect(0, 0, 500, 2000);
+ final Rect secondaryBounds = new Rect(600, 0, 1100, 2000);
+ final int dividerWidthPx = 100;
+ final int dividerPosition = 300;
+
+ final TaskFragmentContainer mockPrimaryContainer =
+ createMockTaskFragmentContainer(mPrimaryContainerToken, primaryBounds);
+ final TaskFragmentContainer mockSecondaryContainer =
+ createMockTaskFragmentContainer(mSecondaryContainerToken, secondaryBounds);
+ when(mSplitContainer.getPrimaryContainer()).thenReturn(mockPrimaryContainer);
+ when(mSplitContainer.getSecondaryContainer()).thenReturn(mockSecondaryContainer);
+
+ assertEquals(
+ 0.3f, // Primary is 300px after dragging.
+ DividerPresenter.calculateNewSplitRatio(
+ mSplitContainer,
+ dividerPosition,
+ taskBounds,
+ dividerWidthPx,
+ true /* isVerticalSplit */,
+ false /* isReversedLayout */),
+ 0.0001 /* delta */);
+ }
+
+ @Test
+ public void testCalculateNewSplitRatio_bottomToTop() {
+ // Primary is at bottom. Secondary is at top.
+ // primary=500px; secondary=500px; divider=100px; total=1100px.
+ final Rect taskBounds = new Rect(0, 0, 2000, 1100);
+ final Rect primaryBounds = new Rect(0, 0, 2000, 1100);
+ final Rect secondaryBounds = new Rect(0, 0, 2000, 500);
+ final int dividerWidthPx = 100;
+ final int dividerPosition = 300;
+
+ final TaskFragmentContainer mockPrimaryContainer =
+ createMockTaskFragmentContainer(mPrimaryContainerToken, primaryBounds);
+ final TaskFragmentContainer mockSecondaryContainer =
+ createMockTaskFragmentContainer(mSecondaryContainerToken, secondaryBounds);
+ when(mSplitContainer.getPrimaryContainer()).thenReturn(mockPrimaryContainer);
+ when(mSplitContainer.getSecondaryContainer()).thenReturn(mockSecondaryContainer);
+
+ assertEquals(
+ // After dragging, secondary is [0, 0, 2000, 300]. Primary is [0, 400, 2000, 1100].
+ 0.7f,
+ DividerPresenter.calculateNewSplitRatio(
+ mSplitContainer,
+ dividerPosition,
+ taskBounds,
+ dividerWidthPx,
+ false /* isVerticalSplit */,
+ true /* isReversedLayout */),
+ 0.0001 /* delta */);
+ }
+
private TaskFragmentContainer createMockTaskFragmentContainer(
@NonNull IBinder token, @NonNull Rect bounds) {
final TaskFragmentContainer container = mock(TaskFragmentContainer.class);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 014b8413bb10..cda94440affa 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -78,13 +78,13 @@ cc_defaults {
include_dirs: [
"external/skia/include/private",
"external/skia/src/core",
+ "external/skia/src/utils",
],
target: {
android: {
include_dirs: [
"external/skia/src/image",
- "external/skia/src/utils",
"external/skia/src/gpu",
"external/skia/src/shaders",
],
@@ -345,6 +345,7 @@ cc_defaults {
"jni/android_nio_utils.cpp",
"jni/android_util_PathParser.cpp",
+ "jni/AnimatedImageDrawable.cpp",
"jni/Bitmap.cpp",
"jni/BitmapRegionDecoder.cpp",
"jni/BufferUtils.cpp",
@@ -418,7 +419,6 @@ cc_defaults {
target: {
android: {
srcs: [ // sources that depend on android only libraries
- "jni/AnimatedImageDrawable.cpp",
"jni/android_graphics_TextureLayer.cpp",
"jni/android_graphics_HardwareRenderer.cpp",
"jni/android_graphics_HardwareBufferRenderer.cpp",
@@ -529,7 +529,9 @@ cc_defaults {
"effects/GainmapRenderer.cpp",
"pipeline/skia/BackdropFilterDrawable.cpp",
"pipeline/skia/HolePunch.cpp",
+ "pipeline/skia/SkiaCpuPipeline.cpp",
"pipeline/skia/SkiaDisplayList.cpp",
+ "pipeline/skia/SkiaPipeline.cpp",
"pipeline/skia/SkiaRecordingCanvas.cpp",
"pipeline/skia/StretchMask.cpp",
"pipeline/skia/RenderNodeDrawable.cpp",
@@ -539,6 +541,7 @@ cc_defaults {
"renderthread/RenderTask.cpp",
"renderthread/TimeLord.cpp",
"hwui/AnimatedImageDrawable.cpp",
+ "hwui/AnimatedImageThread.cpp",
"hwui/Bitmap.cpp",
"hwui/BlurDrawLooper.cpp",
"hwui/Canvas.cpp",
@@ -567,6 +570,7 @@ cc_defaults {
"HWUIProperties.sysprop",
"Interpolator.cpp",
"JankTracker.cpp",
+ "LayerUpdateQueue.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
"Mesh.cpp",
@@ -599,14 +603,13 @@ cc_defaults {
local_include_dirs: ["platform/android"],
srcs: [
- "hwui/AnimatedImageThread.cpp",
"pipeline/skia/ATraceMemoryDump.cpp",
"pipeline/skia/GLFunctorDrawable.cpp",
"pipeline/skia/LayerDrawable.cpp",
"pipeline/skia/ShaderCache.cpp",
+ "pipeline/skia/SkiaGpuPipeline.cpp",
"pipeline/skia/SkiaMemoryTracer.cpp",
"pipeline/skia/SkiaOpenGLPipeline.cpp",
- "pipeline/skia/SkiaPipeline.cpp",
"pipeline/skia/SkiaProfileRenderer.cpp",
"pipeline/skia/SkiaVulkanPipeline.cpp",
"pipeline/skia/VkFunctorDrawable.cpp",
@@ -630,7 +633,6 @@ cc_defaults {
"DeferredLayerUpdater.cpp",
"HardwareBitmapUploader.cpp",
"Layer.cpp",
- "LayerUpdateQueue.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
"TreeInfo.cpp",
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index ec53070f6cb8..c1510d96461f 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -242,7 +242,7 @@ enum class ProfileType { None, Console, Bars };
enum class OverdrawColorSet { Default = 0, Deuteranomaly };
-enum class RenderPipelineType { SkiaGL, SkiaVulkan, NotInitialized = 128 };
+enum class RenderPipelineType { SkiaGL, SkiaVulkan, SkiaCpu, NotInitialized = 128 };
enum class StretchEffectBehavior {
ShaderHWUI, // Stretch shader in HWUI only, matrix scale in SF
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 27773a60355a..69613c7d17cb 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -15,18 +15,16 @@
*/
#include "AnimatedImageDrawable.h"
-#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread
-#include "AnimatedImageThread.h"
-#endif
-
-#include <gui/TraceUtils.h>
-#include "pipeline/skia/SkiaUtils.h"
#include <SkPicture.h>
#include <SkRefCnt.h>
+#include <gui/TraceUtils.h>
#include <optional>
+#include "AnimatedImageThread.h"
+#include "pipeline/skia/SkiaUtils.h"
+
namespace android {
AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed,
@@ -185,10 +183,8 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) {
} else if (starting) {
// The image has animated, and now is being reset. Queue up the first
// frame, but keep showing the current frame until the first is ready.
-#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread
auto& thread = uirenderer::AnimatedImageThread::getInstance();
mNextSnapshot = thread.reset(sk_ref_sp(this));
-#endif
}
bool finalFrame = false;
@@ -214,10 +210,8 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) {
}
if (mRunning && !mNextSnapshot.valid()) {
-#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread
auto& thread = uirenderer::AnimatedImageThread::getInstance();
mNextSnapshot = thread.decodeNextFrame(sk_ref_sp(this));
-#endif
}
if (!drawDirectly) {
diff --git a/libs/hwui/hwui/AnimatedImageThread.cpp b/libs/hwui/hwui/AnimatedImageThread.cpp
index 825dd4cf2bf1..e39c8d57d31c 100644
--- a/libs/hwui/hwui/AnimatedImageThread.cpp
+++ b/libs/hwui/hwui/AnimatedImageThread.cpp
@@ -16,7 +16,9 @@
#include "AnimatedImageThread.h"
+#ifdef __ANDROID__
#include <sys/resource.h>
+#endif
namespace android {
namespace uirenderer {
@@ -31,7 +33,9 @@ AnimatedImageThread& AnimatedImageThread::getInstance() {
}
AnimatedImageThread::AnimatedImageThread() {
+#ifdef __ANDROID__
setpriority(PRIO_PROCESS, 0, PRIORITY_NORMAL + PRIORITY_MORE_FAVORABLE);
+#endif
}
std::future<AnimatedImageDrawable::Snapshot> AnimatedImageThread::decodeNextFrame(
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index 90b1da846205..0f80c55d0ed0 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -25,7 +25,9 @@
#include <hwui/AnimatedImageDrawable.h>
#include <hwui/Canvas.h>
#include <hwui/ImageDecoder.h>
+#ifdef __ANDROID__
#include <utils/Looper.h>
+#endif
#include "ColorFilter.h"
#include "GraphicsJNI.h"
@@ -180,6 +182,23 @@ static void AnimatedImageDrawable_nSetRepeatCount(JNIEnv* env, jobject /*clazz*/
drawable->setRepetitionCount(loopCount);
}
+#ifndef __ANDROID__
+struct Message {
+ Message(int w) {}
+};
+
+class MessageHandler : public virtual RefBase {
+protected:
+ virtual ~MessageHandler() override {}
+
+public:
+ /**
+ * Handles a message.
+ */
+ virtual void handleMessage(const Message& message) = 0;
+};
+#endif
+
class InvokeListener : public MessageHandler {
public:
InvokeListener(JNIEnv* env, jobject javaObject) {
@@ -204,6 +223,7 @@ private:
};
class JniAnimationEndListener : public OnAnimationEndListener {
+#ifdef __ANDROID__
public:
JniAnimationEndListener(sp<Looper>&& looper, JNIEnv* env, jobject javaObject) {
mListener = new InvokeListener(env, javaObject);
@@ -215,6 +235,17 @@ public:
private:
sp<InvokeListener> mListener;
sp<Looper> mLooper;
+#else
+public:
+ JniAnimationEndListener(JNIEnv* env, jobject javaObject) {
+ mListener = new InvokeListener(env, javaObject);
+ }
+
+ void onAnimationEnd() override { mListener->handleMessage(0); }
+
+private:
+ sp<InvokeListener> mListener;
+#endif
};
static void AnimatedImageDrawable_nSetOnAnimationEndListener(JNIEnv* env, jobject /*clazz*/,
@@ -223,6 +254,7 @@ static void AnimatedImageDrawable_nSetOnAnimationEndListener(JNIEnv* env, jobjec
if (!jdrawable) {
drawable->setOnAnimationEndListener(nullptr);
} else {
+#ifdef __ANDROID__
sp<Looper> looper = Looper::getForThread();
if (!looper.get()) {
doThrowISE(env,
@@ -233,6 +265,10 @@ static void AnimatedImageDrawable_nSetOnAnimationEndListener(JNIEnv* env, jobjec
drawable->setOnAnimationEndListener(
std::make_unique<JniAnimationEndListener>(std::move(looper), env, jdrawable));
+#else
+ drawable->setOnAnimationEndListener(
+ std::make_unique<JniAnimationEndListener>(env, jdrawable));
+#endif
}
}
diff --git a/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp b/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp
new file mode 100644
index 000000000000..9b8373cea66d
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+#include "pipeline/skia/SkiaCpuPipeline.h"
+
+#include <system/window.h>
+
+#include "DeviceInfo.h"
+#include "LightingInfo.h"
+#include "renderthread/Frame.h"
+#include "utils/Color.h"
+
+using namespace android::uirenderer::renderthread;
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+void SkiaCpuPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
+ // Render all layers that need to be updated, in order.
+ for (size_t i = 0; i < layers.entries().size(); i++) {
+ renderLayerImpl(layers.entries()[i].renderNode.get(), layers.entries()[i].damage);
+ }
+}
+
+// If the given node didn't have a layer surface, or had one of the wrong size, this method
+// creates a new one and returns true. Otherwise does nothing and returns false.
+bool SkiaCpuPipeline::createOrUpdateLayer(RenderNode* node,
+ const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) {
+ // compute the size of the surface (i.e. texture) to be allocated for this layer
+ const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
+ const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
+
+ SkSurface* layer = node->getLayerSurface();
+ if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
+ SkImageInfo info;
+ info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
+ kPremul_SkAlphaType, getSurfaceColorSpace());
+ SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
+ node->setLayerSurface(SkSurfaces::Raster(info, &props));
+ if (node->getLayerSurface()) {
+ // update the transform in window of the layer to reset its origin wrt light source
+ // position
+ Matrix4 windowTransform;
+ damageAccumulator.computeCurrentTransform(&windowTransform);
+ node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
+ } else {
+ String8 cachesOutput;
+ mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
+ &mRenderThread.renderState());
+ ALOGE("%s", cachesOutput.c_str());
+ if (errorHandler) {
+ std::ostringstream err;
+ err << "Unable to create layer for " << node->getName();
+ const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
+ err << ", size " << info.width() << "x" << info.height() << " max size "
+ << maxTextureSize << " color type " << (int)info.colorType() << " has context "
+ << (int)(mRenderThread.getGrContext() != nullptr);
+ errorHandler->onError(err.str());
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+MakeCurrentResult SkiaCpuPipeline::makeCurrent() {
+ return MakeCurrentResult::AlreadyCurrent;
+}
+
+Frame SkiaCpuPipeline::getFrame() {
+ return Frame(mSurface->width(), mSurface->height(), 0);
+}
+
+IRenderPipeline::DrawResult SkiaCpuPipeline::draw(
+ const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+ const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+ const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
+ LightingInfo::updateLighting(lightGeometry, lightInfo);
+ renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, mSurface,
+ SkMatrix::I());
+ return {true, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd{}};
+}
+
+bool SkiaCpuPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) {
+ if (surface) {
+ ANativeWindowBuffer* buffer;
+ surface->dequeueBuffer(surface, &buffer, nullptr);
+ int width, height;
+ surface->query(surface, NATIVE_WINDOW_WIDTH, &width);
+ surface->query(surface, NATIVE_WINDOW_HEIGHT, &height);
+ SkImageInfo imageInfo =
+ SkImageInfo::Make(width, height, mSurfaceColorType,
+ SkAlphaType::kPremul_SkAlphaType, mSurfaceColorSpace);
+ size_t widthBytes = width * imageInfo.bytesPerPixel();
+ void* pixels = buffer->reserved[0];
+ mSurface = SkSurfaces::WrapPixels(imageInfo, pixels, widthBytes);
+ } else {
+ mSurface = sk_sp<SkSurface>();
+ }
+ return true;
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaCpuPipeline.h b/libs/hwui/pipeline/skia/SkiaCpuPipeline.h
new file mode 100644
index 000000000000..5a1014c2c2de
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaCpuPipeline.h
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaPipeline.h"
+
+namespace android {
+
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaCpuPipeline : public SkiaPipeline {
+public:
+ SkiaCpuPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {}
+ ~SkiaCpuPipeline() {}
+
+ bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
+ bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
+ void unpinImages() override {}
+
+ // If the given node didn't have a layer surface, or had one of the wrong size, this method
+ // creates a new one and returns true. Otherwise does nothing and returns false.
+ bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) override;
+ void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override;
+ void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override {}
+ bool hasHardwareBuffer() override { return false; }
+
+ renderthread::MakeCurrentResult makeCurrent() override;
+ renderthread::Frame getFrame() override;
+ renderthread::IRenderPipeline::DrawResult draw(
+ const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+ const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+ const renderthread::HardwareBufferRenderParams& bufferParams,
+ std::mutex& profilerLock) override;
+ bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) override {
+ return false;
+ }
+ DeferredLayerUpdater* createTextureLayer() override { return nullptr; }
+ bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
+ [[nodiscard]] android::base::unique_fd flush() override {
+ return android::base::unique_fd(-1);
+ };
+ void onStop() override {}
+ bool isSurfaceReady() override { return mSurface.get() != nullptr; }
+ bool isContextReady() override { return true; }
+
+ const SkM44& getPixelSnapMatrix() const override {
+ static const SkM44 sSnapMatrix = SkM44();
+ return sSnapMatrix;
+ }
+
+private:
+ sk_sp<SkSurface> mSurface;
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp b/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp
new file mode 100644
index 000000000000..cd9daf437bdb
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pipeline/skia/SkiaGpuPipeline.h"
+
+#include <SkImageAndroid.h>
+#include <gui/TraceUtils.h>
+#include <include/android/SkSurfaceAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+
+using namespace android::uirenderer::renderthread;
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+SkiaGpuPipeline::SkiaGpuPipeline(RenderThread& thread) : SkiaPipeline(thread) {}
+
+SkiaGpuPipeline::~SkiaGpuPipeline() {
+ unpinImages();
+}
+
+void SkiaGpuPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
+ sk_sp<GrDirectContext> cachedContext;
+
+ // Render all layers that need to be updated, in order.
+ for (size_t i = 0; i < layers.entries().size(); i++) {
+ RenderNode* layerNode = layers.entries()[i].renderNode.get();
+ renderLayerImpl(layerNode, layers.entries()[i].damage);
+ // cache the current context so that we can defer flushing it until
+ // either all the layers have been rendered or the context changes
+ GrDirectContext* currentContext =
+ GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext());
+ if (cachedContext.get() != currentContext) {
+ if (cachedContext.get()) {
+ ATRACE_NAME("flush layers (context changed)");
+ cachedContext->flushAndSubmit();
+ }
+ cachedContext.reset(SkSafeRef(currentContext));
+ }
+ }
+ if (cachedContext.get()) {
+ ATRACE_NAME("flush layers");
+ cachedContext->flushAndSubmit();
+ }
+}
+
+// If the given node didn't have a layer surface, or had one of the wrong size, this method
+// creates a new one and returns true. Otherwise does nothing and returns false.
+bool SkiaGpuPipeline::createOrUpdateLayer(RenderNode* node,
+ const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) {
+ // compute the size of the surface (i.e. texture) to be allocated for this layer
+ const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
+ const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
+
+ SkSurface* layer = node->getLayerSurface();
+ if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
+ SkImageInfo info;
+ info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
+ kPremul_SkAlphaType, getSurfaceColorSpace());
+ SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
+ SkASSERT(mRenderThread.getGrContext() != nullptr);
+ node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes, info, 0,
+ this->getSurfaceOrigin(), &props));
+ if (node->getLayerSurface()) {
+ // update the transform in window of the layer to reset its origin wrt light source
+ // position
+ Matrix4 windowTransform;
+ damageAccumulator.computeCurrentTransform(&windowTransform);
+ node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
+ } else {
+ String8 cachesOutput;
+ mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
+ &mRenderThread.renderState());
+ ALOGE("%s", cachesOutput.c_str());
+ if (errorHandler) {
+ std::ostringstream err;
+ err << "Unable to create layer for " << node->getName();
+ const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
+ err << ", size " << info.width() << "x" << info.height() << " max size "
+ << maxTextureSize << " color type " << (int)info.colorType() << " has context "
+ << (int)(mRenderThread.getGrContext() != nullptr);
+ errorHandler->onError(err.str());
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SkiaGpuPipeline::pinImages(std::vector<SkImage*>& mutableImages) {
+ if (!mRenderThread.getGrContext()) {
+ ALOGD("Trying to pin an image with an invalid GrContext");
+ return false;
+ }
+ for (SkImage* image : mutableImages) {
+ if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
+ mPinnedImages.emplace_back(sk_ref_sp(image));
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+void SkiaGpuPipeline::unpinImages() {
+ for (auto& image : mPinnedImages) {
+ skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
+ }
+ mPinnedImages.clear();
+}
+
+void SkiaGpuPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
+ GrDirectContext* context = thread.getGrContext();
+ if (context && !bitmap->isHardware()) {
+ ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
+ auto image = bitmap->makeImage();
+ if (image.get()) {
+ skgpu::ganesh::PinAsTexture(context, image.get());
+ skgpu::ganesh::UnpinTexture(context, image.get());
+ // A submit is necessary as there may not be a frame coming soon, so without a call
+ // to submit these texture uploads can just sit in the queue building up until
+ // we run out of RAM
+ context->flushAndSubmit();
+ }
+ }
+}
+
+sk_sp<SkSurface> SkiaGpuPipeline::getBufferSkSurface(
+ const renderthread::HardwareBufferRenderParams& bufferParams) {
+ auto bufferColorSpace = bufferParams.getColorSpace();
+ if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
+ !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
+ mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
+ mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
+ bufferColorSpace, nullptr, true);
+ mBufferColorSpace = bufferColorSpace;
+ }
+ return mBufferSurface;
+}
+
+void SkiaGpuPipeline::dumpResourceCacheUsage() const {
+ int resources;
+ size_t bytes;
+ mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes);
+ size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit();
+
+ SkString log("Resource Cache Usage:\n");
+ log.appendf("%8d items\n", resources);
+ log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes,
+ bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f)));
+
+ ALOGD("%s", log.c_str());
+}
+
+void SkiaGpuPipeline::setHardwareBuffer(AHardwareBuffer* buffer) {
+ if (mHardwareBuffer) {
+ AHardwareBuffer_release(mHardwareBuffer);
+ mHardwareBuffer = nullptr;
+ }
+
+ if (buffer) {
+ AHardwareBuffer_acquire(buffer);
+ mHardwareBuffer = buffer;
+ }
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index c8d598702a7c..e4b1f916b4d6 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -14,25 +14,25 @@
* limitations under the License.
*/
-#include "SkiaOpenGLPipeline.h"
+#include "pipeline/skia/SkiaOpenGLPipeline.h"
-#include <include/gpu/ganesh/SkSurfaceGanesh.h>
-#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
-#include <include/gpu/gl/GrGLTypes.h>
#include <GrBackendSurface.h>
#include <SkBlendMode.h>
#include <SkImageInfo.h>
#include <cutils/properties.h>
#include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/gl/GrGLTypes.h>
#include <strings.h>
#include "DeferredLayerUpdater.h"
#include "FrameInfo.h"
-#include "LayerDrawable.h"
#include "LightingInfo.h"
-#include "SkiaPipeline.h"
-#include "SkiaProfileRenderer.h"
#include "hwui/Bitmap.h"
+#include "pipeline/skia/LayerDrawable.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
+#include "pipeline/skia/SkiaProfileRenderer.h"
#include "private/hwui/DrawGlInfo.h"
#include "renderstate/RenderState.h"
#include "renderthread/EglManager.h"
@@ -47,7 +47,7 @@ namespace uirenderer {
namespace skiapipeline {
SkiaOpenGLPipeline::SkiaOpenGLPipeline(RenderThread& thread)
- : SkiaPipeline(thread), mEglManager(thread.eglManager()) {
+ : SkiaGpuPipeline(thread), mEglManager(thread.eglManager()) {
thread.renderState().registerContextCallback(this);
}
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 99469d1e3628..2cfdd3fb0315 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -14,11 +14,8 @@
* limitations under the License.
*/
-#include "SkiaPipeline.h"
+#include "pipeline/skia/SkiaPipeline.h"
-#include <include/android/SkSurfaceAndroid.h>
-#include <include/gpu/ganesh/SkSurfaceGanesh.h>
-#include <include/encode/SkPngEncoder.h>
#include <SkCanvas.h>
#include <SkColor.h>
#include <SkColorSpace.h>
@@ -40,6 +37,9 @@
#include <SkTypeface.h>
#include <android-base/properties.h>
#include <gui/TraceUtils.h>
+#include <include/android/SkSurfaceAndroid.h>
+#include <include/encode/SkPngEncoder.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <unistd.h>
#include <sstream>
@@ -62,37 +62,13 @@ SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) {
setSurfaceColorProperties(mColorMode);
}
-SkiaPipeline::~SkiaPipeline() {
- unpinImages();
-}
+SkiaPipeline::~SkiaPipeline() {}
void SkiaPipeline::onDestroyHardwareResources() {
unpinImages();
mRenderThread.cacheManager().trimStaleResources();
}
-bool SkiaPipeline::pinImages(std::vector<SkImage*>& mutableImages) {
- if (!mRenderThread.getGrContext()) {
- ALOGD("Trying to pin an image with an invalid GrContext");
- return false;
- }
- for (SkImage* image : mutableImages) {
- if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
- mPinnedImages.emplace_back(sk_ref_sp(image));
- } else {
- return false;
- }
- }
- return true;
-}
-
-void SkiaPipeline::unpinImages() {
- for (auto& image : mPinnedImages) {
- skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
- }
- mPinnedImages.clear();
-}
-
void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue, bool opaque,
const LightInfo& lightInfo) {
@@ -102,136 +78,53 @@ void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry,
layerUpdateQueue->clear();
}
-void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
- sk_sp<GrDirectContext> cachedContext;
-
- // Render all layers that need to be updated, in order.
- for (size_t i = 0; i < layers.entries().size(); i++) {
- RenderNode* layerNode = layers.entries()[i].renderNode.get();
- // only schedule repaint if node still on layer - possible it may have been
- // removed during a dropped frame, but layers may still remain scheduled so
- // as not to lose info on what portion is damaged
- if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
- continue;
- }
- SkASSERT(layerNode->getLayerSurface());
- SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl();
- if (!displayList || displayList->isEmpty()) {
- ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
- return;
- }
-
- const Rect& layerDamage = layers.entries()[i].damage;
-
- SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
-
- int saveCount = layerCanvas->save();
- SkASSERT(saveCount == 1);
-
- layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
-
- // TODO: put localized light center calculation and storage to a drawable related code.
- // It does not seem right to store something localized in a global state
- // fix here and in recordLayers
- const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
- Vector3 transformedLightCenter(savedLightCenter);
- // map current light center into RenderNode's coordinate space
- layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
- LightingInfo::setLightCenterRaw(transformedLightCenter);
-
- const RenderProperties& properties = layerNode->properties();
- const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
- if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
- return;
- }
-
- ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
- bounds.height());
+void SkiaPipeline::renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage) {
+ // only schedule repaint if node still on layer - possible it may have been
+ // removed during a dropped frame, but layers may still remain scheduled so
+ // as not to lose info on what portion is damaged
+ if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
+ return;
+ }
+ SkASSERT(layerNode->getLayerSurface());
+ SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl();
+ if (!displayList || displayList->isEmpty()) {
+ ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
+ return;
+ }
- layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
- layerCanvas->clear(SK_ColorTRANSPARENT);
+ SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
- RenderNodeDrawable root(layerNode, layerCanvas, false);
- root.forceDraw(layerCanvas);
- layerCanvas->restoreToCount(saveCount);
+ int saveCount = layerCanvas->save();
+ SkASSERT(saveCount == 1);
- LightingInfo::setLightCenterRaw(savedLightCenter);
+ layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
- // cache the current context so that we can defer flushing it until
- // either all the layers have been rendered or the context changes
- GrDirectContext* currentContext =
- GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext());
- if (cachedContext.get() != currentContext) {
- if (cachedContext.get()) {
- ATRACE_NAME("flush layers (context changed)");
- cachedContext->flushAndSubmit();
- }
- cachedContext.reset(SkSafeRef(currentContext));
- }
+ // TODO: put localized light center calculation and storage to a drawable related code.
+ // It does not seem right to store something localized in a global state
+ // fix here and in recordLayers
+ const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
+ Vector3 transformedLightCenter(savedLightCenter);
+ // map current light center into RenderNode's coordinate space
+ layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
+ LightingInfo::setLightCenterRaw(transformedLightCenter);
+
+ const RenderProperties& properties = layerNode->properties();
+ const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
+ if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
+ return;
}
- if (cachedContext.get()) {
- ATRACE_NAME("flush layers");
- cachedContext->flushAndSubmit();
- }
-}
+ ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
+ bounds.height());
-bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
- ErrorHandler* errorHandler) {
- // compute the size of the surface (i.e. texture) to be allocated for this layer
- const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
- const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
-
- SkSurface* layer = node->getLayerSurface();
- if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
- SkImageInfo info;
- info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
- kPremul_SkAlphaType, getSurfaceColorSpace());
- SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
- SkASSERT(mRenderThread.getGrContext() != nullptr);
- node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes, info, 0,
- this->getSurfaceOrigin(), &props));
- if (node->getLayerSurface()) {
- // update the transform in window of the layer to reset its origin wrt light source
- // position
- Matrix4 windowTransform;
- damageAccumulator.computeCurrentTransform(&windowTransform);
- node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
- } else {
- String8 cachesOutput;
- mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
- &mRenderThread.renderState());
- ALOGE("%s", cachesOutput.c_str());
- if (errorHandler) {
- std::ostringstream err;
- err << "Unable to create layer for " << node->getName();
- const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
- err << ", size " << info.width() << "x" << info.height() << " max size "
- << maxTextureSize << " color type " << (int)info.colorType() << " has context "
- << (int)(mRenderThread.getGrContext() != nullptr);
- errorHandler->onError(err.str());
- }
- }
- return true;
- }
- return false;
-}
+ layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
+ layerCanvas->clear(SK_ColorTRANSPARENT);
-void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
- GrDirectContext* context = thread.getGrContext();
- if (context && !bitmap->isHardware()) {
- ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
- auto image = bitmap->makeImage();
- if (image.get()) {
- skgpu::ganesh::PinAsTexture(context, image.get());
- skgpu::ganesh::UnpinTexture(context, image.get());
- // A submit is necessary as there may not be a frame coming soon, so without a call
- // to submit these texture uploads can just sit in the queue building up until
- // we run out of RAM
- context->flushAndSubmit();
- }
- }
+ RenderNodeDrawable root(layerNode, layerCanvas, false);
+ root.forceDraw(layerCanvas);
+ layerCanvas->restoreToCount(saveCount);
+
+ LightingInfo::setLightCenterRaw(savedLightCenter);
}
static void savePictureAsync(const sk_sp<SkData>& data, const std::string& filename) {
@@ -599,45 +492,6 @@ void SkiaPipeline::renderFrameImpl(const SkRect& clip,
}
}
-void SkiaPipeline::dumpResourceCacheUsage() const {
- int resources;
- size_t bytes;
- mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes);
- size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit();
-
- SkString log("Resource Cache Usage:\n");
- log.appendf("%8d items\n", resources);
- log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes,
- bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f)));
-
- ALOGD("%s", log.c_str());
-}
-
-void SkiaPipeline::setHardwareBuffer(AHardwareBuffer* buffer) {
- if (mHardwareBuffer) {
- AHardwareBuffer_release(mHardwareBuffer);
- mHardwareBuffer = nullptr;
- }
-
- if (buffer) {
- AHardwareBuffer_acquire(buffer);
- mHardwareBuffer = buffer;
- }
-}
-
-sk_sp<SkSurface> SkiaPipeline::getBufferSkSurface(
- const renderthread::HardwareBufferRenderParams& bufferParams) {
- auto bufferColorSpace = bufferParams.getColorSpace();
- if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
- !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
- mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
- mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
- bufferColorSpace, nullptr, true);
- mBufferColorSpace = bufferColorSpace;
- }
- return mBufferSurface;
-}
-
void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
mColorMode = colorMode;
switch (colorMode) {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index befee8989383..f9d37b924321 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -42,18 +42,9 @@ public:
void onDestroyHardwareResources() override;
- bool pinImages(std::vector<SkImage*>& mutableImages) override;
- bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
- void unpinImages() override;
-
void renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
bool opaque, const LightInfo& lightInfo) override;
- // If the given node didn't have a layer surface, or had one of the wrong size, this method
- // creates a new one and returns true. Otherwise does nothing and returns false.
- bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
- ErrorHandler* errorHandler) override;
-
void setSurfaceColorProperties(ColorMode colorMode) override;
SkColorType getSurfaceColorType() const override { return mSurfaceColorType; }
sk_sp<SkColorSpace> getSurfaceColorSpace() override { return mSurfaceColorSpace; }
@@ -63,9 +54,8 @@ public:
const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
const SkMatrix& preTransform);
- static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
-
- void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque);
+ void renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage);
+ virtual void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) = 0;
// Sets the recording callback to the provided function and the recording mode
// to CallbackAPI
@@ -75,19 +65,11 @@ public:
mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None;
}
- virtual void setHardwareBuffer(AHardwareBuffer* buffer) override;
- bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; }
-
void setTargetSdrHdrRatio(float ratio) override;
protected:
- sk_sp<SkSurface> getBufferSkSurface(
- const renderthread::HardwareBufferRenderParams& bufferParams);
- void dumpResourceCacheUsage() const;
-
renderthread::RenderThread& mRenderThread;
- AHardwareBuffer* mHardwareBuffer = nullptr;
sk_sp<SkSurface> mBufferSurface = nullptr;
sk_sp<SkColorSpace> mBufferColorSpace = nullptr;
@@ -125,8 +107,6 @@ private:
// Set up a multi frame capture.
bool setupMultiFrameCapture();
- std::vector<sk_sp<SkImage>> mPinnedImages;
-
// Block of properties used only for debugging to record a SkPicture and save it in a file.
// There are three possible ways of recording drawing commands.
enum class CaptureMode {
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index fd0a8e06f39c..d06dba05ee88 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "SkiaVulkanPipeline.h"
+#include "pipeline/skia/SkiaVulkanPipeline.h"
#include <GrDirectContext.h>
#include <GrTypes.h>
@@ -28,10 +28,10 @@
#include "DeferredLayerUpdater.h"
#include "LightingInfo.h"
#include "Readback.h"
-#include "ShaderCache.h"
-#include "SkiaPipeline.h"
-#include "SkiaProfileRenderer.h"
-#include "VkInteropFunctorDrawable.h"
+#include "pipeline/skia/ShaderCache.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
+#include "pipeline/skia/SkiaProfileRenderer.h"
+#include "pipeline/skia/VkInteropFunctorDrawable.h"
#include "renderstate/RenderState.h"
#include "renderthread/Frame.h"
#include "renderthread/IRenderPipeline.h"
@@ -42,7 +42,8 @@ namespace android {
namespace uirenderer {
namespace skiapipeline {
-SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {
+SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread)
+ : SkiaGpuPipeline(thread) {
thread.renderState().registerContextCallback(this);
}
diff --git a/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h
new file mode 100644
index 000000000000..9159eae46065
--- /dev/null
+++ b/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaPipeline.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaGpuPipeline : public SkiaPipeline {
+public:
+ SkiaGpuPipeline(renderthread::RenderThread& thread);
+ virtual ~SkiaGpuPipeline();
+
+ virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
+
+ // If the given node didn't have a layer surface, or had one of the wrong size, this method
+ // creates a new one and returns true. Otherwise does nothing and returns false.
+ bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) override;
+
+ bool pinImages(std::vector<SkImage*>& mutableImages) override;
+ bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
+ void unpinImages() override;
+ void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override;
+ void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override;
+ bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; }
+
+ static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
+
+protected:
+ sk_sp<SkSurface> getBufferSkSurface(
+ const renderthread::HardwareBufferRenderParams& bufferParams);
+ void dumpResourceCacheUsage() const;
+
+ AHardwareBuffer* mHardwareBuffer = nullptr;
+
+private:
+ std::vector<sk_sp<SkImage>> mPinnedImages;
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h
index ebe8b6e15d44..6e7478288777 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h
@@ -19,7 +19,7 @@
#include <EGL/egl.h>
#include <system/window.h>
-#include "SkiaPipeline.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
#include "renderstate/RenderState.h"
#include "renderthread/HardwareBufferRenderParams.h"
@@ -30,7 +30,7 @@ class Bitmap;
namespace uirenderer {
namespace skiapipeline {
-class SkiaOpenGLPipeline : public SkiaPipeline, public IGpuContextCallback {
+class SkiaOpenGLPipeline : public SkiaGpuPipeline, public IGpuContextCallback {
public:
SkiaOpenGLPipeline(renderthread::RenderThread& thread);
virtual ~SkiaOpenGLPipeline();
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h
index 624eaa51a584..0d30df48baee 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h
@@ -17,7 +17,7 @@
#pragma once
#include "SkRefCnt.h"
-#include "SkiaPipeline.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
#include "renderstate/RenderState.h"
#include "renderthread/HardwareBufferRenderParams.h"
#include "renderthread/VulkanManager.h"
@@ -30,7 +30,7 @@ namespace android {
namespace uirenderer {
namespace skiapipeline {
-class SkiaVulkanPipeline : public SkiaPipeline, public IGpuContextCallback {
+class SkiaVulkanPipeline : public SkiaGpuPipeline, public IGpuContextCallback {
public:
explicit SkiaVulkanPipeline(renderthread::RenderThread& thread);
virtual ~SkiaVulkanPipeline();
diff --git a/libs/hwui/platform/host/android/api-level.h b/libs/hwui/platform/host/android/api-level.h
new file mode 120000
index 000000000000..4fb4784f9f60
--- /dev/null
+++ b/libs/hwui/platform/host/android/api-level.h
@@ -0,0 +1 @@
+../../../../../../../bionic/libc/include/android/api-level.h \ No newline at end of file
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h
new file mode 100644
index 000000000000..a71726585081
--- /dev/null
+++ b/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaPipeline.h"
+#include "renderthread/Frame.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaGpuPipeline : public SkiaPipeline {
+public:
+ SkiaGpuPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {}
+ ~SkiaGpuPipeline() {}
+
+ bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
+ bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
+ void unpinImages() override {}
+
+ // If the given node didn't have a layer surface, or had one of the wrong size, this method
+ // creates a new one and returns true. Otherwise does nothing and returns false.
+ bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) override {
+ return false;
+ }
+ void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override {}
+ void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override {}
+ bool hasHardwareBuffer() override { return false; }
+
+ renderthread::MakeCurrentResult makeCurrent() override {
+ return renderthread::MakeCurrentResult::Failed;
+ }
+ renderthread::Frame getFrame() override { return renderthread::Frame(0, 0, 0); }
+ renderthread::IRenderPipeline::DrawResult draw(
+ const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+ const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+ const renderthread::HardwareBufferRenderParams& bufferParams,
+ std::mutex& profilerLock) override {
+ return {false, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd(-1)};
+ }
+ bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) override {
+ return false;
+ }
+ DeferredLayerUpdater* createTextureLayer() override { return nullptr; }
+ bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override {
+ return false;
+ }
+ [[nodiscard]] android::base::unique_fd flush() override {
+ return android::base::unique_fd(-1);
+ };
+ void onStop() override {}
+ bool isSurfaceReady() override { return false; }
+ bool isContextReady() override { return false; }
+
+ const SkM44& getPixelSnapMatrix() const override {
+ static const SkM44 sSnapMatrix = SkM44();
+ return sSnapMatrix;
+ }
+ static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap) {}
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h
new file mode 100644
index 000000000000..4fafbcc4748d
--- /dev/null
+++ b/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaGpuPipeline.h"
+
+namespace android {
+
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaOpenGLPipeline : public SkiaGpuPipeline {
+public:
+ SkiaOpenGLPipeline(renderthread::RenderThread& thread) : SkiaGpuPipeline(thread) {}
+
+ static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor) {}
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h
new file mode 100644
index 000000000000..d54caef45bb5
--- /dev/null
+++ b/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaGpuPipeline.h"
+
+namespace android {
+
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaVulkanPipeline : public SkiaGpuPipeline {
+public:
+ SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaGpuPipeline(thread) {}
+
+ static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor) {}
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index abf64d099935..984916cb3986 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -35,8 +35,8 @@
#include "Properties.h"
#include "RenderThread.h"
#include "hwui/Canvas.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
-#include "pipeline/skia/SkiaPipeline.h"
#include "pipeline/skia/SkiaVulkanPipeline.h"
#include "thread/CommonPool.h"
#include "utils/GLUtils.h"
@@ -108,7 +108,7 @@ void CanvasContext::invokeFunctor(const RenderThread& thread, Functor* functor)
}
void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
- skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap);
+ skiapipeline::SkiaGpuPipeline::prepareToDraw(thread, bitmap);
}
CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index b8c3a4de2bd4..ee1d1f8789d9 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -30,8 +30,6 @@
#include "SwapBehavior.h"
#include "hwui/Bitmap.h"
-class GrDirectContext;
-
struct ANativeWindow;
namespace android {
@@ -94,7 +92,6 @@ public:
virtual void setSurfaceColorProperties(ColorMode colorMode) = 0;
virtual SkColorType getSurfaceColorType() const = 0;
virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
- virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
virtual void setPictureCapturedCallback(
const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 7727078a42ec..63cb94516739 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -50,7 +50,7 @@ interface IMediaRouterService {
// MediaRouterService.java for readability.
// Methods for MediaRouter2
- List<MediaRoute2Info> getSystemRoutes();
+ List<MediaRoute2Info> getSystemRoutes(String callerPackageName, boolean isProxyRouter);
RoutingSessionInfo getSystemSessionInfo();
void registerRouter2(IMediaRouter2 router, String packageName);
@@ -75,7 +75,8 @@ interface IMediaRouterService {
// Methods for MediaRouter2Manager
List<RoutingSessionInfo> getRemoteSessions(IMediaRouter2Manager manager);
- RoutingSessionInfo getSystemSessionInfoForPackage(String packageName);
+ RoutingSessionInfo getSystemSessionInfoForPackage(String callerPackageName,
+ String targetPackageName);
void registerManager(IMediaRouter2Manager manager, String packageName);
void registerProxyRouter(IMediaRouter2Manager manager, String callingPackageName, String targetPackageName, in UserHandle targetUser);
void unregisterManager(IMediaRouter2Manager manager);
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index e0258ba4f61d..3f9440b60202 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -613,7 +613,7 @@ public final class MediaRouter2 {
mImpl = new LocalMediaRouter2Impl(mContext.getPackageName());
mHandler = new Handler(Looper.getMainLooper());
- loadSystemRoutes();
+ loadSystemRoutes(/* isProxyRouter */ false);
RoutingSessionInfo currentSystemSessionInfo = mImpl.getSystemSessionInfo();
if (currentSystemSessionInfo == null) {
@@ -631,21 +631,22 @@ public final class MediaRouter2 {
IMediaRouterService.Stub.asInterface(
ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
- loadSystemRoutes();
+ loadSystemRoutes(/* isProxyRouter */ true);
mSystemController =
new SystemRoutingController(
ProxyMediaRouter2Impl.getSystemSessionInfoImpl(
- mMediaRouterService, clientPackageName));
+ mMediaRouterService, mContext.getPackageName(), clientPackageName));
mImpl = new ProxyMediaRouter2Impl(context, clientPackageName, user);
}
@GuardedBy("mLock")
- private void loadSystemRoutes() {
+ private void loadSystemRoutes(boolean isProxyRouter) {
List<MediaRoute2Info> currentSystemRoutes = null;
try {
- currentSystemRoutes = mMediaRouterService.getSystemRoutes();
+ currentSystemRoutes = mMediaRouterService.getSystemRoutes(mContext.getPackageName(),
+ isProxyRouter);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
@@ -2644,7 +2645,8 @@ public final class MediaRouter2 {
@Override
public RoutingSessionInfo getSystemSessionInfo() {
- return getSystemSessionInfoImpl(mMediaRouterService, mClientPackageName);
+ return getSystemSessionInfoImpl(
+ mMediaRouterService, mContext.getPackageName(), mClientPackageName);
}
/**
@@ -3049,9 +3051,11 @@ public final class MediaRouter2 {
* <p>Extracted into a static method to allow calling this from the constructor.
*/
/* package */ static RoutingSessionInfo getSystemSessionInfoImpl(
- @NonNull IMediaRouterService service, @NonNull String clientPackageName) {
+ @NonNull IMediaRouterService service,
+ @NonNull String callerPackageName,
+ @NonNull String clientPackageName) {
try {
- return service.getSystemSessionInfoForPackage(clientPackageName);
+ return service.getSystemSessionInfoForPackage(callerPackageName, clientPackageName);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 7756d93a5c38..e62d112a7ccb 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -81,6 +81,7 @@ public final class MediaRouter2Manager {
@GuardedBy("sLock")
private static MediaRouter2Manager sInstance;
+ private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
private final Client mClient;
private final IMediaRouterService mMediaRouterService;
@@ -120,6 +121,7 @@ public final class MediaRouter2Manager {
}
private MediaRouter2Manager(Context context) {
+ mContext = context.getApplicationContext();
mMediaRouterService = IMediaRouterService.Stub.asInterface(
ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
mMediaSessionManager = (MediaSessionManager) context
@@ -374,16 +376,17 @@ public final class MediaRouter2Manager {
}
/**
- * Gets the system routing session for the given {@code packageName}.
- * Apps can select a route that is not the global route. (e.g. an app can select the device
- * route while BT route is available.)
+ * Gets the system routing session for the given {@code targetPackageName}. Apps can select a
+ * route that is not the global route. (e.g. an app can select the device route while BT route
+ * is available.)
*
- * @param packageName the package name of the application.
+ * @param targetPackageName the package name of the application.
*/
@Nullable
- public RoutingSessionInfo getSystemRoutingSession(@Nullable String packageName) {
+ public RoutingSessionInfo getSystemRoutingSession(@Nullable String targetPackageName) {
try {
- return mMediaRouterService.getSystemSessionInfoForPackage(packageName);
+ return mMediaRouterService.getSystemSessionInfoForPackage(
+ mContext.getPackageName(), targetPackageName);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 80b2be2567a7..6d4cc3a9ca44 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -194,13 +194,13 @@ package android.nfc {
package android.nfc.cardemulation {
public final class CardEmulation {
- method @Deprecated public boolean categoryAllowsForegroundPreference(String);
+ method public boolean categoryAllowsForegroundPreference(String);
method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
- method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
+ method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
- method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
- method @Deprecated public int getSelectionModeForCategory(String);
+ method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
+ method public int getSelectionModeForCategory(String);
method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 67697a429a32..de9eada18104 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -36,6 +36,7 @@ import android.nfc.Constants;
import android.nfc.Flags;
import android.nfc.INfcCardEmulation;
import android.nfc.NfcAdapter;
+import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
@@ -271,30 +272,31 @@ public final class CardEmulation {
}
/**
+ * <p>
* Returns whether the user has allowed AIDs registered in the
* specified category to be handled by a service that is preferred
* by the foreground application, instead of by a pre-configured default.
*
* Foreground applications can set such preferences using the
* {@link #setPreferredService(Activity, ComponentName)} method.
+ * <p class="note">
+ * Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, this method will always
+ * return true.
*
* @param category The category, e.g. {@link #CATEGORY_PAYMENT}
* @return whether AIDs in the category can be handled by a service
* specified by the foreground app.
- *
- * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
- * Preferred Payment service is no longer valid. All routings will be done in a AID
- * category agnostic manner.
*/
@SuppressWarnings("NonUserGetterCalled")
- @Deprecated
public boolean categoryAllowsForegroundPreference(String category) {
Context contextAsUser = mContext.createContextAsUser(
UserHandle.of(UserHandle.myUserId()), 0);
+
RoleManager roleManager = contextAsUser.getSystemService(RoleManager.class);
if (roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) {
return true;
}
+
if (CATEGORY_PAYMENT.equals(category)) {
boolean preferForeground = false;
try {
@@ -319,14 +321,14 @@ public final class CardEmulation {
* every time what service they would like to use in this category.
* <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked
* to pick a service if there is a conflict.
+ *
+ * <p class="note">
+ * Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default service defined
+ * by the holder of {@link android.app.role.RoleManager#ROLE_WALLET} and is category agnostic.
+ *
* @param category The category, for example {@link #CATEGORY_PAYMENT}
* @return the selection mode for the passed in category
- *
- * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
- * Preferred Payment service is no longer valid. All routings will be done in a AID
- * category agnostic manner.
*/
- @Deprecated
public int getSelectionModeForCategory(String category) {
if (CATEGORY_PAYMENT.equals(category)) {
boolean paymentRegistered = false;
@@ -919,6 +921,13 @@ public final class CardEmulation {
/**
* Retrieves the route destination for the preferred payment service.
*
+ * <p class="note">
+ * Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the preferred payment service
+ * no longer exists and is replaced by {@link android.app.role.RoleManager#ROLE_WALLET}. This
+ * will return the route for one of the services registered by the role holder (if any). If
+ * there are multiple services registered, it is unspecified which of those will be used to
+ * determine the route.
+ *
* @return The route destination secure element name of the preferred payment service.
* HCE payment: "Host"
* OffHost payment: 1. String with prefix SIM or prefix eSE string.
@@ -931,15 +940,8 @@ public final class CardEmulation {
* (e.g. eSE/eSE1, eSE2, etc.).
* 2. "OffHost" if the payment service does not specify secure element
* name.
- *
- * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
- * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app.
- * A payment service will be selected automatically based on registered AIDs. In the case of
- * multiple services that register for the same payment AID, the selection will be done on
- * an alphabetical order based on the component names.
*/
@RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
- @Deprecated
@Nullable
public String getRouteDestinationForPreferredPaymentService() {
try {
@@ -981,16 +983,16 @@ public final class CardEmulation {
/**
* Returns a user-visible description of the preferred payment service.
*
- * @return the preferred payment service description
+ * <p class="note">
+ * Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the preferred payment service
+ * no longer exists and is replaced by {@link android.app.role.RoleManager#ROLE_WALLET}. This
+ * will return the description for one of the services registered by the role holder (if any).
+ * If there are multiple services registered, it is unspecified which of those will be used
+ * to obtain the service description here.
*
- * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
- * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app.
- * A payment service will be selected automatically based on registered AIDs. In the case of
- * multiple services that register for the same payment AID, the selection will be done on
- * an alphabetical order based on the component names.
+ * @return the preferred payment service description
*/
@RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
- @Deprecated
@Nullable
public CharSequence getDescriptionForPreferredPaymentService() {
try {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
index 9111e6162cc1..68c2244f7622 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
@@ -23,6 +23,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.style.Hyphens
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import com.android.compose.theme.LocalAndroidColorScheme
@@ -39,7 +40,7 @@ fun HeadlineText(text: String, modifier: Modifier = Modifier) {
text = text,
color = LocalAndroidColorScheme.current.onSurface,
textAlign = TextAlign.Center,
- style = MaterialTheme.typography.headlineSmall,
+ style = MaterialTheme.typography.headlineSmall.copy(hyphens = Hyphens.Auto),
)
}
@@ -52,7 +53,7 @@ fun BodyMediumText(text: String, modifier: Modifier = Modifier) {
modifier = modifier.wrapContentSize(),
text = text,
color = LocalAndroidColorScheme.current.onSurfaceVariant,
- style = MaterialTheme.typography.bodyMedium,
+ style = MaterialTheme.typography.bodyMedium.copy(hyphens = Hyphens.Auto),
)
}
@@ -70,7 +71,7 @@ fun BodySmallText(
modifier = modifier.wrapContentSize(),
text = text,
color = LocalAndroidColorScheme.current.onSurfaceVariant,
- style = MaterialTheme.typography.bodySmall,
+ style = MaterialTheme.typography.bodySmall.copy(hyphens = Hyphens.Auto),
overflow = TextOverflow.Ellipsis,
maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
onTextLayout = onTextLayout,
@@ -86,7 +87,7 @@ fun LargeTitleText(text: String, modifier: Modifier = Modifier) {
modifier = modifier.wrapContentSize(),
text = text,
color = LocalAndroidColorScheme.current.onSurface,
- style = MaterialTheme.typography.titleLarge,
+ style = MaterialTheme.typography.titleLarge.copy(hyphens = Hyphens.Auto),
)
}
@@ -104,7 +105,7 @@ fun SmallTitleText(
modifier = modifier.wrapContentSize(),
text = text,
color = LocalAndroidColorScheme.current.onSurface,
- style = MaterialTheme.typography.titleSmall,
+ style = MaterialTheme.typography.titleSmall.copy(hyphens = Hyphens.Auto),
overflow = TextOverflow.Ellipsis,
maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
onTextLayout = onTextLayout,
@@ -120,7 +121,7 @@ fun SectionHeaderText(text: String, modifier: Modifier = Modifier, color: Color)
modifier = modifier.wrapContentSize(),
text = text,
color = color,
- style = MaterialTheme.typography.titleSmall,
+ style = MaterialTheme.typography.titleSmall.copy(hyphens = Hyphens.Auto),
)
}
@@ -133,7 +134,7 @@ fun SnackbarContentText(text: String, modifier: Modifier = Modifier) {
modifier = modifier.wrapContentSize(),
text = text,
color = MaterialTheme.colorScheme.inverseOnSurface,
- style = MaterialTheme.typography.bodyMedium,
+ style = MaterialTheme.typography.bodyMedium.copy(hyphens = Hyphens.Auto),
)
}
@@ -146,7 +147,7 @@ fun SnackbarActionText(text: String, modifier: Modifier = Modifier) {
modifier = modifier.wrapContentSize(),
text = text,
color = MaterialTheme.colorScheme.inversePrimary,
- style = MaterialTheme.typography.labelLarge,
+ style = MaterialTheme.typography.labelLarge.copy(hyphens = Hyphens.Auto),
)
}
@@ -160,7 +161,7 @@ fun LargeLabelTextOnSurfaceVariant(text: String, modifier: Modifier = Modifier)
text = text,
textAlign = TextAlign.Center,
color = LocalAndroidColorScheme.current.onSurfaceVariant,
- style = MaterialTheme.typography.labelLarge,
+ style = MaterialTheme.typography.labelLarge.copy(hyphens = Hyphens.Auto),
)
}
@@ -173,6 +174,6 @@ fun LargeLabelText(text: String, modifier: Modifier = Modifier) {
modifier = modifier.wrapContentSize(),
text = text,
textAlign = TextAlign.Center,
- style = MaterialTheme.typography.labelLarge,
+ style = MaterialTheme.typography.labelLarge.copy(hyphens = Hyphens.Auto),
)
} \ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 283dc7d6fe08..0fe35e695047 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -38,7 +38,7 @@ class CredentialSelectorActivity : Hilt_CredentialSelectorActivity() {
setContent {
MaterialTheme {
WearApp(
- viewModel = viewModel,
+ flowEngine = viewModel,
onCloseApp = { finish() },
)
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 9d9776301518..b7fa33e9372f 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -24,9 +24,6 @@ import com.android.credentialmanager.CredentialSelectorUiState.Get
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.client.CredentialManagerClient
import com.android.credentialmanager.model.EntryInfo
-import com.android.credentialmanager.model.get.ActionEntryInfo
-import com.android.credentialmanager.model.get.AuthenticationEntryInfo
-import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.ui.mappers.toGet
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
@@ -53,7 +50,7 @@ class CredentialSelectorViewModel @Inject constructor(
private val shouldClose = MutableStateFlow(false)
private lateinit var selectedEntry: EntryInfo
private var isAutoSelected: Boolean = false
- val uiState: StateFlow<CredentialSelectorUiState> =
+ override val uiState: StateFlow<CredentialSelectorUiState> =
combine(
credentialManagerClient.requests,
isPrimaryScreen,
@@ -137,29 +134,3 @@ class CredentialSelectorViewModel @Inject constructor(
}
}
-sealed class CredentialSelectorUiState {
- data object Idle : CredentialSelectorUiState()
- sealed class Get : CredentialSelectorUiState() {
- data class SingleEntry(val entry: CredentialEntryInfo) : Get()
- data class SingleEntryPerAccount(
- val sortedEntries: List<CredentialEntryInfo>,
- val authenticationEntryList: List<AuthenticationEntryInfo>,
- ) : Get()
- data class MultipleEntry(
- val accounts: List<PerUserNameEntries>,
- val actionEntryList: List<ActionEntryInfo>,
- val authenticationEntryList: List<AuthenticationEntryInfo>,
- ) : Get() {
- data class PerUserNameEntries(
- val userName: String,
- val sortedCredentialEntryList: List<CredentialEntryInfo>,
- )
- }
-
- // TODO: b/301206470 add the remaining states
- }
-
- data object Create : CredentialSelectorUiState()
- data class Cancel(val appName: String) : CredentialSelectorUiState()
- data object Close : CredentialSelectorUiState()
-}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
index 2e80a7c672f4..c05fc93b8223 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
@@ -20,9 +20,15 @@ import android.content.Intent
import androidx.activity.result.IntentSenderRequest
import androidx.compose.runtime.Composable
import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.model.get.ActionEntryInfo
+import com.android.credentialmanager.model.get.AuthenticationEntryInfo
+import com.android.credentialmanager.model.get.CredentialEntryInfo
+import kotlinx.coroutines.flow.StateFlow
/** Engine of the credential selecting flow. */
interface FlowEngine {
+ /** UI state of the selector app */
+ val uiState: StateFlow<CredentialSelectorUiState>
/** Back from previous stage. */
fun back()
/** Cancels the selection flow. */
@@ -54,4 +60,40 @@ interface FlowEngine {
*/
@Composable
fun getEntrySelector(): (entry: EntryInfo, isAutoSelected: Boolean) -> Unit
+}
+
+/** UI state of the selector app */
+sealed class CredentialSelectorUiState {
+ /** Idle UI state, no request is going on. */
+ data object Idle : CredentialSelectorUiState()
+ /** Getting credential UI state. */
+ sealed class Get : CredentialSelectorUiState() {
+ /** Getting credential UI state when there is only one credential available. */
+ data class SingleEntry(val entry: CredentialEntryInfo) : Get()
+ /**
+ * Getting credential UI state when there is only one account while with multiple
+ * credentials, with different types(eg, passkey vs password) or providers.
+ */
+ data class SingleEntryPerAccount(
+ val sortedEntries: List<CredentialEntryInfo>,
+ val authenticationEntryList: List<AuthenticationEntryInfo>,
+ ) : Get()
+ /** Getting credential UI state when there are multiple accounts available. */
+ data class MultipleEntry(
+ val accounts: List<PerUserNameEntries>,
+ val actionEntryList: List<ActionEntryInfo>,
+ val authenticationEntryList: List<AuthenticationEntryInfo>,
+ ) : Get() {
+ data class PerUserNameEntries(
+ val userName: String,
+ val sortedCredentialEntryList: List<CredentialEntryInfo>,
+ )
+ }
+ }
+ /** Creating credential UI state. */
+ data object Create : CredentialSelectorUiState()
+ /** Request is cancelling by [appName]. */
+ data class Cancel(val appName: String) : CredentialSelectorUiState()
+ /** Request is closed peacefully. */
+ data object Close : CredentialSelectorUiState()
} \ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index bf4c988679b9..018db6899f6e 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -32,7 +32,6 @@ import com.android.credentialmanager.CredentialSelectorUiState
import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntryPerAccount
import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
-import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.TAG
import com.android.credentialmanager.ui.screens.LoadingScreen
@@ -52,8 +51,7 @@ import com.android.credentialmanager.ui.screens.multiple.MultiCredentialsFlatten
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun WearApp(
- viewModel: CredentialSelectorViewModel,
- flowEngine: FlowEngine = viewModel,
+ flowEngine: FlowEngine,
onCloseApp: () -> Unit,
) {
val navController = rememberSwipeDismissableNavController()
@@ -62,7 +60,7 @@ fun WearApp(
rememberSwipeDismissableNavHostState(swipeToDismissBoxState = swipeToDismissBoxState)
val selectEntry = flowEngine.getEntrySelector()
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+ val uiState by flowEngine.uiState.collectAsStateWithLifecycle()
WearNavScaffold(
startDestination = Screen.Loading.route,
navController = navController,
@@ -112,7 +110,7 @@ fun WearApp(
}
}
BackHandler(true) {
- viewModel.back()
+ flowEngine.back()
}
Log.d(TAG, "uiState change, state: $uiState")
when (val state = uiState) {
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index c755623c6f08..4147813bd059 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.7.0-alpha04"
+ extra["jetpackComposeVersion"] = "1.7.0-alpha05"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt
index 247990c69cd8..f1cbc3729a78 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt
@@ -17,6 +17,8 @@
package com.android.settingslib.spa.gallery.page
import android.os.Bundle
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
@@ -32,6 +34,7 @@ import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -72,10 +75,11 @@ object LoadingBarPageProvider : SettingsPageProvider {
Text(text = "Resume")
}
}
+ Spacer(modifier = Modifier.height(SettingsDimension.itemPaddingVertical))
+ LinearLoadingBar(isLoading = loading)
+ Spacer(modifier = Modifier.height(SettingsDimension.itemPaddingVertical))
+ CircularLoadingBar(isLoading = loading)
}
-
- LinearLoadingBar(isLoading = loading, yOffset = 104.dp)
- CircularLoadingBar(isLoading = loading)
}
}
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index ff2a1e8d8184..0ee9d595d875 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,11 +15,11 @@
#
[versions]
-agp = "8.3.0"
-compose-compiler = "1.5.10"
+agp = "8.3.1"
+compose-compiler = "1.5.11"
dexmaker-mockito = "2.28.3"
jvm = "17"
-kotlin = "1.9.22"
+kotlin = "1.9.23"
truth = "1.1.5"
[libraries]
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index f2b9235e92b4..2f2ac2467a6c 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@ dependencies {
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.3.0-alpha02")
+ api("androidx.compose.material3:material3:1.3.0-alpha03")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.8.0-alpha03")
+ api("androidx.navigation:navigation-compose:2.8.0-alpha05")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.7.0-alpha03")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt
index 0e7e49960be1..2de73c0ec289 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt
@@ -19,14 +19,19 @@ package com.android.settingslib.spa.widget.editor
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.selection.toggleable
+import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Checkbox
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
+import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
@@ -110,22 +115,31 @@ private fun CheckboxItem(
option: SettingsDropdownCheckOption,
onClick: (SettingsDropdownCheckOption) -> Unit,
) {
- TextButton(
- onClick = { onClick(option) },
- modifier = Modifier.fillMaxWidth(),
- ) {
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Checkbox(
- checked = option.selected.value,
- onCheckedChange = null,
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .minimumInteractiveComponentSize()
+ .toggleable(
+ value = option.selected.value,
enabled = option.changeable,
+ role = Role.Checkbox,
+ onValueChange = { onClick(option) },
)
- Text(text = option.text, modifier = Modifier.alphaForEnabled(option.changeable))
- }
+ .padding(ButtonDefaults.TextButtonContentPadding),
+ horizontalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Checkbox(
+ checked = option.selected.value,
+ onCheckedChange = null,
+ enabled = option.changeable,
+ )
+ Text(
+ text = option.text,
+ modifier = Modifier.alphaForEnabled(option.changeable),
+ color = MaterialTheme.colorScheme.primary,
+ style = MaterialTheme.typography.labelLarge,
+ )
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
index 354b95ddcbfe..f372a45f9e59 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt
@@ -40,7 +40,8 @@ import com.android.settingslib.spa.framework.theme.toMediumWeight
data class BottomAppBarButton(
val text: String,
- val onClick: () -> Unit,
+ val enabled: Boolean = true,
+ val onClick: () -> Unit
)
@Composable
@@ -122,13 +123,13 @@ private fun BottomBar(
) {
Row(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) {
dismissButton?.apply {
- TextButton(onClick) {
+ TextButton(onClick = onClick, enabled = enabled) {
ActionText(text)
}
}
Spacer(modifier = Modifier.weight(1f))
actionButton?.apply {
- Button(onClick) {
+ Button(onClick = onClick, enabled = enabled) {
ActionText(text)
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/LoadingBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/LoadingBar.kt
index 1741f134f3d1..be178ff26f1f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/LoadingBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/LoadingBar.kt
@@ -17,7 +17,6 @@
package com.android.settingslib.spa.widget.ui
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.CircularProgressIndicator
@@ -25,23 +24,15 @@ import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
/**
* Indeterminate linear progress bar. Expresses an unspecified wait time.
*/
@Composable
-fun LinearLoadingBar(
- isLoading: Boolean,
- xOffset: Dp = 0.dp,
- yOffset: Dp = 0.dp
-) {
+fun LinearLoadingBar(isLoading: Boolean) {
if (isLoading) {
LinearProgressIndicator(
- modifier = Modifier
- .fillMaxWidth()
- .absoluteOffset(xOffset, yOffset)
+ modifier = Modifier.fillMaxWidth()
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
index a12f0990b581..acd9e3dc83cb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.composable.blueprint
+import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneKey
@@ -39,7 +40,7 @@ object ClockTransition {
transitioningToSmallClock()
}
from(ClockScenes.splitShadeLargeClockScene, to = ClockScenes.largeClockScene) {
- spec = tween(1000)
+ spec = tween(1000, easing = LinearEasing)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
index 2781f39fc479..1c938a6c19a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.composable.section
+import android.content.res.Resources
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
@@ -23,6 +24,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
@@ -36,6 +38,8 @@ import com.android.systemui.customization.R as customizationR
import com.android.systemui.customization.R
import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey
import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
+import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeLargeClockScene
import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
@@ -95,6 +99,36 @@ constructor(
if (currentClock?.largeClock?.view == null) {
return
}
+
+ // Centering animation for clocks that have custom position animations.
+ LaunchedEffect(layoutState.currentTransition?.progress) {
+ val transition = layoutState.currentTransition ?: return@LaunchedEffect
+ if (currentClock?.largeClock?.config?.hasCustomPositionUpdatedAnimation != true) {
+ return@LaunchedEffect
+ }
+
+ // If we are not doing the centering animation, do not animate.
+ val progress =
+ if (transition.isTransitioningBetween(largeClockScene, splitShadeLargeClockScene)) {
+ transition.progress
+ } else {
+ 1f
+ }
+
+ val distance =
+ if (transition.toScene == splitShadeLargeClockScene) {
+ -getClockCenteringDistance()
+ } else {
+ getClockCenteringDistance()
+ }
+ .toFloat()
+ val largeClock = checkNotNull(currentClock).largeClock
+ largeClock.animations.onPositionUpdated(
+ distance = distance,
+ fraction = progress,
+ )
+ }
+
MovableElement(key = largeClockElementKey, modifier = modifier) {
content {
AndroidView(
@@ -120,4 +154,8 @@ constructor(
(clockView.parent as? ViewGroup)?.removeView(clockView)
addView(clockView)
}
+
+ fun getClockCenteringDistance(): Float {
+ return Resources.getSystem().displayMetrics.widthPixels / 4f
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
index d72d5cad31b4..b4472fc15ac4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt
@@ -16,12 +16,16 @@
package com.android.systemui.keyguard.ui.composable.section
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@@ -31,11 +35,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.modifiers.thenIf
import com.android.systemui.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes
import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.largeClockScene
import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.smallClockScene
import com.android.systemui.keyguard.ui.composable.blueprint.ClockScenes.splitShadeLargeClockScene
@@ -63,6 +68,9 @@ constructor(
) {
val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState()
val currentClockLayout by clockViewModel.currentClockLayout.collectAsState()
+ val hasCustomPositionUpdatedAnimation by
+ clockViewModel.hasCustomPositionUpdatedAnimation.collectAsState()
+
val currentScene =
when (currentClockLayout) {
KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK ->
@@ -94,12 +102,10 @@ constructor(
transitions = ClockTransition.defaultClockTransitions,
enableInterruptions = false,
) {
- scene(ClockScenes.splitShadeLargeClockScene) {
- Row(
- modifier = Modifier.fillMaxSize(),
- ) {
+ scene(splitShadeLargeClockScene) {
+ Box(modifier = Modifier.fillMaxSize()) {
Column(
- modifier = Modifier.fillMaxHeight().weight(weight = 1f),
+ modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
with(smartSpaceSection) {
@@ -108,8 +114,34 @@ constructor(
onTopChanged = burnIn.onSmartspaceTopChanged,
)
}
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+
+ with(clockSection) {
+ LargeClock(
+ modifier =
+ Modifier.fillMaxSize().thenIf(
+ !hasCustomPositionUpdatedAnimation
+ ) {
+ // If we do not have a custom position animation, we want
+ // the clock to be on one half of the screen.
+ Modifier.offset {
+ IntOffset(
+ x =
+ -clockSection
+ .getClockCenteringDistance()
+ .toInt(),
+ y = 0,
+ )
+ }
+ }
+ )
+ }
}
+ }
+
+ Row(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ Spacer(modifier = Modifier.weight(weight = 1f))
with(notificationSection) {
Notifications(
modifier =
@@ -121,7 +153,7 @@ constructor(
}
}
- scene(ClockScenes.splitShadeSmallClockScene) {
+ scene(splitShadeSmallClockScene) {
Row(
modifier = Modifier.fillMaxSize(),
) {
@@ -133,7 +165,7 @@ constructor(
SmallClock(
burnInParams = burnIn.parameters,
onTopChanged = burnIn.onSmallClockTopChanged,
- modifier = Modifier.fillMaxWidth()
+ modifier = Modifier.wrapContentSize()
)
}
with(smartSpaceSection) {
@@ -155,13 +187,13 @@ constructor(
}
}
- scene(ClockScenes.smallClockScene) {
+ scene(smallClockScene) {
Column {
with(clockSection) {
SmallClock(
burnInParams = burnIn.parameters,
onTopChanged = burnIn.onSmallClockTopChanged,
- modifier = Modifier.fillMaxWidth()
+ modifier = Modifier.wrapContentSize()
)
}
with(smartSpaceSection) {
@@ -172,15 +204,12 @@ constructor(
}
with(mediaCarouselSection) { MediaCarousel() }
with(notificationSection) {
- Notifications(
- modifier =
- androidx.compose.ui.Modifier.fillMaxWidth().weight(weight = 1f)
- )
+ Notifications(modifier = Modifier.fillMaxWidth().weight(weight = 1f))
}
}
}
- scene(ClockScenes.largeClockScene) {
+ scene(largeClockScene) {
Column {
with(smartSpaceSection) {
SmartSpace(
@@ -188,7 +217,7 @@ constructor(
onTopChanged = burnIn.onSmartspaceTopChanged,
)
}
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) { LargeClock(modifier = Modifier.fillMaxSize()) }
}
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index cea49e1b535e..11c946261816 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -517,24 +517,12 @@ class AnimatableClockView @JvmOverloads constructor(
val currentMoveAmount = left - clockStartLeft
val digitOffsetDirection = if (isLayoutRtl) -1 else 1
for (i in 0 until NUM_DIGITS) {
- // The delay for the digit, in terms of fraction (i.e. the digit should not move
- // during 0.0 - 0.1).
- val digitInitialDelay =
- if (isMovingToCenter) {
- moveToCenterDelays[i] * MOVE_DIGIT_STEP
- } else {
- moveToSideDelays[i] * MOVE_DIGIT_STEP
- }
val digitFraction =
- MOVE_INTERPOLATOR.getInterpolation(
- constrainedMap(
- 0.0f,
- 1.0f,
- digitInitialDelay,
- digitInitialDelay + AVAILABLE_ANIMATION_TIME,
- moveFraction
- )
- )
+ getDigitFraction(
+ digit = i,
+ isMovingToCenter = isMovingToCenter,
+ fraction = moveFraction,
+ )
val moveAmountForDigit = currentMoveAmount * digitFraction
val moveAmountDeltaForDigit = moveAmountForDigit - currentMoveAmount
glyphOffsets[i] = digitOffsetDirection * moveAmountDeltaForDigit
@@ -542,6 +530,57 @@ class AnimatableClockView @JvmOverloads constructor(
invalidate()
}
+ /**
+ * Offsets the glyphs of the clock for the step clock animation.
+ *
+ * The animation makes the glyphs of the clock move at different speeds, when the clock is
+ * moving horizontally. This method uses direction, distance, and fraction to determine offset.
+ *
+ * @param distance is the total distance in pixels to offset the glyphs when animation
+ * completes. Negative distance means we are animating the position towards the center.
+ * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1
+ * means it finished moving.
+ */
+ fun offsetGlyphsForStepClockAnimation(
+ distance: Float,
+ fraction: Float,
+ ) {
+ for (i in 0 until NUM_DIGITS) {
+ val dir = if (isLayoutRtl) -1 else 1
+ val digitFraction =
+ getDigitFraction(digit = i, isMovingToCenter = distance > 0, fraction = fraction)
+ val moveAmountForDigit = dir * distance * digitFraction
+ glyphOffsets[i] = moveAmountForDigit
+
+ if (distance > 0) {
+ // If distance > 0 then we are moving from the left towards the center.
+ // We need ensure that the glyphs are offset to the initial position.
+ glyphOffsets -= dir * distance
+ }
+ }
+ invalidate()
+ }
+
+ private fun getDigitFraction(digit: Int, isMovingToCenter: Boolean, fraction: Float): Float {
+ // The delay for the digit, in terms of fraction (i.e. the digit should not move
+ // during 0.0 - 0.1).
+ val digitInitialDelay =
+ if (isMovingToCenter) {
+ moveToCenterDelays[digit] * MOVE_DIGIT_STEP
+ } else {
+ moveToSideDelays[digit] * MOVE_DIGIT_STEP
+ }
+ return MOVE_INTERPOLATOR.getInterpolation(
+ constrainedMap(
+ 0.0f,
+ 1.0f,
+ digitInitialDelay,
+ digitInitialDelay + AVAILABLE_ANIMATION_TIME,
+ fraction,
+ )
+ )
+ }
+
// DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
// This is an optimization to ensure we only recompute the patterns when the inputs change.
private object Patterns {
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 54c7a0823963..b39201427b46 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -232,6 +232,10 @@ class DefaultClockController(
fun offsetGlyphsForStepClockAnimation(fromLeft: Int, direction: Int, fraction: Float) {
view.offsetGlyphsForStepClockAnimation(fromLeft, direction, fraction)
}
+
+ fun offsetGlyphsForStepClockAnimation(distance: Float, fraction: Float) {
+ view.offsetGlyphsForStepClockAnimation(distance, fraction)
+ }
}
inner class DefaultClockEvents : ClockEvents {
@@ -316,6 +320,8 @@ class DefaultClockController(
}
override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {}
+
+ override fun onPositionUpdated(distance: Float, fraction: Float) {}
}
inner class LargeClockAnimations(
@@ -326,6 +332,10 @@ class DefaultClockController(
override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {
largeClock.offsetGlyphsForStepClockAnimation(fromLeft, direction, fraction)
}
+
+ override fun onPositionUpdated(distance: Float, fraction: Float) {
+ largeClock.offsetGlyphsForStepClockAnimation(distance, fraction)
+ }
}
class AnimationState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index f8321b7e7eb3..07e9815fb5a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -60,9 +60,12 @@ import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
@@ -158,6 +161,9 @@ class SideFpsControllerTest : SysuiTestCase() {
FakeBiometricSettingsRepository(),
FakeSystemClock(),
mock(KeyguardUpdateMonitor::class.java),
+ { mock(DeviceEntryFingerprintAuthInteractor::class.java) },
+ { mock(KeyguardInteractor::class.java) },
+ { mock(KeyguardTransitionInteractor::class.java) },
testScope.backgroundScope,
)
displayStateInteractor =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index b253309104d6..d88260f0760a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -24,14 +24,18 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
+import dagger.Lazy
import kotlinx.coroutines.test.TestScope
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -39,6 +43,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
@SmallTest
@@ -81,10 +86,19 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
biometricSettingsRepository,
systemClock,
keyguardUpdateMonitor,
+ Lazy { mock(DeviceEntryFingerprintAuthInteractor::class.java) },
+ Lazy { mock(KeyguardInteractor::class.java) },
+ Lazy { mock(KeyguardTransitionInteractor::class.java) },
TestScope().backgroundScope,
)
}
+ @Test(expected = IllegalStateException::class)
+ fun enableUdfpsRefactor_deprecatedShowMethod_throwsIllegalStateException() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
+ underTest.show()
+ }
+
@Test
fun canShowAlternateBouncerForFingerprint_givenCanShow() {
givenCanShowAlternateBouncer()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index c308a987455b..2a2b2f1892f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -85,7 +85,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testIsImportantForAccessibility_falseWhenNoNotifs() =
+ fun isImportantForAccessibility_falseWhenNoNotifs() =
testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
@@ -100,7 +100,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testIsImportantForAccessibility_trueWhenNotifs() =
+ fun isImportantForAccessibility_trueWhenNotifs() =
testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
@@ -115,7 +115,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testIsImportantForAccessibility_trueWhenNotKeyguard() =
+ fun isImportantForAccessibility_trueWhenNotKeyguard() =
testScope.runTest {
val important by collectLastValue(underTest.isImportantForAccessibility)
@@ -130,7 +130,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeEmptyShadeView_trueWhenNoNotifs() =
+ fun shouldIncludeEmptyShadeView_trueWhenNoNotifs() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView)
@@ -143,7 +143,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeEmptyShadeView_falseWhenNotifs() =
+ fun shouldIncludeEmptyShadeView_falseWhenNotifs() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView)
@@ -156,7 +156,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeEmptyShadeView_falseWhenQsExpandedDefault() =
+ fun shouldIncludeEmptyShadeView_falseWhenQsExpandedDefault() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView)
@@ -171,7 +171,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeEmptyShadeView_trueWhenQsExpandedInSplitShade() =
+ fun shouldIncludeEmptyShadeView_trueWhenQsExpandedInSplitShade() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView)
@@ -189,7 +189,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeEmptyShadeView_trueWhenLockedShade() =
+ fun shouldIncludeEmptyShadeView_trueWhenLockedShade() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView)
@@ -204,7 +204,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeEmptyShadeView_falseWhenKeyguard() =
+ fun shouldIncludeEmptyShadeView_falseWhenKeyguard() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView)
@@ -219,7 +219,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeEmptyShadeView_falseWhenStartingToSleep() =
+ fun shouldIncludeEmptyShadeView_falseWhenStartingToSleep() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView)
@@ -236,7 +236,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testAreNotificationsHiddenInShade_true() =
+ fun areNotificationsHiddenInShade_true() =
testScope.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
@@ -248,7 +248,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testAreNotificationsHiddenInShade_false() =
+ fun areNotificationsHiddenInShade_false() =
testScope.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
@@ -260,7 +260,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testHasFilteredOutSeenNotifications_true() =
+ fun hasFilteredOutSeenNotifications_true() =
testScope.runTest {
val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
@@ -271,7 +271,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testHasFilteredOutSeenNotifications_false() =
+ fun hasFilteredOutSeenNotifications_false() =
testScope.runTest {
val hasFilteredNotifs by collectLastValue(underTest.hasFilteredOutSeenNotifications)
@@ -282,7 +282,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeFooterView_trueWhenShade() =
+ fun shouldIncludeFooterView_trueWhenShade() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
@@ -298,7 +298,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeFooterView_trueWhenLockedShade() =
+ fun shouldIncludeFooterView_trueWhenLockedShade() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
@@ -314,7 +314,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeFooterView_falseWhenKeyguard() =
+ fun shouldIncludeFooterView_falseWhenKeyguard() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
@@ -329,7 +329,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeFooterView_falseWhenUserNotSetUp() =
+ fun shouldIncludeFooterView_falseWhenUserNotSetUp() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
@@ -347,7 +347,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeFooterView_falseWhenStartingToSleep() =
+ fun shouldIncludeFooterView_falseWhenStartingToSleep() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
@@ -365,7 +365,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeFooterView_falseWhenQsExpandedDefault() =
+ fun shouldIncludeFooterView_falseWhenQsExpandedDefault() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
@@ -384,7 +384,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeFooterView_trueWhenQsExpandedSplitShade() =
+ fun shouldIncludeFooterView_trueWhenQsExpandedSplitShade() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
@@ -405,7 +405,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeFooterView_falseWhenRemoteInputActive() =
+ fun shouldIncludeFooterView_falseWhenRemoteInputActive() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
@@ -423,7 +423,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeFooterView_animatesWhenShade() =
+ fun shouldIncludeFooterView_animatesWhenShade() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
@@ -439,7 +439,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldIncludeFooterView_notAnimatingOnKeyguard() =
+ fun shouldIncludeFooterView_notAnimatingOnKeyguard() =
testScope.runTest {
val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
@@ -455,7 +455,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldHideFooterView_trueWhenShadeIsClosed() =
+ fun shouldHideFooterView_trueWhenShadeIsClosed() =
testScope.runTest {
val shouldHide by collectLastValue(underTest.shouldHideFooterView)
@@ -469,7 +469,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
}
@Test
- fun testShouldHideFooterView_falseWhenShadeIsOpen() =
+ fun shouldHideFooterView_falseWhenShadeIsOpen() =
testScope.runTest {
val shouldHide by collectLastValue(underTest.shouldHideFooterView)
@@ -484,7 +484,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
- fun testPinnedHeadsUpRows_filtersForPinnedItems() =
+ fun pinnedHeadsUpRows_filtersForPinnedItems() =
testScope.runTest {
val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
@@ -527,7 +527,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
- fun testHasPinnedHeadsUpRows_true() =
+ fun hasPinnedHeadsUpRows_true() =
testScope.runTest {
val hasPinnedHeadsUpRow by collectLastValue(underTest.hasPinnedHeadsUpRow)
@@ -542,7 +542,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
- fun testHasPinnedHeadsUpRows_false() =
+ fun hasPinnedHeadsUpRows_false() =
testScope.runTest {
val hasPinnedHeadsUpRow by collectLastValue(underTest.hasPinnedHeadsUpRow)
@@ -557,7 +557,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
- fun testTopHeadsUpRow_emptyList_null() =
+ fun topHeadsUpRow_emptyList_null() =
testScope.runTest {
val topHeadsUpRow by collectLastValue(underTest.topHeadsUpRow)
@@ -569,7 +569,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
- fun testHeadsUpAnimationsEnabled_true() =
+ fun headsUpAnimationsEnabled_true() =
testScope.runTest {
val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
@@ -582,7 +582,7 @@ class NotificationListViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
- fun testHeadsUpAnimationsEnabled_keyguardShowing_false() =
+ fun headsUpAnimationsEnabled_keyguardShowing_false() =
testScope.runTest {
val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 5256bb956bc4..f969ee677763 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -290,10 +290,15 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
testScope.runTest {
val alpha by collectLastValue(underTest.glanceableHubAlpha)
- // Start on dream
- showDream()
+ // Start on lockscreen, notifications should be unhidden.
+ showLockscreen()
assertThat(alpha).isEqualTo(1f)
+ // Transition to dream, notifications should be hidden so that transition
+ // from dream->hub doesn't cause notification flicker.
+ showDream()
+ assertThat(alpha).isEqualTo(0f)
+
// Start transitioning to glanceable hub
val progress = 0.6f
keyguardTransitionRepository.sendTransitionStep(
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index fd7a7f34d258..8e2bd9b2562b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -188,10 +188,21 @@ interface ClockAnimations {
* negative means left.
* @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
* it finished moving.
+ * @deprecated use {@link #onPositionUpdated(float, float)} instead.
*/
fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float)
/**
+ * Runs when the clock's position changed during the move animation.
+ *
+ * @param distance is the total distance in pixels to offset the glyphs when animation
+ * completes. Negative distance means we are animating the position towards the center.
+ * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
+ * it finished moving.
+ */
+ fun onPositionUpdated(distance: Float, fraction: Float)
+
+ /**
* Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview,
* 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize
*/
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 188e7ee439fd..7525ce0f98ac 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -21,16 +21,27 @@ import com.android.systemui.biometrics.data.repository.FingerprintPropertyReposi
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -46,6 +57,9 @@ constructor(
private val biometricSettingsRepository: BiometricSettingsRepository,
private val systemClock: SystemClock,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val deviceEntryFingerprintAuthInteractor: Lazy<DeviceEntryFingerprintAuthInteractor>,
+ private val keyguardInteractor: Lazy<KeyguardInteractor>,
+ keyguardTransitionInteractor: Lazy<KeyguardTransitionInteractor>,
@Application scope: CoroutineScope,
) {
var receivedDownTouch = false
@@ -63,13 +77,80 @@ constructor(
} else {
bouncerRepository.alternateBouncerUIAvailable
}
+ private val isDozingOrAod: Flow<Boolean> =
+ keyguardTransitionInteractor
+ .get()
+ .transitions
+ .map {
+ it.to == KeyguardState.DOZING ||
+ it.to == KeyguardState.AOD ||
+ ((it.from == KeyguardState.DOZING || it.from == KeyguardState.AOD) &&
+ it.transitionState != TransitionState.FINISHED)
+ }
+ .distinctUntilChanged()
+
+ /**
+ * Whether the current biometric, bouncer, and keyguard states allow the alternate bouncer to
+ * show.
+ */
+ val canShowAlternateBouncer: StateFlow<Boolean> =
+ alternateBouncerSupported
+ .flatMapLatest { alternateBouncerSupported ->
+ if (alternateBouncerSupported) {
+ keyguardTransitionInteractor.get().currentKeyguardState.flatMapLatest {
+ currentKeyguardState ->
+ if (currentKeyguardState == KeyguardState.GONE) {
+ flowOf(false)
+ } else {
+ combine(
+ deviceEntryFingerprintAuthInteractor
+ .get()
+ .isFingerprintAuthCurrentlyAllowed,
+ keyguardInteractor.get().isKeyguardDismissible,
+ bouncerRepository.primaryBouncerShow,
+ isDozingOrAod
+ ) {
+ fingerprintAllowed,
+ keyguardDismissible,
+ primaryBouncerShowing,
+ dozing ->
+ fingerprintAllowed &&
+ !keyguardDismissible &&
+ !primaryBouncerShowing &&
+ !dozing
+ }
+ }
+ }
+ } else {
+ flowOf(false)
+ }
+ }
+ .stateIn(
+ scope = scope,
+ started = WhileSubscribed(),
+ initialValue = false,
+ )
+
+ /**
+ * Always shows the alternate bouncer. Requesters must check [canShowAlternateBouncer]` before
+ * calling this.
+ */
+ fun forceShow() {
+ if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) {
+ show()
+ return
+ }
+ bouncerRepository.setAlternateVisible(true)
+ }
/**
* Sets the correct bouncer states to show the alternate bouncer if it can show.
*
* @return whether alternateBouncer is visible
+ * @deprecated use [forceShow] and manually check [canShowAlternateBouncer] beforehand
*/
fun show(): Boolean {
+ DeviceEntryUdfpsRefactor.assertInLegacyMode()
bouncerRepository.setAlternateVisible(canShowAlternateBouncerForFingerprint())
return isVisibleState()
}
@@ -105,6 +186,9 @@ constructor(
}
fun canShowAlternateBouncerForFingerprint(): Boolean {
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ return canShowAlternateBouncer.value
+ }
return alternateBouncerSupported.value &&
biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value &&
!keyguardUpdateMonitor.isFingerprintLockedOut &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index a293afcc28dc..182798e097d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -139,6 +139,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.ui.viewmodel.DreamViewModel;
import com.android.systemui.dump.DumpManager;
@@ -3404,7 +3405,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// Ensure that keyguard becomes visible if the going away animation is canceled
if (showKeyguard && !KeyguardWmStateRefactor.isEnabled()
- && MigrateClocksToBlueprint.isEnabled()) {
+ && (MigrateClocksToBlueprint.isEnabled()
+ || DeviceEntryUdfpsRefactor.isEnabled())) {
mKeyguardInteractor.showKeyguard();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 1c1c33ab7e7e..3d649512f342 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -130,6 +130,17 @@ constructor(
initialValue = ClockLayout.SMALL_CLOCK
)
+ val hasCustomPositionUpdatedAnimation: StateFlow<Boolean> =
+ combine(currentClock, isLargeClockVisible) { currentClock, isLargeClockVisible ->
+ isLargeClockVisible &&
+ currentClock?.largeClock?.config?.hasCustomPositionUpdatedAnimation == true
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false
+ )
+
/** Calculates the top margin for the small clock. */
fun getSmallClockTopMargin(context: Context): Int {
var topMargin: Int
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 18bb51197555..5544f9389787 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -173,7 +173,6 @@ constructor(
footerView,
footerViewModel,
clearAllNotifications = {
- metricsLogger.action(MetricsProto.MetricsEvent.ACTION_DISMISS_ALL_NOTES)
clearAllNotifications(
parentView,
// Hide the silent section header (if present) if there will be
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 ecf737a8650f..4a096a89848a 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
@@ -20,6 +20,7 @@ import android.view.View
import android.view.WindowInsets
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.Flags.communalHub
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
@@ -150,9 +151,11 @@ constructor(
}
}
- launch {
- viewModel.glanceableHubAlpha.collect {
- controller.setMaxAlphaForGlanceableHub(it)
+ if (communalHub()) {
+ launch {
+ viewModel.glanceableHubAlpha.collect {
+ controller.setMaxAlphaForGlanceableHub(it)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index d112edb9772c..f767b9997864 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -29,6 +29,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
@@ -241,7 +242,22 @@ constructor(
started = SharingStarted.Eagerly,
initialValue = false,
)
- .dumpWhileCollecting("isOnGlanceableHubWithoutShade")
+ .dumpValue("isOnGlanceableHubWithoutShade")
+
+ /** Are we on the dream without the shade/qs? */
+ private val isDreamingWithoutShade: Flow<Boolean> =
+ combine(
+ keyguardTransitionInteractor.isFinishedInState(DREAMING),
+ isAnyExpanded,
+ ) { isDreaming, isAnyExpanded ->
+ isDreaming && !isAnyExpanded
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+ .dumpValue("isDreamingWithoutShade")
/**
* Fade in if the user swipes the shade back up, not if collapsed by going to AOD. This is
@@ -460,6 +476,7 @@ constructor(
combineTransform(
isOnGlanceableHubWithoutShade,
isOnLockscreen,
+ isDreamingWithoutShade,
merge(
lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
@@ -467,9 +484,9 @@ constructor(
// Manually emit on start because [notificationAlpha] only starts emitting
// when transitions start.
.onStart { emit(1f) }
- ) { isOnGlanceableHubWithoutShade, isOnLockscreen, alpha,
+ ) { isOnGlanceableHubWithoutShade, isOnLockscreen, isDreamingWithoutShade, alpha,
->
- if (isOnGlanceableHubWithoutShade && !isOnLockscreen) {
+ if ((isOnGlanceableHubWithoutShade || isDreamingWithoutShade) && !isOnLockscreen) {
// Notifications should not be visible on the glanceable hub.
// TODO(b/321075734): implement a way to actually set the notifications to
// gone while on the hub instead of just adjusting alpha
@@ -484,6 +501,7 @@ constructor(
emit(1f)
}
}
+ .distinctUntilChanged()
.dumpWhileCollecting("glanceableHubAlpha")
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 97fc35a062f4..8b7b348ede76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -51,6 +51,9 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
@@ -59,6 +62,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.SystemClock;
import dagger.Lazy;
@@ -286,7 +290,9 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
VibratorHelper vibrator,
SystemClock systemClock,
Lazy<SelectedUserInteractor> selectedUserInteractor,
- BiometricUnlockInteractor biometricUnlockInteractor
+ BiometricUnlockInteractor biometricUnlockInteractor,
+ JavaAdapter javaAdapter,
+ KeyguardTransitionInteractor keyguardTransitionInteractor
) {
mPowerManager = powerManager;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -317,10 +323,19 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
mOrderUnlockAndWake = resources.getBoolean(
com.android.internal.R.bool.config_orderUnlockAndWake);
mSelectedUserInteractor = selectedUserInteractor;
-
+ javaAdapter.alwaysCollectFlow(
+ keyguardTransitionInteractor.getStartedKeyguardTransitionStep(),
+ this::consumeTransitionStepOnStartedKeyguardState);
dumpManager.registerDumpable(this);
}
+ @VisibleForTesting
+ protected void consumeTransitionStepOnStartedKeyguardState(TransitionStep transitionStep) {
+ if (transitionStep.getFrom() == KeyguardState.GONE) {
+ mBiometricUnlockInteractor.setBiometricUnlockState(MODE_NONE);
+ }
+ }
+
public void setKeyguardViewController(KeyguardViewController keyguardViewController) {
mKeyguardViewController = keyguardViewController;
}
@@ -773,7 +788,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) {
listener.onResetMode();
}
- mBiometricUnlockInteractor.setBiometricUnlockState(MODE_NONE);
mNumConsecutiveFpFailures = 0;
mLastFpFailureUptimeMillis = 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index a99834ad3456..febe5a25d3aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -169,6 +169,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private Job mListenForAlternateBouncerTransitionSteps = null;
private Job mListenForKeyguardAuthenticatedBiometricsHandled = null;
+ private Job mListenForCanShowAlternateBouncer = null;
// Local cache of expansion events, to avoid duplicates
private float mFraction = -1f;
@@ -506,6 +507,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mListenForKeyguardAuthenticatedBiometricsHandled.cancel(null);
}
mListenForKeyguardAuthenticatedBiometricsHandled = null;
+ if (mListenForCanShowAlternateBouncer != null) {
+ mListenForCanShowAlternateBouncer.cancel(null);
+ }
+ mListenForCanShowAlternateBouncer = null;
if (!DeviceEntryUdfpsRefactor.isEnabled()) {
mListenForAlternateBouncerTransitionSteps = mJavaAdapter.alwaysCollectFlow(
mKeyguardTransitionInteractor.transitionStepsFromState(
@@ -517,6 +522,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mPrimaryBouncerInteractor.getKeyguardAuthenticatedBiometricsHandled(),
this::consumeKeyguardAuthenticatedBiometricsHandled
);
+ } else {
+ mListenForCanShowAlternateBouncer = mJavaAdapter.alwaysCollectFlow(
+ mAlternateBouncerInteractor.getCanShowAlternateBouncer(),
+ this::consumeCanShowAlternateBouncer
+ );
}
if (KeyguardWmStateRefactor.isEnabled()) {
@@ -558,6 +568,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
}
+ private void consumeCanShowAlternateBouncer(boolean canShow) {
+ // do nothing, we only are registering for the flow to ensure that there's at least
+ // one subscriber that will update AlternateBouncerInteractor.canShowAlternateBouncer.value
+ }
+
/** Register a callback, to be invoked by the Predictive Back system. */
private void registerBackCallback() {
if (!mIsBackCallbackRegistered) {
@@ -723,6 +738,16 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* {@see KeyguardBouncer#show(boolean, boolean)}
*/
public void showBouncer(boolean scrimmed) {
+ if (DeviceEntryUdfpsRefactor.isEnabled()) {
+ if (mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()) {
+ mAlternateBouncerInteractor.forceShow();
+ updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState());
+ } else {
+ showPrimaryBouncer(scrimmed);
+ }
+ return;
+ }
+
if (!mAlternateBouncerInteractor.show()) {
showPrimaryBouncer(scrimmed);
} else {
@@ -834,7 +859,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mKeyguardGoneCancelAction = null;
}
- updateAlternateBouncerShowing(mAlternateBouncerInteractor.show());
+ if (DeviceEntryUdfpsRefactor.isEnabled()) {
+ mAlternateBouncerInteractor.forceShow();
+ updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState());
+ } else {
+ updateAlternateBouncerShowing(mAlternateBouncerInteractor.show());
+ }
setKeyguardMessage(message, null, null);
return;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 5509c048b0da..d4a0c8f74b14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -58,6 +58,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.keyguard.DismissCallbackRegistry
@@ -66,6 +67,8 @@ import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintA
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
@@ -193,6 +196,9 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
biometricSettingsRepository,
FakeSystemClock(),
keyguardUpdateMonitor,
+ { mock(DeviceEntryFingerprintAuthInteractor::class.java) },
+ { mock(KeyguardInteractor::class.java) },
+ { mock(KeyguardTransitionInteractor::class.java) },
testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 2014755bd964..ae20c703b93d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -55,6 +55,7 @@ import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.keyguard.DismissCallbackRegistry
@@ -63,6 +64,8 @@ import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintA
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
@@ -191,6 +194,9 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() {
biometricSettingsRepository,
FakeSystemClock(),
keyguardUpdateMonitor,
+ { mock(DeviceEntryFingerprintAuthInteractor::class.java) },
+ { mock(KeyguardInteractor::class.java) },
+ { mock(KeyguardTransitionInteractor::class.java) },
testScope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index cb8c40c333b3..3b4f683124e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
@@ -108,6 +109,9 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
biometricSettingsRepository,
FakeSystemClock(),
keyguardUpdateMonitor,
+ { mock(DeviceEntryFingerprintAuthInteractor::class.java) },
+ { mock(KeyguardInteractor::class.java) },
+ { mock(KeyguardTransitionInteractor::class.java) },
testScope.backgroundScope,
)
underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt
index e53cd11ebe48..d12980a74a18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelWithKosmosTest.kt
@@ -20,17 +20,23 @@ import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockFaceConfig
+import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
@SmallTest
@RunWith(JUnit4::class)
@@ -98,4 +104,49 @@ class KeyguardClockViewModelWithKosmosTest : SysuiTestCase() {
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
assertThat(currentClockLayout).isEqualTo(KeyguardClockViewModel.ClockLayout.LARGE_CLOCK)
}
+
+ @Test
+ fun hasCustomPositionUpdatedAnimation_withConfigTrue_isTrue() =
+ testScope.runTest {
+ with(kosmos) {
+ keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+ fakeKeyguardClockRepository.setCurrentClock(
+ buildClockController(hasCustomPositionUpdatedAnimation = true)
+ )
+ }
+
+ val hasCustomPositionUpdatedAnimation by
+ collectLastValue(underTest.hasCustomPositionUpdatedAnimation)
+ assertThat(hasCustomPositionUpdatedAnimation).isEqualTo(true)
+ }
+
+ @Test
+ fun hasCustomPositionUpdatedAnimation_withConfigFalse_isFalse() =
+ testScope.runTest {
+ with(kosmos) {
+ keyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+ fakeKeyguardClockRepository.setCurrentClock(
+ buildClockController(hasCustomPositionUpdatedAnimation = false)
+ )
+ }
+
+ val hasCustomPositionUpdatedAnimation by
+ collectLastValue(underTest.hasCustomPositionUpdatedAnimation)
+ assertThat(hasCustomPositionUpdatedAnimation).isEqualTo(false)
+ }
+
+ private fun buildClockController(
+ hasCustomPositionUpdatedAnimation: Boolean = false
+ ): ClockController {
+ val clockController = mock(ClockController::class.java)
+ val largeClock = mock(ClockFaceController::class.java)
+ val config = mock(ClockFaceConfig::class.java)
+
+ whenever(clockController.largeClock).thenReturn(largeClock)
+ whenever(largeClock.config).thenReturn(config)
+ whenever(config.hasCustomPositionUpdatedAnimation)
+ .thenReturn(hasCustomPositionUpdatedAnimation)
+
+ return clockController
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 6f65eb426e64..50f81ff13825 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
import static com.google.common.truth.Truth.assertThat;
@@ -26,6 +27,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -51,6 +53,10 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -58,6 +64,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
@@ -158,7 +165,9 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
mSystemClock,
() -> mSelectedUserInteractor,
- mBiometricUnlockInteractor
+ mBiometricUnlockInteractor,
+ mock(JavaAdapter.class),
+ mock(KeyguardTransitionInteractor.class)
);
biometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
biometricUnlockController.addListener(mBiometricUnlockEventsListener);
@@ -462,6 +471,29 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
}
@Test
+ public void biometricUnlockStateResetOnTransitionFromGone() {
+ mBiometricUnlockController.consumeTransitionStepOnStartedKeyguardState(
+ new TransitionStep(
+ KeyguardState.AOD,
+ KeyguardState.GONE,
+ .1f /* value */,
+ TransitionState.STARTED
+ )
+ );
+ verify(mBiometricUnlockInteractor, never()).setBiometricUnlockState(anyInt());
+
+ mBiometricUnlockController.consumeTransitionStepOnStartedKeyguardState(
+ new TransitionStep(
+ KeyguardState.GONE,
+ KeyguardState.AOD,
+ .1f /* value */,
+ TransitionState.STARTED
+ )
+ );
+ verify(mBiometricUnlockInteractor).setBiometricUnlockState(eq(MODE_NONE));
+ }
+
+ @Test
public void onFingerprintDetect_showBouncer() {
// WHEN fingerprint detect occurs
mBiometricUnlockController.onBiometricDetected(UserHandle.USER_CURRENT,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
index c4fc30de3d06..070a3697df68 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt
@@ -19,7 +19,10 @@ package com.android.systemui.bouncer.domain.interactor
import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.statusBarStateController
@@ -37,6 +40,9 @@ var Kosmos.alternateBouncerInteractor by
biometricSettingsRepository = biometricSettingsRepository,
systemClock = systemClock,
keyguardUpdateMonitor = keyguardUpdateMonitor,
+ deviceEntryFingerprintAuthInteractor = { deviceEntryFingerprintAuthInteractor },
+ keyguardInteractor = { keyguardInteractor },
+ keyguardTransitionInteractor = { keyguardTransitionInteractor },
scope = testScope.backgroundScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
index 5f5d42850619..7eef704c1622 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -28,6 +28,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -85,6 +86,9 @@ object KeyguardDismissInteractorFactory {
FakeBiometricSettingsRepository(),
FakeSystemClock(),
keyguardUpdateMonitor,
+ { mock(DeviceEntryFingerprintAuthInteractor::class.java) },
+ { mock(KeyguardInteractor::class.java) },
+ { mock(KeyguardTransitionInteractor::class.java) },
testScope.backgroundScope,
)
val powerInteractorWithDeps =
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 997f3af3533e..04b19ffd4dfc 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -24,10 +24,13 @@ flag {
}
flag {
- name: "compute_window_changes_on_a11y"
+ name: "compute_window_changes_on_a11y_v2"
namespace: "accessibility"
description: "Computes accessibility window changes in accessibility instead of wm package."
bug: "322444245"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index d30748478741..6007bfd99e7b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -445,7 +445,7 @@ public class AccessibilityWindowManager {
public void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId,
IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows) {
synchronized (mLock) {
- if (!Flags.computeWindowChangesOnA11y()) {
+ if (!Flags.computeWindowChangesOnA11yV2()) {
// If the flag is enabled, it's already done in #createWindowInfoListLocked.
updateWindowsByWindowAttributesLocked(windows);
}
@@ -491,7 +491,7 @@ public class AccessibilityWindowManager {
/**
* Called when the windows for accessibility changed. This is called if
- * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y} is
+ * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is
* true.
*
* @param forceSend Send the windows for accessibility even if they haven't
@@ -996,7 +996,7 @@ public class AccessibilityWindowManager {
final int windowId = findWindowIdLocked(userId, window.token);
// With the flag enabled, createWindowInfoListLocked() already removes invalid windows.
- if (!Flags.computeWindowChangesOnA11y()) {
+ if (!Flags.computeWindowChangesOnA11yV2()) {
if (windowId < 0) {
return null;
}
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
deleted file mode 100644
index 0a4148535451..000000000000
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ /dev/null
@@ -1,567 +0,0 @@
-/*
- * 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.server.companion;
-
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.annotation.UserIdInt;
-import android.companion.AssociationInfo;
-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;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.PerUser;
-import com.android.server.companion.association.AssociationStore;
-import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
-import com.android.server.companion.presence.ObservableUuid;
-import com.android.server.companion.presence.ObservableUuidStore;
-import com.android.server.companion.utils.PackageUtils;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Manages communication with companion applications via
- * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to
- * the services, maintaining the connection (the binding), and invoking callback methods such as
- * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)},
- * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and
- * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} in the
- * application process.
- *
- * <p>
- * The following is the list of the APIs provided by {@link CompanionApplicationController} (to be
- * utilized by {@link CompanionDeviceManagerService}):
- * <ul>
- * <li> {@link #bindCompanionApplication(int, String, boolean)}
- * <li> {@link #unbindCompanionApplication(int, String)}
- * <li> {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)}
- * <li> {@link #isCompanionApplicationBound(int, String)}
- * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
- * </ul>
- *
- * @see CompanionDeviceService
- * @see android.companion.ICompanionDeviceService
- * @see CompanionDeviceServiceConnector
- */
-@SuppressLint("LongLogTag")
-public class CompanionApplicationController {
- static final boolean DEBUG = false;
- private static final String TAG = "CDM_CompanionApplicationController";
-
- private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec
-
- private final @NonNull Context mContext;
- private final @NonNull AssociationStore mAssociationStore;
- private final @NonNull ObservableUuidStore mObservableUuidStore;
- private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
- private final @NonNull CompanionServicesRegister mCompanionServicesRegister;
-
- private final PowerManagerInternal mPowerManagerInternal;
-
- @GuardedBy("mBoundCompanionApplications")
- private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>>
- mBoundCompanionApplications;
- @GuardedBy("mScheduledForRebindingCompanionApplications")
- private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications;
-
- CompanionApplicationController(Context context, AssociationStore associationStore,
- ObservableUuidStore observableUuidStore,
- CompanionDevicePresenceMonitor companionDevicePresenceMonitor,
- PowerManagerInternal powerManagerInternal) {
- mContext = context;
- mAssociationStore = associationStore;
- mObservableUuidStore = observableUuidStore;
- mDevicePresenceMonitor = companionDevicePresenceMonitor;
- mPowerManagerInternal = powerManagerInternal;
- mCompanionServicesRegister = new CompanionServicesRegister();
- mBoundCompanionApplications = new AndroidPackageMap<>();
- mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>();
- }
-
- void onPackagesChanged(@UserIdInt int userId) {
- mCompanionServicesRegister.invalidate(userId);
- }
-
- /**
- * CDM binds to the companion app.
- */
- public void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName,
- boolean isSelfManaged) {
- if (DEBUG) {
- Log.i(TAG, "bind() u" + userId + "/" + packageName
- + " isSelfManaged=" + isSelfManaged);
- }
-
- final List<ComponentName> companionServices =
- mCompanionServicesRegister.forPackage(userId, packageName);
- if (companionServices.isEmpty()) {
- Slog.w(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": "
- + "eligible CompanionDeviceService not found.\n"
- + "A CompanionDeviceService should declare an intent-filter for "
- + "\"android.companion.CompanionDeviceService\" action and require "
- + "\"android.permission.BIND_COMPANION_DEVICE_SERVICE\" permission.");
- return;
- }
-
- final List<CompanionDeviceServiceConnector> serviceConnectors = new ArrayList<>();
- synchronized (mBoundCompanionApplications) {
- if (mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
- if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is ALREADY bound.");
- return;
- }
-
- for (int i = 0; i < companionServices.size(); i++) {
- boolean isPrimary = i == 0;
- serviceConnectors.add(CompanionDeviceServiceConnector.newInstance(mContext, userId,
- companionServices.get(i), isSelfManaged, isPrimary));
- }
-
- mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors);
- }
-
- // Set listeners for both Primary and Secondary connectors.
- for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
- serviceConnector.setListener(this::onBinderDied);
- }
-
- // Now "bind" all the connectors: the primary one and the rest of them.
- for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
- serviceConnector.connect();
- }
- }
-
- /**
- * CDM unbinds the companion app.
- */
- public void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) {
- if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName);
-
- final List<CompanionDeviceServiceConnector> serviceConnectors;
-
- synchronized (mBoundCompanionApplications) {
- serviceConnectors = mBoundCompanionApplications.removePackage(userId, packageName);
- }
-
- synchronized (mScheduledForRebindingCompanionApplications) {
- mScheduledForRebindingCompanionApplications.removePackage(userId, packageName);
- }
-
- if (serviceConnectors == null) {
- if (DEBUG) {
- Log.e(TAG, "unbindCompanionApplication(): "
- + "u" + userId + "/" + packageName + " is NOT bound");
- Log.d(TAG, "Stacktrace", new Throwable());
- }
- return;
- }
-
- for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
- serviceConnector.postUnbind();
- }
- }
-
- /**
- * @return whether the companion application is bound now.
- */
- public boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) {
- synchronized (mBoundCompanionApplications) {
- return mBoundCompanionApplications.containsValueForPackage(userId, packageName);
- }
- }
-
- private void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName,
- CompanionDeviceServiceConnector serviceConnector) {
- Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName);
-
- if (isRebindingCompanionApplicationScheduled(userId, packageName)) {
- if (DEBUG) {
- Log.i(TAG, "CompanionApplication rebinding has been scheduled, skipping "
- + serviceConnector.getComponentName());
- }
- return;
- }
-
- if (serviceConnector.isPrimary()) {
- synchronized (mScheduledForRebindingCompanionApplications) {
- mScheduledForRebindingCompanionApplications.setValueForPackage(
- userId, packageName, true);
- }
- }
-
- // Rebinding in 10 seconds.
- Handler.getMain().postDelayed(() ->
- onRebindingCompanionApplicationTimeout(userId, packageName, serviceConnector),
- REBIND_TIMEOUT);
- }
-
- private boolean isRebindingCompanionApplicationScheduled(
- @UserIdInt int userId, @NonNull String packageName) {
- synchronized (mScheduledForRebindingCompanionApplications) {
- return mScheduledForRebindingCompanionApplications.containsValueForPackage(
- userId, packageName);
- }
- }
-
- private void onRebindingCompanionApplicationTimeout(
- @UserIdInt int userId, @NonNull String packageName,
- @NonNull CompanionDeviceServiceConnector serviceConnector) {
- // Re-mark the application is bound.
- if (serviceConnector.isPrimary()) {
- synchronized (mBoundCompanionApplications) {
- if (!mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
- List<CompanionDeviceServiceConnector> serviceConnectors =
- Collections.singletonList(serviceConnector);
- mBoundCompanionApplications.setValueForPackage(userId, packageName,
- serviceConnectors);
- }
- }
-
- synchronized (mScheduledForRebindingCompanionApplications) {
- mScheduledForRebindingCompanionApplications.removePackage(userId, packageName);
- }
- }
-
- serviceConnector.connect();
- }
-
- /**
- * Notify the app that the device appeared.
- *
- * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead
- */
- @Deprecated
- public void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) {
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
-
- Slog.i(TAG, "notifyDevice_Appeared() id=" + association.getId() + " u" + userId
- + "/" + packageName);
-
- final CompanionDeviceServiceConnector primaryServiceConnector =
- getPrimaryServiceConnector(userId, packageName);
- if (primaryServiceConnector == null) {
- Slog.e(TAG, "notify_CompanionApplicationDevice_Appeared(): "
- + "u" + userId + "/" + packageName + " is NOT bound.");
- Slog.e(TAG, "Stacktrace", new Throwable());
- return;
- }
-
- Log.i(TAG, "Calling onDeviceAppeared to userId=[" + userId + "] package=["
- + packageName + "] associationId=[" + association.getId() + "]");
-
- primaryServiceConnector.postOnDeviceAppeared(association);
- }
-
- /**
- * Notify the app that the device disappeared.
- *
- * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead
- */
- @Deprecated
- public void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) {
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
-
- Slog.i(TAG, "notifyDevice_Disappeared() id=" + association.getId() + " u" + userId
- + "/" + packageName);
-
- final CompanionDeviceServiceConnector primaryServiceConnector =
- getPrimaryServiceConnector(userId, packageName);
- if (primaryServiceConnector == null) {
- Slog.e(TAG, "notify_CompanionApplicationDevice_Disappeared(): "
- + "u" + userId + "/" + packageName + " is NOT bound.");
- Slog.e(TAG, "Stacktrace", new Throwable());
- return;
- }
-
- Log.i(TAG, "Calling onDeviceDisappeared to userId=[" + userId + "] package=["
- + packageName + "] associationId=[" + association.getId() + "]");
-
- primaryServiceConnector.postOnDeviceDisappeared(association);
- }
-
- /**
- * Notify the app that the device appeared.
- */
- public void notifyCompanionDevicePresenceEvent(AssociationInfo association, int event) {
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
- final CompanionDeviceServiceConnector primaryServiceConnector =
- getPrimaryServiceConnector(userId, packageName);
- final DevicePresenceEvent devicePresenceEvent =
- new DevicePresenceEvent(association.getId(), event, null);
-
- if (primaryServiceConnector == null) {
- Slog.e(TAG, "notifyCompanionApplicationDevicePresenceEvent(): "
- + "u" + userId + "/" + packageName
- + " event=[ " + event + " ] is NOT bound.");
- Slog.e(TAG, "Stacktrace", new Throwable());
- return;
- }
-
- Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=["
- + packageName + "] associationId=[" + association.getId()
- + "] event=[" + event + "]");
-
- primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
- }
-
- /**
- * Notify the app that the device disappeared.
- */
- public void notifyUuidDevicePresenceEvent(ObservableUuid uuid, int event) {
- final int userId = uuid.getUserId();
- final ParcelUuid parcelUuid = uuid.getUuid();
- final String packageName = uuid.getPackageName();
- final CompanionDeviceServiceConnector primaryServiceConnector =
- getPrimaryServiceConnector(userId, packageName);
- final DevicePresenceEvent devicePresenceEvent =
- new DevicePresenceEvent(DevicePresenceEvent.NO_ASSOCIATION, event, parcelUuid);
-
- if (primaryServiceConnector == null) {
- Slog.e(TAG, "notifyApplicationDevicePresenceChanged(): "
- + "u" + userId + "/" + packageName
- + " event=[ " + event + " ] is NOT bound.");
- Slog.e(TAG, "Stacktrace", new Throwable());
- return;
- }
-
- Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=["
- + packageName + "]" + "event= [" + event + "]");
-
- primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
- }
-
- void dump(@NonNull PrintWriter out) {
- out.append("Companion Device Application Controller: \n");
-
- synchronized (mBoundCompanionApplications) {
- out.append(" Bound Companion Applications: ");
- if (mBoundCompanionApplications.size() == 0) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- mBoundCompanionApplications.dump(out);
- }
- }
-
- out.append(" Companion Applications Scheduled For Rebinding: ");
- if (mScheduledForRebindingCompanionApplications.size() == 0) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- mScheduledForRebindingCompanionApplications.dump(out);
- }
- }
-
- /**
- * Rebinding for Self-Managed secondary services OR Non-Self-Managed services.
- */
- private void onBinderDied(@UserIdInt int userId, @NonNull String packageName,
- @NonNull CompanionDeviceServiceConnector serviceConnector) {
-
- boolean isPrimary = serviceConnector.isPrimary();
- Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary);
-
- // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY.
- if (isPrimary) {
- final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByPackage(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);
- }
- }
-
- // Second: schedule rebinding if needed.
- final boolean shouldScheduleRebind = shouldScheduleRebind(userId, packageName, isPrimary);
-
- if (shouldScheduleRebind) {
- scheduleRebinding(userId, packageName, serviceConnector);
- }
- }
-
- private @Nullable CompanionDeviceServiceConnector getPrimaryServiceConnector(
- @UserIdInt int userId, @NonNull String packageName) {
- final List<CompanionDeviceServiceConnector> connectors;
- synchronized (mBoundCompanionApplications) {
- connectors = mBoundCompanionApplications.getValueForPackage(userId, packageName);
- }
- return connectors != null ? connectors.get(0) : null;
- }
-
- private boolean shouldScheduleRebind(int userId, String packageName, boolean isPrimary) {
- // Make sure do not schedule rebind for the case ServiceConnector still gets callback after
- // app is uninstalled.
- boolean stillAssociated = false;
- // Make sure to clean up the state for all the associations
- // that associate with this package.
- boolean shouldScheduleRebind = false;
- boolean shouldScheduleRebindForUuid = false;
- final List<ObservableUuid> uuids =
- mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
-
- for (AssociationInfo ai :
- mAssociationStore.getActiveAssociationsByPackage(userId, packageName)) {
- final int associationId = ai.getId();
- stillAssociated = true;
- if (ai.isSelfManaged()) {
- // Do not rebind if primary one is died for selfManaged application.
- if (isPrimary
- && mDevicePresenceMonitor.isDevicePresent(associationId)) {
- mDevicePresenceMonitor.onSelfManagedDeviceReporterBinderDied(associationId);
- shouldScheduleRebind = false;
- }
- // Do not rebind if both primary and secondary services are died for
- // selfManaged application.
- shouldScheduleRebind = isCompanionApplicationBound(userId, packageName);
- } else if (ai.isNotifyOnDeviceNearby()) {
- // Always rebind for non-selfManaged devices.
- shouldScheduleRebind = true;
- }
- }
-
- for (ObservableUuid uuid : uuids) {
- if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) {
- shouldScheduleRebindForUuid = true;
- break;
- }
- }
-
- return (stillAssociated && shouldScheduleRebind) || shouldScheduleRebindForUuid;
- }
-
- private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
- @Override
- public synchronized @NonNull Map<String, List<ComponentName>> forUser(
- @UserIdInt int userId) {
- return super.forUser(userId);
- }
-
- synchronized @NonNull List<ComponentName> forPackage(
- @UserIdInt int userId, @NonNull String packageName) {
- return forUser(userId).getOrDefault(packageName, Collections.emptyList());
- }
-
- synchronized void invalidate(@UserIdInt int userId) {
- remove(userId);
- }
-
- @Override
- protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) {
- return PackageUtils.getCompanionServicesForUser(mContext, userId);
- }
- }
-
- /**
- * Associates an Android package (defined by userId + packageName) with a value of type T.
- */
- private static class AndroidPackageMap<T> extends SparseArray<Map<String, T>> {
-
- void setValueForPackage(
- @UserIdInt int userId, @NonNull String packageName, @NonNull T value) {
- Map<String, T> forUser = get(userId);
- if (forUser == null) {
- forUser = /* Map<String, T> */ new HashMap();
- put(userId, forUser);
- }
-
- forUser.put(packageName, value);
- }
-
- boolean containsValueForPackage(@UserIdInt int userId, @NonNull String packageName) {
- final Map<String, ?> forUser = get(userId);
- return forUser != null && forUser.containsKey(packageName);
- }
-
- T getValueForPackage(@UserIdInt int userId, @NonNull String packageName) {
- final Map<String, T> forUser = get(userId);
- return forUser != null ? forUser.get(packageName) : null;
- }
-
- T removePackage(@UserIdInt int userId, @NonNull String packageName) {
- final Map<String, T> forUser = get(userId);
- if (forUser == null) return null;
- return forUser.remove(packageName);
- }
-
- void dump() {
- if (size() == 0) {
- Log.d(TAG, "<empty>");
- return;
- }
-
- for (int i = 0; i < size(); i++) {
- final int userId = keyAt(i);
- final Map<String, T> forUser = get(userId);
- if (forUser.isEmpty()) {
- Log.d(TAG, "u" + userId + ": <empty>");
- }
-
- for (Map.Entry<String, T> packageValue : forUser.entrySet()) {
- final String packageName = packageValue.getKey();
- final T value = packageValue.getValue();
- Log.d(TAG, "u" + userId + "\\" + packageName + " -> " + value);
- }
- }
- }
-
- private void dump(@NonNull PrintWriter out) {
- for (int i = 0; i < size(); i++) {
- final int userId = keyAt(i);
- final Map<String, T> forUser = get(userId);
- if (forUser.isEmpty()) {
- out.append(" u").append(String.valueOf(userId)).append(": <empty>\n");
- }
-
- for (Map.Entry<String, T> packageValue : forUser.entrySet()) {
- final String packageName = packageValue.getKey();
- final T value = packageValue.getValue();
- out.append(" u").append(String.valueOf(userId)).append("\\")
- .append(packageName).append(" -> ")
- .append(value.toString()).append('\n');
- }
- }
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 712162b2d3b5..edf9fd13d51f 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -20,15 +20,10 @@ 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_SELF_MANAGED;
import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
-import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
-import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
-import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
-import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
-import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
@@ -42,13 +37,10 @@ import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageCompanionDevice;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
-import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
-import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks;
import static java.util.Objects.requireNonNull;
-import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
import android.annotation.EnforcePermission;
@@ -64,7 +56,6 @@ import android.app.PendingIntent;
import android.bluetooth.BluetoothDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
-import android.companion.DeviceNotAssociatedException;
import android.companion.IAssociationRequestCallback;
import android.companion.ICompanionDeviceManager;
import android.companion.IOnAssociationsChangedListener;
@@ -79,7 +70,6 @@ import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
-import android.hardware.power.Mode;
import android.net.MacAddress;
import android.net.NetworkPolicyManager;
import android.os.Binder;
@@ -91,7 +81,6 @@ import android.os.PowerExemptionManager;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
@@ -118,7 +107,8 @@ import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback;
-import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.CompanionAppBinder;
+import com.android.server.companion.presence.DevicePresenceProcessor;
import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.presence.ObservableUuidStore;
import com.android.server.companion.transport.CompanionTransportManager;
@@ -131,10 +121,7 @@ import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
@SuppressLint("LongLogTag")
@@ -146,10 +133,6 @@ public class CompanionDeviceManagerService extends SystemService {
private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
- private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW =
- "debug.cdm.cdmservice.removal_time_window";
-
- private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
private static final int MAX_CN_LENGTH = 500;
private final ActivityTaskManagerInternal mAtmInternal;
@@ -165,8 +148,8 @@ public class CompanionDeviceManagerService extends SystemService {
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
private final SystemDataTransferProcessor mSystemDataTransferProcessor;
private final BackupRestoreProcessor mBackupRestoreProcessor;
- private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
- private final CompanionApplicationController mCompanionAppController;
+ private final DevicePresenceProcessor mDevicePresenceProcessor;
+ private final CompanionAppBinder mCompanionAppBinder;
private final CompanionTransportManager mTransportManager;
private final DisassociationProcessor mDisassociationProcessor;
private final CrossDeviceSyncController mCrossDeviceSyncController;
@@ -185,7 +168,7 @@ public class CompanionDeviceManagerService extends SystemService {
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
final AssociationDiskStore associationDiskStore = new AssociationDiskStore();
- mAssociationStore = new AssociationStore(userManager, associationDiskStore);
+ mAssociationStore = new AssociationStore(context, userManager, associationDiskStore);
mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
mObservableUuidStore = new ObservableUuidStore();
@@ -196,18 +179,17 @@ public class CompanionDeviceManagerService extends SystemService {
mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
mAssociationRequestsProcessor);
- mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(userManager,
- mAssociationStore, mObservableUuidStore, mDevicePresenceCallback);
+ mCompanionAppBinder = new CompanionAppBinder(context);
- mCompanionAppController = new CompanionApplicationController(
- context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor,
+ mDevicePresenceProcessor = new DevicePresenceProcessor(context,
+ mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore,
mPowerManagerInternal);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mDisassociationProcessor = new DisassociationProcessor(context, activityManager,
- mAssociationStore, mPackageManagerInternal, mDevicePresenceMonitor,
- mCompanionAppController, mSystemDataTransferRequestStore, mTransportManager);
+ mAssociationStore, mPackageManagerInternal, mDevicePresenceProcessor,
+ mCompanionAppBinder, mSystemDataTransferRequestStore, mTransportManager);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
@@ -242,7 +224,7 @@ public class CompanionDeviceManagerService extends SystemService {
// delays (even in case of the Main Thread). It may be fine overall, but would require
// updating the tests (adding a delay there).
mPackageMonitor.register(context, FgThread.get().getLooper(), UserHandle.ALL, true);
- mDevicePresenceMonitor.init(context);
+ mDevicePresenceProcessor.init(context);
} else if (phase == PHASE_BOOT_COMPLETED) {
// Run the Inactive Association Removal job service daily.
InactiveAssociationsRemovalService.schedule(getContext());
@@ -271,7 +253,7 @@ public class CompanionDeviceManagerService extends SystemService {
// Notify and bind the app after the phone is unlocked.
final int userId = user.getUserIdentifier();
final Set<BluetoothDevice> blueToothDevices =
- mDevicePresenceMonitor.getPendingConnectedDevices().get(userId);
+ mDevicePresenceProcessor.getPendingConnectedDevices().get(userId);
final List<ObservableUuid> observableUuids =
mObservableUuidStore.getObservableUuidsForUser(userId);
@@ -287,14 +269,14 @@ public class CompanionDeviceManagerService extends SystemService {
mAssociationStore.getActiveAssociationsByAddress(
bluetoothDevice.getAddress())) {
Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected");
- mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId());
+ mDevicePresenceProcessor.onBluetoothCompanionDeviceConnected(ai.getId());
}
for (ObservableUuid observableUuid : observableUuids) {
if (deviceUuids.contains(observableUuid.getUuid())) {
Slog.i(TAG, "onUserUnlocked, UUID( "
+ observableUuid.getUuid() + " ) is connected");
- mDevicePresenceMonitor.onDevicePresenceEventByUuid(
+ mDevicePresenceProcessor.onDevicePresenceEventByUuid(
observableUuid, EVENT_BT_CONNECTED);
}
}
@@ -302,181 +284,6 @@ public class CompanionDeviceManagerService extends SystemService {
}
}
- @NonNull
- AssociationInfo getAssociationWithCallerChecks(
- @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
- AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(
- userId, packageName, macAddress);
- association = sanitizeWithCallerChecks(getContext(), association);
- if (association != null) {
- return association;
- } else {
- throw new IllegalArgumentException("Association does not exist "
- + "or the caller does not have permissions to manage it "
- + "(ie. it belongs to a different package or a different user).");
- }
- }
-
- @NonNull
- AssociationInfo getAssociationWithCallerChecks(int associationId) {
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- association = sanitizeWithCallerChecks(getContext(), association);
- if (association != null) {
- return association;
- } else {
- throw new IllegalArgumentException("Association does not exist "
- + "or the caller does not have permissions to manage it "
- + "(ie. it belongs to a different package or a different user).");
- }
- }
-
- private void onDeviceAppearedInternal(int associationId) {
- if (DEBUG) Log.i(TAG, "onDevice_Appeared_Internal() id=" + associationId);
-
- final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- if (DEBUG) Log.d(TAG, " association=" + association);
-
- if (!association.shouldBindWhenPresent()) return;
-
- bindApplicationIfNeeded(association);
-
- mCompanionAppController.notifyCompanionApplicationDeviceAppeared(association);
- }
-
- private void onDeviceDisappearedInternal(int associationId) {
- if (DEBUG) Log.i(TAG, "onDevice_Disappeared_Internal() id=" + associationId);
-
- final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- if (DEBUG) Log.d(TAG, " association=" + association);
-
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
-
- if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
- if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
- return;
- }
-
- if (association.shouldBindWhenPresent()) {
- mCompanionAppController.notifyCompanionApplicationDeviceDisappeared(association);
- }
- }
-
- private void onDevicePresenceEventInternal(int associationId, int event) {
- Slog.i(TAG, "onDevicePresenceEventInternal() id=" + associationId + " event= " + event);
- final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- final String packageName = association.getPackageName();
- final int userId = association.getUserId();
- switch (event) {
- case EVENT_BLE_APPEARED:
- case EVENT_BT_CONNECTED:
- case EVENT_SELF_MANAGED_APPEARED:
- if (!association.shouldBindWhenPresent()) return;
-
- bindApplicationIfNeeded(association);
-
- mCompanionAppController.notifyCompanionDevicePresenceEvent(
- association, event);
- break;
- case EVENT_BLE_DISAPPEARED:
- case EVENT_BT_DISCONNECTED:
- case EVENT_SELF_MANAGED_DISAPPEARED:
- if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
- if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
- return;
- }
- if (association.shouldBindWhenPresent()) {
- mCompanionAppController.notifyCompanionDevicePresenceEvent(
- association, event);
- }
- // Check if there are other devices associated to the app that are present.
- if (shouldBindPackage(userId, packageName)) return;
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
- break;
- default:
- Slog.e(TAG, "Event: " + event + "is not supported");
- break;
- }
- }
-
- private void onDevicePresenceEventByUuidInternal(ObservableUuid uuid, int event) {
- Slog.i(TAG, "onDevicePresenceEventByUuidInternal() id=" + uuid.getUuid()
- + "for package=" + uuid.getPackageName() + " event=" + event);
- final String packageName = uuid.getPackageName();
- final int userId = uuid.getUserId();
-
- switch (event) {
- case EVENT_BT_CONNECTED:
- if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
- mCompanionAppController.bindCompanionApplication(
- userId, packageName, /*bindImportant*/ false);
-
- } else if (DEBUG) {
- Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound");
- }
-
- mCompanionAppController.notifyUuidDevicePresenceEvent(uuid, event);
-
- break;
- case EVENT_BT_DISCONNECTED:
- if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
- if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
- return;
- }
-
- mCompanionAppController.notifyUuidDevicePresenceEvent(uuid, event);
- // Check if there are other devices associated to the app or the UUID to be
- // observed are present.
- if (shouldBindPackage(userId, packageName)) return;
-
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
-
- break;
- default:
- Slog.e(TAG, "Event: " + event + "is not supported");
- break;
- }
- }
-
- private void bindApplicationIfNeeded(AssociationInfo association) {
- final String packageName = association.getPackageName();
- final int userId = association.getUserId();
- // Set bindImportant to true when the association is self-managed to avoid the target
- // service being killed.
- final boolean bindImportant = association.isSelfManaged();
- if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
- mCompanionAppController.bindCompanionApplication(
- userId, packageName, bindImportant);
- } else if (DEBUG) {
- Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound");
- }
- }
-
- /**
- * @return whether the package should be bound (i.e. at least one of the devices associated with
- * the package is currently present OR the UUID to be observed by this package is
- * currently present).
- */
- private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) {
- final List<AssociationInfo> packageAssociations =
- mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
- final List<ObservableUuid> observableUuids =
- mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
-
- for (AssociationInfo association : packageAssociations) {
- if (!association.shouldBindWhenPresent()) continue;
- if (mDevicePresenceMonitor.isDevicePresent(association.getId())) return true;
- }
-
- for (ObservableUuid uuid : observableUuids) {
- if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) {
- return true;
- }
- }
-
- return false;
- }
-
private void onPackageRemoveOrDataClearedInternal(
@UserIdInt int userId, @NonNull String packageName) {
if (DEBUG) {
@@ -502,7 +309,7 @@ public class CompanionDeviceManagerService extends SystemService {
mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
}
- mCompanionAppController.onPackagesChanged(userId);
+ mCompanionAppBinder.onPackagesChanged(userId);
}
private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
@@ -515,34 +322,15 @@ public class CompanionDeviceManagerService extends SystemService {
association.getPackageName());
}
- mCompanionAppController.onPackagesChanged(userId);
+ mCompanionAppBinder.onPackagesChanged(userId);
}
private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
mBackupRestoreProcessor.restorePendingAssociations(userId, packageName);
}
- // Revoke associations if the selfManaged companion device does not connect for 3 months.
void removeInactiveSelfManagedAssociations() {
- final long currentTime = System.currentTimeMillis();
- long removalWindow = SystemProperties.getLong(SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW, -1);
- if (removalWindow <= 0) {
- // 0 or negative values indicate that the sysprop was never set or should be ignored.
- removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT;
- }
-
- for (AssociationInfo association : mAssociationStore.getAssociations()) {
- if (!association.isSelfManaged()) continue;
-
- final boolean isInactive =
- currentTime - association.getLastTimeConnectedMs() >= removalWindow;
- if (!isInactive) continue;
-
- final int id = association.getId();
-
- Slog.i(TAG, "Removing inactive self-managed association id=" + id);
- mDisassociationProcessor.disassociate(id);
- }
+ mDisassociationProcessor.removeIdleSelfManagedAssociations();
}
public class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
@@ -679,24 +467,15 @@ public class CompanionDeviceManagerService extends SystemService {
@Deprecated
@Override
public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) {
- Log.i(TAG, "legacyDisassociate() pkg=u" + userId + "/" + packageName
- + ", macAddress=" + deviceMacAddress);
-
requireNonNull(deviceMacAddress);
requireNonNull(packageName);
- final AssociationInfo association =
- getAssociationWithCallerChecks(userId, packageName, deviceMacAddress);
- mDisassociationProcessor.disassociate(association.getId());
+ mDisassociationProcessor.disassociate(userId, packageName, deviceMacAddress);
}
@Override
public void disassociate(int associationId) {
- Slog.i(TAG, "disassociate() associationId=" + associationId);
-
- final AssociationInfo association =
- getAssociationWithCallerChecks(associationId);
- mDisassociationProcessor.disassociate(association.getId());
+ mDisassociationProcessor.disassociate(associationId);
}
@Override
@@ -758,21 +537,25 @@ public class CompanionDeviceManagerService extends SystemService {
}
@Override
+ @Deprecated
@EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
- public void registerDevicePresenceListenerService(String deviceAddress,
- String callingPackage, int userId) throws RemoteException {
- registerDevicePresenceListenerService_enforcePermission();
- // TODO: take the userId into account.
- registerDevicePresenceListenerActive(callingPackage, deviceAddress, true);
+ public void legacyStartObservingDevicePresence(String deviceAddress, String callingPackage,
+ int userId) throws RemoteException {
+ legacyStartObservingDevicePresence_enforcePermission();
+
+ mDevicePresenceProcessor.startObservingDevicePresence(userId, callingPackage,
+ deviceAddress);
}
@Override
+ @Deprecated
@EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
- public void unregisterDevicePresenceListenerService(String deviceAddress,
- String callingPackage, int userId) throws RemoteException {
- unregisterDevicePresenceListenerService_enforcePermission();
- // TODO: take the userId into account.
- registerDevicePresenceListenerActive(callingPackage, deviceAddress, false);
+ public void legacyStopObservingDevicePresence(String deviceAddress, String callingPackage,
+ int userId) throws RemoteException {
+ legacyStopObservingDevicePresence_enforcePermission();
+
+ mDevicePresenceProcessor.stopObservingDevicePresence(userId, callingPackage,
+ deviceAddress);
}
@Override
@@ -780,7 +563,8 @@ public class CompanionDeviceManagerService extends SystemService {
public void startObservingDevicePresence(ObservingDevicePresenceRequest request,
String packageName, int userId) {
startObservingDevicePresence_enforcePermission();
- registerDevicePresenceListener(request, packageName, userId, /* active */ true);
+
+ mDevicePresenceProcessor.startObservingDevicePresence(request, packageName, userId);
}
@Override
@@ -788,80 +572,8 @@ public class CompanionDeviceManagerService extends SystemService {
public void stopObservingDevicePresence(ObservingDevicePresenceRequest request,
String packageName, int userId) {
stopObservingDevicePresence_enforcePermission();
- registerDevicePresenceListener(request, packageName, userId, /* active */ false);
- }
-
- private void registerDevicePresenceListener(ObservingDevicePresenceRequest request,
- String packageName, int userId, boolean active) {
- enforceUsesCompanionDeviceFeature(getContext(), userId, packageName);
- enforceCallerIsSystemOr(userId, packageName);
-
- final int associationId = request.getAssociationId();
- final AssociationInfo associationInfo = mAssociationStore.getAssociationById(
- associationId);
- final ParcelUuid uuid = request.getUuid();
-
- if (uuid != null) {
- enforceCallerCanObservingDevicePresenceByUuid(getContext());
- if (active) {
- startObservingDevicePresenceByUuid(uuid, packageName, userId);
- } else {
- stopObservingDevicePresenceByUuid(uuid, packageName, userId);
- }
- } else if (associationInfo == null) {
- throw new IllegalArgumentException("App " + packageName
- + " is not associated with device " + request.getAssociationId()
- + " for user " + userId);
- } else {
- processDevicePresenceListener(
- associationInfo, userId, packageName, active);
- }
- }
-
- private void startObservingDevicePresenceByUuid(ParcelUuid uuid, String packageName,
- int userId) {
- final List<ObservableUuid> observableUuids =
- mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
-
- for (ObservableUuid observableUuid : observableUuids) {
- if (observableUuid.getUuid().equals(uuid)) {
- Slog.i(TAG, "The uuid: " + uuid + " for package:" + packageName
- + "has been already scheduled for observing");
- return;
- }
- }
-
- final ObservableUuid observableUuid = new ObservableUuid(userId, uuid,
- packageName, System.currentTimeMillis());
-
- mObservableUuidStore.writeObservableUuid(userId, observableUuid);
- }
-
- private void stopObservingDevicePresenceByUuid(ParcelUuid uuid, String packageName,
- int userId) {
- final List<ObservableUuid> uuidsTobeObserved =
- mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
- boolean isScheduledObserving = false;
-
- for (ObservableUuid observableUuid : uuidsTobeObserved) {
- if (observableUuid.getUuid().equals(uuid)) {
- isScheduledObserving = true;
- break;
- }
- }
- if (!isScheduledObserving) {
- Slog.i(TAG, "The uuid: " + uuid.toString() + " for package:" + packageName
- + "has NOT been scheduled for observing yet");
- return;
- }
-
- mObservableUuidStore.removeObservableUuid(userId, uuid, packageName);
- mDevicePresenceMonitor.removeCurrentConnectedUuidDevice(uuid);
-
- if (!shouldBindPackage(userId, packageName)) {
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
- }
+ mDevicePresenceProcessor.stopObservingDevicePresence(request, packageName, userId);
}
@Override
@@ -874,8 +586,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public boolean isPermissionTransferUserConsented(String packageName, int userId,
int associationId) {
- return mSystemDataTransferProcessor.isPermissionTransferUserConsented(packageName,
- userId, associationId);
+ return mSystemDataTransferProcessor.isPermissionTransferUserConsented(associationId);
}
@Override
@@ -891,8 +602,7 @@ public class CompanionDeviceManagerService extends SystemService {
ParcelFileDescriptor fd) {
attachSystemDataTransport_enforcePermission();
- getAssociationWithCallerChecks(associationId);
- mTransportManager.attachSystemDataTransport(packageName, userId, associationId, fd);
+ mTransportManager.attachSystemDataTransport(associationId, fd);
}
@Override
@@ -900,161 +610,61 @@ public class CompanionDeviceManagerService extends SystemService {
public void detachSystemDataTransport(String packageName, int userId, int associationId) {
detachSystemDataTransport_enforcePermission();
- getAssociationWithCallerChecks(associationId);
- mTransportManager.detachSystemDataTransport(packageName, userId, associationId);
+ mTransportManager.detachSystemDataTransport(associationId);
+ }
+
+ @Override
+ @EnforcePermission(MANAGE_COMPANION_DEVICES)
+ public void enableSecureTransport(boolean enabled) {
+ enableSecureTransport_enforcePermission();
+
+ mTransportManager.enableSecureTransport(enabled);
}
@Override
public void enableSystemDataSync(int associationId, int flags) {
- getAssociationWithCallerChecks(associationId);
mAssociationRequestsProcessor.enableSystemDataSync(associationId, flags);
}
@Override
public void disableSystemDataSync(int associationId, int flags) {
- getAssociationWithCallerChecks(associationId);
mAssociationRequestsProcessor.disableSystemDataSync(associationId, flags);
}
@Override
public void enablePermissionsSync(int associationId) {
- getAssociationWithCallerChecks(associationId);
mSystemDataTransferProcessor.enablePermissionsSync(associationId);
}
@Override
public void disablePermissionsSync(int associationId) {
- getAssociationWithCallerChecks(associationId);
mSystemDataTransferProcessor.disablePermissionsSync(associationId);
}
@Override
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
- // TODO: temporary fix, will remove soon
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- if (association == null) {
- return null;
- }
- getAssociationWithCallerChecks(associationId);
return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
}
@Override
- @EnforcePermission(MANAGE_COMPANION_DEVICES)
- public void enableSecureTransport(boolean enabled) {
- enableSecureTransport_enforcePermission();
- mTransportManager.enableSecureTransport(enabled);
- }
+ @EnforcePermission(REQUEST_COMPANION_SELF_MANAGED)
+ public void notifySelfManagedDeviceAppeared(int associationId) {
+ notifySelfManagedDeviceAppeared_enforcePermission();
- @Override
- public void notifyDeviceAppeared(int associationId) {
- if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId);
-
- AssociationInfo association = getAssociationWithCallerChecks(associationId);
- if (!association.isSelfManaged()) {
- throw new IllegalArgumentException("Association with ID " + associationId
- + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
- + " self-managed associations.");
- }
- // AssociationInfo class is immutable: create a new AssociationInfo object with updated
- // timestamp.
- association = (new AssociationInfo.Builder(association))
- .setLastTimeConnected(System.currentTimeMillis())
- .build();
- mAssociationStore.updateAssociation(association);
-
- mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId);
-
- 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);
- }
+ mDevicePresenceProcessor.notifySelfManagedDevicePresenceEvent(associationId, true);
}
@Override
- public void notifyDeviceDisappeared(int associationId) {
- if (DEBUG) Log.i(TAG, "notifyDevice_Disappeared() id=" + associationId);
-
- final AssociationInfo association = getAssociationWithCallerChecks(associationId);
- if (!association.isSelfManaged()) {
- throw new IllegalArgumentException("Association with ID " + associationId
- + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
- + " self-managed associations.");
- }
-
- mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId);
+ @EnforcePermission(REQUEST_COMPANION_SELF_MANAGED)
+ public void notifySelfManagedDeviceDisappeared(int associationId) {
+ notifySelfManagedDeviceDisappeared_enforcePermission();
- 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);
- }
+ mDevicePresenceProcessor.notifySelfManagedDevicePresenceEvent(associationId, false);
}
@Override
public boolean isCompanionApplicationBound(String packageName, int userId) {
- return mCompanionAppController.isCompanionApplicationBound(userId, packageName);
- }
-
- private void registerDevicePresenceListenerActive(String packageName, String deviceAddress,
- boolean active) throws RemoteException {
- if (DEBUG) {
- Log.i(TAG, "registerDevicePresenceListenerActive()"
- + " active=" + active
- + " deviceAddress=" + deviceAddress);
- }
- final int userId = getCallingUserId();
- enforceCallerIsSystemOr(userId, packageName);
-
- AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(
- userId, packageName, deviceAddress);
-
- if (association == null) {
- throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
- + " is not associated with device " + deviceAddress
- + " for user " + userId));
- }
-
- processDevicePresenceListener(association, userId, packageName, active);
- }
-
- private void processDevicePresenceListener(AssociationInfo association,
- int userId, String packageName, boolean active) {
- // If already at specified state, then no-op.
- if (active == association.isNotifyOnDeviceNearby()) {
- if (DEBUG) Log.d(TAG, "Device presence listener is already at desired state.");
- return;
- }
-
- // AssociationInfo class is immutable: create a new AssociationInfo object with updated
- // flag.
- association = (new AssociationInfo.Builder(association))
- .setNotifyOnDeviceNearby(active)
- .build();
- // Do not need to call {@link BleCompanionDeviceScanner#restartScan()} since it will
- // trigger {@link BleCompanionDeviceScanner#restartScan(int, AssociationInfo)} when
- // an application sets/unsets the mNotifyOnDeviceNearby flag.
- mAssociationStore.updateAssociation(association);
-
- int associationId = association.getId();
- // If device is already present, then trigger callback.
- if (active && mDevicePresenceMonitor.isDevicePresent(associationId)) {
- Slog.i(TAG, "Device is already present. Triggering callback.");
- if (mDevicePresenceMonitor.isBlePresent(associationId)
- || mDevicePresenceMonitor.isSimulatePresent(associationId)) {
- onDeviceAppearedInternal(associationId);
- onDevicePresenceEventInternal(associationId, EVENT_BLE_APPEARED);
- } else if (mDevicePresenceMonitor.isBtConnected(associationId)) {
- onDevicePresenceEventInternal(associationId, EVENT_BT_CONNECTED);
- }
- }
-
- // If last listener is unregistered, then unbind application.
- if (!active && !shouldBindPackage(userId, packageName)) {
- if (DEBUG) Log.d(TAG, "Last listener unregistered. Unbinding application.");
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
- }
+ return mCompanionAppBinder.isCompanionApplicationBound(userId, packageName);
}
@Override
@@ -1070,7 +680,8 @@ public class CompanionDeviceManagerService extends SystemService {
}
final MacAddress macAddressObj = MacAddress.fromString(macAddress);
- createNewAssociation(userId, packageName, macAddressObj, null, null, false);
+ mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddressObj,
+ null, null, null, false, null, null);
}
private void checkCanCallNotificationApi(String callingPackage, int userId) {
@@ -1099,9 +710,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void setAssociationTag(int associationId, String tag) {
- AssociationInfo association = getAssociationWithCallerChecks(associationId);
- association = (new AssociationInfo.Builder(association)).setTag(tag).build();
- mAssociationStore.updateAssociation(association);
+ mAssociationRequestsProcessor.setAssociationTag(associationId, tag);
}
@Override
@@ -1124,7 +733,7 @@ public class CompanionDeviceManagerService extends SystemService {
@NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
@NonNull String[] args) {
return new CompanionDeviceShellCommand(CompanionDeviceManagerService.this,
- mAssociationStore, mDevicePresenceMonitor, mTransportManager,
+ mAssociationStore, mDevicePresenceProcessor, mTransportManager,
mSystemDataTransferProcessor, mAssociationRequestsProcessor,
mBackupRestoreProcessor, mDisassociationProcessor)
.exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
@@ -1139,21 +748,13 @@ public class CompanionDeviceManagerService extends SystemService {
}
mAssociationStore.dump(out);
- mDevicePresenceMonitor.dump(out);
- mCompanionAppController.dump(out);
+ mDevicePresenceProcessor.dump(out);
+ mCompanionAppBinder.dump(out);
mTransportManager.dump(out);
mSystemDataTransferRequestStore.dump(out);
}
}
- void createNewAssociation(@UserIdInt int userId, @NonNull String packageName,
- @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
- @Nullable String deviceProfile, boolean isSelfManaged) {
- mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
- displayName, deviceProfile, /* associatedDevice */ null, isSelfManaged,
- /* callback */ null, /* resultReceiver */ null);
- }
-
/**
* Update special access for the association's package
*/
@@ -1169,8 +770,6 @@ public class CompanionDeviceManagerService extends SystemService {
return;
}
- Slog.i(TAG, "Updating special access for package=[" + packageInfo.packageName + "]...");
-
if (containsEither(packageInfo.requestedPermissions,
android.Manifest.permission.RUN_IN_BACKGROUND,
android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
@@ -1280,29 +879,6 @@ public class CompanionDeviceManagerService extends SystemService {
}
};
- private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback =
- new CompanionDevicePresenceMonitor.Callback() {
- @Override
- public void onDeviceAppeared(int associationId) {
- onDeviceAppearedInternal(associationId);
- }
-
- @Override
- public void onDeviceDisappeared(int associationId) {
- onDeviceDisappearedInternal(associationId);
- }
-
- @Override
- public void onDevicePresenceEvent(int associationId, int event) {
- onDevicePresenceEventInternal(associationId, event);
- }
-
- @Override
- public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) {
- onDevicePresenceEventByUuidInternal(uuid, event);
- }
- };
-
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override
public void onPackageRemoved(String packageName, int uid) {
@@ -1315,7 +891,7 @@ public class CompanionDeviceManagerService extends SystemService {
}
@Override
- public void onPackageModified(String packageName) {
+ public void onPackageModified(@NonNull String packageName) {
onPackageModifiedInternal(getChangingUserId(), packageName);
}
@@ -1325,25 +901,15 @@ public class CompanionDeviceManagerService extends SystemService {
}
};
- private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) {
- final Map<String, Set<Integer>> copy = new HashMap<>();
-
- for (Map.Entry<String, Set<Integer>> entry : orig.entrySet()) {
- final Set<Integer> valueCopy = new HashSet<>(entry.getValue());
- copy.put(entry.getKey(), Collections.unmodifiableSet(valueCopy));
- }
-
- return Collections.unmodifiableMap(copy);
- }
-
private static <T> boolean containsEither(T[] array, T a, T b) {
return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
}
private class LocalService implements CompanionDeviceManagerServiceInternal {
+
@Override
public void removeInactiveSelfManagedAssociations() {
- CompanionDeviceManagerService.this.removeInactiveSelfManagedAssociations();
+ mDisassociationProcessor.removeIdleSelfManagedAssociations();
}
@Override
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
index cdf832f8c788..9d1250d361c4 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
@@ -27,8 +27,9 @@ import java.util.Collection;
* Companion Device Manager Local System Service Interface.
*/
public interface CompanionDeviceManagerServiceInternal {
+
/**
- * @see CompanionDeviceManagerService#removeInactiveSelfManagedAssociations
+ * Remove idle self-managed associations.
*/
void removeInactiveSelfManagedAssociations();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index a7a73cb6bddb..a78938400a1e 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -18,8 +18,6 @@ package com.android.server.companion;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
-import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks;
-
import android.companion.AssociationInfo;
import android.companion.ContextSyncMessage;
import android.companion.Flags;
@@ -38,7 +36,7 @@ import com.android.server.companion.association.DisassociationProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
-import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.DevicePresenceProcessor;
import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.transport.CompanionTransportManager;
@@ -51,7 +49,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
private final CompanionDeviceManagerService mService;
private final DisassociationProcessor mDisassociationProcessor;
private final AssociationStore mAssociationStore;
- private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+ private final DevicePresenceProcessor mDevicePresenceProcessor;
private final CompanionTransportManager mTransportManager;
private final SystemDataTransferProcessor mSystemDataTransferProcessor;
@@ -60,7 +58,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
AssociationStore associationStore,
- CompanionDevicePresenceMonitor devicePresenceMonitor,
+ DevicePresenceProcessor devicePresenceProcessor,
CompanionTransportManager transportManager,
SystemDataTransferProcessor systemDataTransferProcessor,
AssociationRequestsProcessor associationRequestsProcessor,
@@ -68,7 +66,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
DisassociationProcessor disassociationProcessor) {
mService = service;
mAssociationStore = associationStore;
- mDevicePresenceMonitor = devicePresenceMonitor;
+ mDevicePresenceProcessor = devicePresenceProcessor;
mTransportManager = transportManager;
mSystemDataTransferProcessor = systemDataTransferProcessor;
mAssociationRequestsProcessor = associationRequestsProcessor;
@@ -85,7 +83,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
if ("simulate-device-event".equals(cmd) && Flags.devicePresence()) {
associationId = getNextIntArgRequired();
int event = getNextIntArgRequired();
- mDevicePresenceMonitor.simulateDeviceEvent(associationId, event);
+ mDevicePresenceProcessor.simulateDeviceEvent(associationId, event);
return 0;
}
@@ -97,7 +95,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
ObservableUuid observableUuid = new ObservableUuid(
userId, ParcelUuid.fromString(uuid), packageName,
System.currentTimeMillis());
- mDevicePresenceMonitor.simulateDeviceEventByUuid(observableUuid, event);
+ mDevicePresenceProcessor.simulateDeviceEventByUuid(observableUuid, event);
return 0;
}
@@ -124,8 +122,9 @@ class CompanionDeviceShellCommand extends ShellCommand {
String address = getNextArgRequired();
String deviceProfile = getNextArg();
final MacAddress macAddress = MacAddress.fromString(address);
- mService.createNewAssociation(userId, packageName, macAddress,
- /* displayName= */ deviceProfile, deviceProfile, false);
+ mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
+ deviceProfile, deviceProfile, /* associatedDevice */ null, false,
+ /* callback */ null, /* resultReceiver */ null);
}
break;
@@ -134,8 +133,13 @@ class CompanionDeviceShellCommand extends ShellCommand {
final String packageName = getNextArgRequired();
final String address = getNextArgRequired();
final AssociationInfo association =
- mService.getAssociationWithCallerChecks(userId, packageName, address);
- mDisassociationProcessor.disassociate(association.getId());
+ mAssociationStore.getFirstAssociationByAddress(userId, packageName,
+ address);
+ if (association == null) {
+ out.println("Association doesn't exist.");
+ } else {
+ mDisassociationProcessor.disassociate(association.getId());
+ }
}
break;
@@ -144,9 +148,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
final List<AssociationInfo> userAssociations =
mAssociationStore.getAssociationsByUser(userId);
for (AssociationInfo association : userAssociations) {
- if (sanitizeWithCallerChecks(mService.getContext(), association) != null) {
- mDisassociationProcessor.disassociate(association.getId());
- }
+ mDisassociationProcessor.disassociate(association.getId());
}
}
break;
@@ -157,12 +159,12 @@ class CompanionDeviceShellCommand extends ShellCommand {
case "simulate-device-appeared":
associationId = getNextIntArgRequired();
- mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 0);
+ mDevicePresenceProcessor.simulateDeviceEvent(associationId, /* event */ 0);
break;
case "simulate-device-disappeared":
associationId = getNextIntArgRequired();
- mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 1);
+ mDevicePresenceProcessor.simulateDeviceEvent(associationId, /* event */ 1);
break;
case "get-backup-payload": {
@@ -410,10 +412,9 @@ class CompanionDeviceShellCommand extends ShellCommand {
pw.println(" Remove an existing Association.");
pw.println(" disassociate-all USER_ID");
pw.println(" Remove all Associations for a user.");
- pw.println(" clear-association-memory-cache");
+ pw.println(" refresh-cache");
pw.println(" Clear the in-memory association cache and reload all association ");
- pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
- pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+ pw.println(" information from disk. USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
pw.println(" simulate-device-appeared ASSOCIATION_ID");
pw.println(" Make CDM act as if the given companion device has appeared.");
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index a02d9f912bcd..a18776e67200 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -145,7 +145,8 @@ public class AssociationRequestsProcessor {
/**
* Handle incoming {@link AssociationRequest}s, sent via
- * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, IAssociationRequestCallback, String, int)}
+ * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest,
+ * IAssociationRequestCallback, String, int)}
*/
public void processNewAssociationRequest(@NonNull AssociationRequest request,
@NonNull String packageName, @UserIdInt int userId,
@@ -212,7 +213,8 @@ public class AssociationRequestsProcessor {
// 2b.4. Send the PendingIntent back to the app.
try {
callback.onAssociationPending(pendingIntent);
- } catch (RemoteException ignore) { }
+ } catch (RemoteException ignore) {
+ }
}
/**
@@ -252,7 +254,8 @@ public class AssociationRequestsProcessor {
// forward it back to the application via the callback.
try {
callback.onFailure(e.getMessage());
- } catch (RemoteException ignore) { }
+ } catch (RemoteException ignore) {
+ }
return;
}
@@ -322,7 +325,8 @@ public class AssociationRequestsProcessor {
* Enable system data sync.
*/
public void enableSystemDataSync(int associationId, int flags) {
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
.setSystemDataSyncFlags(association.getSystemDataSyncFlags() | flags).build();
mAssociationStore.updateAssociation(updated);
@@ -332,12 +336,23 @@ public class AssociationRequestsProcessor {
* Disable system data sync.
*/
public void disableSystemDataSync(int associationId, int flags) {
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
.setSystemDataSyncFlags(association.getSystemDataSyncFlags() & (~flags)).build();
mAssociationStore.updateAssociation(updated);
}
+ /**
+ * Set association tag.
+ */
+ public void setAssociationTag(int associationId, String tag) {
+ AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
+ association = (new AssociationInfo.Builder(association)).setTag(tag).build();
+ mAssociationStore.updateAssociation(association);
+ }
+
private void sendCallbackAndFinish(@Nullable AssociationInfo association,
@Nullable IAssociationRequestCallback callback,
@Nullable ResultReceiver resultReceiver) {
@@ -396,14 +411,14 @@ public class AssociationRequestsProcessor {
// If the application already has a pending association request, that PendingIntent
// will be cancelled except application wants to cancel the request by the system.
return Binder.withCleanCallingIdentity(() ->
- PendingIntent.getActivityAsUser(
- mContext, /*requestCode */ packageUid, intent,
- FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
- ActivityOptions.makeBasic()
- .setPendingIntentCreatorBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
- .toBundle(),
- UserHandle.CURRENT)
+ PendingIntent.getActivityAsUser(
+ mContext, /*requestCode */ packageUid, intent,
+ FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
+ ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .toBundle(),
+ UserHandle.CURRENT)
);
}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index edebb55233d0..ae2b70852a35 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -18,6 +18,7 @@ package com.android.server.companion.association;
import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation;
+import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageAssociationsForPackage;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -26,6 +27,7 @@ import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.companion.IOnAssociationsChangedListener;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.net.MacAddress;
import android.os.Binder;
@@ -57,21 +59,22 @@ import java.util.concurrent.Executors;
@SuppressLint("LongLogTag")
public class AssociationStore {
- @IntDef(prefix = { "CHANGE_TYPE_" }, value = {
+ @IntDef(prefix = {"CHANGE_TYPE_"}, value = {
CHANGE_TYPE_ADDED,
CHANGE_TYPE_REMOVED,
CHANGE_TYPE_UPDATED_ADDRESS_CHANGED,
CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface ChangeType {}
+ public @interface ChangeType {
+ }
public static final int CHANGE_TYPE_ADDED = 0;
public static final int CHANGE_TYPE_REMOVED = 1;
public static final int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2;
public static final int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3;
- /** Listener for any changes to associations. */
+ /** Listener for any changes to associations. */
public interface OnChangeListener {
/**
* Called when there are association changes.
@@ -100,25 +103,30 @@ public class AssociationStore {
/**
* Called when an association is added.
*/
- default void onAssociationAdded(AssociationInfo association) {}
+ default void onAssociationAdded(AssociationInfo association) {
+ }
/**
* Called when an association is removed.
*/
- default void onAssociationRemoved(AssociationInfo association) {}
+ default void onAssociationRemoved(AssociationInfo association) {
+ }
/**
* Called when an association is updated.
*/
- default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {}
+ default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {
+ }
}
private static final String TAG = "CDM_AssociationStore";
- private final Object mLock = new Object();
-
+ private final Context mContext;
+ private final UserManager mUserManager;
+ private final AssociationDiskStore mDiskStore;
private final ExecutorService mExecutor;
+ private final Object mLock = new Object();
@GuardedBy("mLock")
private boolean mPersisted = false;
@GuardedBy("mLock")
@@ -132,10 +140,9 @@ public class AssociationStore {
private final RemoteCallbackList<IOnAssociationsChangedListener> mRemoteListeners =
new RemoteCallbackList<>();
- private final UserManager mUserManager;
- private final AssociationDiskStore mDiskStore;
-
- public AssociationStore(UserManager userManager, AssociationDiskStore diskStore) {
+ public AssociationStore(Context context, UserManager userManager,
+ AssociationDiskStore diskStore) {
+ mContext = context;
mUserManager = userManager;
mDiskStore = diskStore;
mExecutor = Executors.newSingleThreadExecutor();
@@ -202,7 +209,7 @@ public class AssociationStore {
synchronized (mLock) {
if (mIdToAssociationMap.containsKey(id)) {
- Slog.e(TAG, "Association with id=[" + id + "] already exists.");
+ Slog.e(TAG, "Association id=[" + id + "] already exists.");
return;
}
@@ -449,6 +456,26 @@ public class AssociationStore {
}
/**
+ * Get association by id with caller checks.
+ */
+ @NonNull
+ public AssociationInfo getAssociationWithCallerChecks(int associationId) {
+ AssociationInfo association = getAssociationById(associationId);
+ if (association == null) {
+ throw new IllegalArgumentException(
+ "getAssociationWithCallerChecks() Association id=[" + associationId
+ + "] doesn't exist.");
+ }
+ if (checkCallerCanManageAssociationsForPackage(mContext, association.getUserId(),
+ association.getPackageName())) {
+ return association;
+ }
+
+ throw new IllegalArgumentException(
+ "The caller can't interact with the association id=[" + associationId + "].");
+ }
+
+ /**
* Register a local listener for association changes.
*/
public void registerLocalListener(@NonNull OnChangeListener listener) {
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
index ec8977918c56..acf683d387a3 100644
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -22,6 +22,8 @@ import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PRO
import static com.android.internal.util.CollectionUtils.any;
import static com.android.server.companion.utils.RolesUtils.removeRoleHolderForAssociation;
+import static java.util.concurrent.TimeUnit.DAYS;
+
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
@@ -30,21 +32,27 @@ import android.companion.AssociationInfo;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.Slog;
-import com.android.server.companion.CompanionApplicationController;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
-import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.CompanionAppBinder;
+import com.android.server.companion.presence.DevicePresenceProcessor;
import com.android.server.companion.transport.CompanionTransportManager;
/**
- * A class response for Association removal.
+ * This class responsible for disassociation.
*/
@SuppressLint("LongLogTag")
public class DisassociationProcessor {
private static final String TAG = "CDM_DisassociationProcessor";
+
+ private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW =
+ "debug.cdm.cdmservice.removal_time_window";
+ private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
+
@NonNull
private final Context mContext;
@NonNull
@@ -52,11 +60,11 @@ public class DisassociationProcessor {
@NonNull
private final PackageManagerInternal mPackageManagerInternal;
@NonNull
- private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+ private final DevicePresenceProcessor mDevicePresenceMonitor;
@NonNull
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
@NonNull
- private final CompanionApplicationController mCompanionAppController;
+ private final CompanionAppBinder mCompanionAppController;
@NonNull
private final CompanionTransportManager mTransportManager;
private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
@@ -66,8 +74,8 @@ public class DisassociationProcessor {
@NonNull ActivityManager activityManager,
@NonNull AssociationStore associationStore,
@NonNull PackageManagerInternal packageManager,
- @NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
- @NonNull CompanionApplicationController applicationController,
+ @NonNull DevicePresenceProcessor devicePresenceMonitor,
+ @NonNull CompanionAppBinder applicationController,
@NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
@NonNull CompanionTransportManager companionTransportManager) {
mContext = context;
@@ -89,11 +97,7 @@ public class DisassociationProcessor {
public void disassociate(int id) {
Slog.i(TAG, "Disassociating id=[" + id + "]...");
- final AssociationInfo association = mAssociationStore.getAssociationById(id);
- if (association == null) {
- Slog.e(TAG, "Can't disassociate id=[" + id + "]. It doesn't exist.");
- return;
- }
+ final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id);
final int userId = association.getUserId();
final String packageName = association.getPackageName();
@@ -118,12 +122,12 @@ public class DisassociationProcessor {
return;
}
+ // Detach transport if exists
+ mTransportManager.detachSystemDataTransport(id);
+
// Association cleanup.
- mAssociationStore.removeAssociation(association.getId());
mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id);
-
- // Detach transport if exists
- mTransportManager.detachSystemDataTransport(packageName, userId, id);
+ mAssociationStore.removeAssociation(association.getId());
// If role is not in use by other associations, revoke the role.
// Do not need to remove the system role since it was pre-granted by the system.
@@ -143,10 +147,28 @@ public class DisassociationProcessor {
it -> it.isNotifyOnDeviceNearby()
&& mDevicePresenceMonitor.isDevicePresent(it.getId()));
if (!shouldStayBound) {
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
+ mCompanionAppController.unbindCompanionApp(userId, packageName);
}
}
+ /**
+ * @deprecated Use {@link #disassociate(int)} instead.
+ */
+ @Deprecated
+ public void disassociate(int userId, String packageName, String macAddress) {
+ AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId,
+ packageName, macAddress);
+
+ if (association == null) {
+ throw new IllegalArgumentException(
+ "Association for mac address=[" + macAddress + "] doesn't exist");
+ }
+
+ mAssociationStore.getAssociationWithCallerChecks(association.getId());
+
+ disassociate(association.getId());
+ }
+
@SuppressLint("MissingPermission")
private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
return Binder.withCleanCallingIdentity(() -> {
@@ -163,7 +185,7 @@ public class DisassociationProcessor {
() -> mActivityManager.addOnUidImportanceListener(
mOnPackageVisibilityChangeListener,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE));
- } catch (IllegalArgumentException e) {
+ } catch (IllegalArgumentException e) {
Slog.e(TAG, "Failed to start listening to uid importance changes.");
}
}
@@ -179,6 +201,34 @@ public class DisassociationProcessor {
}
/**
+ * Remove idle self-managed associations.
+ */
+ public void removeIdleSelfManagedAssociations() {
+ Slog.i(TAG, "Removing idle self-managed associations.");
+
+ final long currentTime = System.currentTimeMillis();
+ long removalWindow = SystemProperties.getLong(SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW, -1);
+ if (removalWindow <= 0) {
+ // 0 or negative values indicate that the sysprop was never set or should be ignored.
+ removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT;
+ }
+
+ for (AssociationInfo association : mAssociationStore.getAssociations()) {
+ if (!association.isSelfManaged()) continue;
+
+ final boolean isInactive =
+ currentTime - association.getLastTimeConnectedMs() >= removalWindow;
+ if (!isInactive) continue;
+
+ final int id = association.getId();
+
+ Slog.i(TAG, "Removing inactive self-managed association=[" + association.toShortString()
+ + "].");
+ disassociate(id);
+ }
+ }
+
+ /**
* An OnUidImportanceListener class which watches the importance of the packages.
* In this class, we ONLY interested in the importance of the running process is greater than
* {@link ActivityManager.RunningAppProcessInfo#IMPORTANCE_VISIBLE}.
diff --git a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
index f28731548dcc..b509e71626ea 100644
--- a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
+++ b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
@@ -30,7 +30,7 @@ import com.android.server.LocalServices;
import com.android.server.companion.CompanionDeviceManagerServiceInternal;
/**
- * A Job Service responsible for clean up idle self-managed associations.
+ * A Job Service responsible for clean up self-managed associations if it's idle for 90 days.
*
* The job will be executed only if the device is charging and in idle mode due to the application
* will be killed if association/role are revoked. See {@link DisassociationProcessor}
@@ -45,10 +45,10 @@ public class InactiveAssociationsRemovalService extends JobService {
@Override
public boolean onStartJob(final JobParameters params) {
Slog.i(TAG, "Execute the Association Removal job");
- // Special policy for selfManaged that need to revoke associations if the device
- // does not connect for 90 days.
+
LocalServices.getService(CompanionDeviceManagerServiceInternal.class)
.removeInactiveSelfManagedAssociations();
+
jobFinished(params, false);
return true;
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index c5ca0bf7e9c5..9069689ee5eb 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -31,7 +31,6 @@ import android.annotation.UserIdInt;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
-import android.companion.DeviceNotAssociatedException;
import android.companion.IOnMessageReceivedListener;
import android.companion.ISystemDataTransferCallback;
import android.companion.datatransfer.PermissionSyncRequest;
@@ -56,7 +55,6 @@ import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.transport.CompanionTransportManager;
import com.android.server.companion.utils.PackageUtils;
-import com.android.server.companion.utils.PermissionsUtils;
import java.util.List;
import java.util.concurrent.ExecutorService;
@@ -120,28 +118,10 @@ public class SystemDataTransferProcessor {
}
/**
- * Resolve the requested association, throwing if the caller doesn't have
- * adequate permissions.
- */
- @NonNull
- private AssociationInfo resolveAssociation(String packageName, int userId,
- int associationId) {
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- association = PermissionsUtils.sanitizeWithCallerChecks(mContext, association);
- if (association == null) {
- throw new DeviceNotAssociatedException("Association "
- + associationId + " is not associated with the app " + packageName
- + " for user " + userId);
- }
- return association;
- }
-
- /**
* Return whether the user has consented to the permission transfer for the association.
*/
- public boolean isPermissionTransferUserConsented(String packageName, @UserIdInt int userId,
- int associationId) {
- resolveAssociation(packageName, userId, associationId);
+ public boolean isPermissionTransferUserConsented(int associationId) {
+ mAssociationStore.getAssociationWithCallerChecks(associationId);
PermissionSyncRequest request = getPermissionSyncRequest(associationId);
if (request == null) {
@@ -167,7 +147,8 @@ public class SystemDataTransferProcessor {
return null;
}
- final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
+ final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId
+ "] associationId [" + associationId + "]");
@@ -207,7 +188,7 @@ public class SystemDataTransferProcessor {
Slog.i(LOG_TAG, "Start system data transfer for package [" + packageName
+ "] userId [" + userId + "] associationId [" + associationId + "]");
- final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
+ mAssociationStore.getAssociationWithCallerChecks(associationId);
// Check if the request has been consented by the user.
PermissionSyncRequest request = getPermissionSyncRequest(associationId);
@@ -239,24 +220,20 @@ public class SystemDataTransferProcessor {
* Enable perm sync for the association
*/
public void enablePermissionsSync(int associationId) {
- Binder.withCleanCallingIdentity(() -> {
- int userId = mAssociationStore.getAssociationById(associationId).getUserId();
- PermissionSyncRequest request = new PermissionSyncRequest(associationId);
- request.setUserConsented(true);
- mSystemDataTransferRequestStore.writeRequest(userId, request);
- });
+ int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId();
+ PermissionSyncRequest request = new PermissionSyncRequest(associationId);
+ request.setUserConsented(true);
+ mSystemDataTransferRequestStore.writeRequest(userId, request);
}
/**
* Disable perm sync for the association
*/
public void disablePermissionsSync(int associationId) {
- Binder.withCleanCallingIdentity(() -> {
- int userId = mAssociationStore.getAssociationById(associationId).getUserId();
- PermissionSyncRequest request = new PermissionSyncRequest(associationId);
- request.setUserConsented(false);
- mSystemDataTransferRequestStore.writeRequest(userId, request);
- });
+ int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId();
+ PermissionSyncRequest request = new PermissionSyncRequest(associationId);
+ request.setUserConsented(false);
+ mSystemDataTransferRequestStore.writeRequest(userId, request);
}
/**
@@ -264,18 +241,17 @@ public class SystemDataTransferProcessor {
*/
@Nullable
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
- return Binder.withCleanCallingIdentity(() -> {
- int userId = mAssociationStore.getAssociationById(associationId).getUserId();
- List<SystemDataTransferRequest> requests =
- mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
- associationId);
- for (SystemDataTransferRequest request : requests) {
- if (request instanceof PermissionSyncRequest) {
- return (PermissionSyncRequest) request;
- }
+ int userId = mAssociationStore.getAssociationWithCallerChecks(associationId)
+ .getUserId();
+ List<SystemDataTransferRequest> requests =
+ mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
+ associationId);
+ for (SystemDataTransferRequest request : requests) {
+ if (request instanceof PermissionSyncRequest) {
+ return (PermissionSyncRequest) request;
}
- return null;
- });
+ }
+ return null;
}
/**
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index c89ce11c169d..9c37881499bd 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -33,7 +33,7 @@ import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_FIRST_MATCH;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER;
-import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
+import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG;
import static com.android.server.companion.utils.Utils.btDeviceToString;
import static java.util.Objects.requireNonNull;
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index cb363a7c9d7f..2d345c48a8eb 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -19,7 +19,7 @@ package com.android.server.companion.presence;
import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
-import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
+import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG;
import static com.android.server.companion.utils.Utils.btDeviceToString;
import android.annotation.NonNull;
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java
new file mode 100644
index 000000000000..b6348ea9594d
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java
@@ -0,0 +1,321 @@
+/*
+ * 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.server.companion.presence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.companion.AssociationInfo;
+import android.companion.CompanionDeviceService;
+import android.companion.DevicePresenceEvent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.PerUser;
+import com.android.server.companion.CompanionDeviceManagerService;
+import com.android.server.companion.utils.PackageUtils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages communication with companion applications via
+ * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to
+ * the services, maintaining the connection (the binding), and invoking callback methods such as
+ * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)},
+ * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} in the
+ * application process.
+ *
+ * <p>
+ * The following is the list of the APIs provided by {@link CompanionAppBinder} (to be
+ * utilized by {@link CompanionDeviceManagerService}):
+ * <ul>
+ * <li> {@link #bindCompanionApp(int, String, boolean, CompanionServiceConnector.Listener)}
+ * <li> {@link #unbindCompanionApp(int, String)}
+ * <li> {@link #isCompanionApplicationBound(int, String)}
+ * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
+ * </ul>
+ *
+ * @see CompanionDeviceService
+ * @see android.companion.ICompanionDeviceService
+ * @see CompanionServiceConnector
+ */
+@SuppressLint("LongLogTag")
+public class CompanionAppBinder {
+ private static final String TAG = "CDM_CompanionAppBinder";
+
+ private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec
+
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final CompanionServicesRegister mCompanionServicesRegister;
+
+ @NonNull
+ @GuardedBy("mBoundCompanionApplications")
+ private final Map<Pair<Integer, String>, List<CompanionServiceConnector>>
+ mBoundCompanionApplications;
+ @NonNull
+ @GuardedBy("mScheduledForRebindingCompanionApplications")
+ private final Set<Pair<Integer, String>> mScheduledForRebindingCompanionApplications;
+
+ public CompanionAppBinder(@NonNull Context context) {
+ mContext = context;
+ mCompanionServicesRegister = new CompanionServicesRegister();
+ mBoundCompanionApplications = new HashMap<>();
+ mScheduledForRebindingCompanionApplications = new HashSet<>();
+ }
+
+ /**
+ * On package changed.
+ */
+ public void onPackagesChanged(@UserIdInt int userId) {
+ mCompanionServicesRegister.invalidate(userId);
+ }
+
+ /**
+ * CDM binds to the companion app.
+ */
+ public void bindCompanionApp(@UserIdInt int userId, @NonNull String packageName,
+ boolean isSelfManaged, CompanionServiceConnector.Listener listener) {
+ Slog.i(TAG, "Binding user=[" + userId + "], package=[" + packageName + "], isSelfManaged=["
+ + isSelfManaged + "]...");
+
+ final List<ComponentName> companionServices =
+ mCompanionServicesRegister.forPackage(userId, packageName);
+ if (companionServices.isEmpty()) {
+ Slog.e(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": "
+ + "eligible CompanionDeviceService not found.\n"
+ + "A CompanionDeviceService should declare an intent-filter for "
+ + "\"android.companion.CompanionDeviceService\" action and require "
+ + "\"android.permission.BIND_COMPANION_DEVICE_SERVICE\" permission.");
+ return;
+ }
+
+ final List<CompanionServiceConnector> serviceConnectors = new ArrayList<>();
+ synchronized (mBoundCompanionApplications) {
+ if (mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName))) {
+ Slog.w(TAG, "The package is ALREADY bound.");
+ return;
+ }
+
+ for (int i = 0; i < companionServices.size(); i++) {
+ boolean isPrimary = i == 0;
+ serviceConnectors.add(CompanionServiceConnector.newInstance(mContext, userId,
+ companionServices.get(i), isSelfManaged, isPrimary));
+ }
+
+ mBoundCompanionApplications.put(new Pair<>(userId, packageName), serviceConnectors);
+ }
+
+ // Set listeners for both Primary and Secondary connectors.
+ for (CompanionServiceConnector serviceConnector : serviceConnectors) {
+ serviceConnector.setListener(listener);
+ }
+
+ // Now "bind" all the connectors: the primary one and the rest of them.
+ for (CompanionServiceConnector serviceConnector : serviceConnectors) {
+ serviceConnector.connect();
+ }
+ }
+
+ /**
+ * CDM unbinds the companion app.
+ */
+ public void unbindCompanionApp(@UserIdInt int userId, @NonNull String packageName) {
+ Slog.i(TAG, "Unbinding user=[" + userId + "], package=[" + packageName + "]...");
+
+ final List<CompanionServiceConnector> serviceConnectors;
+
+ synchronized (mBoundCompanionApplications) {
+ serviceConnectors = mBoundCompanionApplications.remove(new Pair<>(userId, packageName));
+ }
+
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName));
+ }
+
+ if (serviceConnectors == null) {
+ Slog.e(TAG, "The package is not bound.");
+ return;
+ }
+
+ for (CompanionServiceConnector serviceConnector : serviceConnectors) {
+ serviceConnector.postUnbind();
+ }
+ }
+
+ /**
+ * @return whether the companion application is bound now.
+ */
+ public boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mBoundCompanionApplications) {
+ return mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName));
+ }
+ }
+
+ /**
+ * Remove bound apps for package.
+ */
+ public void removePackage(int userId, String packageName) {
+ synchronized (mBoundCompanionApplications) {
+ mBoundCompanionApplications.remove(new Pair<>(userId, packageName));
+ }
+
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName));
+ }
+ }
+
+ /**
+ * Schedule rebinding for the package.
+ */
+ public void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName,
+ CompanionServiceConnector serviceConnector) {
+ Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName);
+
+ if (isRebindingCompanionApplicationScheduled(userId, packageName)) {
+ Slog.i(TAG, "CompanionApplication rebinding has been scheduled, skipping "
+ + serviceConnector.getComponentName());
+ return;
+ }
+
+ if (serviceConnector.isPrimary()) {
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ mScheduledForRebindingCompanionApplications.add(new Pair<>(userId, packageName));
+ }
+ }
+
+ // Rebinding in 10 seconds.
+ Handler.getMain().postDelayed(() ->
+ onRebindingCompanionApplicationTimeout(userId, packageName,
+ serviceConnector),
+ REBIND_TIMEOUT);
+ }
+
+ private boolean isRebindingCompanionApplicationScheduled(
+ @UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ return mScheduledForRebindingCompanionApplications.contains(
+ new Pair<>(userId, packageName));
+ }
+ }
+
+ private void onRebindingCompanionApplicationTimeout(
+ @UserIdInt int userId, @NonNull String packageName,
+ @NonNull CompanionServiceConnector serviceConnector) {
+ // Re-mark the application is bound.
+ if (serviceConnector.isPrimary()) {
+ synchronized (mBoundCompanionApplications) {
+ if (!mBoundCompanionApplications.containsKey(new Pair<>(userId, packageName))) {
+ List<CompanionServiceConnector> serviceConnectors =
+ Collections.singletonList(serviceConnector);
+ mBoundCompanionApplications.put(new Pair<>(userId, packageName),
+ serviceConnectors);
+ }
+ }
+
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ mScheduledForRebindingCompanionApplications.remove(new Pair<>(userId, packageName));
+ }
+ }
+
+ serviceConnector.connect();
+ }
+
+ /**
+ * Dump bound apps.
+ */
+ public void dump(@NonNull PrintWriter out) {
+ out.append("Companion Device Application Controller: \n");
+
+ synchronized (mBoundCompanionApplications) {
+ out.append(" Bound Companion Applications: ");
+ if (mBoundCompanionApplications.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (Map.Entry<Pair<Integer, String>, List<CompanionServiceConnector>> entry :
+ mBoundCompanionApplications.entrySet()) {
+ out.append("<u").append(String.valueOf(entry.getKey().first)).append(", ")
+ .append(entry.getKey().second).append(">");
+ for (CompanionServiceConnector serviceConnector : entry.getValue()) {
+ out.append(", isPrimary=").append(
+ String.valueOf(serviceConnector.isPrimary()));
+ }
+ }
+ }
+ }
+
+ out.append(" Companion Applications Scheduled For Rebinding: ");
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ if (mScheduledForRebindingCompanionApplications.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (Pair<Integer, String> app : mScheduledForRebindingCompanionApplications) {
+ out.append("<u").append(String.valueOf(app.first)).append(", ")
+ .append(app.second).append(">");
+ }
+ }
+ }
+ }
+
+ @Nullable
+ CompanionServiceConnector getPrimaryServiceConnector(
+ @UserIdInt int userId, @NonNull String packageName) {
+ final List<CompanionServiceConnector> connectors;
+ synchronized (mBoundCompanionApplications) {
+ connectors = mBoundCompanionApplications.get(new Pair<>(userId, packageName));
+ }
+ return connectors != null ? connectors.get(0) : null;
+ }
+
+ private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
+ @Override
+ public synchronized @NonNull Map<String, List<ComponentName>> forUser(
+ @UserIdInt int userId) {
+ return super.forUser(userId);
+ }
+
+ synchronized @NonNull List<ComponentName> forPackage(
+ @UserIdInt int userId, @NonNull String packageName) {
+ return forUser(userId).getOrDefault(packageName, Collections.emptyList());
+ }
+
+ synchronized void invalidate(@UserIdInt int userId) {
+ remove(userId);
+ }
+
+ @Override
+ protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) {
+ return PackageUtils.getCompanionServicesForUser(mContext, userId);
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
deleted file mode 100644
index 7a1a83f53315..000000000000
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ /dev/null
@@ -1,620 +0,0 @@
-/*
- * 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.server.companion.presence;
-
-import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
-import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
-import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
-import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
-import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
-import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
-import static android.os.Process.ROOT_UID;
-import static android.os.Process.SHELL_UID;
-
-import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.annotation.TestApi;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.companion.AssociationInfo;
-import android.content.Context;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelUuid;
-import android.os.UserManager;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.server.companion.association.AssociationStore;
-
-import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Class responsible for monitoring companion devices' "presence" status (i.e.
- * connected/disconnected for Bluetooth devices; nearby or not for BLE devices).
- *
- * <p>
- * Should only be used by
- * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
- * to which it provides the following API:
- * <ul>
- * <li> {@link #onSelfManagedDeviceConnected(int)}
- * <li> {@link #onSelfManagedDeviceDisconnected(int)}
- * <li> {@link #isDevicePresent(int)}
- * <li> {@link Callback#onDeviceAppeared(int) Callback.onDeviceAppeared(int)}
- * <li> {@link Callback#onDeviceDisappeared(int) Callback.onDeviceDisappeared(int)}
- * <li> {@link Callback#onDevicePresenceEvent(int, int)}}
- * </ul>
- */
-@SuppressLint("LongLogTag")
-public class CompanionDevicePresenceMonitor implements AssociationStore.OnChangeListener,
- BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
- static final boolean DEBUG = false;
- private static final String TAG = "CDM_CompanionDevicePresenceMonitor";
-
- /** Callback for notifying about changes to status of companion devices. */
- public interface Callback {
- /** Invoked when companion device is found nearby or connects. */
- void onDeviceAppeared(int associationId);
-
- /** Invoked when a companion device no longer seen nearby or disconnects. */
- void onDeviceDisappeared(int associationId);
-
- /** Invoked when device has corresponding event changes. */
- void onDevicePresenceEvent(int associationId, int event);
-
- /** Invoked when device has corresponding event changes base on the UUID */
- void onDevicePresenceEventByUuid(ObservableUuid uuid, int event);
- }
-
- private final @NonNull AssociationStore mAssociationStore;
- private final @NonNull ObservableUuidStore mObservableUuidStore;
- private final @NonNull Callback mCallback;
- private final @NonNull BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
- private final @NonNull BleCompanionDeviceScanner mBleScanner;
-
- // NOTE: Same association may appear in more than one of the following sets at the same time.
- // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
- // companion applications, while at the same be connected via BT, or detected nearby by BLE
- // scanner)
- private final @NonNull Set<Integer> mConnectedBtDevices = new HashSet<>();
- private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
- private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
- private final @NonNull Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
- @GuardedBy("mBtDisconnectedDevices")
- private final @NonNull Set<Integer> mBtDisconnectedDevices = new HashSet<>();
-
- // A map to track device presence within 10 seconds of Bluetooth disconnection.
- // The key is the association ID, and the boolean value indicates if the device
- // was detected again within that time frame.
- @GuardedBy("mBtDisconnectedDevices")
- private final @NonNull SparseBooleanArray mBtDisconnectedDevicesBlePresence =
- new SparseBooleanArray();
-
- // Tracking "simulated" presence. Used for debugging and testing only.
- private final @NonNull Set<Integer> mSimulated = new HashSet<>();
- private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
- new SimulatedDevicePresenceSchedulerHelper();
-
- private final BleDeviceDisappearedScheduler mBleDeviceDisappearedScheduler =
- new BleDeviceDisappearedScheduler();
-
- public CompanionDevicePresenceMonitor(UserManager userManager,
- @NonNull AssociationStore associationStore,
- @NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
- mAssociationStore = associationStore;
- mObservableUuidStore = observableUuidStore;
- mCallback = callback;
- mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
- associationStore, mObservableUuidStore,
- /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
- mBleScanner = new BleCompanionDeviceScanner(associationStore,
- /* BleCompanionDeviceScanner.Callback */ this);
- }
-
- /** Initialize {@link CompanionDevicePresenceMonitor} */
- public void init(Context context) {
- if (DEBUG) Log.i(TAG, "init()");
-
- final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
- if (btAdapter != null) {
- mBtConnectionListener.init(btAdapter);
- mBleScanner.init(context, btAdapter);
- } else {
- Log.w(TAG, "BluetoothAdapter is NOT available.");
- }
-
- mAssociationStore.registerLocalListener(this);
- }
-
- /**
- * @return current connected UUID devices.
- */
- public Set<ParcelUuid> getCurrentConnectedUuidDevices() {
- return mConnectedUuidDevices;
- }
-
- /**
- * Remove current connected UUID device.
- */
- public void removeCurrentConnectedUuidDevice(ParcelUuid uuid) {
- mConnectedUuidDevices.remove(uuid);
- }
-
- /**
- * @return whether the associated companion devices is present. I.e. device is nearby (for BLE);
- * or devices is connected (for Bluetooth); or reported (by the application) to be
- * nearby (for "self-managed" associations).
- */
- public boolean isDevicePresent(int associationId) {
- return mReportedSelfManagedDevices.contains(associationId)
- || mConnectedBtDevices.contains(associationId)
- || mNearbyBleDevices.contains(associationId)
- || mSimulated.contains(associationId);
- }
-
- /**
- * @return whether the current uuid to be observed is present.
- */
- public boolean isDeviceUuidPresent(ParcelUuid uuid) {
- return mConnectedUuidDevices.contains(uuid);
- }
-
- /**
- * @return whether the current device is BT connected and had already reported to the app.
- */
-
- public boolean isBtConnected(int associationId) {
- return mConnectedBtDevices.contains(associationId);
- }
-
- /**
- * @return whether the current device in BLE range and had already reported to the app.
- */
- public boolean isBlePresent(int associationId) {
- return mNearbyBleDevices.contains(associationId);
- }
-
- /**
- * @return whether the current device had been already reported by the simulator.
- */
- public boolean isSimulatePresent(int associationId) {
- return mSimulated.contains(associationId);
- }
-
- /**
- * Marks a "self-managed" device as connected.
- *
- * <p>
- * Must ONLY be invoked by the
- * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
- * when an application invokes
- * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) notifyDeviceAppeared()}
- */
- public void onSelfManagedDeviceConnected(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
- associationId, EVENT_SELF_MANAGED_APPEARED);
- }
-
- /**
- * Marks a "self-managed" device as disconnected.
- *
- * <p>
- * Must ONLY be invoked by the
- * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
- * when an application invokes
- * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) notifyDeviceDisappeared()}
- */
- public void onSelfManagedDeviceDisconnected(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
- associationId, EVENT_SELF_MANAGED_DISAPPEARED);
- }
-
- /**
- * Marks a "self-managed" device as disconnected when binderDied.
- */
- public void onSelfManagedDeviceReporterBinderDied(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
- associationId, EVENT_SELF_MANAGED_DISAPPEARED);
- }
-
- @Override
- public void onBluetoothCompanionDeviceConnected(int associationId) {
- synchronized (mBtDisconnectedDevices) {
- // A device is considered reconnected within 10 seconds if a pending BLE lost report is
- // followed by a detected Bluetooth connection.
- boolean isReconnected = mBtDisconnectedDevices.contains(associationId);
- if (isReconnected) {
- Slog.i(TAG, "Device ( " + associationId + " ) is reconnected within 10s.");
- mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
- }
-
- Slog.i(TAG, "onBluetoothCompanionDeviceConnected: "
- + "associationId( " + associationId + " )");
- onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
-
- // Stop the BLE scan if all devices report BT connected status and BLE was present.
- if (canStopBleScan()) {
- mBleScanner.stopScanIfNeeded();
- }
-
- }
- }
-
- @Override
- public void onBluetoothCompanionDeviceDisconnected(int associationId) {
- Slog.i(TAG, "onBluetoothCompanionDeviceDisconnected "
- + "associationId( " + associationId + " )");
- // Start BLE scanning when the device is disconnected.
- mBleScanner.startScan();
-
- onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
- // If current device is BLE present but BT is disconnected , means it will be
- // potentially out of range later. Schedule BLE disappeared callback.
- if (isBlePresent(associationId)) {
- synchronized (mBtDisconnectedDevices) {
- mBtDisconnectedDevices.add(associationId);
- }
- mBleDeviceDisappearedScheduler.scheduleBleDeviceDisappeared(associationId);
- }
- }
-
- @Override
- public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) {
- final ParcelUuid parcelUuid = uuid.getUuid();
-
- switch(event) {
- case EVENT_BT_CONNECTED:
- boolean added = mConnectedUuidDevices.add(parcelUuid);
-
- if (!added) {
- Slog.w(TAG, "Uuid= " + parcelUuid + "is ALREADY reported as "
- + "present by this event=" + event);
- }
-
- break;
- case EVENT_BT_DISCONNECTED:
- final boolean removed = mConnectedUuidDevices.remove(parcelUuid);
-
- if (!removed) {
- Slog.w(TAG, "UUID= " + parcelUuid + " was NOT reported "
- + "as present by this event= " + event);
-
- return;
- }
-
- break;
- }
-
- mCallback.onDevicePresenceEventByUuid(uuid, event);
- }
-
-
- @Override
- public void onBleCompanionDeviceFound(int associationId) {
- onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
- synchronized (mBtDisconnectedDevices) {
- final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(associationId);
- if (mBtDisconnectedDevices.contains(associationId) && isCurrentPresent) {
- mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
- }
- }
- }
-
- @Override
- public void onBleCompanionDeviceLost(int associationId) {
- onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
- }
-
- /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
- @TestApi
- public void simulateDeviceEvent(int associationId, int event) {
- // IMPORTANT: this API should only be invoked via the
- // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
- // make this call are SHELL and ROOT.
- // No other caller (including SYSTEM!) should be allowed.
- enforceCallerShellOrRoot();
- // Make sure the association exists.
- enforceAssociationExists(associationId);
-
- switch (event) {
- case EVENT_BLE_APPEARED:
- simulateDeviceAppeared(associationId, event);
- break;
- case EVENT_BT_CONNECTED:
- onBluetoothCompanionDeviceConnected(associationId);
- break;
- case EVENT_BLE_DISAPPEARED:
- simulateDeviceDisappeared(associationId, event);
- break;
- case EVENT_BT_DISCONNECTED:
- onBluetoothCompanionDeviceDisconnected(associationId);
- break;
- default:
- throw new IllegalArgumentException("Event: " + event + "is not supported");
- }
- }
-
- /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
- @TestApi
- public void simulateDeviceEventByUuid(ObservableUuid uuid, int event) {
- // IMPORTANT: this API should only be invoked via the
- // 'companiondevice simulate-device-uuid-events' Shell command, so the only uid-s allowed to
- // make this call are SHELL and ROOT.
- // No other caller (including SYSTEM!) should be allowed.
- enforceCallerShellOrRoot();
- onDevicePresenceEventByUuid(uuid, event);
- }
-
- private void simulateDeviceAppeared(int associationId, int state) {
- onDevicePresenceEvent(mSimulated, associationId, state);
- mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
- }
-
- private void simulateDeviceDisappeared(int associationId, int state) {
- mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
- onDevicePresenceEvent(mSimulated, associationId, state);
- }
-
- private void enforceAssociationExists(int associationId) {
- if (mAssociationStore.getAssociationById(associationId) == null) {
- throw new IllegalArgumentException(
- "Association with id " + associationId + " does not exist.");
- }
- }
-
- private void onDevicePresenceEvent(@NonNull Set<Integer> presentDevicesForSource,
- int associationId, int event) {
- Slog.i(TAG, "onDevicePresenceEvent() id=" + associationId + ", event=" + event);
-
- switch (event) {
- case EVENT_BLE_APPEARED:
- synchronized (mBtDisconnectedDevices) {
- // If a BLE device is detected within 10 seconds after BT is disconnected,
- // flag it as BLE is present.
- if (mBtDisconnectedDevices.contains(associationId)) {
- Slog.i(TAG, "Device ( " + associationId + " ) is present,"
- + " do not need to send the callback with event ( "
- + EVENT_BLE_APPEARED + " ).");
- mBtDisconnectedDevicesBlePresence.append(associationId, true);
- }
- }
- case EVENT_BT_CONNECTED:
- case EVENT_SELF_MANAGED_APPEARED:
- final boolean added = presentDevicesForSource.add(associationId);
-
- if (!added) {
- Slog.w(TAG, "Association with id "
- + associationId + " is ALREADY reported as "
- + "present by this source, event=" + event);
- }
-
- mCallback.onDeviceAppeared(associationId);
-
- break;
- case EVENT_BLE_DISAPPEARED:
- case EVENT_BT_DISCONNECTED:
- case EVENT_SELF_MANAGED_DISAPPEARED:
- final boolean removed = presentDevicesForSource.remove(associationId);
-
- if (!removed) {
- Slog.w(TAG, "Association with id " + associationId + " was NOT reported "
- + "as present by this source, event= " + event);
-
- return;
- }
-
- mCallback.onDeviceDisappeared(associationId);
-
- break;
- default:
- Slog.e(TAG, "Event: " + event + " is not supported");
- return;
- }
-
- mCallback.onDevicePresenceEvent(associationId, event);
- }
-
- /**
- * Implements
- * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
- */
- @Override
- public void onAssociationRemoved(@NonNull AssociationInfo association) {
- final int id = association.getId();
- if (DEBUG) {
- Log.i(TAG, "onAssociationRemoved() id=" + id);
- Log.d(TAG, " > association=" + association);
- }
-
- mConnectedBtDevices.remove(id);
- mNearbyBleDevices.remove(id);
- mReportedSelfManagedDevices.remove(id);
- mSimulated.remove(id);
- mBtDisconnectedDevices.remove(id);
- mBtDisconnectedDevicesBlePresence.delete(id);
-
- // Do NOT call mCallback.onDeviceDisappeared()!
- // CompanionDeviceManagerService will know that the association is removed, and will do
- // what's needed.
- }
-
- /**
- * Return a set of devices that pending to report connectivity
- */
- public SparseArray<Set<BluetoothDevice>> getPendingConnectedDevices() {
- synchronized (mBtConnectionListener.mPendingConnectedDevices) {
- return mBtConnectionListener.mPendingConnectedDevices;
- }
- }
-
- private static void enforceCallerShellOrRoot() {
- final int callingUid = Binder.getCallingUid();
- if (callingUid == SHELL_UID || callingUid == ROOT_UID) return;
-
- throw new SecurityException("Caller is neither Shell nor Root");
- }
-
- /**
- * The BLE scan can be only stopped if all the devices have been reported
- * BT connected and BLE presence and are not pending to report BLE lost.
- */
- private boolean canStopBleScan() {
- for (AssociationInfo ai : mAssociationStore.getActiveAssociations()) {
- int id = ai.getId();
- synchronized (mBtDisconnectedDevices) {
- if (ai.isNotifyOnDeviceNearby() && !(isBtConnected(id)
- && isBlePresent(id) && mBtDisconnectedDevices.isEmpty())) {
- Slog.i(TAG, "The BLE scan cannot be stopped, "
- + "device( " + id + " ) is not yet connected "
- + "OR the BLE is not current present Or is pending to report BLE lost");
- return false;
- }
- }
- }
- return true;
- }
-
- /**
- * Dumps system information about devices that are marked as "present".
- */
- public void dump(@NonNull PrintWriter out) {
- out.append("Companion Device Present: ");
- if (mConnectedBtDevices.isEmpty()
- && mNearbyBleDevices.isEmpty()
- && mReportedSelfManagedDevices.isEmpty()) {
- out.append("<empty>\n");
- return;
- } else {
- out.append("\n");
- }
-
- out.append(" Connected Bluetooth Devices: ");
- if (mConnectedBtDevices.isEmpty()) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- for (int associationId : mConnectedBtDevices) {
- AssociationInfo a = mAssociationStore.getAssociationById(associationId);
- out.append(" ").append(a.toShortString()).append('\n');
- }
- }
-
- out.append(" Nearby BLE Devices: ");
- if (mNearbyBleDevices.isEmpty()) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- for (int associationId : mNearbyBleDevices) {
- AssociationInfo a = mAssociationStore.getAssociationById(associationId);
- out.append(" ").append(a.toShortString()).append('\n');
- }
- }
-
- out.append(" Self-Reported Devices: ");
- if (mReportedSelfManagedDevices.isEmpty()) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- for (int associationId : mReportedSelfManagedDevices) {
- AssociationInfo a = mAssociationStore.getAssociationById(associationId);
- out.append(" ").append(a.toShortString()).append('\n');
- }
- }
- }
-
- private class SimulatedDevicePresenceSchedulerHelper extends Handler {
- SimulatedDevicePresenceSchedulerHelper() {
- super(Looper.getMainLooper());
- }
-
- void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
- // First, unschedule if it was scheduled previously.
- if (hasMessages(/* what */ associationId)) {
- removeMessages(/* what */ associationId);
- }
-
- sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */);
- }
-
- void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
- removeMessages(/* what */ associationId);
- }
-
- @Override
- public void handleMessage(@NonNull Message msg) {
- final int associationId = msg.what;
- if (mSimulated.contains(associationId)) {
- onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_DISAPPEARED);
- }
- }
- }
-
- private class BleDeviceDisappearedScheduler extends Handler {
- BleDeviceDisappearedScheduler() {
- super(Looper.getMainLooper());
- }
-
- void scheduleBleDeviceDisappeared(int associationId) {
- if (hasMessages(associationId)) {
- removeMessages(associationId);
- }
- Slog.i(TAG, "scheduleBleDeviceDisappeared for Device: ( " + associationId + " ).");
- sendEmptyMessageDelayed(associationId, 10 * 1000 /* 10 seconds */);
- }
-
- void unScheduleDeviceDisappeared(int associationId) {
- if (hasMessages(associationId)) {
- Slog.i(TAG, "unScheduleDeviceDisappeared for Device( " + associationId + " )");
- synchronized (mBtDisconnectedDevices) {
- mBtDisconnectedDevices.remove(associationId);
- mBtDisconnectedDevicesBlePresence.delete(associationId);
- }
-
- removeMessages(associationId);
- }
- }
-
- @Override
- public void handleMessage(@NonNull Message msg) {
- final int associationId = msg.what;
- synchronized (mBtDisconnectedDevices) {
- final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(
- associationId);
- // If a device hasn't reported after 10 seconds and is not currently present,
- // assume BLE is lost and trigger the onDeviceEvent callback with the
- // EVENT_BLE_DISAPPEARED event.
- if (mBtDisconnectedDevices.contains(associationId)
- && !isCurrentPresent) {
- Slog.i(TAG, "Device ( " + associationId + " ) is likely BLE out of range, "
- + "sending callback with event ( " + EVENT_BLE_DISAPPEARED + " )");
- onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
- }
-
- mBtDisconnectedDevices.remove(associationId);
- mBtDisconnectedDevicesBlePresence.delete(associationId);
- }
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java
index 5abdb42b34fc..c01c3195e04d 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.presence;
import static android.content.Context.BIND_ALMOST_PERCEPTIBLE;
import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
@@ -33,36 +33,42 @@ import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.infra.ServiceConnector;
import com.android.server.ServiceThread;
+import com.android.server.companion.CompanionDeviceManagerService;
/**
* Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the
* application process.
*/
@SuppressLint("LongLogTag")
-class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> {
- private static final String TAG = "CDM_CompanionServiceConnector";
- private static final boolean DEBUG = false;
+public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> {
- /* Unbinding before executing the callbacks can cause problems. Wait 5-seconds before unbind. */
- private static final long UNBIND_POST_DELAY_MS = 5_000;
-
- /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector} */
- interface Listener {
+ /** Listener for changes to the state of the {@link CompanionServiceConnector} */
+ public interface Listener {
+ /**
+ * Called when service binding is died.
+ */
void onBindingDied(@UserIdInt int userId, @NonNull String packageName,
- @NonNull CompanionDeviceServiceConnector serviceConnector);
+ @NonNull CompanionServiceConnector serviceConnector);
}
- private final @UserIdInt int mUserId;
- private final @NonNull ComponentName mComponentName;
+ private static final String TAG = "CDM_CompanionServiceConnector";
+
+ /* Unbinding before executing the callbacks can cause problems. Wait 5-seconds before unbind. */
+ private static final long UNBIND_POST_DELAY_MS = 5_000;
+ @UserIdInt
+ private final int mUserId;
+ @NonNull
+ private final ComponentName mComponentName;
+ private final boolean mIsPrimary;
// IMPORTANT: this can (and will!) be null (at the moment, CompanionApplicationController only
// installs a listener to the primary ServiceConnector), hence we should always null-check the
// reference before calling on it.
- private @Nullable Listener mListener;
- private boolean mIsPrimary;
+ @Nullable
+ private Listener mListener;
/**
* Create a CompanionDeviceServiceConnector instance.
@@ -79,16 +85,16 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
* IMPORTANCE_FOREGROUND_SERVICE = 125. In order to kill the one time permission session, the
* service importance level should be higher than 125.
*/
- static CompanionDeviceServiceConnector newInstance(@NonNull Context context,
+ static CompanionServiceConnector newInstance(@NonNull Context context,
@UserIdInt int userId, @NonNull ComponentName componentName, boolean isSelfManaged,
boolean isPrimary) {
final int bindingFlags = isSelfManaged ? BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
: BIND_ALMOST_PERCEPTIBLE;
- return new CompanionDeviceServiceConnector(
+ return new CompanionServiceConnector(
context, userId, componentName, bindingFlags, isPrimary);
}
- private CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId,
+ private CompanionServiceConnector(@NonNull Context context, @UserIdInt int userId,
@NonNull ComponentName componentName, int bindingFlags, boolean isPrimary) {
super(context, buildIntent(componentName), bindingFlags, userId, null);
mUserId = userId;
@@ -133,6 +139,7 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
return mIsPrimary;
}
+ @NonNull
ComponentName getComponentName() {
return mComponentName;
}
@@ -140,17 +147,15 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
@Override
protected void onServiceConnectionStatusChanged(
@NonNull ICompanionDeviceService service, boolean isConnected) {
- if (DEBUG) {
- Log.d(TAG, "onServiceConnection_StatusChanged() " + mComponentName.toShortString()
- + " connected=" + isConnected);
- }
+ Slog.d(TAG, "onServiceConnectionStatusChanged() " + mComponentName.toShortString()
+ + " connected=" + isConnected);
}
@Override
public void binderDied() {
super.binderDied();
- if (DEBUG) Log.d(TAG, "binderDied() " + mComponentName.toShortString());
+ Slog.d(TAG, "binderDied() " + mComponentName.toShortString());
// Handle primary process being killed
if (mListener != null) {
@@ -172,7 +177,8 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
* within system_server and thus tends to get heavily congested)
*/
@Override
- protected @NonNull Handler getJobHandler() {
+ @NonNull
+ protected Handler getJobHandler() {
return getServiceThread().getThreadHandler();
}
@@ -182,12 +188,14 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
return -1;
}
- private static @NonNull Intent buildIntent(@NonNull ComponentName componentName) {
+ @NonNull
+ private static Intent buildIntent(@NonNull ComponentName componentName) {
return new Intent(CompanionDeviceService.SERVICE_INTERFACE)
.setComponent(componentName);
}
- private static @NonNull ServiceThread getServiceThread() {
+ @NonNull
+ private static ServiceThread getServiceThread() {
if (sServiceThread == null) {
synchronized (CompanionDeviceManagerService.class) {
if (sServiceThread == null) {
@@ -206,5 +214,6 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
* <p>
* Do NOT reference directly, use {@link #getServiceThread()} method instead.
*/
- private static volatile @Nullable ServiceThread sServiceThread;
+ @Nullable
+ private static volatile ServiceThread sServiceThread;
}
diff --git a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
new file mode 100644
index 000000000000..642460e64216
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
@@ -0,0 +1,1042 @@
+/*
+ * 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.server.companion.presence;
+
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.NO_ASSOCIATION;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SHELL_UID;
+
+import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObserveDevicePresenceByUuid;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.companion.AssociationInfo;
+import android.companion.DeviceNotAssociatedException;
+import android.companion.DevicePresenceEvent;
+import android.companion.ObservingDevicePresenceRequest;
+import android.content.Context;
+import android.hardware.power.Mode;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.os.PowerManagerInternal;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.companion.association.AssociationStore;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Class responsible for monitoring companion devices' "presence" status (i.e.
+ * connected/disconnected for Bluetooth devices; nearby or not for BLE devices).
+ *
+ * <p>
+ * Should only be used by
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * to which it provides the following API:
+ * <ul>
+ * <li> {@link #onSelfManagedDeviceConnected(int)}
+ * <li> {@link #onSelfManagedDeviceDisconnected(int)}
+ * <li> {@link #isDevicePresent(int)}
+ * </ul>
+ */
+@SuppressLint("LongLogTag")
+public class DevicePresenceProcessor implements AssociationStore.OnChangeListener,
+ BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
+ static final boolean DEBUG = false;
+ private static final String TAG = "CDM_DevicePresenceProcessor";
+
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final CompanionAppBinder mCompanionAppBinder;
+ @NonNull
+ private final AssociationStore mAssociationStore;
+ @NonNull
+ private final ObservableUuidStore mObservableUuidStore;
+ @NonNull
+ private final BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
+ @NonNull
+ private final BleCompanionDeviceScanner mBleScanner;
+ @NonNull
+ private final PowerManagerInternal mPowerManagerInternal;
+
+ // NOTE: Same association may appear in more than one of the following sets at the same time.
+ // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
+ // companion applications, while at the same be connected via BT, or detected nearby by BLE
+ // scanner)
+ @NonNull
+ private final Set<Integer> mConnectedBtDevices = new HashSet<>();
+ @NonNull
+ private final Set<Integer> mNearbyBleDevices = new HashSet<>();
+ @NonNull
+ private final Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
+ @NonNull
+ private final Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
+ @NonNull
+ @GuardedBy("mBtDisconnectedDevices")
+ private final Set<Integer> mBtDisconnectedDevices = new HashSet<>();
+
+ // A map to track device presence within 10 seconds of Bluetooth disconnection.
+ // The key is the association ID, and the boolean value indicates if the device
+ // was detected again within that time frame.
+ @GuardedBy("mBtDisconnectedDevices")
+ private final @NonNull SparseBooleanArray mBtDisconnectedDevicesBlePresence =
+ new SparseBooleanArray();
+
+ // Tracking "simulated" presence. Used for debugging and testing only.
+ private final @NonNull Set<Integer> mSimulated = new HashSet<>();
+ private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
+ new SimulatedDevicePresenceSchedulerHelper();
+
+ private final BleDeviceDisappearedScheduler mBleDeviceDisappearedScheduler =
+ new BleDeviceDisappearedScheduler();
+
+ public DevicePresenceProcessor(@NonNull Context context,
+ @NonNull CompanionAppBinder companionAppBinder,
+ UserManager userManager,
+ @NonNull AssociationStore associationStore,
+ @NonNull ObservableUuidStore observableUuidStore,
+ @NonNull PowerManagerInternal powerManagerInternal) {
+ mContext = context;
+ mCompanionAppBinder = companionAppBinder;
+ mAssociationStore = associationStore;
+ mObservableUuidStore = observableUuidStore;
+ mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
+ associationStore, mObservableUuidStore,
+ /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
+ mBleScanner = new BleCompanionDeviceScanner(associationStore,
+ /* BleCompanionDeviceScanner.Callback */ this);
+ mPowerManagerInternal = powerManagerInternal;
+ }
+
+ /** Initialize {@link DevicePresenceProcessor} */
+ public void init(Context context) {
+ if (DEBUG) Slog.i(TAG, "init()");
+
+ final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (btAdapter != null) {
+ mBtConnectionListener.init(btAdapter);
+ mBleScanner.init(context, btAdapter);
+ } else {
+ Slog.w(TAG, "BluetoothAdapter is NOT available.");
+ }
+
+ mAssociationStore.registerLocalListener(this);
+ }
+
+ /**
+ * Process device presence start request.
+ */
+ public void startObservingDevicePresence(ObservingDevicePresenceRequest request,
+ String packageName, int userId) {
+ Slog.i(TAG,
+ "Start observing request=[" + request + "] for userId=[" + userId + "], package=["
+ + packageName + "]...");
+ final ParcelUuid requestUuid = request.getUuid();
+
+ if (requestUuid != null) {
+ enforceCallerCanObserveDevicePresenceByUuid(mContext);
+
+ // If it's already being observed, then no-op.
+ if (mObservableUuidStore.isUuidBeingObserved(requestUuid, userId, packageName)) {
+ Slog.i(TAG, "UUID=[" + requestUuid + "], package=[" + packageName + "], userId=["
+ + userId + "] is already being observed.");
+ return;
+ }
+
+ final ObservableUuid observableUuid = new ObservableUuid(userId, requestUuid,
+ packageName, System.currentTimeMillis());
+ mObservableUuidStore.writeObservableUuid(userId, observableUuid);
+ } else {
+ final int associationId = request.getAssociationId();
+ AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
+
+ // If it's already being observed, then no-op.
+ if (association.isNotifyOnDeviceNearby()) {
+ Slog.i(TAG, "Associated device id=[" + association.getId()
+ + "] is already being observed. No-op.");
+ return;
+ }
+
+ association = (new AssociationInfo.Builder(association)).setNotifyOnDeviceNearby(true)
+ .build();
+ mAssociationStore.updateAssociation(association);
+
+ // Send callback immediately if the device is present.
+ if (isDevicePresent(associationId)) {
+ Slog.i(TAG, "Device is already present. Triggering callback.");
+ if (isBlePresent(associationId)) {
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
+ } else if (isBtConnected(associationId)) {
+ onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
+ } else if (isSimulatePresent(associationId)) {
+ onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_APPEARED);
+ }
+ }
+ }
+
+ Slog.i(TAG, "Registered device presence listener.");
+ }
+
+ /**
+ * Process device presence stop request.
+ */
+ public void stopObservingDevicePresence(ObservingDevicePresenceRequest request,
+ String packageName, int userId) {
+ Slog.i(TAG,
+ "Stop observing request=[" + request + "] for userId=[" + userId + "], package=["
+ + packageName + "]...");
+
+ final ParcelUuid requestUuid = request.getUuid();
+
+ if (requestUuid != null) {
+ enforceCallerCanObserveDevicePresenceByUuid(mContext);
+
+ if (!mObservableUuidStore.isUuidBeingObserved(requestUuid, userId, packageName)) {
+ Slog.i(TAG, "UUID=[" + requestUuid + "], package=[" + packageName + "], userId=["
+ + userId + "] is already not being observed.");
+ return;
+ }
+
+ mObservableUuidStore.removeObservableUuid(userId, requestUuid, packageName);
+ removeCurrentConnectedUuidDevice(requestUuid);
+ } else {
+ final int associationId = request.getAssociationId();
+ AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
+
+ // If it's already being observed, then no-op.
+ if (!association.isNotifyOnDeviceNearby()) {
+ Slog.i(TAG, "Associated device id=[" + association.getId()
+ + "] is already not being observed. No-op.");
+ return;
+ }
+
+ association = (new AssociationInfo.Builder(association)).setNotifyOnDeviceNearby(false)
+ .build();
+ mAssociationStore.updateAssociation(association);
+ }
+
+ Slog.i(TAG, "Unregistered device presence listener.");
+
+ // If last listener is unregistered, then unbind application.
+ if (!shouldBindPackage(userId, packageName)) {
+ mCompanionAppBinder.unbindCompanionApp(userId, packageName);
+ }
+ }
+
+ /**
+ * For legacy device presence below Android V.
+ *
+ * @deprecated Use {@link #startObservingDevicePresence(ObservingDevicePresenceRequest, String,
+ * int)}
+ */
+ @Deprecated
+ public void startObservingDevicePresence(int userId, String packageName, String deviceAddress)
+ throws RemoteException {
+ Slog.i(TAG,
+ "Start observing device=[" + deviceAddress + "] for userId=[" + userId
+ + "], package=["
+ + packageName + "]...");
+
+ enforceCallerCanManageAssociationsForPackage(mContext, userId, packageName, null);
+
+ AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId,
+ packageName, deviceAddress);
+
+ if (association == null) {
+ throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
+ + " is not associated with device " + deviceAddress
+ + " for user " + userId));
+ }
+
+ startObservingDevicePresence(
+ new ObservingDevicePresenceRequest.Builder().setAssociationId(association.getId())
+ .build(), packageName, userId);
+ }
+
+ /**
+ * For legacy device presence below Android V.
+ *
+ * @deprecated Use {@link #stopObservingDevicePresence(ObservingDevicePresenceRequest, String,
+ * int)}
+ */
+ @Deprecated
+ public void stopObservingDevicePresence(int userId, String packageName, String deviceAddress)
+ throws RemoteException {
+ Slog.i(TAG,
+ "Stop observing device=[" + deviceAddress + "] for userId=[" + userId
+ + "], package=["
+ + packageName + "]...");
+
+ enforceCallerCanManageAssociationsForPackage(mContext, userId, packageName, null);
+
+ AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId,
+ packageName, deviceAddress);
+
+ if (association == null) {
+ throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
+ + " is not associated with device " + deviceAddress
+ + " for user " + userId));
+ }
+
+ stopObservingDevicePresence(
+ new ObservingDevicePresenceRequest.Builder().setAssociationId(association.getId())
+ .build(), packageName, userId);
+ }
+
+ /**
+ * @return whether the package should be bound (i.e. at least one of the devices associated with
+ * the package is currently present OR the UUID to be observed by this package is
+ * currently present).
+ */
+ private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) {
+ final List<AssociationInfo> packageAssociations =
+ mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+
+ for (AssociationInfo association : packageAssociations) {
+ if (!association.shouldBindWhenPresent()) continue;
+ if (isDevicePresent(association.getId())) return true;
+ }
+
+ for (ObservableUuid uuid : observableUuids) {
+ if (isDeviceUuidPresent(uuid.getUuid())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Bind the system to the app if it's not bound.
+ *
+ * Set bindImportant to true when the association is self-managed to avoid the target service
+ * being killed.
+ */
+ private void bindApplicationIfNeeded(int userId, String packageName, boolean bindImportant) {
+ if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) {
+ mCompanionAppBinder.bindCompanionApp(
+ userId, packageName, bindImportant, this::onBinderDied);
+ } else {
+ Slog.i(TAG,
+ "UserId=[" + userId + "], packageName=[" + packageName + "] is already bound.");
+ }
+ }
+
+ /**
+ * @return current connected UUID devices.
+ */
+ public Set<ParcelUuid> getCurrentConnectedUuidDevices() {
+ return mConnectedUuidDevices;
+ }
+
+ /**
+ * Remove current connected UUID device.
+ */
+ public void removeCurrentConnectedUuidDevice(ParcelUuid uuid) {
+ mConnectedUuidDevices.remove(uuid);
+ }
+
+ /**
+ * @return whether the associated companion devices is present. I.e. device is nearby (for BLE);
+ * or devices is connected (for Bluetooth); or reported (by the application) to be
+ * nearby (for "self-managed" associations).
+ */
+ public boolean isDevicePresent(int associationId) {
+ return mReportedSelfManagedDevices.contains(associationId)
+ || mConnectedBtDevices.contains(associationId)
+ || mNearbyBleDevices.contains(associationId)
+ || mSimulated.contains(associationId);
+ }
+
+ /**
+ * @return whether the current uuid to be observed is present.
+ */
+ public boolean isDeviceUuidPresent(ParcelUuid uuid) {
+ return mConnectedUuidDevices.contains(uuid);
+ }
+
+ /**
+ * @return whether the current device is BT connected and had already reported to the app.
+ */
+
+ public boolean isBtConnected(int associationId) {
+ return mConnectedBtDevices.contains(associationId);
+ }
+
+ /**
+ * @return whether the current device in BLE range and had already reported to the app.
+ */
+ public boolean isBlePresent(int associationId) {
+ return mNearbyBleDevices.contains(associationId);
+ }
+
+ /**
+ * @return whether the current device had been already reported by the simulator.
+ */
+ public boolean isSimulatePresent(int associationId) {
+ return mSimulated.contains(associationId);
+ }
+
+ /**
+ * Marks a "self-managed" device as connected.
+ *
+ * <p>
+ * Must ONLY be invoked by the
+ * {@link com.android.server.companion.CompanionDeviceManagerService
+ * CompanionDeviceManagerService}
+ * when an application invokes
+ * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int)
+ * notifyDeviceAppeared()}
+ */
+ public void onSelfManagedDeviceConnected(int associationId) {
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_APPEARED);
+ }
+
+ /**
+ * Marks a "self-managed" device as disconnected.
+ *
+ * <p>
+ * Must ONLY be invoked by the
+ * {@link com.android.server.companion.CompanionDeviceManagerService
+ * CompanionDeviceManagerService}
+ * when an application invokes
+ * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int)
+ * notifyDeviceDisappeared()}
+ */
+ public void onSelfManagedDeviceDisconnected(int associationId) {
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_DISAPPEARED);
+ }
+
+ /**
+ * Marks a "self-managed" device as disconnected when binderDied.
+ */
+ public void onSelfManagedDeviceReporterBinderDied(int associationId) {
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_DISAPPEARED);
+ }
+
+ @Override
+ public void onBluetoothCompanionDeviceConnected(int associationId) {
+ synchronized (mBtDisconnectedDevices) {
+ // A device is considered reconnected within 10 seconds if a pending BLE lost report is
+ // followed by a detected Bluetooth connection.
+ boolean isReconnected = mBtDisconnectedDevices.contains(associationId);
+ if (isReconnected) {
+ Slog.i(TAG, "Device ( " + associationId + " ) is reconnected within 10s.");
+ mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
+ }
+
+ Slog.i(TAG, "onBluetoothCompanionDeviceConnected: "
+ + "associationId( " + associationId + " )");
+ onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
+
+ // Stop the BLE scan if all devices report BT connected status and BLE was present.
+ if (canStopBleScan()) {
+ mBleScanner.stopScanIfNeeded();
+ }
+
+ }
+ }
+
+ @Override
+ public void onBluetoothCompanionDeviceDisconnected(int associationId) {
+ Slog.i(TAG, "onBluetoothCompanionDeviceDisconnected "
+ + "associationId( " + associationId + " )");
+ // Start BLE scanning when the device is disconnected.
+ mBleScanner.startScan();
+
+ onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
+ // If current device is BLE present but BT is disconnected , means it will be
+ // potentially out of range later. Schedule BLE disappeared callback.
+ if (isBlePresent(associationId)) {
+ synchronized (mBtDisconnectedDevices) {
+ mBtDisconnectedDevices.add(associationId);
+ }
+ mBleDeviceDisappearedScheduler.scheduleBleDeviceDisappeared(associationId);
+ }
+ }
+
+
+ @Override
+ public void onBleCompanionDeviceFound(int associationId) {
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
+ synchronized (mBtDisconnectedDevices) {
+ final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(associationId);
+ if (mBtDisconnectedDevices.contains(associationId) && isCurrentPresent) {
+ mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
+ }
+ }
+ }
+
+ @Override
+ public void onBleCompanionDeviceLost(int associationId) {
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
+ }
+
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ public void simulateDeviceEvent(int associationId, int event) {
+ // IMPORTANT: this API should only be invoked via the
+ // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
+ // make this call are SHELL and ROOT.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ // Make sure the association exists.
+ enforceAssociationExists(associationId);
+
+ switch (event) {
+ case EVENT_BLE_APPEARED:
+ simulateDeviceAppeared(associationId, event);
+ break;
+ case EVENT_BT_CONNECTED:
+ onBluetoothCompanionDeviceConnected(associationId);
+ break;
+ case EVENT_BLE_DISAPPEARED:
+ simulateDeviceDisappeared(associationId, event);
+ break;
+ case EVENT_BT_DISCONNECTED:
+ onBluetoothCompanionDeviceDisconnected(associationId);
+ break;
+ default:
+ throw new IllegalArgumentException("Event: " + event + "is not supported");
+ }
+ }
+
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ public void simulateDeviceEventByUuid(ObservableUuid uuid, int event) {
+ // IMPORTANT: this API should only be invoked via the
+ // 'companiondevice simulate-device-uuid-events' Shell command, so the only uid-s allowed to
+ // make this call are SHELL and ROOT.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ onDevicePresenceEventByUuid(uuid, event);
+ }
+
+ private void simulateDeviceAppeared(int associationId, int state) {
+ onDevicePresenceEvent(mSimulated, associationId, state);
+ mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
+ }
+
+ private void simulateDeviceDisappeared(int associationId, int state) {
+ mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
+ onDevicePresenceEvent(mSimulated, associationId, state);
+ }
+
+ private void enforceAssociationExists(int associationId) {
+ if (mAssociationStore.getAssociationById(associationId) == null) {
+ throw new IllegalArgumentException(
+ "Association with id " + associationId + " does not exist.");
+ }
+ }
+
+ private void onDevicePresenceEvent(@NonNull Set<Integer> presentDevicesForSource,
+ int associationId, int eventType) {
+ Slog.i(TAG,
+ "onDevicePresenceEvent() id=[" + associationId + "], event=[" + eventType + "]...");
+
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ if (association == null) {
+ Slog.e(TAG, "Association doesn't exist.");
+ return;
+ }
+
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+ final DevicePresenceEvent event = new DevicePresenceEvent(associationId, eventType, null);
+
+ if (eventType == EVENT_BLE_APPEARED) {
+ synchronized (mBtDisconnectedDevices) {
+ // If a BLE device is detected within 10 seconds after BT is disconnected,
+ // flag it as BLE is present.
+ if (mBtDisconnectedDevices.contains(associationId)) {
+ Slog.i(TAG, "Device ( " + associationId + " ) is present,"
+ + " do not need to send the callback with event ( "
+ + EVENT_BLE_APPEARED + " ).");
+ mBtDisconnectedDevicesBlePresence.append(associationId, true);
+ }
+ }
+ }
+
+ switch (eventType) {
+ case EVENT_BLE_APPEARED:
+ case EVENT_BT_CONNECTED:
+ case EVENT_SELF_MANAGED_APPEARED:
+ final boolean added = presentDevicesForSource.add(associationId);
+ if (!added) {
+ Slog.w(TAG, "The association is already present.");
+ }
+
+ if (association.shouldBindWhenPresent()) {
+ bindApplicationIfNeeded(userId, packageName, association.isSelfManaged());
+ } else {
+ return;
+ }
+
+ if (association.isSelfManaged() || added) {
+ notifyDevicePresenceEvent(userId, packageName, event);
+ // Also send the legacy callback.
+ legacyNotifyDevicePresenceEvent(association, true);
+ }
+ break;
+ case EVENT_BLE_DISAPPEARED:
+ case EVENT_BT_DISCONNECTED:
+ case EVENT_SELF_MANAGED_DISAPPEARED:
+ final boolean removed = presentDevicesForSource.remove(associationId);
+ if (!removed) {
+ Slog.w(TAG, "The association is already NOT present.");
+ }
+
+ if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) {
+ Slog.e(TAG, "Package is not bound");
+ return;
+ }
+
+ if (association.isSelfManaged() || removed) {
+ notifyDevicePresenceEvent(userId, packageName, event);
+ // Also send the legacy callback.
+ legacyNotifyDevicePresenceEvent(association, false);
+ }
+
+ // Check if there are other devices associated to the app that are present.
+ if (!shouldBindPackage(userId, packageName)) {
+ mCompanionAppBinder.unbindCompanionApp(userId, packageName);
+ }
+ break;
+ default:
+ Slog.e(TAG, "Event: " + eventType + " is not supported.");
+ break;
+ }
+ }
+
+ @Override
+ public void onDevicePresenceEventByUuid(ObservableUuid uuid, int eventType) {
+ Slog.i(TAG, "onDevicePresenceEventByUuid ObservableUuid=[" + uuid + "], event=[" + eventType
+ + "]...");
+
+ final ParcelUuid parcelUuid = uuid.getUuid();
+ final String packageName = uuid.getPackageName();
+ final int userId = uuid.getUserId();
+ final DevicePresenceEvent event = new DevicePresenceEvent(NO_ASSOCIATION, eventType,
+ parcelUuid);
+
+ switch (eventType) {
+ case EVENT_BT_CONNECTED:
+ boolean added = mConnectedUuidDevices.add(parcelUuid);
+ if (!added) {
+ Slog.w(TAG, "This device is already connected.");
+ }
+
+ bindApplicationIfNeeded(userId, packageName, false);
+
+ notifyDevicePresenceEvent(userId, packageName, event);
+ break;
+ case EVENT_BT_DISCONNECTED:
+ final boolean removed = mConnectedUuidDevices.remove(parcelUuid);
+ if (!removed) {
+ Slog.w(TAG, "This device is already disconnected.");
+ return;
+ }
+
+ if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) {
+ Slog.e(TAG, "Package is not bound.");
+ return;
+ }
+
+ notifyDevicePresenceEvent(userId, packageName, event);
+
+ if (!shouldBindPackage(userId, packageName)) {
+ mCompanionAppBinder.unbindCompanionApp(userId, packageName);
+ }
+ break;
+ default:
+ Slog.e(TAG, "Event: " + eventType + " is not supported");
+ break;
+ }
+ }
+
+ /**
+ * Notify device presence event to the app.
+ *
+ * @deprecated Use {@link #notifyDevicePresenceEvent(int, String, DevicePresenceEvent)} instead.
+ */
+ @Deprecated
+ private void legacyNotifyDevicePresenceEvent(AssociationInfo association,
+ boolean isAppeared) {
+ Slog.i(TAG, "legacyNotifyDevicePresenceEvent() association=[" + association.toShortString()
+ + "], isAppeared=[" + isAppeared + "]");
+
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+
+ final CompanionServiceConnector primaryServiceConnector =
+ mCompanionAppBinder.getPrimaryServiceConnector(userId, packageName);
+ if (primaryServiceConnector == null) {
+ Slog.e(TAG, "Package is not bound.");
+ return;
+ }
+
+ if (isAppeared) {
+ primaryServiceConnector.postOnDeviceAppeared(association);
+ } else {
+ primaryServiceConnector.postOnDeviceDisappeared(association);
+ }
+ }
+
+ /**
+ * Notify the device presence event to the app.
+ */
+ private void notifyDevicePresenceEvent(int userId, String packageName,
+ DevicePresenceEvent event) {
+ Slog.i(TAG,
+ "notifyCompanionDevicePresenceEvent userId=[" + userId + "], packageName=["
+ + packageName + "], event=[" + event + "]...");
+
+ final CompanionServiceConnector primaryServiceConnector =
+ mCompanionAppBinder.getPrimaryServiceConnector(userId, packageName);
+
+ if (primaryServiceConnector == null) {
+ Slog.e(TAG, "Package is NOT bound.");
+ return;
+ }
+
+ primaryServiceConnector.postOnDevicePresenceEvent(event);
+ }
+
+ /**
+ * Notify the self-managed device presence event to the app.
+ */
+ public void notifySelfManagedDevicePresenceEvent(int associationId, boolean isAppeared) {
+ Slog.i(TAG, "notifySelfManagedDeviceAppeared() id=" + associationId);
+
+ AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
+ if (!association.isSelfManaged()) {
+ throw new IllegalArgumentException("Association id=[" + associationId
+ + "] is not self-managed.");
+ }
+ // AssociationInfo class is immutable: create a new AssociationInfo object with updated
+ // timestamp.
+ association = (new AssociationInfo.Builder(association))
+ .setLastTimeConnected(System.currentTimeMillis())
+ .build();
+ mAssociationStore.updateAssociation(association);
+
+ if (isAppeared) {
+ onSelfManagedDeviceConnected(associationId);
+ } else {
+ onSelfManagedDeviceDisconnected(associationId);
+ }
+
+ 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, isAppeared);
+ }
+ }
+
+ private void onBinderDied(@UserIdInt int userId, @NonNull String packageName,
+ @NonNull CompanionServiceConnector serviceConnector) {
+
+ boolean isPrimary = serviceConnector.isPrimary();
+ Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary);
+
+ // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY.
+ if (isPrimary) {
+ final List<AssociationInfo> associations =
+ mAssociationStore.getActiveAssociationsByPackage(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;
+ }
+ }
+
+ mCompanionAppBinder.removePackage(userId, packageName);
+ }
+
+ // Second: schedule rebinding if needed.
+ final boolean shouldScheduleRebind = shouldScheduleRebind(userId, packageName, isPrimary);
+
+ if (shouldScheduleRebind) {
+ mCompanionAppBinder.scheduleRebinding(userId, packageName, serviceConnector);
+ }
+ }
+
+ /**
+ * Check if the system should rebind the self-managed secondary services
+ * OR non-self-managed services.
+ */
+ private boolean shouldScheduleRebind(int userId, String packageName, boolean isPrimary) {
+ // Make sure do not schedule rebind for the case ServiceConnector still gets callback after
+ // app is uninstalled.
+ boolean stillAssociated = false;
+ // Make sure to clean up the state for all the associations
+ // that associate with this package.
+ boolean shouldScheduleRebind = false;
+ boolean shouldScheduleRebindForUuid = false;
+ final List<ObservableUuid> uuids =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+
+ for (AssociationInfo ai :
+ mAssociationStore.getActiveAssociationsByPackage(userId, packageName)) {
+ final int associationId = ai.getId();
+ stillAssociated = true;
+ if (ai.isSelfManaged()) {
+ // Do not rebind if primary one is died for selfManaged application.
+ if (isPrimary && isDevicePresent(associationId)) {
+ onSelfManagedDeviceReporterBinderDied(associationId);
+ shouldScheduleRebind = false;
+ }
+ // Do not rebind if both primary and secondary services are died for
+ // selfManaged application.
+ shouldScheduleRebind = mCompanionAppBinder.isCompanionApplicationBound(userId,
+ packageName);
+ } else if (ai.isNotifyOnDeviceNearby()) {
+ // Always rebind for non-selfManaged devices.
+ shouldScheduleRebind = true;
+ }
+ }
+
+ for (ObservableUuid uuid : uuids) {
+ if (isDeviceUuidPresent(uuid.getUuid())) {
+ shouldScheduleRebindForUuid = true;
+ break;
+ }
+ }
+
+ return (stillAssociated && shouldScheduleRebind) || shouldScheduleRebindForUuid;
+ }
+
+ /**
+ * Implements
+ * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
+ */
+ @Override
+ public void onAssociationRemoved(@NonNull AssociationInfo association) {
+ final int id = association.getId();
+ if (DEBUG) {
+ Log.i(TAG, "onAssociationRemoved() id=" + id);
+ Log.d(TAG, " > association=" + association);
+ }
+
+ mConnectedBtDevices.remove(id);
+ mNearbyBleDevices.remove(id);
+ mReportedSelfManagedDevices.remove(id);
+ mSimulated.remove(id);
+ synchronized (mBtDisconnectedDevices) {
+ mBtDisconnectedDevices.remove(id);
+ mBtDisconnectedDevicesBlePresence.delete(id);
+ }
+
+ // Do NOT call mCallback.onDeviceDisappeared()!
+ // CompanionDeviceManagerService will know that the association is removed, and will do
+ // what's needed.
+ }
+
+ /**
+ * Return a set of devices that pending to report connectivity
+ */
+ public SparseArray<Set<BluetoothDevice>> getPendingConnectedDevices() {
+ synchronized (mBtConnectionListener.mPendingConnectedDevices) {
+ return mBtConnectionListener.mPendingConnectedDevices;
+ }
+ }
+
+ private static void enforceCallerShellOrRoot() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == SHELL_UID || callingUid == ROOT_UID) return;
+
+ throw new SecurityException("Caller is neither Shell nor Root");
+ }
+
+ /**
+ * The BLE scan can be only stopped if all the devices have been reported
+ * BT connected and BLE presence and are not pending to report BLE lost.
+ */
+ private boolean canStopBleScan() {
+ for (AssociationInfo ai : mAssociationStore.getActiveAssociations()) {
+ int id = ai.getId();
+ synchronized (mBtDisconnectedDevices) {
+ if (ai.isNotifyOnDeviceNearby() && !(isBtConnected(id)
+ && isBlePresent(id) && mBtDisconnectedDevices.isEmpty())) {
+ Slog.i(TAG, "The BLE scan cannot be stopped, "
+ + "device( " + id + " ) is not yet connected "
+ + "OR the BLE is not current present Or is pending to report BLE lost");
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Dumps system information about devices that are marked as "present".
+ */
+ public void dump(@NonNull PrintWriter out) {
+ out.append("Companion Device Present: ");
+ if (mConnectedBtDevices.isEmpty()
+ && mNearbyBleDevices.isEmpty()
+ && mReportedSelfManagedDevices.isEmpty()) {
+ out.append("<empty>\n");
+ return;
+ } else {
+ out.append("\n");
+ }
+
+ out.append(" Connected Bluetooth Devices: ");
+ if (mConnectedBtDevices.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (int associationId : mConnectedBtDevices) {
+ AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+ out.append(" ").append(a.toShortString()).append('\n');
+ }
+ }
+
+ out.append(" Nearby BLE Devices: ");
+ if (mNearbyBleDevices.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (int associationId : mNearbyBleDevices) {
+ AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+ out.append(" ").append(a.toShortString()).append('\n');
+ }
+ }
+
+ out.append(" Self-Reported Devices: ");
+ if (mReportedSelfManagedDevices.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (int associationId : mReportedSelfManagedDevices) {
+ AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+ out.append(" ").append(a.toShortString()).append('\n');
+ }
+ }
+ }
+
+ private class SimulatedDevicePresenceSchedulerHelper extends Handler {
+ SimulatedDevicePresenceSchedulerHelper() {
+ super(Looper.getMainLooper());
+ }
+
+ void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
+ // First, unschedule if it was scheduled previously.
+ if (hasMessages(/* what */ associationId)) {
+ removeMessages(/* what */ associationId);
+ }
+
+ sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */);
+ }
+
+ void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
+ removeMessages(/* what */ associationId);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ final int associationId = msg.what;
+ if (mSimulated.contains(associationId)) {
+ onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_DISAPPEARED);
+ }
+ }
+ }
+
+ private class BleDeviceDisappearedScheduler extends Handler {
+ BleDeviceDisappearedScheduler() {
+ super(Looper.getMainLooper());
+ }
+
+ void scheduleBleDeviceDisappeared(int associationId) {
+ if (hasMessages(associationId)) {
+ removeMessages(associationId);
+ }
+ Slog.i(TAG, "scheduleBleDeviceDisappeared for Device: ( " + associationId + " ).");
+ sendEmptyMessageDelayed(associationId, 10 * 1000 /* 10 seconds */);
+ }
+
+ void unScheduleDeviceDisappeared(int associationId) {
+ if (hasMessages(associationId)) {
+ Slog.i(TAG, "unScheduleDeviceDisappeared for Device( " + associationId + " )");
+ synchronized (mBtDisconnectedDevices) {
+ mBtDisconnectedDevices.remove(associationId);
+ mBtDisconnectedDevicesBlePresence.delete(associationId);
+ }
+
+ removeMessages(associationId);
+ }
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ final int associationId = msg.what;
+ synchronized (mBtDisconnectedDevices) {
+ final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(
+ associationId);
+ // If a device hasn't reported after 10 seconds and is not currently present,
+ // assume BLE is lost and trigger the onDeviceEvent callback with the
+ // EVENT_BLE_DISAPPEARED event.
+ if (mBtDisconnectedDevices.contains(associationId)
+ && !isCurrentPresent) {
+ Slog.i(TAG, "Device ( " + associationId + " ) is likely BLE out of range, "
+ + "sending callback with event ( " + EVENT_BLE_DISAPPEARED + " )");
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
+ }
+
+ mBtDisconnectedDevices.remove(associationId);
+ mBtDisconnectedDevicesBlePresence.delete(associationId);
+ }
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
index db15da2922cf..fa0f6bd92acb 100644
--- a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
+++ b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
@@ -300,4 +300,18 @@ public class ObservableUuidStore {
return readObservableUuidsFromCache(userId);
}
}
+
+ /**
+ * Check if a UUID is being observed by the package.
+ */
+ public boolean isUuidBeingObserved(ParcelUuid uuid, int userId, String packageName) {
+ final List<ObservableUuid> uuidsBeingObserved = getObservableUuidsForPackage(userId,
+ packageName);
+ for (ObservableUuid observableUuid : uuidsBeingObserved) {
+ if (observableUuid.getUuid().equals(uuid)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 793fb7ff74b1..697ef87b5a12 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -46,7 +46,6 @@ import java.util.concurrent.Future;
@SuppressLint("LongLogTag")
public class CompanionTransportManager {
private static final String TAG = "CDM_CompanionTransportManager";
- private static final boolean DEBUG = false;
private boolean mSecureTransportEnabled = true;
@@ -137,11 +136,17 @@ public class CompanionTransportManager {
}
}
- public void attachSystemDataTransport(String packageName, int userId, int associationId,
- ParcelFileDescriptor fd) {
+ /**
+ * Attach transport.
+ */
+ public void attachSystemDataTransport(int associationId, ParcelFileDescriptor fd) {
+ Slog.i(TAG, "Attaching transport for association id=[" + associationId + "]...");
+
+ mAssociationStore.getAssociationWithCallerChecks(associationId);
+
synchronized (mTransports) {
if (mTransports.contains(associationId)) {
- detachSystemDataTransport(packageName, userId, associationId);
+ detachSystemDataTransport(associationId);
}
// TODO: Implement new API to pass a PSK
@@ -149,9 +154,18 @@ public class CompanionTransportManager {
notifyOnTransportsChanged();
}
+
+ Slog.i(TAG, "Transport attached.");
}
- public void detachSystemDataTransport(String packageName, int userId, int associationId) {
+ /**
+ * Detach transport.
+ */
+ public void detachSystemDataTransport(int associationId) {
+ Slog.i(TAG, "Detaching transport for association id=[" + associationId + "]...");
+
+ mAssociationStore.getAssociationWithCallerChecks(associationId);
+
synchronized (mTransports) {
final Transport transport = mTransports.removeReturnOld(associationId);
if (transport == null) {
@@ -161,6 +175,8 @@ public class CompanionTransportManager {
transport.stop();
notifyOnTransportsChanged();
}
+
+ Slog.i(TAG, "Transport detached.");
}
private void notifyOnTransportsChanged() {
@@ -307,8 +323,7 @@ public class CompanionTransportManager {
int associationId = transport.mAssociationId;
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
if (association != null) {
- detachSystemDataTransport(association.getPackageName(),
- association.getUserId(),
+ detachSystemDataTransport(
association.getId());
}
}
diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
index 2cf1f462a7d1..d7e766eed209 100644
--- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -39,7 +39,6 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.content.Context;
@@ -208,7 +207,7 @@ public final class PermissionsUtils {
/**
* Require the caller to hold necessary permission to observe device presence by UUID.
*/
- public static void enforceCallerCanObservingDevicePresenceByUuid(@NonNull Context context) {
+ public static void enforceCallerCanObserveDevicePresenceByUuid(@NonNull Context context) {
if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
!= PERMISSION_GRANTED) {
throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
@@ -235,23 +234,6 @@ public final class PermissionsUtils {
return checkCallerCanManageCompanionDevice(context);
}
- /**
- * Check if CDM can trust the context to process the association.
- */
- @Nullable
- public static AssociationInfo sanitizeWithCallerChecks(@NonNull Context context,
- @Nullable AssociationInfo association) {
- if (association == null) return null;
-
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
- if (!checkCallerCanManageAssociationsForPackage(context, userId, packageName)) {
- return null;
- }
-
- return association;
- }
-
private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) {
try {
return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 272e84b80870..f7ed702f1044 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -163,7 +163,6 @@ final class ActivityManagerConstants extends ContentObserver {
static final String KEY_USE_TIERED_CACHED_ADJ = "use_tiered_cached_adj";
static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time";
- static final String KEY_USE_MODERN_TRIM = "use_modern_trim";
/**
* Whether or not to enable the new oom adjuster implementation.
@@ -239,8 +238,6 @@ final class ActivityManagerConstants extends ContentObserver {
private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false;
private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000;
- private static final boolean DEFAULT_USE_MODERN_TRIM = true;
-
/**
* The default value to {@link #KEY_ENABLE_NEW_OOMADJ}.
*/
@@ -1136,9 +1133,6 @@ final class ActivityManagerConstants extends ContentObserver {
/** @see #KEY_TIERED_CACHED_ADJ_DECAY_TIME */
public long TIERED_CACHED_ADJ_DECAY_TIME = DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME;
- /** @see #KEY_USE_MODERN_TRIM */
- public boolean USE_MODERN_TRIM = DEFAULT_USE_MODERN_TRIM;
-
/** @see #KEY_ENABLE_NEW_OOMADJ */
public boolean ENABLE_NEW_OOMADJ = DEFAULT_ENABLE_NEW_OOM_ADJ;
@@ -1343,9 +1337,6 @@ final class ActivityManagerConstants extends ContentObserver {
case KEY_TIERED_CACHED_ADJ_DECAY_TIME:
updateUseTieredCachedAdj();
break;
- case KEY_USE_MODERN_TRIM:
- updateUseModernTrim();
- break;
case KEY_DISABLE_APP_PROFILER_PSS_PROFILING:
updateDisableAppProfilerPssProfiling();
break;
@@ -2233,13 +2224,6 @@ final class ActivityManagerConstants extends ContentObserver {
DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME);
}
- private void updateUseModernTrim() {
- USE_MODERN_TRIM = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- KEY_USE_MODERN_TRIM,
- DEFAULT_USE_MODERN_TRIM);
- }
-
private void updateEnableNewOomAdj() {
ENABLE_NEW_OOMADJ = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 48daef801245..51aae771542e 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1357,7 +1357,7 @@ public class AppProfiler {
}
@GuardedBy({"mService", "mProcLock"})
- boolean updateLowMemStateLSP(int numCached, int numEmpty, int numTrimming, long now) {
+ void updateLowMemStateLSP(int numCached, int numEmpty, int numTrimming, long now) {
int memFactor;
if (mLowMemDetector != null && mLowMemDetector.isAvailable()) {
memFactor = mLowMemDetector.getMemFactor();
@@ -1422,114 +1422,37 @@ public class AppProfiler {
mLastMemoryLevel = memFactor;
mLastNumProcesses = mService.mProcessList.getLruSizeLOSP();
- if (mService.mConstants.USE_MODERN_TRIM) {
- // Modern trim is not sent based on lowmem state
- // Dispatch UI_HIDDEN to processes that need it
- mService.mProcessList.forEachLruProcessesLOSP(true, app -> {
- final ProcessProfileRecord profile = app.mProfile;
- final IApplicationThread thread;
- final ProcessStateRecord state = app.mState;
- if (state.hasProcStateChanged()) {
- state.setProcStateChanged(false);
- }
- int procState = app.mState.getCurProcState();
- if (((procState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
- && procState < ActivityManager.PROCESS_STATE_CACHED_ACTIVITY)
- || app.mState.isSystemNoUi()) && app.mProfile.hasPendingUiClean()) {
- // If this application is now in the background and it
- // had done UI, then give it the special trim level to
- // have it free UI resources.
- if ((thread = app.getThread()) != null) {
- try {
- thread.scheduleTrimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
- app.mProfile.setPendingUiClean(false);
- } catch (RemoteException e) {
- }
+ // Dispatch UI_HIDDEN to processes that need it
+ mService.mProcessList.forEachLruProcessesLOSP(
+ true,
+ app -> {
+ final ProcessProfileRecord profile = app.mProfile;
+ final IApplicationThread thread;
+ final ProcessStateRecord state = app.mState;
+ if (state.hasProcStateChanged()) {
+ state.setProcStateChanged(false);
}
- }
- });
- return false;
- }
+ int procState = app.mState.getCurProcState();
+ if (((procState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+ && procState
+ < ActivityManager.PROCESS_STATE_CACHED_ACTIVITY)
+ || app.mState.isSystemNoUi())
+ && app.mProfile.hasPendingUiClean()) {
+ // If this application is now in the background and it
+ // had done UI, then give it the special trim level to
+ // have it free UI resources.
+ if ((thread = app.getThread()) != null) {
+ try {
+ thread.scheduleTrimMemory(
+ ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+ app.mProfile.setPendingUiClean(false);
+ } catch (RemoteException e) {
- if (memFactor != ADJ_MEM_FACTOR_NORMAL) {
- if (mLowRamStartTime == 0) {
- mLowRamStartTime = now;
- }
- int fgTrimLevel;
- switch (memFactor) {
- case ADJ_MEM_FACTOR_CRITICAL:
- fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
- break;
- case ADJ_MEM_FACTOR_LOW:
- fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
- break;
- default:
- fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;
- break;
- }
- int factor = numTrimming / 3;
- int minFactor = 2;
- if (mHasHomeProcess) minFactor++;
- if (mHasPreviousProcess) minFactor++;
- if (factor < minFactor) factor = minFactor;
- final int actualFactor = factor;
- final int[] step = {0};
- final int[] curLevel = {ComponentCallbacks2.TRIM_MEMORY_COMPLETE};
- mService.mProcessList.forEachLruProcessesLOSP(true, app -> {
- final ProcessProfileRecord profile = app.mProfile;
- final int trimMemoryLevel = profile.getTrimMemoryLevel();
- final ProcessStateRecord state = app.mState;
- final int curProcState = state.getCurProcState();
- IApplicationThread thread;
- if (allChanged || state.hasProcStateChanged()) {
- mService.setProcessTrackerStateLOSP(app, trackerMemFactor);
- state.setProcStateChanged(false);
- }
- trimMemoryUiHiddenIfNecessaryLSP(app);
- if (curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.isKilledByAm()) {
- scheduleTrimMemoryLSP(app, curLevel[0], "Trimming memory of ");
- profile.setTrimMemoryLevel(curLevel[0]);
- step[0]++;
- if (step[0] >= actualFactor) {
- step[0] = 0;
- switch (curLevel[0]) {
- case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
- curLevel[0] = ComponentCallbacks2.TRIM_MEMORY_MODERATE;
- break;
- case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
- curLevel[0] = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
- break;
+ }
}
}
- } else if (curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
- && !app.isKilledByAm()) {
- scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
- "Trimming memory of heavy-weight ");
- profile.setTrimMemoryLevel(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
- } else {
- scheduleTrimMemoryLSP(app, fgTrimLevel, "Trimming memory of fg ");
- profile.setTrimMemoryLevel(fgTrimLevel);
- }
- });
- } else {
- if (mLowRamStartTime != 0) {
- mLowRamTimeSinceLastIdle += now - mLowRamStartTime;
- mLowRamStartTime = 0;
- }
- mService.mProcessList.forEachLruProcessesLOSP(true, app -> {
- final ProcessProfileRecord profile = app.mProfile;
- final IApplicationThread thread;
- final ProcessStateRecord state = app.mState;
- if (allChanged || state.hasProcStateChanged()) {
- mService.setProcessTrackerStateLOSP(app, trackerMemFactor);
- state.setProcStateChanged(false);
- }
- trimMemoryUiHiddenIfNecessaryLSP(app);
- profile.setTrimMemoryLevel(0);
- });
- }
- return allChanged;
+ });
}
@GuardedBy({"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 6e20f6cc877d..de27bc708ffd 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1461,8 +1461,7 @@ public final class CachedAppOptimizer {
return;
}
- if (mAm.mConstants.USE_MODERN_TRIM
- && app.mState.getSetAdj() >= ProcessList.CACHED_APP_MIN_ADJ) {
+ if (app.mState.getSetAdj() >= ProcessList.CACHED_APP_MIN_ADJ) {
final IApplicationThread thread = app.getThread();
if (thread != null) {
try {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 1a7629f3182d..5e91cd39b698 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1090,7 +1090,7 @@ public class OomAdjuster {
mNumNonCachedProcs = 0;
mNumCachedHiddenProcs = 0;
- final boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
+ updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
oomAdjReason, doingAll);
mNumServiceProcs = mNewNumServiceProcs;
@@ -1100,11 +1100,6 @@ public class OomAdjuster {
mService.mAtmInternal.scheduleDestroyAllActivities("always-finish");
}
- if (allChanged) {
- mService.mAppProfiler.requestPssAllProcsLPr(now, false,
- mService.mProcessStats.isMemFactorLowered());
- }
-
updateUidsLSP(activeUids, nowElapsed);
synchronized (mService.mProcessStats.mLock) {
@@ -1300,7 +1295,7 @@ public class OomAdjuster {
}
@GuardedBy({"mService", "mProcLock"})
- private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
+ private void updateAndTrimProcessLSP(final long now, final long nowElapsed,
final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason,
boolean doingAll) {
ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
@@ -1450,7 +1445,7 @@ public class OomAdjuster {
mLastFreeSwapPercent = freeSwapPercent;
- return mService.mAppProfiler.updateLowMemStateLSP(numCached, numEmpty, numTrimming, now);
+ mService.mAppProfiler.updateLowMemStateLSP(numCached, numEmpty, numTrimming, now);
}
@GuardedBy({"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 9950d8ff42ed..da26209ad495 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -1097,13 +1097,34 @@ public class DisplayDeviceConfig {
return mBrightnessToBacklightSpline.interpolate(brightness);
}
- private float getBrightnessFromBacklight(float brightness) {
+ /**
+ * Calculates the screen brightness value - as used among the system from the HAL backlight
+ * level
+ * @param backlight value from 0-1 HAL scale
+ * @return brightness value from 0-1 framework scale
+ */
+ public float getBrightnessFromBacklight(float backlight) {
if (mLowBrightnessData != null) {
- return mLowBrightnessData.mBacklightToBrightness.interpolate(brightness);
+ return mLowBrightnessData.mBacklightToBrightness.interpolate(backlight);
}
- return mBacklightToBrightnessSpline.interpolate(brightness);
+ return mBacklightToBrightnessSpline.interpolate(backlight);
}
+ /**
+ *
+ * @return low brightness mode transition point
+ */
+ public float getLowBrightnessTransitionPoint() {
+ if (mLowBrightnessData == null) {
+ return PowerManager.BRIGHTNESS_MIN;
+ }
+ return mLowBrightnessData.mTransitionPoint;
+ }
+
+ /**
+ *
+ * @return HAL backlight mapping to framework brightness
+ */
private Spline getBacklightToBrightnessSpline() {
if (mLowBrightnessData != null) {
return mLowBrightnessData.mBacklightToBrightness;
@@ -1133,7 +1154,12 @@ public class DisplayDeviceConfig {
return mBacklightToNitsSpline.interpolate(backlight);
}
- private float getBacklightFromNits(float nits) {
+ /**
+ *
+ * @param nits - display brightness
+ * @return corresponding HAL backlight value
+ */
+ public float getBacklightFromNits(float nits) {
if (mLowBrightnessData != null) {
return mLowBrightnessData.mNitsToBacklight.interpolate(nits);
}
@@ -1148,6 +1174,18 @@ public class DisplayDeviceConfig {
}
/**
+ *
+ * @param lux - ambient brightness
+ * @return minimum allowed nits, given the lux.
+ */
+ public float getMinNitsFromLux(float lux) {
+ if (mLowBrightnessData == null) {
+ return INVALID_NITS;
+ }
+ return mLowBrightnessData.mMinLuxToNits.interpolate(lux);
+ }
+
+ /**
* @return true if there is sdrHdrRatioMap, false otherwise.
*/
public boolean hasSdrToHdrRatioSpline() {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 0807cc056a39..d99f7121a14c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1336,12 +1336,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
&& (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
|| userSetBrightnessChanged);
- mBrightnessRangeController.setAutoBrightnessEnabled(
- mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
+ final int autoBrightnessState = mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
: mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff()
? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
- : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
+ : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
+
+ mBrightnessRangeController.setAutoBrightnessEnabled(autoBrightnessState);
+ mBrightnessClamperController.setAutoBrightnessState(autoBrightnessState);
boolean updateScreenBrightnessSetting =
displayBrightnessState.shouldUpdateScreenBrightnessSetting();
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index d8a45009f236..9c7504db0cf0 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -66,6 +66,7 @@ public class BrightnessClamperController {
private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
@Nullable
private Type mClamperType = null;
+ private int mAutoBrightnessState = -1;
private boolean mClamperApplied = false;
@@ -94,7 +95,8 @@ public class BrightnessClamperController {
mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags,
context);
- mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListener);
+ mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListener,
+ data.mDisplayDeviceConfig);
mOnPropertiesChangedListener =
properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
start();
@@ -197,6 +199,19 @@ public class BrightnessClamperController {
mModifiers.forEach(modifier -> modifier.onAmbientLuxChange(ambientLux));
}
+ /**
+ * Sets the autobrightness state for clampers that need to be aware of the state.
+ * @param state autobrightness state
+ */
+ public void setAutoBrightnessState(int state) {
+ if (state == mAutoBrightnessState) {
+ return;
+ }
+ mModifiers.forEach(modifier -> modifier.setAutoBrightnessState(state));
+ mAutoBrightnessState = state;
+ recalculateBrightnessCap();
+ }
+
// Called in DisplayControllerHandler
private void recalculateBrightnessCap() {
float brightnessCap = PowerManager.BRIGHTNESS_MAX;
@@ -265,12 +280,14 @@ public class BrightnessClamperController {
}
List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
- Handler handler, ClamperChangeListener listener) {
+ Handler handler, ClamperChangeListener listener,
+ DisplayDeviceConfig displayDeviceConfig) {
List<BrightnessStateModifier> modifiers = new ArrayList<>();
modifiers.add(new DisplayDimModifier(context));
modifiers.add(new BrightnessLowPowerModeModifier());
if (flags.isEvenDimmerEnabled()) {
- modifiers.add(new BrightnessLowLuxModifier(handler, listener, context));
+ modifiers.add(new BrightnessLowLuxModifier(handler, listener, context,
+ displayDeviceConfig));
}
return modifiers;
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
index a91bb59b0bc0..7f88c3029820 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java
@@ -16,13 +16,14 @@
package com.android.server.display.brightness.clamper;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.hardware.display.DisplayManagerInternal;
import android.net.Uri;
import android.os.Handler;
-import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
@@ -30,6 +31,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.utils.DebugUtils;
@@ -45,19 +47,23 @@ public class BrightnessLowLuxModifier extends BrightnessModifier {
// 'adb shell setprop persist.log.tag.BrightnessLowLuxModifier DEBUG && adb reboot'
private static final String TAG = "BrightnessLowLuxModifier";
private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
- private static final float MIN_NITS = 2.0f;
+ private static final float MIN_NITS_DEFAULT = 0.2f;
private final SettingsObserver mSettingsObserver;
private final ContentResolver mContentResolver;
private final Handler mHandler;
private final BrightnessClamperController.ClamperChangeListener mChangeListener;
private int mReason;
private float mBrightnessLowerBound;
+ private float mMinNitsAllowed;
private boolean mIsActive;
+ private boolean mAutoBrightnessEnabled;
private float mAmbientLux;
+ private final DisplayDeviceConfig mDisplayDeviceConfig;
@VisibleForTesting
BrightnessLowLuxModifier(Handler handler,
- BrightnessClamperController.ClamperChangeListener listener, Context context) {
+ BrightnessClamperController.ClamperChangeListener listener, Context context,
+ DisplayDeviceConfig displayDeviceConfig) {
super();
mChangeListener = listener;
@@ -67,6 +73,8 @@ public class BrightnessLowLuxModifier extends BrightnessModifier {
mHandler.post(() -> {
start();
});
+
+ mDisplayDeviceConfig = displayDeviceConfig;
}
/**
@@ -78,41 +86,45 @@ public class BrightnessLowLuxModifier extends BrightnessModifier {
int userId = UserHandle.USER_CURRENT;
float settingNitsLowerBound = Settings.Secure.getFloatForUser(
mContentResolver, Settings.Secure.EVEN_DIMMER_MIN_NITS,
- /* def= */ MIN_NITS, userId);
+ /* def= */ MIN_NITS_DEFAULT, userId);
boolean isActive = Settings.Secure.getFloatForUser(mContentResolver,
Settings.Secure.EVEN_DIMMER_ACTIVATED,
- /* def= */ 0, userId) == 1.0f;
-
- // TODO: luxBasedNitsLowerBound = mMinLuxToNitsSpline(currentLux);
- float luxBasedNitsLowerBound = 2.0f;
-
- final float nitsLowerBound = isActive ? Math.max(settingNitsLowerBound,
- luxBasedNitsLowerBound) : MIN_NITS;
-
- final int reason = settingNitsLowerBound > luxBasedNitsLowerBound
- ? BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND
- : BrightnessReason.MODIFIER_MIN_LUX;
-
- // TODO: brightnessLowerBound = nitsToBrightnessSpline(nitsLowerBound);
- final float brightnessLowerBound = PowerManager.BRIGHTNESS_MIN;
+ /* def= */ 0, userId) == 1.0f && mAutoBrightnessEnabled;
+
+ float luxBasedNitsLowerBound = mDisplayDeviceConfig.getMinNitsFromLux(mAmbientLux);
+
+ final int reason;
+ float minNitsAllowed = -1f; // undefined, if setting is off.
+ final float minBrightnessAllowed;
+
+ if (isActive) {
+ minNitsAllowed = Math.max(settingNitsLowerBound,
+ luxBasedNitsLowerBound);
+ minBrightnessAllowed = getBrightnessFromNits(minNitsAllowed);
+ reason = settingNitsLowerBound > luxBasedNitsLowerBound
+ ? BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND
+ : BrightnessReason.MODIFIER_MIN_LUX;
+ } else {
+ minBrightnessAllowed = mDisplayDeviceConfig.getLowBrightnessTransitionPoint();
+ reason = 0;
+ }
- if (mBrightnessLowerBound != brightnessLowerBound
+ if (mBrightnessLowerBound != minBrightnessAllowed
|| mReason != reason
|| mIsActive != isActive) {
mIsActive = isActive;
mReason = reason;
if (DEBUG) {
Slog.i(TAG, "isActive: " + isActive
- + ", brightnessLowerBound: " + brightnessLowerBound
+ + ", minBrightnessAllowed: " + minBrightnessAllowed
+ ", mAmbientLux: " + mAmbientLux
- + ", mReason: " + (
- mReason == BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND ? "minSetting"
- : "lux")
- + ", nitsLowerBound: " + nitsLowerBound
+ + ", mReason: " + (mReason)
+ + ", minNitsAllowed: " + minNitsAllowed
);
}
- mBrightnessLowerBound = brightnessLowerBound;
+ mMinNitsAllowed = minNitsAllowed;
+ mBrightnessLowerBound = minBrightnessAllowed;
mChangeListener.onChanged();
}
}
@@ -177,11 +189,23 @@ public class BrightnessLowLuxModifier extends BrightnessModifier {
}
@Override
+ public void setAutoBrightnessState(int state) {
+ mAutoBrightnessEnabled = state == AUTO_BRIGHTNESS_ENABLED;
+ }
+
+ @Override
public void dump(PrintWriter pw) {
pw.println("BrightnessLowLuxModifier:");
pw.println(" mIsActive=" + mIsActive);
pw.println(" mBrightnessLowerBound=" + mBrightnessLowerBound);
pw.println(" mReason=" + mReason);
+ pw.println(" mAmbientLux=" + mAmbientLux);
+ pw.println(" mMinNitsAllowed=" + mMinNitsAllowed);
+ }
+
+ private float getBrightnessFromNits(float nits) {
+ return mDisplayDeviceConfig.getBrightnessFromBacklight(
+ mDisplayDeviceConfig.getBacklightFromNits(nits));
}
private final class SettingsObserver extends ContentObserver {
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
index 2a3dd8752615..db5a524da71d 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
@@ -73,4 +73,9 @@ abstract class BrightnessModifier implements BrightnessStateModifier {
public void onAmbientLuxChange(float ambientLux) {
// do nothing
}
+
+ @Override
+ public void setAutoBrightnessState(int state) {
+ // do nothing
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java
index 22342581fa8b..1606159cb247 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessStateModifier.java
@@ -48,4 +48,10 @@ public interface BrightnessStateModifier {
* @param ambientLux current debounced lux.
*/
void onAmbientLuxChange(float ambientLux);
+
+ /**
+ * Sets the autobrightness state for clampers that need to be aware of the state.
+ * @param state autobrightness state
+ */
+ void setAutoBrightnessState(int state);
}
diff --git a/services/core/java/com/android/server/display/config/LowBrightnessData.java b/services/core/java/com/android/server/display/config/LowBrightnessData.java
index aa82533bf6a7..1a4e807fece6 100644
--- a/services/core/java/com/android/server/display/config/LowBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/LowBrightnessData.java
@@ -66,11 +66,13 @@ public class LowBrightnessData {
* Spline, mapping between backlight and brightness
*/
public final Spline mBacklightToBrightness;
+ public final Spline mMinLuxToNits;
@VisibleForTesting
public LowBrightnessData(float transitionPoint, float[] nits,
float[] backlight, float[] brightness, Spline backlightToNits,
- Spline nitsToBacklight, Spline brightnessToBacklight, Spline backlightToBrightness) {
+ Spline nitsToBacklight, Spline brightnessToBacklight, Spline backlightToBrightness,
+ Spline minLuxToNits) {
mTransitionPoint = transitionPoint;
mNits = nits;
mBacklight = backlight;
@@ -79,6 +81,7 @@ public class LowBrightnessData {
mNitsToBacklight = nitsToBacklight;
mBrightnessToBacklight = brightnessToBacklight;
mBacklightToBrightness = backlightToBrightness;
+ mMinLuxToNits = minLuxToNits;
}
@Override
@@ -92,6 +95,7 @@ public class LowBrightnessData {
+ ", mNitsToBacklight: " + mNitsToBacklight
+ ", mBrightnessToBacklight: " + mBrightnessToBacklight
+ ", mBacklightToBrightness: " + mBacklightToBrightness
+ + ", mMinLuxToNits: " + mMinLuxToNits
+ "} ";
}
@@ -132,11 +136,40 @@ public class LowBrightnessData {
brightness[i] = brightnessList.get(i);
}
+ final NitsMap map = lbm.getLuxToMinimumNitsMap();
+ if (map == null) {
+ Slog.e(TAG, "Invalid min lux to nits mapping");
+ return null;
+ }
+ final List<Point> points = map.getPoint();
+ final int size = points.size();
+
+ float[] minLux = new float[size];
+ float[] minNits = new float[size];
+
+ int i = 0;
+ for (Point point : points) {
+ minLux[i] = point.getValue().floatValue();
+ minNits[i] = point.getNits().floatValue();
+ if (i > 0) {
+ if (minLux[i] < minLux[i - 1]) {
+ Slog.e(TAG, "minLuxToNitsSpline must be non-decreasing, ignoring rest "
+ + " of configuration. Value: " + minLux[i] + " < " + minLux[i - 1]);
+ }
+ if (minNits[i] < minNits[i - 1]) {
+ Slog.e(TAG, "minLuxToNitsSpline must be non-decreasing, ignoring rest "
+ + " of configuration. Nits: " + minNits[i] + " < " + minNits[i - 1]);
+ }
+ }
+ ++i;
+ }
+
return new LowBrightnessData(transitionPoints, nits, backlight, brightness,
Spline.createSpline(backlight, nits),
Spline.createSpline(nits, backlight),
Spline.createSpline(brightness, backlight),
- Spline.createSpline(backlight, brightness)
- );
+ Spline.createSpline(backlight, brightness),
+ Spline.createSpline(minLux, minNits)
+ );
}
}
diff --git a/services/core/java/com/android/server/inputmethod/LocaleUtils.java b/services/core/java/com/android/server/inputmethod/LocaleUtils.java
index 0b16af29da28..dbcd21a36ab6 100644
--- a/services/core/java/com/android/server/inputmethod/LocaleUtils.java
+++ b/services/core/java/com/android/server/inputmethod/LocaleUtils.java
@@ -46,29 +46,44 @@ final class LocaleUtils {
* @param desired The locale preferred by user.
* @return A score based on the locale matching for the default subtype enabling.
*/
- @IntRange(from = 1, to = 3)
+ @IntRange(from = 1, to = 4)
private static byte calculateMatchingSubScore(@NonNull final ULocale supported,
@NonNull final ULocale desired) {
// Assuming supported/desired is fully expanded.
if (supported.equals(desired)) {
- return 3; // Exact match.
+ return 4; // Exact match.
}
+ // addLikelySubtags is a maximization process as per
+ // https://www.unicode.org/reports/tr35/#Likely_Subtags
+ ULocale maxDesired = ULocale.addLikelySubtags(desired);
+
// Skip language matching since it was already done in calculateMatchingScore.
final String supportedScript = supported.getScript();
- if (supportedScript.isEmpty() || !supportedScript.equals(desired.getScript())) {
+ if (supportedScript.isEmpty() || !supportedScript.equals(maxDesired.getScript())) {
// TODO: Need subscript matching. For example, Hanb should match with Bopo.
return 1;
}
final String supportedCountry = supported.getCountry();
- if (supportedCountry.isEmpty() || !supportedCountry.equals(desired.getCountry())) {
+ if (supportedCountry.isEmpty() || !supportedCountry.equals(maxDesired.getCountry())) {
return 2;
}
// Ignore others e.g. variants, extensions.
- return 3;
+
+ // Since addLikelySubtags can canonicalize subtags, e.g. the deprecated country codes
+ // an locale with an identical script and country before addLikelySubtags is in favour,
+ // and a score of 4 is returned.
+ String desiredScript = desired.getScript();
+ String desiredCountry = desired.getCountry();
+ if ((desiredScript.isEmpty() || desiredScript.equals(maxDesired.getScript()))
+ && (desiredCountry.isEmpty() || desiredCountry.equals(maxDesired.getCountry()))) {
+ return 4;
+ } else {
+ return 3;
+ }
}
private static final class ScoreEntry implements Comparable<ScoreEntry> {
@@ -180,8 +195,7 @@ final class LocaleUtils {
ULocale.forLocale(preferredLocale));
}
score[j] = calculateMatchingSubScore(
- preferredULocaleCache[j],
- ULocale.addLikelySubtags(ULocale.forLocale(locale)));
+ preferredULocaleCache[j], ULocale.forLocale(locale));
if (canSkip && score[j] != 0) {
canSkip = false;
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index ae6a7e968b0f..ec15ff35676c 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -221,18 +221,27 @@ class MediaRouter2ServiceImpl {
// Start of methods that implement MediaRouter2 operations.
@NonNull
- public List<MediaRoute2Info> getSystemRoutes() {
+ public List<MediaRoute2Info> getSystemRoutes(@NonNull String callerPackageName,
+ boolean isProxyRouter) {
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
- final boolean hasSystemRoutingPermission = checkCallerHasSystemRoutingPermissions(pid, uid);
+
+ boolean hasSystemRoutingPermissions;
+ if (!isProxyRouter) {
+ hasSystemRoutingPermissions = checkCallerHasSystemRoutingPermissions(pid, uid);
+ } else {
+ // Request from ProxyRouter.
+ hasSystemRoutingPermissions =
+ checkCallerHasPrivilegedRoutingPermissions(pid, uid, callerPackageName);
+ }
final long token = Binder.clearCallingIdentity();
try {
Collection<MediaRoute2Info> systemRoutes;
synchronized (mLock) {
UserRecord userRecord = getOrCreateUserRecordLocked(userId);
- if (hasSystemRoutingPermission) {
+ if (hasSystemRoutingPermissions) {
MediaRoute2ProviderInfo providerInfo =
userRecord.mHandler.mSystemProvider.getProviderInfo();
if (providerInfo != null) {
@@ -795,12 +804,21 @@ class MediaRouter2ServiceImpl {
@Nullable
public RoutingSessionInfo getSystemSessionInfo(
- @Nullable String packageName, boolean setDeviceRouteSelected) {
+ @NonNull String callerPackageName,
+ @Nullable String targetPackageName,
+ boolean setDeviceRouteSelected) {
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
- final boolean hasSystemRoutingPermissions =
- checkCallerHasSystemRoutingPermissions(pid, uid);
+
+ boolean hasSystemRoutingPermissions;
+ if (targetPackageName == null) {
+ hasSystemRoutingPermissions = checkCallerHasSystemRoutingPermissions(pid, uid);
+ } else {
+ // Request from ProxyRouter.
+ hasSystemRoutingPermissions =
+ checkCallerHasPrivilegedRoutingPermissions(pid, uid, callerPackageName);
+ }
final long token = Binder.clearCallingIdentity();
try {
@@ -812,14 +830,14 @@ class MediaRouter2ServiceImpl {
// Return a fake system session that shows the device route as selected and
// available bluetooth routes as transferable.
return userRecord.mHandler.mSystemProvider
- .generateDeviceRouteSelectedSessionInfo(packageName);
+ .generateDeviceRouteSelectedSessionInfo(targetPackageName);
} else {
sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
if (!sessionInfos.isEmpty()) {
// Return a copy of the current system session with no modification,
// except setting the client package name.
return new RoutingSessionInfo.Builder(sessionInfos.get(0))
- .setClientPackageName(packageName)
+ .setClientPackageName(targetPackageName)
.build();
} else {
Slog.w(TAG, "System provider does not have any session info.");
@@ -828,7 +846,7 @@ class MediaRouter2ServiceImpl {
} else {
return new RoutingSessionInfo.Builder(
userRecord.mHandler.mSystemProvider.getDefaultSessionInfo())
- .setClientPackageName(packageName)
+ .setClientPackageName(targetPackageName)
.build();
}
}
@@ -843,6 +861,12 @@ class MediaRouter2ServiceImpl {
|| checkCallerHasBluetoothPermissions(pid, uid);
}
+ private boolean checkCallerHasPrivilegedRoutingPermissions(
+ int pid, int uid, @NonNull String callerPackageName) {
+ return checkMediaContentControlPermission(uid, pid)
+ || checkMediaRoutingControlPermission(uid, pid, callerPackageName);
+ }
+
private boolean checkCallerHasModifyAudioRoutingPermission(int pid, int uid) {
return mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_ROUTING, pid, uid)
== PackageManager.PERMISSION_GRANTED;
@@ -864,30 +888,29 @@ class MediaRouter2ServiceImpl {
Manifest.permission.MEDIA_CONTENT_CONTROL
})
private void enforcePrivilegedRoutingPermissions(
- int callerUid, int callerPid, @Nullable String callerPackageName) {
- if (hasMediaContentControlPermission(callerUid, callerPid)) {
+ int callerUid, int callerPid, @NonNull String callerPackageName) {
+ if (checkMediaContentControlPermission(callerUid, callerPid)) {
return;
}
- if (!Flags.enablePrivilegedRoutingForMediaRoutingControl()) {
- throw new SecurityException("Must hold MEDIA_CONTENT_CONTROL");
- }
-
if (!checkMediaRoutingControlPermission(callerUid, callerPid, callerPackageName)) {
throw new SecurityException(
"Must hold MEDIA_CONTENT_CONTROL or MEDIA_ROUTING_CONTROL permissions.");
}
}
- @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
- private boolean hasMediaContentControlPermission(int callerUid, int callerPid) {
+ private boolean checkMediaContentControlPermission(int callerUid, int callerPid) {
return mContext.checkPermission(
Manifest.permission.MEDIA_CONTENT_CONTROL, callerPid, callerUid)
== PackageManager.PERMISSION_GRANTED;
}
private boolean checkMediaRoutingControlPermission(
- int callerUid, int callerPid, @Nullable String callerPackageName) {
+ int callerUid, int callerPid, @NonNull String callerPackageName) {
+ if (!Flags.enablePrivilegedRoutingForMediaRoutingControl()) {
+ return false;
+ }
+
return PermissionChecker.checkPermissionForDataDelivery(
mContext,
Manifest.permission.MEDIA_ROUTING_CONTROL,
@@ -1520,7 +1543,7 @@ class MediaRouter2ServiceImpl {
boolean hasMediaRoutingControl =
checkMediaRoutingControlPermission(callerUid, callerPid, callerPackageName);
- boolean hasMediaContentControl = hasMediaContentControlPermission(callerUid, callerPid);
+ boolean hasMediaContentControl = checkMediaContentControlPermission(callerUid, callerPid);
Slog.i(
TAG,
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 6af3480989fa..76b8db685f52 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -411,15 +411,21 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
- public List<MediaRoute2Info> getSystemRoutes() {
- return mService2.getSystemRoutes();
+ public List<MediaRoute2Info> getSystemRoutes(@NonNull String callerPackageName,
+ boolean isProxyRouter) {
+ if (!validatePackageName(Binder.getCallingUid(), callerPackageName)) {
+ throw new SecurityException("callerPackageName does not match calling uid.");
+ }
+ return mService2.getSystemRoutes(callerPackageName, isProxyRouter);
}
// Binder call
@Override
public RoutingSessionInfo getSystemSessionInfo() {
return mService2.getSystemSessionInfo(
- null /* packageName */, false /* setDeviceRouteSelected */);
+ /* callerPackageName */ null,
+ /* targetPackageName */ null, /* setDeviceRouteSelected */
+ false);
}
// Binder call
@@ -530,16 +536,22 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
- public RoutingSessionInfo getSystemSessionInfoForPackage(@Nullable String packageName) {
+ public RoutingSessionInfo getSystemSessionInfoForPackage(
+ @NonNull String callerPackageName, @Nullable String targetPackageName) {
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+
+ if (!validatePackageName(uid, callerPackageName)) {
+ throw new SecurityException("callerPackageName does not match calling uid.");
+ }
+
boolean setDeviceRouteSelected = false;
synchronized (mLock) {
UserRecord userRecord = mUserRecords.get(userId);
List<ClientRecord> userClientRecords =
userRecord != null ? userRecord.mClientRecords : Collections.emptyList();
for (ClientRecord clientRecord : userClientRecords) {
- if (TextUtils.equals(clientRecord.mPackageName, packageName)) {
+ if (TextUtils.equals(clientRecord.mPackageName, targetPackageName)) {
if (mDefaultAudioRouteId.equals(clientRecord.mSelectedRouteId)) {
setDeviceRouteSelected = true;
break;
@@ -547,7 +559,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
}
}
}
- return mService2.getSystemSessionInfo(packageName, setDeviceRouteSelected);
+ return mService2.getSystemSessionInfo(
+ callerPackageName, targetPackageName, setDeviceRouteSelected);
}
// Binder call
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index ce31ac84cbe3..eb4e6e44c8a4 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -431,28 +431,9 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
int stream = getVolumeStream(mAudioAttrs);
final int volumeValue = value;
mHandler.post(
- new Runnable() {
- @Override
- public void run() {
- try {
- mAudioManager.setStreamVolumeForUid(
- stream,
- volumeValue,
- flags,
- opPackageName,
- uid,
- pid,
- mContext.getApplicationInfo().targetSdkVersion);
- } catch (IllegalArgumentException | SecurityException e) {
- Slog.e(
- TAG,
- "Cannot set volume: stream=" + stream
- + ", value=" + volumeValue
- + ", flags=" + flags,
- e);
- }
- }
- });
+ () ->
+ setStreamVolumeForUid(
+ opPackageName, pid, uid, flags, stream, volumeValue));
} else {
if (mVolumeControlType != VOLUME_CONTROL_ABSOLUTE) {
if (DEBUG) {
@@ -482,6 +463,27 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
+ private void setStreamVolumeForUid(
+ String opPackageName, int pid, int uid, int flags, int stream, int volumeValue) {
+ try {
+ mAudioManager.setStreamVolumeForUid(
+ stream,
+ volumeValue,
+ flags,
+ opPackageName,
+ uid,
+ pid,
+ mContext.getApplicationInfo().targetSdkVersion);
+ } catch (IllegalArgumentException | SecurityException e) {
+ Slog.e(
+ TAG,
+ "Cannot set volume: stream=" + stream
+ + ", value=" + volumeValue
+ + ", flags=" + flags,
+ e);
+ }
+ }
+
/**
* Check if this session has been set to active by the app.
* <p>
@@ -749,52 +751,70 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
pid = callingPid;
}
mHandler.post(
- new Runnable() {
- @Override
- public void run() {
- try {
- if (useSuggested) {
- if (AudioSystem.isStreamActive(stream, 0)) {
- mAudioManager.adjustSuggestedStreamVolumeForUid(
- stream,
- direction,
- flags,
- opPackageName,
- uid,
- pid,
- mContext.getApplicationInfo().targetSdkVersion);
- } else {
- mAudioManager.adjustSuggestedStreamVolumeForUid(
- AudioManager.USE_DEFAULT_STREAM_TYPE,
- direction,
- flags | previousFlagPlaySound,
- opPackageName,
- uid,
- pid,
- mContext.getApplicationInfo().targetSdkVersion);
- }
- } else {
- mAudioManager.adjustStreamVolumeForUid(
- stream,
- direction,
- flags,
- opPackageName,
- uid,
- pid,
- mContext.getApplicationInfo().targetSdkVersion);
- }
- } catch (IllegalArgumentException | SecurityException e) {
- Slog.e(
- TAG,
- "Cannot adjust volume: direction=" + direction
- + ", stream=" + stream + ", flags=" + flags
- + ", opPackageName=" + opPackageName + ", uid=" + uid
- + ", useSuggested=" + useSuggested
- + ", previousFlagPlaySound=" + previousFlagPlaySound,
- e);
- }
- }
- });
+ () ->
+ adjustSuggestedStreamVolumeForUid(
+ stream,
+ direction,
+ flags,
+ useSuggested,
+ previousFlagPlaySound,
+ opPackageName,
+ uid,
+ pid));
+ }
+
+ private void adjustSuggestedStreamVolumeForUid(
+ int stream,
+ int direction,
+ int flags,
+ boolean useSuggested,
+ int previousFlagPlaySound,
+ String opPackageName,
+ int uid,
+ int pid) {
+ try {
+ if (useSuggested) {
+ if (AudioSystem.isStreamActive(stream, 0)) {
+ mAudioManager.adjustSuggestedStreamVolumeForUid(
+ stream,
+ direction,
+ flags,
+ opPackageName,
+ uid,
+ pid,
+ mContext.getApplicationInfo().targetSdkVersion);
+ } else {
+ mAudioManager.adjustSuggestedStreamVolumeForUid(
+ AudioManager.USE_DEFAULT_STREAM_TYPE,
+ direction,
+ flags | previousFlagPlaySound,
+ opPackageName,
+ uid,
+ pid,
+ mContext.getApplicationInfo().targetSdkVersion);
+ }
+ } else {
+ mAudioManager.adjustStreamVolumeForUid(
+ stream,
+ direction,
+ flags,
+ opPackageName,
+ uid,
+ pid,
+ mContext.getApplicationInfo().targetSdkVersion);
+ }
+ } catch (IllegalArgumentException | SecurityException e) {
+ Slog.e(
+ TAG,
+ "Cannot adjust volume: direction=" + direction
+ + ", stream=" + stream
+ + ", flags=" + flags
+ + ", opPackageName=" + opPackageName
+ + ", uid=" + uid
+ + ", useSuggested=" + useSuggested
+ + ", previousFlagPlaySound=" + previousFlagPlaySound,
+ e);
+ }
}
private void logCallbackException(
@@ -1089,16 +1109,14 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
volumeType, VOLUME_CONTROL_ABSOLUTE, max, current, attributes, null);
}
- private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
- @Override
- public void run() {
- boolean needUpdate = (mOptimisticVolume != mCurrentVolume);
- mOptimisticVolume = -1;
- if (needUpdate) {
- pushVolumeUpdate();
- }
- }
- };
+ private final Runnable mClearOptimisticVolumeRunnable =
+ () -> {
+ boolean needUpdate = (mOptimisticVolume != mCurrentVolume);
+ mOptimisticVolume = -1;
+ if (needUpdate) {
+ pushVolumeUpdate();
+ }
+ };
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
private static boolean componentNameExists(
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 3ecc58e2aef2..e1e2b3efd1eb 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -25,6 +25,8 @@ import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
+import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled;
+
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -1433,7 +1435,7 @@ abstract public class ManagedServices {
protected void rebindServices(boolean forceRebind, int userToRebind) {
if (DEBUG) Slog.d(TAG, "rebindServices " + forceRebind + " " + userToRebind);
IntArray userIds = mUserProfiles.getCurrentProfileIds();
- boolean rebindAllCurrentUsers = mUserProfiles.isProfileUser(userToRebind)
+ boolean rebindAllCurrentUsers = mUserProfiles.isProfileUser(userToRebind, mContext)
&& allowRebindForParentUser();
if (userToRebind != USER_ALL && !rebindAllCurrentUsers) {
userIds = new IntArray(1);
@@ -1958,7 +1960,7 @@ abstract public class ManagedServices {
* from receiving events from the profile.
*/
public boolean isPermittedForProfile(int userId) {
- if (!mUserProfiles.isProfileUser(userId)) {
+ if (!mUserProfiles.isProfileUser(userId, mContext)) {
return true;
}
DevicePolicyManager dpm =
@@ -2036,16 +2038,26 @@ abstract public class ManagedServices {
}
}
- public boolean isProfileUser(int userId) {
+ public boolean isProfileUser(int userId, Context context) {
synchronized (mCurrentProfiles) {
UserInfo user = mCurrentProfiles.get(userId);
if (user == null) {
return false;
}
- if (user.isManagedProfile() || user.isCloneProfile()) {
- return true;
+ if (privateSpaceFlagsEnabled()) {
+ return user.isProfile() && hasParent(user, context);
}
- return false;
+ return user.isManagedProfile() || user.isCloneProfile();
+ }
+ }
+
+ boolean hasParent(UserInfo profile, Context context) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ UserManager um = context.getSystemService(UserManager.class);
+ return um.getProfileParent(profile.id) != null;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 097daf2e51e6..5563caee3743 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import static android.app.Flags.updateRankingTime;
import static android.app.Notification.FLAG_INSISTENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
import static android.app.NotificationManager.IMPORTANCE_MIN;
@@ -496,6 +497,11 @@ public final class NotificationAttentionHelper {
Slog.v(TAG, "INTERRUPTIVENESS: "
+ record.getKey() + " is interruptive: alerted");
}
+ if (updateRankingTime()) {
+ if (buzz || beep) {
+ record.resetRankingTime();
+ }
+ }
}
}
final int buzzBeepBlinkLoggingCode =
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9fcdfdd564b6..f48f66fae404 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -142,6 +142,7 @@ import static android.service.notification.NotificationListenerService.TRIM_LIGH
import static android.view.contentprotection.flags.Flags.rapidClearNotificationsByListenerAppOpEnabled;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.app.Flags.updateRankingTime;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -238,9 +239,6 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.AudioManagerInternal;
-import android.media.IRingtonePlayer;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.Binder;
@@ -612,8 +610,7 @@ public class NotificationManagerService extends SystemService {
PackageManagerInternal mPackageManagerInternal;
private PermissionManager mPermissionManager;
private PermissionPolicyInternal mPermissionPolicyInternal;
- AudioManager mAudioManager;
- AudioManagerInternal mAudioManagerInternal;
+
// Can be null for wear
@Nullable StatusBarManagerInternal mStatusBar;
private WindowManagerInternal mWindowManagerInternal;
@@ -641,34 +638,12 @@ public class NotificationManagerService extends SystemService {
private final HandlerThread mRankingThread = new HandlerThread("ranker",
Process.THREAD_PRIORITY_BACKGROUND);
- private LogicalLight mNotificationLight;
- LogicalLight mAttentionLight;
-
- private boolean mUseAttentionLight;
- boolean mHasLight = true;
- boolean mSystemReady;
-
- private boolean mDisableNotificationEffects;
- private int mCallState;
- private String mSoundNotificationKey;
- private String mVibrateNotificationKey;
-
private final SparseArray<ArraySet<ComponentName>> mListenersDisablingEffects =
new SparseArray<>();
private List<ComponentName> mEffectsSuppressors = new ArrayList<>();
private int mListenerHints; // right now, all hints are global
private int mInterruptionFilter = NotificationListenerService.INTERRUPTION_FILTER_UNKNOWN;
- // for enabling and disabling notification pulse behavior
- boolean mScreenOn = true;
- protected boolean mInCallStateOffHook = false;
- boolean mNotificationPulseEnabled;
-
- private Uri mInCallNotificationUri;
- private AudioAttributes mInCallNotificationAudioAttributes;
- private float mInCallNotificationVolume;
- private Binder mCallNotificationToken = null;
-
private SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver;
// used as a mutex for access to all active notifications & listeners
@@ -696,11 +671,6 @@ public class NotificationManagerService extends SystemService {
// Used for rate limiting toasts by package.
private MultiRateLimiter mToastRateLimiter;
- private KeyguardManager mKeyguardManager;
-
- // The last key in this list owns the hardware.
- ArrayList<String> mLights = new ArrayList<>();
-
private AppOpsManager mAppOps;
private UsageStatsManagerInternal mAppUsageStats;
private DevicePolicyManagerInternal mDpm;
@@ -725,7 +695,6 @@ public class NotificationManagerService extends SystemService {
RankingHelper mRankingHelper;
@VisibleForTesting
PreferencesHelper mPreferencesHelper;
- private VibratorHelper mVibratorHelper;
private final UserProfiles mUserProfiles = new UserProfiles();
private NotificationListeners mListeners;
@@ -751,8 +720,6 @@ public class NotificationManagerService extends SystemService {
private GroupHelper mGroupHelper;
private int mAutoGroupAtCount;
private boolean mIsTelevision;
- private boolean mIsAutomotive;
- private boolean mNotificationEffectsEnabledForAutomotive;
private DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener;
protected NotificationAttentionHelper mAttentionHelper;
@@ -957,8 +924,7 @@ public class NotificationManagerService extends SystemService {
final List<UserInfo> activeUsers = mUm.getUsers();
for (UserInfo userInfo : activeUsers) {
int userId = userInfo.getUserHandle().getIdentifier();
- if (isNASMigrationDone(userId)
- || userInfo.isManagedProfile() || userInfo.isCloneProfile()) {
+ if (isNASMigrationDone(userId) || isProfileUser(userInfo)) {
continue;
}
List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId);
@@ -989,6 +955,17 @@ public class NotificationManagerService extends SystemService {
Settings.Secure.NAS_SETTINGS_UPDATED, 0, userId) == 1);
}
+ boolean isProfileUser(UserInfo userInfo) {
+ if (privateSpaceFlagsEnabled()) {
+ return userInfo.isProfile() && hasParent(userInfo);
+ }
+ return userInfo.isManagedProfile() || userInfo.isCloneProfile();
+ }
+
+ boolean hasParent(UserInfo profile) {
+ return mUmInternal.getProfileParentId(profile.id) != profile.id;
+ }
+
protected void setDefaultAssistantForUser(int userId) {
String overrideDefaultAssistantString = DeviceConfig.getProperty(
DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -1097,8 +1074,7 @@ public class NotificationManagerService extends SystemService {
XmlUtils.beginDocument(parser, TAG_NOTIFICATION_POLICY);
boolean migratedManagedServices = false;
UserInfo userInfo = mUmInternal.getUserInfo(userId);
- boolean ineligibleForManagedServices = forRestore &&
- (userInfo.isManagedProfile() || userInfo.isCloneProfile());
+ boolean ineligibleForManagedServices = forRestore && isProfileUser(userInfo);
int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (ZenModeConfig.ZEN_TAG.equals(parser.getName())) {
@@ -1205,7 +1181,7 @@ public class NotificationManagerService extends SystemService {
}
}
- private static boolean privateSpaceFlagsEnabled() {
+ protected static boolean privateSpaceFlagsEnabled() {
return allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures();
}
@@ -1270,17 +1246,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void onSetDisabled(int status) {
synchronized (mNotificationLock) {
- if (Flags.refactorAttentionHelper()) {
- mAttentionHelper.updateDisableNotificationEffectsLocked(status);
- } else {
- mDisableNotificationEffects =
- (status & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
- if (disableNotificationEffects(null) != null) {
- // cancel whatever's going on
- clearSoundLocked();
- clearVibrateLocked();
- }
- }
+ mAttentionHelper.updateDisableNotificationEffectsLocked(status);
}
}
@@ -1421,13 +1387,7 @@ public class NotificationManagerService extends SystemService {
public void clearEffects() {
synchronized (mNotificationLock) {
if (DBG) Slog.d(TAG, "clearEffects");
- if (Flags.refactorAttentionHelper()) {
- mAttentionHelper.clearAttentionEffects();
- } else {
- clearSoundLocked();
- clearVibrateLocked();
- clearLightsLocked();
- }
+ mAttentionHelper.clearAttentionEffects();
}
}
@@ -1695,11 +1655,7 @@ public class NotificationManagerService extends SystemService {
int changedFlags = data.getFlags() ^ flags;
if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) {
// Suppress notification flag changed, clear any effects
- if (Flags.refactorAttentionHelper()) {
- mAttentionHelper.clearEffectsLocked(key);
- } else {
- clearEffectsLocked(key);
- }
+ mAttentionHelper.clearEffectsLocked(key);
}
data.setFlags(flags);
// Shouldn't alert again just because of a flag change.
@@ -1832,53 +1788,6 @@ public class NotificationManagerService extends SystemService {
hasSensitiveContent, lifespanMs);
}
- @GuardedBy("mNotificationLock")
- void clearSoundLocked() {
- mSoundNotificationKey = null;
- final long identity = Binder.clearCallingIdentity();
- try {
- final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
- if (player != null) {
- player.stopAsync();
- }
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @GuardedBy("mNotificationLock")
- void clearVibrateLocked() {
- mVibrateNotificationKey = null;
- final long identity = Binder.clearCallingIdentity();
- try {
- mVibratorHelper.cancelVibration();
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @GuardedBy("mNotificationLock")
- private void clearLightsLocked() {
- // light
- mLights.clear();
- updateLightsLocked();
- }
-
- @GuardedBy("mNotificationLock")
- private void clearEffectsLocked(String key) {
- if (key.equals(mSoundNotificationKey)) {
- clearSoundLocked();
- }
- if (key.equals(mVibrateNotificationKey)) {
- clearVibrateLocked();
- }
- boolean removed = mLights.remove(key);
- if (removed) {
- updateLightsLocked();
- }
- }
-
protected final BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -2068,27 +1977,6 @@ public class NotificationManagerService extends SystemService {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (!Flags.refactorAttentionHelper()) {
- if (action.equals(Intent.ACTION_SCREEN_ON)) {
- // Keep track of screen on/off state, but do not turn off the notification light
- // until user passes through the lock screen or views the notification.
- mScreenOn = true;
- updateNotificationPulse();
- } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
- mScreenOn = false;
- updateNotificationPulse();
- } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
- mInCallStateOffHook = TelephonyManager.EXTRA_STATE_OFFHOOK
- .equals(intent.getStringExtra(TelephonyManager.EXTRA_STATE));
- updateNotificationPulse();
- } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
- // turn off LED when user passes through lock screen
- if (mNotificationLight != null) {
- mNotificationLight.turnOff();
- }
- }
- }
-
if (action.equals(Intent.ACTION_USER_STOPPED)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userHandle >= 0) {
@@ -2106,7 +1994,7 @@ public class NotificationManagerService extends SystemService {
} else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
mUserProfiles.updateCache(context);
- if (!mUserProfiles.isProfileUser(userId)) {
+ if (!mUserProfiles.isProfileUser(userId, context)) {
// reload per-user settings
mSettingsObserver.update(null);
// Refresh managed services
@@ -2121,7 +2009,7 @@ public class NotificationManagerService extends SystemService {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
if (userId != USER_NULL) {
mUserProfiles.updateCache(context);
- if (!mUserProfiles.isProfileUser(userId)) {
+ if (!mUserProfiles.isProfileUser(userId, context)) {
allowDefaultApprovedServices(userId);
}
mHistoryManager.onUserAdded(userId);
@@ -2142,7 +2030,7 @@ public class NotificationManagerService extends SystemService {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
mUserProfiles.updateCache(context);
mAssistants.onUserUnlocked(userId);
- if (!mUserProfiles.isProfileUser(userId)) {
+ if (!mUserProfiles.isProfileUser(userId, context)) {
mConditionProviders.onUserUnlocked(userId);
mListeners.onUserUnlocked(userId);
if (!android.app.Flags.modesApi()) {
@@ -2164,8 +2052,6 @@ public class NotificationManagerService extends SystemService {
= Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
private final Uri NOTIFICATION_BUBBLES_URI
= Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES);
- private final Uri NOTIFICATION_LIGHT_PULSE_URI
- = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
private final Uri NOTIFICATION_RATE_LIMIT_URI
= Settings.Global.getUriFor(Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE);
private final Uri NOTIFICATION_HISTORY_ENABLED
@@ -2188,10 +2074,6 @@ public class NotificationManagerService extends SystemService {
ContentResolver resolver = getContext().getContentResolver();
resolver.registerContentObserver(NOTIFICATION_BADGING_URI,
false, this, UserHandle.USER_ALL);
- if (!Flags.refactorAttentionHelper()) {
- resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
- false, this, UserHandle.USER_ALL);
- }
resolver.registerContentObserver(NOTIFICATION_RATE_LIMIT_URI,
false, this, UserHandle.USER_ALL);
resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI,
@@ -2218,17 +2100,6 @@ public class NotificationManagerService extends SystemService {
public void update(Uri uri) {
ContentResolver resolver = getContext().getContentResolver();
- if (!Flags.refactorAttentionHelper()) {
- if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
- boolean pulseEnabled = Settings.System.getIntForUser(resolver,
- Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT)
- != 0;
- if (mNotificationPulseEnabled != pulseEnabled) {
- mNotificationPulseEnabled = pulseEnabled;
- updateNotificationPulse();
- }
- }
- }
if (uri == null || NOTIFICATION_RATE_LIMIT_URI.equals(uri)) {
mMaxPackageEnqueueRate = Settings.Global.getFloat(resolver,
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE, mMaxPackageEnqueueRate);
@@ -2347,21 +2218,11 @@ public class NotificationManagerService extends SystemService {
// TODO - replace these methods with new fields in the VisibleForTesting constructor
@VisibleForTesting
- void setAudioManager(AudioManager audioManager) {
- mAudioManager = audioManager;
- }
-
- @VisibleForTesting
void setStrongAuthTracker(StrongAuthTracker strongAuthTracker) {
mStrongAuthTracker = strongAuthTracker;
}
@VisibleForTesting
- void setKeyguardManager(KeyguardManager keyguardManager) {
- mKeyguardManager = keyguardManager;
- }
-
- @VisibleForTesting
ShortcutHelper getShortcutHelper() {
return mShortcutHelper;
}
@@ -2372,33 +2233,6 @@ public class NotificationManagerService extends SystemService {
}
@VisibleForTesting
- VibratorHelper getVibratorHelper() {
- return mVibratorHelper;
- }
-
- @VisibleForTesting
- void setVibratorHelper(VibratorHelper helper) {
- mVibratorHelper = helper;
- }
-
- @VisibleForTesting
- void setHints(int hints) {
- mListenerHints = hints;
- }
-
- @VisibleForTesting
- void setLights(LogicalLight light) {
- mNotificationLight = light;
- mAttentionLight = light;
- mNotificationPulseEnabled = true;
- }
-
- @VisibleForTesting
- void setScreenOn(boolean on) {
- mScreenOn = on;
- }
-
- @VisibleForTesting
int getNotificationRecordCount() {
synchronized (mNotificationLock) {
int count = mNotificationList.size() + mNotificationsByKey.size()
@@ -2446,12 +2280,6 @@ public class NotificationManagerService extends SystemService {
return mNotificationsByKey.get(key);
}
-
- @VisibleForTesting
- void setSystemReady(boolean systemReady) {
- mSystemReady = systemReady;
- }
-
@VisibleForTesting
void setHandler(WorkerHandler handler) {
mHandler = handler;
@@ -2471,13 +2299,8 @@ public class NotificationManagerService extends SystemService {
}
@VisibleForTesting
- void setIsAutomotive(boolean isAutomotive) {
- mIsAutomotive = isAutomotive;
- }
-
- @VisibleForTesting
- void setNotificationEffectsEnabledForAutomotive(boolean isEnabled) {
- mNotificationEffectsEnabledForAutomotive = isEnabled;
+ void setAttentionHelper(NotificationAttentionHelper nah) {
+ mAttentionHelper = nah;
}
@VisibleForTesting
@@ -2486,16 +2309,6 @@ public class NotificationManagerService extends SystemService {
}
@VisibleForTesting
- void setUsageStats(NotificationUsageStats us) {
- mUsageStats = us;
- }
-
- @VisibleForTesting
- void setAccessibilityManager(AccessibilityManager am) {
- mAccessibilityManager = am;
- }
-
- @VisibleForTesting
void setTelecomManager(TelecomManager tm) {
mTelecomManager = tm;
}
@@ -2513,7 +2326,7 @@ public class NotificationManagerService extends SystemService {
DevicePolicyManagerInternal dpm, IUriGrantsManager ugm,
UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, UserManager userManager,
NotificationHistoryManager historyManager, StatsManager statsManager,
- TelephonyManager telephonyManager, ActivityManagerInternal ami,
+ ActivityManagerInternal ami,
MultiRateLimiter toastRateLimiter, PermissionHelper permissionHelper,
UsageStatsManagerInternal usageStatsManagerInternal,
TelecomManager telecomManager, NotificationChannelLogger channelLogger,
@@ -2645,7 +2458,6 @@ public class NotificationManagerService extends SystemService {
extractorNames);
mSnoozeHelper = snoozeHelper;
mGroupHelper = groupHelper;
- mVibratorHelper = new VibratorHelper(getContext());
mHistoryManager = historyManager;
// This is a ManagedServices object that keeps track of the listeners.
@@ -2664,43 +2476,9 @@ public class NotificationManagerService extends SystemService {
mStatusBar.setNotificationDelegate(mNotificationDelegate);
}
- mNotificationLight = lightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
- mAttentionLight = lightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION);
-
- mInCallNotificationUri = Uri.parse("file://" +
- resources.getString(R.string.config_inCallNotificationSound));
- mInCallNotificationAudioAttributes = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
- .build();
- mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
-
- mUseAttentionLight = resources.getBoolean(R.bool.config_useAttentionLight);
- mHasLight =
- resources.getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed);
-
- // Don't start allowing notifications until the setup wizard has run once.
- // After that, including subsequent boots, init with notifications turned on.
- // This works on the first boot because the setup wizard will toggle this
- // flag at least once and we'll go back to 0 after that.
- if (0 == Settings.Global.getInt(getContext().getContentResolver(),
- Settings.Global.DEVICE_PROVISIONED, 0)) {
- mDisableNotificationEffects = true;
- }
mZenModeHelper.initZenMode();
mInterruptionFilter = mZenModeHelper.getZenModeListenerInterruptionFilter();
- if (mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
- telephonyManager.listen(new PhoneStateListener() {
- @Override
- public void onCallStateChanged(int state, String incomingNumber) {
- if (mCallState == state) return;
- if (DBG) Slog.d(TAG, "Call state changed: " + callStateToString(state));
- mCallState = state;
- }
- }, PhoneStateListener.LISTEN_CALL_STATE);
- }
-
mSettingsObserver = new SettingsObserver(mHandler);
mArchive = new Archive(resources.getInteger(
@@ -2709,11 +2487,6 @@ public class NotificationManagerService extends SystemService {
mIsTelevision = mPackageManagerClient.hasSystemFeature(FEATURE_LEANBACK)
|| mPackageManagerClient.hasSystemFeature(FEATURE_TELEVISION);
- mIsAutomotive =
- mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0);
- mNotificationEffectsEnabledForAutomotive =
- resources.getBoolean(R.bool.config_enableServerNotificationEffectsForAutomotive);
-
mZenModeHelper.setPriorityOnlyDndExemptPackages(getContext().getResources().getStringArray(
com.android.internal.R.array.config_priorityOnlyDndExemptPackages));
@@ -2733,22 +2506,14 @@ public class NotificationManagerService extends SystemService {
mToastRateLimiter = toastRateLimiter;
- if (Flags.refactorAttentionHelper()) {
- mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager,
+ mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager,
mAccessibilityManager, mPackageManagerClient, userManager, usageStats,
mNotificationManagerPrivate, mZenModeHelper, flagResolver);
- }
// register for various Intents.
// If this is called within a test, make sure to unregister the intent receivers by
// calling onDestroy()
IntentFilter filter = new IntentFilter();
- if (!Flags.refactorAttentionHelper()) {
- filter.addAction(Intent.ACTION_SCREEN_ON);
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
- filter.addAction(Intent.ACTION_USER_PRESENT);
- }
filter.addAction(Intent.ACTION_USER_STOPPED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_ADDED);
@@ -2874,7 +2639,6 @@ public class NotificationManagerService extends SystemService {
new NotificationHistoryManager(getContext(), handler),
mStatsManager = (StatsManager) getContext().getSystemService(
Context.STATS_MANAGER),
- getContext().getSystemService(TelephonyManager.class),
LocalServices.getService(ActivityManagerInternal.class),
createToastRateLimiter(), new PermissionHelper(getContext(),
AppGlobals.getPackageManager(),
@@ -3054,14 +2818,7 @@ public class NotificationManagerService extends SystemService {
@VisibleForTesting
void onBootPhase(int phase, Looper mainLooper) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
- // no beeping until we're basically done booting
- mSystemReady = true;
-
- // Grab our optional AudioService
- mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
- mAudioManagerInternal = getLocalService(AudioManagerInternal.class);
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
- mKeyguardManager = getContext().getSystemService(KeyguardManager.class);
mZenModeHelper.onSystemReady();
RoleObserver roleObserver = new RoleObserver(getContext(),
getContext().getSystemService(RoleManager.class),
@@ -3080,9 +2837,7 @@ public class NotificationManagerService extends SystemService {
}
registerNotificationPreferencesPullers();
new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker);
- if (Flags.refactorAttentionHelper()) {
- mAttentionHelper.onSystemReady();
- }
+ mAttentionHelper.onSystemReady();
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
// This observer will force an update when observe is called, causing us to
// bind to listener services.
@@ -6866,33 +6621,6 @@ public class NotificationManagerService extends SystemService {
return null;
}
- private String disableNotificationEffects(NotificationRecord record) {
- if (mDisableNotificationEffects) {
- return "booleanState";
- }
- if ((mListenerHints & HINT_HOST_DISABLE_EFFECTS) != 0) {
- return "listenerHints";
- }
- if (record != null && record.getAudioAttributes() != null) {
- if ((mListenerHints & HINT_HOST_DISABLE_NOTIFICATION_EFFECTS) != 0) {
- if (record.getAudioAttributes().getUsage()
- != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
- return "listenerNoti";
- }
- }
- if ((mListenerHints & HINT_HOST_DISABLE_CALL_EFFECTS) != 0) {
- if (record.getAudioAttributes().getUsage()
- == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
- return "listenerCall";
- }
- }
- }
- if (mCallState != TelephonyManager.CALL_STATE_IDLE && !mZenModeHelper.isCall(record)) {
- return "callState";
- }
- return null;
- }
-
// Gets packages that have requested notification permission, and whether that has been
// allowed/denied, for all users on the device.
// Returns a single map containing that info keyed by (uid, package name) for all users.
@@ -7061,33 +6789,10 @@ public class NotificationManagerService extends SystemService {
dumpNotificationRecords(pw, filter);
}
if (!filter.filtered) {
- N = mLights.size();
- if (N > 0) {
- pw.println(" Lights List:");
- for (int i=0; i<N; i++) {
- if (i == N - 1) {
- pw.print(" > ");
- } else {
- pw.print(" ");
- }
- pw.println(mLights.get(i));
- }
- pw.println(" ");
- }
- pw.println(" mUseAttentionLight=" + mUseAttentionLight);
- pw.println(" mHasLight=" + mHasLight);
- pw.println(" mNotificationPulseEnabled=" + mNotificationPulseEnabled);
- pw.println(" mSoundNotificationKey=" + mSoundNotificationKey);
- pw.println(" mVibrateNotificationKey=" + mVibrateNotificationKey);
- pw.println(" mDisableNotificationEffects=" + mDisableNotificationEffects);
- pw.println(" mCallState=" + callStateToString(mCallState));
- pw.println(" mSystemReady=" + mSystemReady);
pw.println(" mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate);
pw.println(" hideSilentStatusBar="
+ mPreferencesHelper.shouldHideSilentStatusIcons());
- if (Flags.refactorAttentionHelper()) {
- mAttentionHelper.dump(pw, " ", filter);
- }
+ mAttentionHelper.dump(pw, " ", filter);
}
pw.println(" mArchive=" + mArchive.toString());
mArchive.dumpImpl(pw, filter);
@@ -8379,11 +8084,7 @@ public class NotificationManagerService extends SystemService {
boolean wasPosted = removeFromNotificationListsLocked(r);
cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null,
SystemClock.elapsedRealtime());
- if (Flags.refactorAttentionHelper()) {
- mAttentionHelper.updateLightsLocked();
- } else {
- updateLightsLocked();
- }
+ mAttentionHelper.updateLightsLocked();
if (isSnoozable(r)) {
if (mSnoozeCriterionId != null) {
mAssistants.notifyAssistantSnoozedLocked(r, mSnoozeCriterionId);
@@ -8519,11 +8220,7 @@ public class NotificationManagerService extends SystemService {
cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
mSendDelete, childrenFlagChecker, mReason,
mCancellationElapsedTimeMs);
- if (Flags.refactorAttentionHelper()) {
- mAttentionHelper.updateLightsLocked();
- } else {
- updateLightsLocked();
- }
+ mAttentionHelper.updateLightsLocked();
if (mShortcutHelper != null) {
mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
true /* isRemoved */,
@@ -8802,6 +8499,11 @@ public class NotificationManagerService extends SystemService {
r.isUpdate = true;
final boolean isInterruptive = isVisuallyInterruptive(old, r);
r.setTextChanged(isInterruptive);
+ if (updateRankingTime()) {
+ if (isInterruptive) {
+ r.resetRankingTime();
+ }
+ }
}
mNotificationsByKey.put(n.getKey(), r);
@@ -8818,14 +8520,10 @@ public class NotificationManagerService extends SystemService {
int buzzBeepBlinkLoggingCode = 0;
if (!r.isHidden()) {
- if (Flags.refactorAttentionHelper()) {
- buzzBeepBlinkLoggingCode = mAttentionHelper.buzzBeepBlinkLocked(r,
+ buzzBeepBlinkLoggingCode = mAttentionHelper.buzzBeepBlinkLocked(r,
new NotificationAttentionHelper.Signals(
- mUserProfiles.isCurrentProfile(r.getUserId()),
- mListenerHints));
- } else {
- buzzBeepBlinkLoggingCode = buzzBeepBlinkLocked(r);
- }
+ mUserProfiles.isCurrentProfile(r.getUserId()),
+ mListenerHints));
}
if (notification.getSmallIcon() != null) {
@@ -9150,425 +8848,6 @@ public class NotificationManagerService extends SystemService {
}
}
- @VisibleForTesting
- @GuardedBy("mNotificationLock")
- /**
- * Determine whether this notification should attempt to make noise, vibrate, or flash the LED
- * @return buzzBeepBlink - bitfield (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)
- */
- int buzzBeepBlinkLocked(NotificationRecord record) {
- if (mIsAutomotive && !mNotificationEffectsEnabledForAutomotive) {
- return 0;
- }
- boolean buzz = false;
- boolean beep = false;
- boolean blink = false;
-
- final String key = record.getKey();
-
- // Should this notification make noise, vibe, or use the LED?
- final boolean aboveThreshold =
- mIsAutomotive
- ? record.getImportance() > NotificationManager.IMPORTANCE_DEFAULT
- : record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT;
- // Remember if this notification already owns the notification channels.
- boolean wasBeep = key != null && key.equals(mSoundNotificationKey);
- boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey);
- // These are set inside the conditional if the notification is allowed to make noise.
- boolean hasValidVibrate = false;
- boolean hasValidSound = false;
- boolean sentAccessibilityEvent = false;
-
- // If the notification will appear in the status bar, it should send an accessibility event
- final boolean suppressedByDnd = record.isIntercepted()
- && (record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_STATUS_BAR) != 0;
- if (!record.isUpdate
- && record.getImportance() > IMPORTANCE_MIN
- && !suppressedByDnd
- && isNotificationForCurrentUser(record)) {
- sendAccessibilityEvent(record);
- sentAccessibilityEvent = true;
- }
-
- if (aboveThreshold && isNotificationForCurrentUser(record)) {
- if (mSystemReady && mAudioManager != null) {
- Uri soundUri = record.getSound();
- hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
- VibrationEffect vibration = record.getVibration();
- // Demote sound to vibration if vibration missing & phone in vibration mode.
- if (vibration == null
- && hasValidSound
- && (mAudioManager.getRingerModeInternal()
- == AudioManager.RINGER_MODE_VIBRATE)
- && mAudioManager.getStreamVolume(
- AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) {
- boolean insistent = (record.getFlags() & Notification.FLAG_INSISTENT) != 0;
- vibration = mVibratorHelper.createFallbackVibration(insistent);
- }
- hasValidVibrate = vibration != null;
- boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
- if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
- if (!sentAccessibilityEvent) {
- sendAccessibilityEvent(record);
- sentAccessibilityEvent = true;
- }
- if (DBG) Slog.v(TAG, "Interrupting!");
- boolean isInsistentUpdate = isInsistentUpdate(record);
- if (hasValidSound) {
- if (isInsistentUpdate) {
- // don't reset insistent sound, it's jarring
- beep = true;
- } else {
- if (isInCall()) {
- playInCallNotification();
- beep = true;
- } else {
- beep = playSound(record, soundUri);
- }
- if (beep) {
- mSoundNotificationKey = key;
- }
- }
- }
-
- final boolean ringerModeSilent =
- mAudioManager.getRingerModeInternal()
- == AudioManager.RINGER_MODE_SILENT;
- if (!isInCall() && hasValidVibrate && !ringerModeSilent) {
- if (isInsistentUpdate) {
- buzz = true;
- } else {
- buzz = playVibration(record, vibration, hasValidSound);
- if (buzz) {
- mVibrateNotificationKey = key;
- }
- }
- }
-
- // Try to start flash notification event whenever an audible and non-suppressed
- // notification is received
- mAccessibilityManager.startFlashNotificationEvent(getContext(),
- AccessibilityManager.FLASH_REASON_NOTIFICATION,
- record.getSbn().getPackageName());
-
- } else if ((record.getFlags() & Notification.FLAG_INSISTENT) != 0) {
- hasValidSound = false;
- }
- }
- }
- // If a notification is updated to remove the actively playing sound or vibrate,
- // cancel that feedback now
- if (wasBeep && !hasValidSound) {
- clearSoundLocked();
- }
- if (wasBuzz && !hasValidVibrate) {
- clearVibrateLocked();
- }
-
- // light
- // release the light
- boolean wasShowLights = mLights.remove(key);
- if (canShowLightsLocked(record, aboveThreshold)) {
- mLights.add(key);
- updateLightsLocked();
- if (mUseAttentionLight && mAttentionLight != null) {
- mAttentionLight.pulse();
- }
- blink = true;
- } else if (wasShowLights) {
- updateLightsLocked();
- }
- final int buzzBeepBlink = (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0);
- if (buzzBeepBlink > 0) {
- // Ignore summary updates because we don't display most of the information.
- if (record.getSbn().isGroup() && record.getSbn().getNotification().isGroupSummary()) {
- if (DEBUG_INTERRUPTIVENESS) {
- Slog.v(TAG, "INTERRUPTIVENESS: "
- + record.getKey() + " is not interruptive: summary");
- }
- } else if (record.canBubble()) {
- if (DEBUG_INTERRUPTIVENESS) {
- Slog.v(TAG, "INTERRUPTIVENESS: "
- + record.getKey() + " is not interruptive: bubble");
- }
- } else {
- record.setInterruptive(true);
- if (DEBUG_INTERRUPTIVENESS) {
- Slog.v(TAG, "INTERRUPTIVENESS: "
- + record.getKey() + " is interruptive: alerted");
- }
- }
- MetricsLogger.action(record.getLogMaker()
- .setCategory(MetricsEvent.NOTIFICATION_ALERT)
- .setType(MetricsEvent.TYPE_OPEN)
- .setSubtype(buzzBeepBlink));
- EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0, 0);
- }
- record.setAudiblyAlerted(buzz || beep);
- return buzzBeepBlink;
- }
-
- @GuardedBy("mNotificationLock")
- boolean canShowLightsLocked(final NotificationRecord record, boolean aboveThreshold) {
- // device lacks light
- if (!mHasLight) {
- return false;
- }
- // user turned lights off globally
- if (!mNotificationPulseEnabled) {
- return false;
- }
- // the notification/channel has no light
- if (record.getLight() == null) {
- return false;
- }
- // unimportant notification
- if (!aboveThreshold) {
- return false;
- }
- // suppressed due to DND
- if ((record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_LIGHTS) != 0) {
- return false;
- }
- // Suppressed because it's a silent update
- final Notification notification = record.getNotification();
- if (record.isUpdate && (notification.flags & FLAG_ONLY_ALERT_ONCE) != 0) {
- return false;
- }
- // Suppressed because another notification in its group handles alerting
- if (record.getSbn().isGroup() && record.getNotification().suppressAlertingDueToGrouping()) {
- return false;
- }
- // not if in call
- if (isInCall()) {
- return false;
- }
- // check current user
- if (!isNotificationForCurrentUser(record)) {
- return false;
- }
- // Light, but only when the screen is off
- return true;
- }
-
- @GuardedBy("mNotificationLock")
- boolean isInsistentUpdate(final NotificationRecord record) {
- return (Objects.equals(record.getKey(), mSoundNotificationKey)
- || Objects.equals(record.getKey(), mVibrateNotificationKey))
- && isCurrentlyInsistent();
- }
-
- @GuardedBy("mNotificationLock")
- boolean isCurrentlyInsistent() {
- return isLoopingRingtoneNotification(mNotificationsByKey.get(mSoundNotificationKey))
- || isLoopingRingtoneNotification(mNotificationsByKey.get(mVibrateNotificationKey));
- }
-
- @GuardedBy("mNotificationLock")
- boolean shouldMuteNotificationLocked(final NotificationRecord record) {
- // Suppressed because it's a silent update
- final Notification notification = record.getNotification();
- if (record.isUpdate && (notification.flags & FLAG_ONLY_ALERT_ONCE) != 0) {
- return true;
- }
-
- // Suppressed because a user manually unsnoozed something (or similar)
- if (record.shouldPostSilently()) {
- return true;
- }
-
- // muted by listener
- final String disableEffects = disableNotificationEffects(record);
- if (disableEffects != null) {
- ZenLog.traceDisableEffects(record, disableEffects);
- return true;
- }
-
- // suppressed due to DND
- if (record.isIntercepted()) {
- return true;
- }
-
- // Suppressed because another notification in its group handles alerting
- if (record.getSbn().isGroup()) {
- if (notification.suppressAlertingDueToGrouping()) {
- return true;
- }
- }
-
- // Suppressed for being too recently noisy
- final String pkg = record.getSbn().getPackageName();
- if (mUsageStats.isAlertRateLimited(pkg)) {
- Slog.e(TAG, "Muting recently noisy " + record.getKey());
- return true;
- }
-
- // A different looping ringtone, such as an incoming call is playing
- if (isCurrentlyInsistent() && !isInsistentUpdate(record)) {
- return true;
- }
-
- // Suppressed since it's a non-interruptive update to a bubble-suppressed notification
- final boolean isBubbleOrOverflowed = record.canBubble() && (record.isFlagBubbleRemoved()
- || record.getNotification().isBubbleNotification());
- if (record.isUpdate && !record.isInterruptive() && isBubbleOrOverflowed
- && record.getNotification().getBubbleMetadata() != null) {
- if (record.getNotification().getBubbleMetadata().isNotificationSuppressed()) {
- return true;
- }
- }
-
- return false;
- }
-
- @GuardedBy("mNotificationLock")
- private boolean isLoopingRingtoneNotification(final NotificationRecord playingRecord) {
- if (playingRecord != null) {
- if (playingRecord.getAudioAttributes().getUsage() == USAGE_NOTIFICATION_RINGTONE
- && (playingRecord.getNotification().flags & FLAG_INSISTENT) != 0) {
- return true;
- }
- }
- return false;
- }
-
- private boolean playSound(final NotificationRecord record, Uri soundUri) {
- final boolean shouldPlay;
- if (focusExclusiveWithRecording()) {
- // flagged path
- shouldPlay = mAudioManager.shouldNotificationSoundPlay(record.getAudioAttributes());
- } else {
- // legacy path
- // play notifications if there is no user of exclusive audio focus
- // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or
- // VIBRATE ringer mode)
- shouldPlay = !mAudioManager.isAudioFocusExclusive()
- && (mAudioManager.getStreamVolume(
- AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0);
- }
- if (!shouldPlay) {
- if (DBG) Slog.v(TAG, "Not playing sound " + soundUri + " due to focus/volume");
- return false;
- }
-
- boolean looping = (record.getNotification().flags & FLAG_INSISTENT) != 0;
- final long identity = Binder.clearCallingIdentity();
- try {
- final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
- if (player != null) {
- if (DBG) {
- Slog.v(TAG, "Playing sound " + soundUri
- + " with attributes " + record.getAudioAttributes());
- }
- player.playAsync(soundUri, record.getSbn().getUser(), looping,
- record.getAudioAttributes(), 1.0f);
- return true;
- }
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- return false;
- }
-
- private boolean playVibration(final NotificationRecord record, final VibrationEffect effect,
- boolean delayVibForSound) {
- // Escalate privileges so we can use the vibrator even if the
- // notifying app does not have the VIBRATE permission.
- final long identity = Binder.clearCallingIdentity();
- try {
- if (delayVibForSound) {
- new Thread(() -> {
- // delay the vibration by the same amount as the notification sound
- final int waitMs = mAudioManager.getFocusRampTimeMs(
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
- record.getAudioAttributes());
- if (DBG) {
- Slog.v(TAG, "Delaying vibration for notification "
- + record.getKey() + " by " + waitMs + "ms");
- }
- try {
- Thread.sleep(waitMs);
- } catch (InterruptedException e) { }
- // Notifications might be canceled before it actually vibrates due to waitMs,
- // so need to check that the notification is still valid for vibrate.
- synchronized (mNotificationLock) {
- if (mNotificationsByKey.get(record.getKey()) != null) {
- if (record.getKey().equals(mVibrateNotificationKey)) {
- vibrate(record, effect, true);
- } else {
- if (DBG) {
- Slog.v(TAG, "No vibration for notification "
- + record.getKey() + ": a new notification is "
- + "vibrating, or effects were cleared while waiting");
- }
- }
- } else {
- Slog.w(TAG, "No vibration for canceled notification "
- + record.getKey());
- }
- }
- }).start();
- } else {
- vibrate(record, effect, false);
- }
- return true;
- } finally{
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- private void vibrate(NotificationRecord record, VibrationEffect effect, boolean delayed) {
- // We need to vibrate as "android" so we can breakthrough DND. VibratorManagerService
- // doesn't have a concept of vibrating on an app's behalf, so add the app information
- // to the reason so we can still debug from bugreports
- String reason = "Notification (" + record.getSbn().getOpPkg() + " "
- + record.getSbn().getUid() + ") " + (delayed ? "(Delayed)" : "");
- mVibratorHelper.vibrate(effect, record.getAudioAttributes(), reason);
- }
-
- private boolean isNotificationForCurrentUser(NotificationRecord record) {
- final int currentUser;
- final long token = Binder.clearCallingIdentity();
- try {
- currentUser = ActivityManager.getCurrentUser();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- return (record.getUserId() == UserHandle.USER_ALL ||
- record.getUserId() == currentUser ||
- mUserProfiles.isCurrentProfile(record.getUserId()));
- }
-
- protected void playInCallNotification() {
- final ContentResolver cr = getContext().getContentResolver();
- if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_NORMAL
- && Settings.Secure.getIntForUser(cr,
- Settings.Secure.IN_CALL_NOTIFICATION_ENABLED, 1, cr.getUserId()) != 0) {
- new Thread() {
- @Override
- public void run() {
- final long identity = Binder.clearCallingIdentity();
- try {
- final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
- if (player != null) {
- if (mCallNotificationToken != null) {
- player.stop(mCallNotificationToken);
- }
- mCallNotificationToken = new Binder();
- player.play(mCallNotificationToken, mInCallNotificationUri,
- mInCallNotificationAudioAttributes,
- mInCallNotificationVolume, false);
- }
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }.start();
- }
- }
-
@GuardedBy("mToastQueue")
void showNextToastLocked(boolean lastToastWasTextRecord) {
if (mIsCurrentToastShown) {
@@ -9840,13 +9119,10 @@ public class NotificationManagerService extends SystemService {
|| interruptiveChanged;
if (interceptBefore && !record.isIntercepted()
&& record.isNewEnoughForAlerting(System.currentTimeMillis())) {
- if (Flags.refactorAttentionHelper()) {
- mAttentionHelper.buzzBeepBlinkLocked(record,
- new NotificationAttentionHelper.Signals(
- mUserProfiles.isCurrentProfile(record.getUserId()), mListenerHints));
- } else {
- buzzBeepBlinkLocked(record);
- }
+
+ mAttentionHelper.buzzBeepBlinkLocked(record,
+ new NotificationAttentionHelper.Signals(mUserProfiles.isCurrentProfile(
+ record.getUserId()), mListenerHints));
// Log alert after change in intercepted state to Zen Log as well
ZenLog.traceAlertOnUpdatedIntercept(record);
@@ -10113,37 +9389,6 @@ public class NotificationManagerService extends SystemService {
return (x < low) ? low : ((x > high) ? high : x);
}
- void sendAccessibilityEvent(NotificationRecord record) {
- if (!mAccessibilityManager.isEnabled()) {
- return;
- }
-
- final Notification notification = record.getNotification();
- final CharSequence packageName = record.getSbn().getPackageName();
- final AccessibilityEvent event =
- AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
- event.setPackageName(packageName);
- event.setClassName(Notification.class.getName());
- final int visibilityOverride = record.getPackageVisibilityOverride();
- final int notifVisibility = visibilityOverride == NotificationManager.VISIBILITY_NO_OVERRIDE
- ? notification.visibility : visibilityOverride;
- final int userId = record.getUser().getIdentifier();
- final boolean needPublic = userId >= 0 && mKeyguardManager.isDeviceLocked(userId);
- if (needPublic && notifVisibility != Notification.VISIBILITY_PUBLIC) {
- // Emit the public version if we're on the lockscreen and this notification isn't
- // publicly visible.
- event.setParcelableData(notification.publicVersion);
- } else {
- event.setParcelableData(notification);
- }
- final CharSequence tickerText = notification.tickerText;
- if (!TextUtils.isEmpty(tickerText)) {
- event.getText().add(tickerText);
- }
-
- mAccessibilityManager.sendAccessibilityEvent(event);
- }
-
/**
* Removes all NotificationsRecords with the same key as the given notification record
* from both lists. Do not call this method while iterating over either list.
@@ -10228,22 +9473,7 @@ public class NotificationManagerService extends SystemService {
}
}
- if (Flags.refactorAttentionHelper()) {
- mAttentionHelper.clearEffectsLocked(canceledKey);
- } else {
- // sound
- if (canceledKey.equals(mSoundNotificationKey)) {
- clearSoundLocked();
- }
-
- // vibrate
- if (canceledKey.equals(mVibrateNotificationKey)) {
- clearVibrateLocked();
- }
-
- // light
- mLights.remove(canceledKey);
- }
+ mAttentionHelper.clearEffectsLocked(canceledKey);
}
// Record usage stats
@@ -10592,11 +9822,7 @@ public class NotificationManagerService extends SystemService {
cancellationElapsedTimeMs);
}
}
- if (Flags.refactorAttentionHelper()) {
- mAttentionHelper.updateLightsLocked();
- } else {
- updateLightsLocked();
- }
+ mAttentionHelper.updateLightsLocked();
}
}
@@ -10745,37 +9971,6 @@ public class NotificationManagerService extends SystemService {
}
@GuardedBy("mNotificationLock")
- void updateLightsLocked()
- {
- if (mNotificationLight == null) {
- return;
- }
-
- // handle notification lights
- NotificationRecord ledNotification = null;
- while (ledNotification == null && !mLights.isEmpty()) {
- final String owner = mLights.get(mLights.size() - 1);
- ledNotification = mNotificationsByKey.get(owner);
- if (ledNotification == null) {
- Slog.wtfStack(TAG, "LED Notification does not exist: " + owner);
- mLights.remove(owner);
- }
- }
-
- // Don't flash while we are in a call or screen is on
- if (ledNotification == null || isInCall() || mScreenOn) {
- mNotificationLight.turnOff();
- } else {
- NotificationRecord.Light light = ledNotification.getLight();
- if (light != null && mNotificationPulseEnabled) {
- // pulse repeatedly
- mNotificationLight.setFlashing(light.color, LogicalLight.LIGHT_FLASH_TIMED,
- light.onMs, light.offMs);
- }
- }
- }
-
- @GuardedBy("mNotificationLock")
@NonNull
List<NotificationRecord> findCurrentAndSnoozedGroupNotificationsLocked(String pkg,
String groupKey, int userId) {
@@ -10974,12 +10169,6 @@ public class NotificationManagerService extends SystemService {
}
}
- private void updateNotificationPulse() {
- synchronized (mNotificationLock) {
- updateLightsLocked();
- }
- }
-
protected boolean isCallingUidSystem() {
final int uid = Binder.getCallingUid();
return uid == Process.SYSTEM_UID;
@@ -11350,18 +10539,6 @@ public class NotificationManagerService extends SystemService {
}
}
- private boolean isInCall() {
- if (mInCallStateOffHook) {
- return true;
- }
- int audioMode = mAudioManager.getMode();
- if (audioMode == AudioManager.MODE_IN_CALL
- || audioMode == AudioManager.MODE_IN_COMMUNICATION) {
- return true;
- }
- return false;
- }
-
public class NotificationAssistants extends ManagedServices {
static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants";
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 6ab4b994df73..7e58d0af6195 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import static android.app.Flags.updateRankingTime;
import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -65,14 +66,12 @@ import android.util.Log;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.widget.RemoteViews;
-
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.uri.UriGrantsManagerInternal;
-
import dalvik.annotation.optimization.NeverCompile;
import java.io.PrintWriter;
@@ -1090,8 +1089,14 @@ public final class NotificationRecord {
private long calculateRankingTimeMs(long previousRankingTimeMs) {
Notification n = getNotification();
// Take developer provided 'when', unless it's in the future.
- if (n.when != 0 && n.when <= getSbn().getPostTime()) {
- return n.when;
+ if (updateRankingTime()) {
+ if (n.when != n.creationTime && n.when <= getSbn().getPostTime()){
+ return n.when;
+ }
+ } else {
+ if (n.when != 0 && n.when <= getSbn().getPostTime()) {
+ return n.when;
+ }
}
// If we've ranked a previous instance with a timestamp, inherit it. This case is
// important in order to have ranking stability for updating notifications.
@@ -1193,6 +1198,12 @@ public final class NotificationRecord {
return mPeopleOverride;
}
+ public void resetRankingTime() {
+ if (updateRankingTime()) {
+ mRankingTimeMs = calculateRankingTimeMs(getSbn().getPostTime());
+ }
+ }
+
public void setInterruptive(boolean interruptive) {
mIsInterruptive = interruptive;
final long now = System.currentTimeMillis();
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 596a0b0fd42c..4c95e8305c17 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -162,9 +162,8 @@ import java.util.zip.ZipOutputStream;
public class LauncherAppsService extends SystemService {
private static final String WM_TRACE_DIR = "/data/misc/wmtrace/";
private static final String VC_FILE_SUFFIX = ".vc";
- // TODO(b/310027945): Update the intent name.
private static final String PS_SETTINGS_INTENT =
- "com.android.settings.action.PRIVATE_SPACE_SETUP_FLOW";
+ "com.android.settings.action.OPEN_PRIVATE_SPACE_SETTINGS";
private static final Set<PosixFilePermission> WM_TRACE_FILE_PERMISSIONS = Set.of(
PosixFilePermission.OWNER_WRITE,
@@ -1801,15 +1800,26 @@ public class LauncherAppsService extends SystemService {
Slog.e(TAG, "Caller cannot access hidden profiles");
return null;
}
+ final int callingUser = getCallingUserId();
+ final int callingUid = getCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
Intent psSettingsIntent = new Intent(PS_SETTINGS_INTENT);
psSettingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
- final PendingIntent pi = PendingIntent.getActivity(mContext,
+ List<ResolveInfo> ri = mPackageManagerInternal.queryIntentActivities(
+ psSettingsIntent,
+ psSettingsIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ PackageManager.MATCH_SYSTEM_ONLY, callingUid, callingUser);
+ if (ri.isEmpty()) {
+ return null;
+ }
+ final PendingIntent pi = PendingIntent.getActivityAsUser(mContext,
/* requestCode */ 0,
psSettingsIntent,
- PendingIntent.FLAG_IMMUTABLE | FLAG_UPDATE_CURRENT);
+ PendingIntent.FLAG_IMMUTABLE | FLAG_UPDATE_CURRENT,
+ null,
+ UserHandle.of(callingUser));
return pi == null ? null : pi.getIntentSender();
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index ec98fff25af7..e2f4d18bbd6d 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -293,9 +293,26 @@ public class PackageArchiver {
return START_PERMISSION_DENIED;
}
- Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName));
-
try {
+ boolean openAppDetailsIfOngoingUnarchival = getAppOpsManager().checkOp(
+ AppOpsManager.OP_UNARCHIVAL_CONFIRMATION, callingUid, callerPackageName)
+ == MODE_ALLOWED;
+ if (openAppDetailsIfOngoingUnarchival) {
+ PackageInstaller.SessionInfo activeUnarchivalSession = getActiveUnarchivalSession(
+ packageName, userId);
+ if (activeUnarchivalSession != null) {
+ mPm.mHandler.post(() -> {
+ Slog.i(TAG, "Opening app details page for ongoing unarchival of: "
+ + packageName);
+ getLauncherApps().startPackageInstallerSessionDetailsActivity(
+ activeUnarchivalSession, null, null);
+ });
+ return START_ABORTED;
+ }
+ }
+
+ Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName));
+
requestUnarchive(packageName, callerPackageName,
getOrCreateLauncherListener(userId, packageName),
UserHandle.of(userId),
@@ -793,8 +810,27 @@ public class PackageArchiver {
}
}
- mPm.mHandler.post(
- () -> unarchiveInternal(packageName, userHandle, installerPackage, draftSessionId));
+ mPm.mHandler.post(() -> {
+ Slog.i(TAG, "Starting app unarchival for: " + packageName);
+ unarchiveInternal(packageName, userHandle, installerPackage,
+ draftSessionId);
+ });
+ }
+
+ @Nullable
+ private PackageInstaller.SessionInfo getActiveUnarchivalSession(String packageName,
+ int userId) {
+ List<PackageInstaller.SessionInfo> activeSessions =
+ mPm.mInstallerService.getAllSessions(userId).getList();
+ for (int idx = 0; idx < activeSessions.size(); idx++) {
+ PackageInstaller.SessionInfo activeSession = activeSessions.get(idx);
+ if (activeSession.appPackageName.equals(packageName)
+ && activeSession.userId == userId && activeSession.active
+ && activeSession.isUnarchival()) {
+ return activeSession;
+ }
+ }
+ return null;
}
private void requestUnarchiveConfirmation(String packageName, IntentSender statusReceiver,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 621026751b1b..095a233bde64 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6261,9 +6261,22 @@ public class PackageManagerService implements PackageSender, TestUtilityService
packageStateWrite.setMimeGroup(mimeGroup, mimeTypesSet);
});
if (mComponentResolver.updateMimeGroup(snapshotComputer(), packageName, mimeGroup)) {
- Binder.withCleanCallingIdentity(() ->
- mPreferredActivityHelper.clearPackagePreferredActivities(packageName,
- UserHandle.USER_ALL));
+ Binder.withCleanCallingIdentity(() -> {
+ mPreferredActivityHelper.clearPackagePreferredActivities(packageName,
+ UserHandle.USER_ALL);
+ // Send the ACTION_PACKAGE_CHANGED when the mimeGroup has changes
+ final Computer snapShot = snapshotComputer();
+ final ArrayList<String> components = new ArrayList<>(
+ Collections.singletonList(packageName));
+ final int appId = packageState.getAppId();
+ final int[] userIds = resolveUserIds(UserHandle.USER_ALL);
+ final String reason = "The mimeGroup is changed";
+ for (int i = 0; i < userIds.length; i++) {
+ final int packageUid = UserHandle.getUid(userIds[i], appId);
+ mBroadcastHelper.sendPackageChangedBroadcast(snapShot, packageName,
+ true /* dontKillApp */, components, packageUid, reason);
+ }
+ });
}
scheduleWriteSettings();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 88e75966b12e..f5ac8306cfa9 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1717,20 +1717,26 @@ public class UserManagerService extends IUserManager.Stub {
return false;
}
- if (android.multiuser.Flags.showSetScreenLockDialog()) {
- // Show the prompt to set a new screen lock if the device does not have one
- final KeyguardManager km = mContext.getSystemService(KeyguardManager.class);
- if (km != null && !km.isDeviceSecure()) {
- Intent setScreenLockPromptIntent =
- SetScreenLockDialogActivity
- .createBaseIntent(LAUNCH_REASON_DISABLE_QUIET_MODE);
- setScreenLockPromptIntent.putExtra(EXTRA_ORIGIN_USER_ID, userId);
- mContext.startActivity(setScreenLockPromptIntent);
- return false;
- }
+ final KeyguardManager km = mContext.getSystemService(KeyguardManager.class);
+ if (km != null && km.isDeviceSecure()) {
+ showConfirmCredentialToDisableQuietMode(userId, target, callingPackage);
+ return false;
+ } else if (km != null && !km.isDeviceSecure()
+ && android.multiuser.Flags.showSetScreenLockDialog()
+ // TODO(b/330720545): Add a better way to accomplish this, also use it
+ // to block profile creation w/o device credentials present.
+ && Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE, 0, userId) == 1) {
+ Intent setScreenLockPromptIntent =
+ SetScreenLockDialogActivity
+ .createBaseIntent(LAUNCH_REASON_DISABLE_QUIET_MODE);
+ setScreenLockPromptIntent.putExtra(EXTRA_ORIGIN_USER_ID, userId);
+ mContext.startActivity(setScreenLockPromptIntent);
+ return false;
+ } else {
+ Slog.w(LOG_TAG, "Allowing profile unlock even when device credentials "
+ + "are not set for user " + userId);
}
- showConfirmCredentialToDisableQuietMode(userId, target, callingPackage);
- return false;
}
}
final boolean hasUnifiedChallenge =
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 b18503d7d5cb..1c70af0a56ea 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -596,7 +596,7 @@ public class PackageInfoUtils {
ai.requiredDisplayCategory = a.getRequiredDisplayCategory();
ai.requireContentUriPermissionFromCaller = a.getRequireContentUriPermissionFromCaller();
ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts());
- assignFieldsComponentInfoParsedMainComponent(ai, a, pkgSetting, state, userId);
+ assignFieldsComponentInfoParsedMainComponent(ai, a, pkgSetting, userId);
return ai;
}
@@ -659,7 +659,7 @@ public class PackageInfoUtils {
// Backwards compatibility, coerce to null if empty
si.metaData = metaData.isEmpty() ? null : metaData;
}
- assignFieldsComponentInfoParsedMainComponent(si, s, pkgSetting, state, userId);
+ assignFieldsComponentInfoParsedMainComponent(si, s, pkgSetting, userId);
return si;
}
@@ -710,7 +710,7 @@ public class PackageInfoUtils {
pi.metaData = metaData.isEmpty() ? null : metaData;
}
pi.applicationInfo = applicationInfo;
- assignFieldsComponentInfoParsedMainComponent(pi, p, pkgSetting, state, userId);
+ assignFieldsComponentInfoParsedMainComponent(pi, p, pkgSetting, userId);
return pi;
}
@@ -903,13 +903,8 @@ public class PackageInfoUtils {
private static void assignFieldsComponentInfoParsedMainComponent(
@NonNull ComponentInfo info, @NonNull ParsedMainComponent component,
- @NonNull PackageStateInternal pkgSetting, @NonNull PackageUserStateInternal state,
- @UserIdInt int userId) {
+ @NonNull PackageStateInternal pkgSetting, @UserIdInt int userId) {
assignFieldsComponentInfoParsedMainComponent(info, component);
- // overwrite the enabled state with the current user state
- info.enabled = PackageUserStateUtils.isEnabled(state, info.applicationInfo.enabled,
- info.enabled, info.name, /* flags */ 0);
-
Pair<CharSequence, Integer> labelAndIcon =
ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting,
userId);
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 3f041cb48ee2..1da9f25afbc7 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1723,7 +1723,7 @@ final class AccessibilityController {
mA11yWindowsPopulator.populateVisibleWindowsOnScreenLocked(
mDisplayId, visibleWindows);
- if (!com.android.server.accessibility.Flags.computeWindowChangesOnA11y()) {
+ if (!com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2()) {
windows = buildWindowInfoListLocked(visibleWindows, screenSize);
}
@@ -1732,7 +1732,7 @@ final class AccessibilityController {
topFocusedWindowToken = topFocusedWindowState.mClient.asBinder();
}
- if (com.android.server.accessibility.Flags.computeWindowChangesOnA11y()) {
+ if (com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2()) {
mCallback.onAccessibilityWindowsChanged(forceSend, topFocusedDisplayId,
topFocusedWindowToken, screenSize, visibleWindows);
} else {
@@ -1747,7 +1747,7 @@ final class AccessibilityController {
mInitialized = true;
}
- // Here are old code paths, called when computeWindowChangesOnA11y flag is disabled.
+ // Here are old code paths, called when computeWindowChangesOnA11yV2 flag is disabled.
// LINT.IfChange
/**
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index ac3251c9bb12..f6afc52fd8d8 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -722,7 +722,7 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener {
}
// Compute system bar insets frame if needed.
- if (com.android.server.accessibility.Flags.computeWindowChangesOnA11y()
+ if (com.android.server.accessibility.Flags.computeWindowChangesOnA11yV2()
&& windowState != null && instance.isUntouchableNavigationBar()) {
final InsetsSourceProvider provider =
windowState.getControllableInsetProvider();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 18d2718437a6..9b2ca3953b0d 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5786,7 +5786,7 @@ class Task extends TaskFragment {
// If we have a watcher, preflight the move before committing to it. First check
// for *other* available tasks, but if none are available, then try again allowing the
// current task to be selected.
- if (isTopRootTaskInDisplayArea() && mAtmService.mController != null) {
+ if (mAtmService.mController != null && isTopRootTaskInDisplayArea()) {
ActivityRecord next = topRunningActivity(null, task.mTaskId);
if (next == null) {
next = topRunningActivity(null, INVALID_TASK_ID);
@@ -5830,6 +5830,15 @@ class Task extends TaskFragment {
+ tr.mTaskId);
if (mTransitionController.isShellTransitionsEnabled()) {
+ // TODO(b/277838915): Consider to make it concurrent to eliminate the special case.
+ final Transition collecting = mTransitionController.getCollectingTransition();
+ if (collecting != null && collecting.mType == TRANSIT_OPEN) {
+ // It can be a CLOSING participate of an OPEN transition. This avoids the deferred
+ // transition from moving task to back after the task was moved to front.
+ collecting.collect(tr);
+ moveTaskToBackInner(tr, collecting);
+ return true;
+ }
final Transition transition = new Transition(TRANSIT_TO_BACK, 0 /* flags */,
mTransitionController, mWmService.mSyncEngine);
// Guarantee that this gets its own transition by queueing on SyncEngine
@@ -5858,7 +5867,7 @@ class Task extends TaskFragment {
return true;
}
- private boolean moveTaskToBackInner(@NonNull Task task, @Nullable Transition transition) {
+ private void moveTaskToBackInner(@NonNull Task task, @Nullable Transition transition) {
final Transition.ReadyCondition movedToBack =
new Transition.ReadyCondition("moved-to-back", task);
if (transition != null) {
@@ -5873,7 +5882,7 @@ class Task extends TaskFragment {
if (inPinnedWindowingMode()) {
mTaskSupervisor.removeRootTask(this);
- return true;
+ return;
}
mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
@@ -5896,7 +5905,6 @@ class Task extends TaskFragment {
} else {
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
- return true;
}
boolean willActivityBeVisible(IBinder token) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index daf8129f1683..7e2ffd486c7e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -160,7 +160,7 @@ public abstract class WindowManagerInternal {
/**
* Called when the windows for accessibility changed. This is called if
- * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y} is
+ * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is
* false.
*
* @param forceSend Send the windows for accessibility even if they haven't changed.
@@ -173,7 +173,7 @@ public abstract class WindowManagerInternal {
/**
* Called when the windows for accessibility changed. This is called if
- * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y} is
+ * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is
* true.
* TODO(b/322444245): Remove screenSize parameter by getting it from
* DisplayManager#getDisplay(int).getRealSize() on the a11y side.
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 1f5451813dae..912ff4ae2022 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -232,6 +232,9 @@
</xs:element>
<xs:element name="brightness" type="xs:float" maxOccurs="unbounded">
</xs:element>
+ <!-- Mapping of current lux to minimum allowed nits values. -->
+ <xs:element name="luxToMinimumNitsMap" type="nitsMap" maxOccurs="1">
+ </xs:element>
</xs:sequence>
<xs:attribute name="enabled" type="xs:boolean" use="optional"/>
</xs:complexType>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index c39c3d7ee7c6..3c708900c64e 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -255,9 +255,11 @@ package com.android.server.display.config {
method public java.util.List<java.lang.Float> getBacklight();
method public java.util.List<java.lang.Float> getBrightness();
method public boolean getEnabled();
+ method public com.android.server.display.config.NitsMap getLuxToMinimumNitsMap();
method public java.util.List<java.lang.Float> getNits();
method public java.math.BigDecimal getTransitionPoint();
method public void setEnabled(boolean);
+ method public void setLuxToMinimumNitsMap(com.android.server.display.config.NitsMap);
method public void setTransitionPoint(java.math.BigDecimal);
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java
index d0b46f58d626..a3aa7de00c71 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/LocaleUtilsTest.java
@@ -351,6 +351,29 @@ public final class LocaleUtilsTest {
assertEquals(1, dest.size());
assertEquals(availableLocales.get(0), dest.get(0)); // "sr-Latn-RS"
}
+ // Locale with deprecated subtag, e.g. CS for Serbia and Montenegro, should not win
+ // even if the other available locale doesn't have explicit script / country.
+ // On Android, users don't normally use deprecated subtags unless the application requests.
+ {
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-RS");
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS"));
+ availableLocales.add(Locale.forLanguageTag("sr-RS"));
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ assertEquals(availableLocales.get(1), dest.get(0)); // "sr-RS"
+ }
+ {
+ final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-RS");
+ final ArrayList<Locale> availableLocales = new ArrayList<>();
+ availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS"));
+ availableLocales.add(Locale.forLanguageTag("sr"));
+ final ArrayList<Locale> dest = new ArrayList<>();
+ LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest);
+ assertEquals(1, dest.size());
+ assertEquals(availableLocales.get(1), dest.get(0)); // "sr"
+ }
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 73a2f655da8d..5a022c0f5d27 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -1651,6 +1651,17 @@ public final class DisplayDeviceConfigTest {
+ " <brightness>0.1</brightness>\n"
+ " <brightness>0.5</brightness>\n"
+ " <brightness>1.0</brightness>\n"
+ + " <luxToMinimumNitsMap>\n"
+ + " <point>\n"
+ + " <value>10</value> <nits>0.3</nits>\n"
+ + " </point>\n"
+ + " <point>\n"
+ + " <value>50</value> <nits>0.7</nits>\n"
+ + " </point>\n"
+ + " <point>\n"
+ + " <value>100</value> <nits>1.0</nits>\n"
+ + " </point>\n"
+ + " </luxToMinimumNitsMap>\n"
+ "</lowBrightness>";
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 5294943fa387..5487bc53ffce 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -35,6 +35,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.DisplayDeviceConfig;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -280,7 +281,8 @@ public class BrightnessClamperControllerTest {
@Override
List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context,
- Handler handler, BrightnessClamperController.ClamperChangeListener listener) {
+ Handler handler, BrightnessClamperController.ClamperChangeListener listener,
+ DisplayDeviceConfig displayDeviceConfig) {
return mModifiers;
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
index e4a7d982514f..749c400f819e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowLuxModifierTest.kt
@@ -15,13 +15,18 @@
*/
package com.android.server.display.brightness.clamper
-import android.os.PowerManager
import android.os.UserHandle
+import android.platform.test.annotations.RequiresFlagsEnabled
import android.provider.Settings
import android.testing.TestableContext
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED
+import com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+import com.android.server.display.DisplayDeviceConfig
import com.android.server.display.brightness.BrightnessReason
+import com.android.server.display.feature.flags.Flags
import com.android.server.testutils.TestHandler
+import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -32,71 +37,197 @@ private const val userId = UserHandle.USER_CURRENT
class BrightnessLowLuxModifierTest {
private var mockClamperChangeListener =
- mock<BrightnessClamperController.ClamperChangeListener>()
+ mock<BrightnessClamperController.ClamperChangeListener>()
val context = TestableContext(
- InstrumentationRegistry.getInstrumentation().getContext())
+ InstrumentationRegistry.getInstrumentation().getContext())
private val testHandler = TestHandler(null)
private lateinit var modifier: BrightnessLowLuxModifier
+ private var mockDisplayDeviceConfig = mock<DisplayDeviceConfig>()
+
+ private val LOW_LUX_BRIGHTNESS = 0.1f
+ private val TRANSITION_POINT = 0.25f
+ private val NORMAL_RANGE_BRIGHTNESS = 0.3f
+
@Before
fun setUp() {
- modifier = BrightnessLowLuxModifier(testHandler, mockClamperChangeListener, context)
+ modifier =
+ BrightnessLowLuxModifier(testHandler,
+ mockClamperChangeListener,
+ context,
+ mockDisplayDeviceConfig)
+
+ // values below transition point (even dimmer range)
+ // nits: 0.1 -> backlight 0.02 -> brightness -> 0.1
+ whenever(mockDisplayDeviceConfig.getBacklightFromNits(/* nits= */ 1.0f))
+ .thenReturn(0.02f)
+ whenever(mockDisplayDeviceConfig.getBrightnessFromBacklight(/* backlight = */ 0.02f))
+ .thenReturn(LOW_LUX_BRIGHTNESS)
+
+ // values above transition point (noraml range)
+ // nits: 10 -> backlight 0.2 -> brightness -> 0.3
+ whenever(mockDisplayDeviceConfig.getBacklightFromNits(/* nits= */ 2f))
+ .thenReturn(0.15f)
+ whenever(mockDisplayDeviceConfig.getBrightnessFromBacklight(/* backlight = */ 0.15f))
+ .thenReturn(0.24f)
+
+ // values above transition point (normal range)
+ // nits: 10 -> backlight 0.2 -> brightness -> 0.3
+ whenever(mockDisplayDeviceConfig.getBacklightFromNits(/* nits= */ 10f))
+ .thenReturn(0.2f)
+ whenever(mockDisplayDeviceConfig.getBrightnessFromBacklight(/* backlight = */ 0.2f))
+ .thenReturn(NORMAL_RANGE_BRIGHTNESS)
+
+ // min nits when lux of 400
+ whenever(mockDisplayDeviceConfig.getMinNitsFromLux(/* lux= */ 400f))
+ .thenReturn(1.0f)
+
+
+ whenever(mockDisplayDeviceConfig.lowBrightnessTransitionPoint).thenReturn(TRANSITION_POINT)
+
testHandler.flush()
}
@Test
- fun testThrottlingBounds() {
+ fun testSettingOffDisablesModifier() {
+ // test transition point ensures brightness doesn't drop when setting is off.
Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId) // true
- Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.7f, userId)
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, userId)
+ modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
modifier.recalculateLowerBound()
testHandler.flush()
- assertThat(modifier.isActive).isTrue()
+ assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
+ assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - ie off
+ modifier.onAmbientLuxChange(3000.0f)
+ testHandler.flush()
+ assertThat(modifier.isActive).isFalse()
+ assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
+ assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - ie off
+ }
- // TODO: code currently returns MIN/MAX; update with lux values
- assertThat(modifier.brightnessLowerBound).isEqualTo(PowerManager.BRIGHTNESS_MIN)
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ fun testLuxRestrictsBrightnessRange() {
+ // test that high lux prevents low brightness range.
+ Settings.Secure.putIntForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId)
+ Settings.Secure.putFloatForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.1f, userId)
+ modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
+ modifier.onAmbientLuxChange(400.0f)
+ testHandler.flush()
+
+ assertThat(modifier.isActive).isTrue()
+ // Test restriction from lux setting
+ assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
+ assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
}
@Test
- fun testGetReason_UserSet() {
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ fun testUserRestrictsBrightnessRange() {
+ // test that user minimum nits setting prevents low brightness range.
Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId)
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId)
Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 30.0f, userId)
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 10.0f, userId)
+ modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
modifier.recalculateLowerBound()
testHandler.flush()
- assertThat(modifier.isActive).isTrue()
// Test restriction from user setting
+ assertThat(modifier.isActive).isTrue()
assertThat(modifier.brightnessReason)
.isEqualTo(BrightnessReason.MODIFIER_MIN_USER_SET_LOWER_BOUND)
+ assertThat(modifier.brightnessLowerBound).isEqualTo(NORMAL_RANGE_BRIGHTNESS)
}
@Test
- fun testGetReason_Lux() {
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ fun testOnToOff() {
+ // test that high lux prevents low brightness range.
Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId)
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId) // on
Settings.Secure.putFloatForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_MIN_NITS, 0.0f, userId)
- modifier.onAmbientLuxChange(3000.0f)
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, userId)
+ modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
+ modifier.onAmbientLuxChange(400.0f)
testHandler.flush()
+
assertThat(modifier.isActive).isTrue()
+ // Test restriction from lux setting
+ assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
+ assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
+
+ Settings.Secure.putIntForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, userId) // off
+
+ modifier.recalculateLowerBound()
+ testHandler.flush()
+
+ assertThat(modifier.isActive).isFalse()
+ assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
+ assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - ie off
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ fun testOffToOn() {
+ // test that high lux prevents low brightness range.
+ Settings.Secure.putIntForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, userId) // off
+ Settings.Secure.putFloatForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, userId)
+ modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
+ modifier.onAmbientLuxChange(400.0f)
+ testHandler.flush()
+
+ assertThat(modifier.isActive).isFalse()
+ assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
+ assertThat(modifier.brightnessReason).isEqualTo(0) // no reason - ie off
+
+
+
+ Settings.Secure.putIntForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId) // on
+ modifier.recalculateLowerBound()
+ testHandler.flush()
+ assertThat(modifier.isActive).isTrue()
// Test restriction from lux setting
assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
+ assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
}
@Test
- fun testSettingOffDisablesModifier() {
+ @RequiresFlagsEnabled(Flags.FLAG_EVEN_DIMMER)
+ fun testDisabledWhenAutobrightnessIsOff() {
+ // test that high lux prevents low brightness range.
Settings.Secure.putIntForUser(context.contentResolver,
- Settings.Secure.EVEN_DIMMER_ACTIVATED, 0, userId)
- assertThat(modifier.brightnessLowerBound).isEqualTo(PowerManager.BRIGHTNESS_MIN)
- modifier.onAmbientLuxChange(3000.0f)
+ Settings.Secure.EVEN_DIMMER_ACTIVATED, 1, userId) // on
+ Settings.Secure.putFloatForUser(context.contentResolver,
+ Settings.Secure.EVEN_DIMMER_MIN_NITS, 1.0f, userId)
+
+ modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_ENABLED)
+ modifier.onAmbientLuxChange(400.0f)
testHandler.flush()
+
+ assertThat(modifier.isActive).isTrue()
+ // Test restriction from lux setting
+ assertThat(modifier.brightnessReason).isEqualTo(BrightnessReason.MODIFIER_MIN_LUX)
+ assertThat(modifier.brightnessLowerBound).isEqualTo(LOW_LUX_BRIGHTNESS)
+
+
+ modifier.setAutoBrightnessState(AUTO_BRIGHTNESS_DISABLED)
+ modifier.onAmbientLuxChange(400.0f)
+ testHandler.flush()
+
assertThat(modifier.isActive).isFalse()
- assertThat(modifier.brightnessLowerBound).isEqualTo(PowerManager.BRIGHTNESS_MIN)
+ // Test restriction from lux setting
+ assertThat(modifier.brightnessReason).isEqualTo(0)
+ assertThat(modifier.brightnessLowerBound).isEqualTo(TRANSITION_POINT)
}
}
+
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
index 680ab1634cb2..daa827eacf44 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
@@ -186,7 +186,7 @@ public final class ServiceBindingOomAdjPolicyTest {
doReturn(true).when(mAms.mOomAdjuster.mCachedAppOptimizer).useFreezer();
doNothing().when(mAms.mOomAdjuster.mCachedAppOptimizer).freezeAppAsyncAtEarliestLSP(
any());
- doReturn(false).when(mAms.mAppProfiler).updateLowMemStateLSP(anyInt(), anyInt(),
+ doNothing().when(mAms.mAppProfiler).updateLowMemStateLSP(anyInt(), anyInt(),
anyInt(), anyLong());
mCurrentCallingUid = TEST_APP1_UID;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 8717a0500e57..403930d96a12 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -86,11 +86,12 @@ import java.util.List;
// LINT.IfChange
/**
- * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y enabled.
+ * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2
+ * enabled.
* TODO(b/322444245): Merge with AccessibilityWindowManagerWithAccessibilityWindowTest
* after completing the flag migration.
*/
-@RequiresFlagsDisabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y)
+@RequiresFlagsDisabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2)
public class AccessibilityWindowManagerTest {
private static final String PACKAGE_NAME = "com.android.server.accessibility";
private static final boolean FORCE_SEND = true;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
index f44879fa54d9..9083a1e28e2c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -89,11 +89,11 @@ import java.util.Arrays;
import java.util.List;
/**
- * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y
+ * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2
* TODO(b/322444245): Merge with AccessibilityWindowManagerTest
* after completing the flag migration.
*/
-@RequiresFlagsEnabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y)
+@RequiresFlagsEnabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2)
public class AccessibilityWindowManagerWithAccessibilityWindowTest {
private static final String PACKAGE_NAME = "com.android.server.accessibility";
private static final boolean FORCE_SEND = true;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
deleted file mode 100644
index 517dcb4f21f6..000000000000
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ /dev/null
@@ -1,1969 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.notification;
-
-import static android.app.Notification.FLAG_BUBBLE;
-import static android.app.Notification.GROUP_ALERT_ALL;
-import static android.app.Notification.GROUP_ALERT_CHILDREN;
-import static android.app.Notification.GROUP_ALERT_SUMMARY;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_LOW;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
-import static android.media.AudioAttributes.USAGE_NOTIFICATION;
-import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.SuppressLint;
-import android.app.ActivityManager;
-import android.app.KeyguardManager;
-import android.app.Notification;
-import android.app.Notification.Builder;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.Color;
-import android.graphics.drawable.Icon;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.IAccessibilityManager;
-import android.view.accessibility.IAccessibilityManagerClient;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.logging.InstanceIdSequence;
-import com.android.internal.logging.InstanceIdSequenceFake;
-import com.android.internal.util.IntPair;
-import com.android.server.UiServiceTestCase;
-import com.android.server.lights.LogicalLight;
-import com.android.server.pm.PackageManagerService;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.verification.VerificationMode;
-
-import java.util.Objects;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
-public class BuzzBeepBlinkTest extends UiServiceTestCase {
-
- @Mock AudioManager mAudioManager;
- @Mock Vibrator mVibrator;
- @Mock android.media.IRingtonePlayer mRingtonePlayer;
- @Mock LogicalLight mLight;
- @Mock
- NotificationManagerService.WorkerHandler mHandler;
- @Mock
- NotificationUsageStats mUsageStats;
- @Mock
- IAccessibilityManager mAccessibilityService;
- @Mock
- KeyguardManager mKeyguardManager;
- NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
- private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
- 1 << 30);
-
- private NotificationManagerService mService;
- private String mPkg = "com.android.server.notification";
- private int mId = 1001;
- private int mOtherId = 1002;
- private String mTag = null;
- private int mUid = 1000;
- private int mPid = 2000;
- private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
- private NotificationChannel mChannel;
-
- private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1);
- private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0);
-
- private static final long[] CUSTOM_VIBRATION = new long[] {
- 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
- 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
- 300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400 };
- private static final Uri CUSTOM_SOUND = Settings.System.DEFAULT_ALARM_ALERT_URI;
- private static final AudioAttributes CUSTOM_ATTRIBUTES = new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
- .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
- .build();
- private static final int CUSTOM_LIGHT_COLOR = Color.BLACK;
- private static final int CUSTOM_LIGHT_ON = 10000;
- private static final int CUSTOM_LIGHT_OFF = 10000;
- private static final int MAX_VIBRATION_DELAY = 1000;
- private static final float DEFAULT_VOLUME = 1.0f;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- getContext().addMockSystemService(Vibrator.class, mVibrator);
-
- when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
- when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
- when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
- // consistent with focus not exclusive and volume not muted
- when(mAudioManager.shouldNotificationSoundPlay(any(AudioAttributes.class)))
- .thenReturn(true);
- when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
- when(mAudioManager.getFocusRampTimeMs(anyInt(), any(AudioAttributes.class))).thenReturn(50);
- when(mUsageStats.isAlertRateLimited(any())).thenReturn(false);
- when(mVibrator.hasFrequencyControl()).thenReturn(false);
- when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(false);
-
- long serviceReturnValue = IntPair.of(
- AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED,
- AccessibilityEvent.TYPES_ALL_MASK);
- when(mAccessibilityService.addClient(any(), anyInt())).thenReturn(serviceReturnValue);
- AccessibilityManager accessibilityManager =
- new AccessibilityManager(getContext(), Handler.getMain(), mAccessibilityService,
- 0, true);
- verify(mAccessibilityService).addClient(any(IAccessibilityManagerClient.class), anyInt());
- assertTrue(accessibilityManager.isEnabled());
-
- mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger,
- mNotificationInstanceIdSequence));
- mService.setVibratorHelper(new VibratorHelper(getContext()));
- mService.setAudioManager(mAudioManager);
- mService.setSystemReady(true);
- mService.setHandler(mHandler);
- mService.setLights(mLight);
- mService.setScreenOn(false);
- mService.setUsageStats(mUsageStats);
- mService.setAccessibilityManager(accessibilityManager);
- mService.setKeyguardManager(mKeyguardManager);
- mService.mScreenOn = false;
- mService.mInCallStateOffHook = false;
- mService.mNotificationPulseEnabled = true;
-
- mChannel = new NotificationChannel("test", "test", IMPORTANCE_HIGH);
- }
-
- //
- // Convenience functions for creating notification records
- //
-
- private NotificationRecord getNoisyOtherNotification() {
- return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
- true /* noisy */, true /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getBeepyNotification() {
- return getNotificationRecord(mId, false /* insistent */, false /* once */,
- true /* noisy */, false /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getBeepyOtherNotification() {
- return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
- true /* noisy */, false /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getBeepyOnceNotification() {
- return getNotificationRecord(mId, false /* insistent */, true /* once */,
- true /* noisy */, false /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getQuietNotification() {
- return getNotificationRecord(mId, false /* insistent */, true /* once */,
- false /* noisy */, false /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getQuietOtherNotification() {
- return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
- false /* noisy */, false /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getQuietOnceNotification() {
- return getNotificationRecord(mId, false /* insistent */, true /* once */,
- false /* noisy */, false /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getInsistentBeepyNotification() {
- return getNotificationRecord(mId, true /* insistent */, false /* once */,
- true /* noisy */, false /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getInsistentBeepyOnceNotification() {
- return getNotificationRecord(mId, true /* insistent */, true /* once */,
- true /* noisy */, false /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getInsistentBeepyLeanbackNotification() {
- return getLeanbackNotificationRecord(mId, true /* insistent */, false /* once */,
- true /* noisy */, false /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getBuzzyNotification() {
- return getNotificationRecord(mId, false /* insistent */, false /* once */,
- false /* noisy */, true /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getBuzzyOtherNotification() {
- return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
- false /* noisy */, true /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getBuzzyOnceNotification() {
- return getNotificationRecord(mId, false /* insistent */, true /* once */,
- false /* noisy */, true /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getInsistentBuzzyNotification() {
- return getNotificationRecord(mId, true /* insistent */, false /* once */,
- false /* noisy */, true /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getBuzzyBeepyNotification() {
- return getNotificationRecord(mId, false /* insistent */, false /* once */,
- true /* noisy */, true /* buzzy*/, false /* lights */);
- }
-
- private NotificationRecord getLightsNotification() {
- return getNotificationRecord(mId, false /* insistent */, false /* once */,
- false /* noisy */, false /* buzzy*/, true /* lights */);
- }
-
- private NotificationRecord getLightsOnceNotification() {
- return getNotificationRecord(mId, false /* insistent */, true /* once */,
- false /* noisy */, false /* buzzy*/, true /* lights */);
- }
-
- private NotificationRecord getCallRecord(int id, NotificationChannel channel, boolean looping) {
- final Builder builder = new Builder(getContext())
- .setContentTitle("foo")
- .setSmallIcon(android.R.drawable.sym_def_app_icon)
- .setPriority(Notification.PRIORITY_HIGH);
- Notification n = builder.build();
- if (looping) {
- n.flags |= Notification.FLAG_INSISTENT;
- }
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
- mPid, n, mUser, null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
- mService.addNotification(r);
-
- return r;
- }
-
- private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
- boolean noisy, boolean buzzy, boolean lights) {
- return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, buzzy, noisy,
- lights, null, Notification.GROUP_ALERT_ALL, false);
- }
-
- private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent,
- boolean once,
- boolean noisy, boolean buzzy, boolean lights) {
- return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true,
- true,
- null, Notification.GROUP_ALERT_ALL, true);
- }
-
- private NotificationRecord getBeepyNotificationRecord(String groupKey, int groupAlertBehavior) {
- return getNotificationRecord(mId, false, false, true, false, false, true, true, true,
- groupKey, groupAlertBehavior, false);
- }
-
- private NotificationRecord getLightsNotificationRecord(String groupKey,
- int groupAlertBehavior) {
- return getNotificationRecord(mId, false, false, false, false, true /*lights*/, true,
- true, true, groupKey, groupAlertBehavior, false);
- }
-
- private NotificationRecord getNotificationRecord(int id,
- boolean insistent, boolean once,
- boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
- boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
- boolean isLeanback) {
-
- final Builder builder = new Builder(getContext())
- .setContentTitle("foo")
- .setSmallIcon(android.R.drawable.sym_def_app_icon)
- .setPriority(Notification.PRIORITY_HIGH)
- .setOnlyAlertOnce(once);
-
- int defaults = 0;
- if (noisy) {
- if (defaultSound) {
- defaults |= Notification.DEFAULT_SOUND;
- mChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
- Notification.AUDIO_ATTRIBUTES_DEFAULT);
- } else {
- builder.setSound(CUSTOM_SOUND);
- mChannel.setSound(CUSTOM_SOUND, CUSTOM_ATTRIBUTES);
- }
- } else {
- mChannel.setSound(null, null);
- }
- if (buzzy) {
- if (defaultVibration) {
- defaults |= Notification.DEFAULT_VIBRATE;
- } else {
- builder.setVibrate(CUSTOM_VIBRATION);
- mChannel.setVibrationPattern(CUSTOM_VIBRATION);
- }
- mChannel.enableVibration(true);
- } else {
- mChannel.setVibrationPattern(null);
- mChannel.enableVibration(false);
- }
-
- if (lights) {
- if (defaultLights) {
- defaults |= Notification.DEFAULT_LIGHTS;
- } else {
- builder.setLights(CUSTOM_LIGHT_COLOR, CUSTOM_LIGHT_ON, CUSTOM_LIGHT_OFF);
- }
- mChannel.enableLights(true);
- } else {
- mChannel.enableLights(false);
- }
- builder.setDefaults(defaults);
-
- builder.setGroup(groupKey);
- builder.setGroupAlertBehavior(groupAlertBehavior);
-
- Notification n = builder.build();
- if (insistent) {
- n.flags |= Notification.FLAG_INSISTENT;
- }
-
- Context context = spy(getContext());
- PackageManager packageManager = spy(context.getPackageManager());
- when(context.getPackageManager()).thenReturn(packageManager);
- when(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
- .thenReturn(isLeanback);
-
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
- mPid, n, mUser, null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(context, sbn, mChannel);
- mService.addNotification(r);
- return r;
- }
-
- //
- // Convenience functions for interacting with mocks
- //
-
- private void verifyNeverBeep() throws RemoteException {
- verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any(), anyFloat());
- }
-
- private void verifyBeepUnlooped() throws RemoteException {
- verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any(),
- eq(DEFAULT_VOLUME));
- }
-
- private void verifyBeepLooped() throws RemoteException {
- verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any(),
- eq(DEFAULT_VOLUME));
- }
-
- private void verifyBeep(int times) throws RemoteException {
- verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any(),
- eq(DEFAULT_VOLUME));
- }
-
- private void verifyNeverStopAudio() throws RemoteException {
- verify(mRingtonePlayer, never()).stopAsync();
- }
-
- private void verifyStopAudio() throws RemoteException {
- verify(mRingtonePlayer, times(1)).stopAsync();
- }
-
- private void verifyNeverVibrate() {
- verify(mVibrator, never()).vibrate(anyInt(), anyString(), any(), anyString(),
- any(VibrationAttributes.class));
- }
-
- private void verifyVibrate() {
- verifyVibrate(/* times= */ 1);
- }
-
- private void verifyVibrate(int times) {
- verifyVibrate(mVibrateOnceMatcher, times(times));
- }
-
- private void verifyVibrateLooped() {
- verifyVibrate(mVibrateLoopMatcher, times(1));
- }
-
- private void verifyDelayedVibrateLooped() {
- verifyVibrate(mVibrateLoopMatcher, timeout(MAX_VIBRATION_DELAY).times(1));
- }
-
- private void verifyDelayedVibrate(VibrationEffect effect) {
- verifyVibrate(argument -> Objects.equals(effect, argument),
- timeout(MAX_VIBRATION_DELAY).times(1));
- }
-
- private void verifyDelayedNeverVibrate() {
- verify(mVibrator, after(MAX_VIBRATION_DELAY).never()).vibrate(anyInt(), anyString(), any(),
- anyString(), any(VibrationAttributes.class));
- }
-
- private void verifyVibrate(ArgumentMatcher<VibrationEffect> effectMatcher,
- VerificationMode verification) {
- ArgumentCaptor<VibrationAttributes> captor =
- ArgumentCaptor.forClass(VibrationAttributes.class);
- verify(mVibrator, verification).vibrate(eq(Process.SYSTEM_UID),
- eq(PackageManagerService.PLATFORM_PACKAGE_NAME), argThat(effectMatcher),
- anyString(), captor.capture());
- assertEquals(0, (captor.getValue().getFlags()
- & VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
- }
-
- private void verifyStopVibrate() {
- int alarmClassUsageFilter =
- VibrationAttributes.USAGE_CLASS_ALARM | ~VibrationAttributes.USAGE_CLASS_MASK;
- verify(mVibrator, times(1)).cancel(eq(alarmClassUsageFilter));
- }
-
- private void verifyNeverStopVibrate() {
- verify(mVibrator, never()).cancel();
- verify(mVibrator, never()).cancel(anyInt());
- }
-
- private void verifyNeverLights() {
- verify(mLight, never()).setFlashing(anyInt(), anyInt(), anyInt(), anyInt());
- }
-
- private void verifyLights() {
- verify(mLight, times(1)).setFlashing(anyInt(), anyInt(), anyInt(), anyInt());
- }
-
- //
- // Tests
- //
-
- @Test
- public void testLights() throws Exception {
- NotificationRecord r = getLightsNotification();
- r.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyLights();
- assertTrue(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testBeep() throws Exception {
- NotificationRecord r = getBeepyNotification();
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyBeepUnlooped();
- verifyNeverVibrate();
- verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testLockedPrivateA11yRedaction() throws Exception {
- NotificationRecord r = getBeepyNotification();
- r.setPackageVisibilityOverride(NotificationManager.VISIBILITY_NO_OVERRIDE);
- r.getNotification().visibility = Notification.VISIBILITY_PRIVATE;
- when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(true);
- AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class);
- when(accessibilityManager.isEnabled()).thenReturn(true);
- mService.setAccessibilityManager(accessibilityManager);
-
- mService.buzzBeepBlinkLocked(r);
-
- ArgumentCaptor<AccessibilityEvent> eventCaptor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
-
- verify(accessibilityManager, times(1))
- .sendAccessibilityEvent(eventCaptor.capture());
-
- AccessibilityEvent event = eventCaptor.getValue();
- assertEquals(r.getNotification().publicVersion, event.getParcelableData());
- }
-
- @Test
- public void testLockedOverridePrivateA11yRedaction() throws Exception {
- NotificationRecord r = getBeepyNotification();
- r.setPackageVisibilityOverride(Notification.VISIBILITY_PRIVATE);
- r.getNotification().visibility = Notification.VISIBILITY_PUBLIC;
- when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(true);
- AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class);
- when(accessibilityManager.isEnabled()).thenReturn(true);
- mService.setAccessibilityManager(accessibilityManager);
-
- mService.buzzBeepBlinkLocked(r);
-
- ArgumentCaptor<AccessibilityEvent> eventCaptor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
-
- verify(accessibilityManager, times(1))
- .sendAccessibilityEvent(eventCaptor.capture());
-
- AccessibilityEvent event = eventCaptor.getValue();
- assertEquals(r.getNotification().publicVersion, event.getParcelableData());
- }
-
- @Test
- public void testLockedPublicA11yNoRedaction() throws Exception {
- NotificationRecord r = getBeepyNotification();
- r.setPackageVisibilityOverride(NotificationManager.VISIBILITY_NO_OVERRIDE);
- r.getNotification().visibility = Notification.VISIBILITY_PUBLIC;
- when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(true);
- AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class);
- when(accessibilityManager.isEnabled()).thenReturn(true);
- mService.setAccessibilityManager(accessibilityManager);
-
- mService.buzzBeepBlinkLocked(r);
-
- ArgumentCaptor<AccessibilityEvent> eventCaptor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
-
- verify(accessibilityManager, times(1))
- .sendAccessibilityEvent(eventCaptor.capture());
-
- AccessibilityEvent event = eventCaptor.getValue();
- assertEquals(r.getNotification(), event.getParcelableData());
- }
-
- @Test
- public void testUnlockedPrivateA11yNoRedaction() throws Exception {
- NotificationRecord r = getBeepyNotification();
- r.setPackageVisibilityOverride(NotificationManager.VISIBILITY_NO_OVERRIDE);
- r.getNotification().visibility = Notification.VISIBILITY_PRIVATE;
- when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(false);
- AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class);
- when(accessibilityManager.isEnabled()).thenReturn(true);
- mService.setAccessibilityManager(accessibilityManager);
-
- mService.buzzBeepBlinkLocked(r);
-
- ArgumentCaptor<AccessibilityEvent> eventCaptor =
- ArgumentCaptor.forClass(AccessibilityEvent.class);
-
- verify(accessibilityManager, times(1))
- .sendAccessibilityEvent(eventCaptor.capture());
-
- AccessibilityEvent event = eventCaptor.getValue();
- assertEquals(r.getNotification(), event.getParcelableData());
- }
-
- @Test
- public void testBeepInsistently() throws Exception {
- NotificationRecord r = getInsistentBeepyNotification();
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyBeepLooped();
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testNoLeanbackBeep() throws Exception {
- NotificationRecord r = getInsistentBeepyLeanbackNotification();
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyNeverBeep();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testNoBeepForAutomotiveIfEffectsDisabled() throws Exception {
- mService.setIsAutomotive(true);
- mService.setNotificationEffectsEnabledForAutomotive(false);
-
- NotificationRecord r = getBeepyNotification();
- r.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyNeverBeep();
- assertFalse(r.isInterruptive());
- }
-
- @Test
- public void testNoBeepForImportanceDefaultInAutomotiveIfEffectsEnabled() throws Exception {
- mService.setIsAutomotive(true);
- mService.setNotificationEffectsEnabledForAutomotive(true);
-
- NotificationRecord r = getBeepyNotification();
- r.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyNeverBeep();
- assertFalse(r.isInterruptive());
- }
-
- @Test
- public void testBeepForImportanceHighInAutomotiveIfEffectsEnabled() throws Exception {
- mService.setIsAutomotive(true);
- mService.setNotificationEffectsEnabledForAutomotive(true);
-
- NotificationRecord r = getBeepyNotification();
- r.setSystemImportance(NotificationManager.IMPORTANCE_HIGH);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyBeepUnlooped();
- assertTrue(r.isInterruptive());
- }
-
- @Test
- public void testNoInterruptionForMin() throws Exception {
- NotificationRecord r = getBeepyNotification();
- r.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyNeverBeep();
- verifyNeverVibrate();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testNoInterruptionForIntercepted() throws Exception {
- NotificationRecord r = getBeepyNotification();
- r.setIntercepted(true);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyNeverBeep();
- verifyNeverVibrate();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testBeepTwice() throws Exception {
- NotificationRecord r = getBeepyNotification();
-
- // set up internal state
- mService.buzzBeepBlinkLocked(r);
- Mockito.reset(mRingtonePlayer);
-
- // update should beep
- r.isUpdate = true;
- mService.buzzBeepBlinkLocked(r);
- verifyBeepUnlooped();
- verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testHonorAlertOnlyOnceForBeep() throws Exception {
- NotificationRecord r = getBeepyNotification();
- NotificationRecord s = getBeepyOnceNotification();
- s.isUpdate = true;
-
- // set up internal state
- mService.buzzBeepBlinkLocked(r);
- Mockito.reset(mRingtonePlayer);
-
- // update should not beep
- mService.buzzBeepBlinkLocked(s);
- verifyNeverBeep();
- verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
- }
-
- @Test
- public void testNoisyUpdateDoesNotCancelAudio() throws Exception {
- NotificationRecord r = getBeepyNotification();
-
- mService.buzzBeepBlinkLocked(r);
- r.isUpdate = true;
- mService.buzzBeepBlinkLocked(r);
-
- verifyNeverStopAudio();
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testNoisyOnceUpdateDoesNotCancelAudio() throws Exception {
- NotificationRecord r = getBeepyNotification();
- NotificationRecord s = getBeepyOnceNotification();
- s.isUpdate = true;
-
- mService.buzzBeepBlinkLocked(r);
- mService.buzzBeepBlinkLocked(s);
-
- verifyNeverStopAudio();
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- assertFalse(s.isInterruptive());
- assertEquals(-1, s.getLastAudiblyAlertedMs());
- }
-
- /**
- * Tests the case where the user re-posts a {@link Notification} with looping sound where
- * {@link Notification.Builder#setOnlyAlertOnce(true)} has been called. This should silence
- * the sound associated with the notification.
- * @throws Exception
- */
- @Test
- public void testNoisyOnceUpdateDoesCancelAudio() throws Exception {
- NotificationRecord r = getInsistentBeepyNotification();
- NotificationRecord s = getInsistentBeepyOnceNotification();
- s.isUpdate = true;
-
- mService.buzzBeepBlinkLocked(r);
- mService.buzzBeepBlinkLocked(s);
-
- verifyStopAudio();
- }
-
- @Test
- public void testQuietUpdateDoesNotCancelAudioFromOther() throws Exception {
- NotificationRecord r = getBeepyNotification();
- NotificationRecord s = getQuietNotification();
- s.isUpdate = true;
- NotificationRecord other = getNoisyOtherNotification();
-
- // set up internal state
- mService.buzzBeepBlinkLocked(r);
- mService.buzzBeepBlinkLocked(other); // this takes the audio stream
- Mockito.reset(mRingtonePlayer);
-
- // should not stop noise, since we no longer own it
- mService.buzzBeepBlinkLocked(s); // this no longer owns the stream
- verifyNeverStopAudio();
- assertTrue(other.isInterruptive());
- assertNotEquals(-1, other.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testQuietInterloperDoesNotCancelAudio() throws Exception {
- NotificationRecord r = getBeepyNotification();
- NotificationRecord other = getQuietOtherNotification();
-
- // set up internal state
- mService.buzzBeepBlinkLocked(r);
- Mockito.reset(mRingtonePlayer);
-
- // should not stop noise, since it does not own it
- mService.buzzBeepBlinkLocked(other);
- verifyNeverStopAudio();
- }
-
- @Test
- public void testQuietUpdateCancelsAudio() throws Exception {
- NotificationRecord r = getBeepyNotification();
- NotificationRecord s = getQuietNotification();
- s.isUpdate = true;
-
- // set up internal state
- mService.buzzBeepBlinkLocked(r);
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- Mockito.reset(mRingtonePlayer);
-
- // quiet update should stop making noise
- mService.buzzBeepBlinkLocked(s);
- verifyStopAudio();
- assertFalse(s.isInterruptive());
- assertEquals(-1, s.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testQuietOnceUpdateCancelsAudio() throws Exception {
- NotificationRecord r = getBeepyNotification();
- NotificationRecord s = getQuietOnceNotification();
- s.isUpdate = true;
-
- // set up internal state
- mService.buzzBeepBlinkLocked(r);
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- Mockito.reset(mRingtonePlayer);
-
- // stop making noise - this is a weird corner case, but quiet should override once
- mService.buzzBeepBlinkLocked(s);
- verifyStopAudio();
- assertFalse(s.isInterruptive());
- assertEquals(-1, s.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testInCallNotification() throws Exception {
- NotificationRecord r = getBeepyNotification();
-
- // set up internal state
- mService.buzzBeepBlinkLocked(r);
- Mockito.reset(mRingtonePlayer);
-
- mService.mInCallStateOffHook = true;
- mService.buzzBeepBlinkLocked(r);
-
- verify(mService, times(1)).playInCallNotification();
- verifyNeverBeep(); // doesn't play normal beep
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testNoDemoteSoundToVibrateIfVibrateGiven() throws Exception {
- NotificationRecord r = getBuzzyBeepyNotification();
- assertTrue(r.getSound() != null);
-
- // the phone is quiet
- when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
- when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
- when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyDelayedVibrate(r.getVibration());
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testNoDemoteSoundToVibrateIfNonNotificationStream() throws Exception {
- NotificationRecord r = getBeepyNotification();
- assertTrue(r.getSound() != null);
- assertNull(r.getVibration());
-
- // the phone is quiet
- when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
- when(mAudioManager.getStreamVolume(anyInt())).thenReturn(1);
- // all streams at 1 means no muting from audio framework
- when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(true);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyNeverVibrate();
- verifyBeepUnlooped();
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testDemoteSoundToVibrate() throws Exception {
- NotificationRecord r = getBeepyNotification();
- assertTrue(r.getSound() != null);
- assertNull(r.getVibration());
-
- // the phone is quiet
- when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
- when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
- when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyDelayedVibrate(
- mService.getVibratorHelper().createFallbackVibration(/* insistent= */ false));
- verify(mRingtonePlayer, never()).playAsync
- (anyObject(), anyObject(), anyBoolean(), anyObject(), anyFloat());
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testDemoteInsistentSoundToVibrate() throws Exception {
- NotificationRecord r = getInsistentBeepyNotification();
- assertTrue(r.getSound() != null);
- assertNull(r.getVibration());
-
- // the phone is quiet
- when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
- when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
- when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyDelayedVibrateLooped();
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testVibrate() throws Exception {
- NotificationRecord r = getBuzzyNotification();
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyNeverBeep();
- verifyVibrate();
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testInsistentVibrate() {
- NotificationRecord r = getInsistentBuzzyNotification();
-
- mService.buzzBeepBlinkLocked(r);
- verifyVibrateLooped();
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testVibrateTwice() {
- NotificationRecord r = getBuzzyNotification();
-
- // set up internal state
- mService.buzzBeepBlinkLocked(r);
- Mockito.reset(mVibrator);
-
- // update should vibrate
- r.isUpdate = true;
- mService.buzzBeepBlinkLocked(r);
- verifyVibrate();
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testPostSilently() throws Exception {
- NotificationRecord r = getBuzzyNotification();
- r.setPostSilently(true);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyNeverBeep();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testGroupAlertSummarySilenceChild() throws Exception {
- NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
-
- mService.buzzBeepBlinkLocked(child);
-
- verifyNeverBeep();
- assertFalse(child.isInterruptive());
- assertEquals(-1, child.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testGroupAlertSummaryNoSilenceSummary() throws Exception {
- NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
- summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
-
- mService.buzzBeepBlinkLocked(summary);
-
- verifyBeepUnlooped();
- // summaries are never interruptive for notification counts
- assertFalse(summary.isInterruptive());
- assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testGroupAlertSummaryNoSilenceNonGroupChild() throws Exception {
- NotificationRecord nonGroup = getBeepyNotificationRecord(null, GROUP_ALERT_SUMMARY);
-
- mService.buzzBeepBlinkLocked(nonGroup);
-
- verifyBeepUnlooped();
- assertTrue(nonGroup.isInterruptive());
- assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testGroupAlertChildSilenceSummary() throws Exception {
- NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
- summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
-
- mService.buzzBeepBlinkLocked(summary);
-
- verifyNeverBeep();
- assertFalse(summary.isInterruptive());
- assertEquals(-1, summary.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testGroupAlertChildNoSilenceChild() throws Exception {
- NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
-
- mService.buzzBeepBlinkLocked(child);
-
- verifyBeepUnlooped();
- assertTrue(child.isInterruptive());
- assertNotEquals(-1, child.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testGroupAlertChildNoSilenceNonGroupSummary() throws Exception {
- NotificationRecord nonGroup = getBeepyNotificationRecord(null, GROUP_ALERT_CHILDREN);
-
- mService.buzzBeepBlinkLocked(nonGroup);
-
- verifyBeepUnlooped();
- assertTrue(nonGroup.isInterruptive());
- assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testGroupAlertAllNoSilenceGroup() throws Exception {
- NotificationRecord group = getBeepyNotificationRecord("a", GROUP_ALERT_ALL);
-
- mService.buzzBeepBlinkLocked(group);
-
- verifyBeepUnlooped();
- assertTrue(group.isInterruptive());
- assertNotEquals(-1, group.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testHonorAlertOnlyOnceForBuzz() throws Exception {
- NotificationRecord r = getBuzzyNotification();
- NotificationRecord s = getBuzzyOnceNotification();
- s.isUpdate = true;
-
- // set up internal state
- mService.buzzBeepBlinkLocked(r);
- Mockito.reset(mVibrator);
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
-
- // update should not beep
- mService.buzzBeepBlinkLocked(s);
- verifyNeverVibrate();
- assertFalse(s.isInterruptive());
- assertEquals(-1, s.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testNoisyUpdateDoesNotCancelVibrate() throws Exception {
- NotificationRecord r = getBuzzyNotification();
-
- mService.buzzBeepBlinkLocked(r);
- r.isUpdate = true;
- mService.buzzBeepBlinkLocked(r);
-
- verifyNeverStopVibrate();
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testNoisyOnceUpdateDoesNotCancelVibrate() throws Exception {
- NotificationRecord r = getBuzzyNotification();
- NotificationRecord s = getBuzzyOnceNotification();
- s.isUpdate = true;
-
- mService.buzzBeepBlinkLocked(r);
- mService.buzzBeepBlinkLocked(s);
-
- verifyNeverStopVibrate();
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- assertFalse(s.isInterruptive());
- assertEquals(-1, s.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testQuietUpdateDoesNotCancelVibrateFromOther() throws Exception {
- NotificationRecord r = getBuzzyNotification();
- NotificationRecord s = getQuietNotification();
- s.isUpdate = true;
- NotificationRecord other = getNoisyOtherNotification();
-
- // set up internal state
- mService.buzzBeepBlinkLocked(r);
- mService.buzzBeepBlinkLocked(other); // this takes the vibrate stream
- Mockito.reset(mVibrator);
-
- // should not stop vibrate, since we no longer own it
- mService.buzzBeepBlinkLocked(s); // this no longer owns the stream
- verifyNeverStopVibrate();
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- assertTrue(other.isInterruptive());
- assertNotEquals(-1, other.getLastAudiblyAlertedMs());
- assertFalse(s.isInterruptive());
- assertEquals(-1, s.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testQuietInterloperDoesNotCancelVibrate() throws Exception {
- NotificationRecord r = getBuzzyNotification();
- NotificationRecord other = getQuietOtherNotification();
-
- // set up internal state
- mService.buzzBeepBlinkLocked(r);
- Mockito.reset(mVibrator);
-
- // should not stop noise, since it does not own it
- mService.buzzBeepBlinkLocked(other);
- verifyNeverStopVibrate();
- assertFalse(other.isInterruptive());
- assertEquals(-1, other.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testQuietUpdateCancelsVibrate() {
- NotificationRecord r = getBuzzyNotification();
- NotificationRecord s = getQuietNotification();
- s.isUpdate = true;
-
- // set up internal state
- mService.buzzBeepBlinkLocked(r);
- verifyVibrate();
-
- // quiet update should stop making noise
- mService.buzzBeepBlinkLocked(s);
- verifyStopVibrate();
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- assertFalse(s.isInterruptive());
- assertEquals(-1, s.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testQuietOnceUpdateCancelVibrate() throws Exception {
- NotificationRecord r = getBuzzyNotification();
- NotificationRecord s = getQuietOnceNotification();
- s.isUpdate = true;
-
- // set up internal state
- mService.buzzBeepBlinkLocked(r);
- verifyVibrate();
-
- // stop making noise - this is a weird corner case, but quiet should override once
- mService.buzzBeepBlinkLocked(s);
- verifyStopVibrate();
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- assertFalse(s.isInterruptive());
- assertEquals(-1, s.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testQuietUpdateCancelsDemotedVibrate() throws Exception {
- NotificationRecord r = getBeepyNotification();
- NotificationRecord s = getQuietNotification();
-
- // the phone is quiet
- when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
- when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
- when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false);
-
- mService.buzzBeepBlinkLocked(r);
- verifyDelayedVibrate(mService.getVibratorHelper().createFallbackVibration(false));
-
- // quiet update should stop making noise
- mService.buzzBeepBlinkLocked(s);
- verifyStopVibrate();
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- assertFalse(s.isInterruptive());
- assertEquals(-1, s.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testEmptyUriSoundTreatedAsNoSound() throws Exception {
- NotificationChannel channel = new NotificationChannel("test", "test", IMPORTANCE_HIGH);
- channel.setSound(Uri.EMPTY, null);
- final Notification n = new Builder(getContext(), "test")
- .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
-
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid,
- mPid, n, mUser, null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
- mService.addNotification(r);
-
- mService.buzzBeepBlinkLocked(r);
- verifyNeverBeep();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testRepeatedSoundOverLimitMuted() throws Exception {
- when(mUsageStats.isAlertRateLimited(any())).thenReturn(true);
-
- NotificationRecord r = getBeepyNotification();
-
- mService.buzzBeepBlinkLocked(r);
- verifyNeverBeep();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testPostingSilentNotificationDoesNotAffectRateLimiting() throws Exception {
- NotificationRecord r = getQuietNotification();
- mService.buzzBeepBlinkLocked(r);
-
- verify(mUsageStats, never()).isAlertRateLimited(any());
- }
-
- @Test
- public void testPostingGroupSuppressedDoesNotAffectRateLimiting() throws Exception {
- NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
- summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
-
- mService.buzzBeepBlinkLocked(summary);
- verify(mUsageStats, never()).isAlertRateLimited(any());
- }
-
- @Test
- public void testGroupSuppressionFailureDoesNotAffectRateLimiting() {
- NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
- summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
-
- mService.buzzBeepBlinkLocked(summary);
- verify(mUsageStats, times(1)).isAlertRateLimited(any());
- }
-
- @Test
- public void testCrossUserSoundMuted() throws Exception {
- final Notification n = new Builder(getContext(), "test")
- .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
-
- int userId = mUser.getIdentifier() + 1;
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid,
- mPid, n, UserHandle.of(userId), null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(getContext(), sbn,
- new NotificationChannel("test", "test", IMPORTANCE_HIGH));
-
- mService.buzzBeepBlinkLocked(r);
- verifyNeverBeep();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testA11yMinInitialPost() throws Exception {
- NotificationRecord r = getQuietNotification();
- r.setSystemImportance(IMPORTANCE_MIN);
- mService.buzzBeepBlinkLocked(r);
- verify(mAccessibilityService, never()).sendAccessibilityEvent(any(), anyInt());
- }
-
- @Test
- public void testA11yQuietInitialPost() throws Exception {
- NotificationRecord r = getQuietNotification();
- mService.buzzBeepBlinkLocked(r);
- verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
- }
-
- @Test
- public void testA11yQuietUpdate() throws Exception {
- NotificationRecord r = getQuietNotification();
- mService.buzzBeepBlinkLocked(r);
- r.isUpdate = true;
- mService.buzzBeepBlinkLocked(r);
- verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
- }
-
- @Test
- public void testA11yCrossUserEventNotSent() throws Exception {
- final Notification n = new Builder(getContext(), "test")
- .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
- int userId = mUser.getIdentifier() + 1;
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid,
- mPid, n, UserHandle.of(userId), null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(getContext(), sbn,
- new NotificationChannel("test", "test", IMPORTANCE_HIGH));
-
- mService.buzzBeepBlinkLocked(r);
-
- verify(mAccessibilityService, never()).sendAccessibilityEvent(any(), anyInt());
- }
-
- @Test
- public void testLightsScreenOn() {
- mService.mScreenOn = true;
- NotificationRecord r = getLightsNotification();
- mService.buzzBeepBlinkLocked(r);
- verifyNeverLights();
- assertTrue(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testLightsInCall() {
- mService.mInCallStateOffHook = true;
- NotificationRecord r = getLightsNotification();
- mService.buzzBeepBlinkLocked(r);
- verifyNeverLights();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testLightsSilentUpdate() {
- NotificationRecord r = getLightsOnceNotification();
- mService.buzzBeepBlinkLocked(r);
- verifyLights();
- assertTrue(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
-
- r = getLightsOnceNotification();
- r.isUpdate = true;
- mService.buzzBeepBlinkLocked(r);
- // checks that lights happened once, i.e. this new call didn't trigger them again
- verifyLights();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testLightsUnimportant() {
- NotificationRecord r = getLightsNotification();
- r.setSystemImportance(IMPORTANCE_LOW);
- mService.buzzBeepBlinkLocked(r);
- verifyNeverLights();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testLightsNoLights() {
- NotificationRecord r = getQuietNotification();
- mService.buzzBeepBlinkLocked(r);
- verifyNeverLights();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testLightsNoLightOnDevice() {
- mService.mHasLight = false;
- NotificationRecord r = getLightsNotification();
- mService.buzzBeepBlinkLocked(r);
- verifyNeverLights();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testLightsLightsOffGlobally() {
- mService.mNotificationPulseEnabled = false;
- NotificationRecord r = getLightsNotification();
- mService.buzzBeepBlinkLocked(r);
- verifyNeverLights();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testLightsDndIntercepted() {
- NotificationRecord r = getLightsNotification();
- r.setSuppressedVisualEffects(SUPPRESSED_EFFECT_LIGHTS);
- mService.buzzBeepBlinkLocked(r);
- verifyNeverLights();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testGroupAlertSummaryNoLightsChild() {
- NotificationRecord child = getLightsNotificationRecord("a", GROUP_ALERT_SUMMARY);
-
- mService.buzzBeepBlinkLocked(child);
-
- verifyNeverLights();
- assertFalse(child.isInterruptive());
- assertEquals(-1, child.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testGroupAlertSummaryLightsSummary() {
- NotificationRecord summary = getLightsNotificationRecord("a", GROUP_ALERT_SUMMARY);
- summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
-
- mService.buzzBeepBlinkLocked(summary);
-
- verifyLights();
- // summaries should never count for interruptiveness counts
- assertFalse(summary.isInterruptive());
- assertEquals(-1, summary.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testGroupAlertSummaryLightsNonGroupChild() {
- NotificationRecord nonGroup = getLightsNotificationRecord(null, GROUP_ALERT_SUMMARY);
-
- mService.buzzBeepBlinkLocked(nonGroup);
-
- verifyLights();
- assertTrue(nonGroup.isInterruptive());
- assertEquals(-1, nonGroup.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testGroupAlertChildNoLightsSummary() {
- NotificationRecord summary = getLightsNotificationRecord("a", GROUP_ALERT_CHILDREN);
- summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
-
- mService.buzzBeepBlinkLocked(summary);
-
- verifyNeverLights();
- assertFalse(summary.isInterruptive());
- assertEquals(-1, summary.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testGroupAlertChildLightsChild() {
- NotificationRecord child = getLightsNotificationRecord("a", GROUP_ALERT_CHILDREN);
-
- mService.buzzBeepBlinkLocked(child);
-
- verifyLights();
- assertTrue(child.isInterruptive());
- assertEquals(-1, child.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testGroupAlertChildLightsNonGroupSummary() {
- NotificationRecord nonGroup = getLightsNotificationRecord(null, GROUP_ALERT_CHILDREN);
-
- mService.buzzBeepBlinkLocked(nonGroup);
-
- verifyLights();
- assertTrue(nonGroup.isInterruptive());
- assertEquals(-1, nonGroup.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testGroupAlertAllLightsGroup() {
- NotificationRecord group = getLightsNotificationRecord("a", GROUP_ALERT_ALL);
-
- mService.buzzBeepBlinkLocked(group);
-
- verifyLights();
- assertTrue(group.isInterruptive());
- assertEquals(-1, group.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testLightsCheckCurrentUser() {
- final Notification n = new Builder(getContext(), "test")
- .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
- int userId = mUser.getIdentifier() + 10;
- StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid,
- mPid, n, UserHandle.of(userId), null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(getContext(), sbn,
- new NotificationChannel("test", "test", IMPORTANCE_HIGH));
-
- mService.buzzBeepBlinkLocked(r);
- verifyNeverLights();
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testListenerHintCall() throws Exception {
- NotificationChannel ringtoneChannel =
- new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
- ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
- new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
- NotificationRecord r = getCallRecord(1, ringtoneChannel, true);
-
- mService.setHints(NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyNeverBeep();
- }
-
- @Test
- public void testListenerHintCall_notificationSound() throws Exception {
- NotificationRecord r = getBeepyNotification();
-
- mService.setHints(NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyBeepUnlooped();
- }
-
- @Test
- public void testListenerHintNotification() throws Exception {
- NotificationRecord r = getBeepyNotification();
-
- mService.setHints(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyNeverBeep();
- }
-
- @Test
- public void testListenerHintBoth() throws Exception {
- NotificationChannel ringtoneChannel =
- new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
- ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
- new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
- NotificationRecord r = getCallRecord(1, ringtoneChannel, true);
- NotificationRecord s = getBeepyNotification();
-
- mService.setHints(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS
- | NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS);
-
- mService.buzzBeepBlinkLocked(r);
- mService.buzzBeepBlinkLocked(s);
-
- verifyNeverBeep();
- }
-
- @Test
- public void testListenerHintNotification_callSound() throws Exception {
- NotificationChannel ringtoneChannel =
- new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
- ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
- new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
- NotificationRecord r = getCallRecord(1, ringtoneChannel, true);
-
- mService.setHints(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyBeepLooped();
- }
-
- @Test
- public void testCannotInterruptRingtoneInsistentBeep() throws Exception {
- NotificationChannel ringtoneChannel =
- new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
- ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
- new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
- NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
- mService.addNotification(ringtoneNotification);
-
- mService.buzzBeepBlinkLocked(ringtoneNotification);
- verifyBeepLooped();
-
- NotificationRecord interrupter = getBeepyOtherNotification();
- assertTrue(mService.shouldMuteNotificationLocked(interrupter));
- mService.buzzBeepBlinkLocked(interrupter);
-
- verifyBeep(1);
-
- assertFalse(interrupter.isInterruptive());
- assertEquals(-1, interrupter.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testRingtoneInsistentBeep_canUpdate() throws Exception {
- NotificationChannel ringtoneChannel =
- new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
- ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"),
- new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
- ringtoneChannel.enableVibration(true);
- NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
- mService.addNotification(ringtoneNotification);
- assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
- mService.buzzBeepBlinkLocked(ringtoneNotification);
- verifyBeepLooped();
- verifyDelayedVibrateLooped();
- Mockito.reset(mVibrator);
- Mockito.reset(mRingtonePlayer);
-
- assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
- mService.buzzBeepBlinkLocked(ringtoneNotification);
-
- // beep wasn't reset
- verifyNeverBeep();
- verifyNeverVibrate();
- verifyNeverStopAudio();
- verifyNeverStopVibrate();
- }
-
- @Test
- public void testRingtoneInsistentBeep_clearEffectsStopsSoundAndVibration() throws Exception {
- NotificationChannel ringtoneChannel =
- new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
- ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"),
- new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
- ringtoneChannel.enableVibration(true);
- NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
- mService.addNotification(ringtoneNotification);
- assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
- mService.buzzBeepBlinkLocked(ringtoneNotification);
- verifyBeepLooped();
- verifyDelayedVibrateLooped();
-
- mService.clearSoundLocked();
- mService.clearVibrateLocked();
-
- verifyStopAudio();
- verifyStopVibrate();
- }
-
- @Test
- public void testRingtoneInsistentBeep_neverVibratesWhenEffectsClearedBeforeDelay()
- throws Exception {
- NotificationChannel ringtoneChannel =
- new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
- ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"),
- new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
- ringtoneChannel.enableVibration(true);
- NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
- mService.addNotification(ringtoneNotification);
- assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
- mService.buzzBeepBlinkLocked(ringtoneNotification);
- verifyBeepLooped();
- verifyNeverVibrate();
-
- mService.clearSoundLocked();
- mService.clearVibrateLocked();
-
- verifyStopAudio();
- verifyDelayedNeverVibrate();
- }
-
- @Test
- public void testCannotInterruptRingtoneInsistentBuzz() {
- NotificationChannel ringtoneChannel =
- new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
- ringtoneChannel.setSound(Uri.EMPTY,
- new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
- ringtoneChannel.enableVibration(true);
- NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
- assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
-
- mService.buzzBeepBlinkLocked(ringtoneNotification);
- verifyVibrateLooped();
-
- NotificationRecord interrupter = getBuzzyOtherNotification();
- assertTrue(mService.shouldMuteNotificationLocked(interrupter));
- mService.buzzBeepBlinkLocked(interrupter);
-
- verifyVibrate(1);
-
- assertFalse(interrupter.isInterruptive());
- assertEquals(-1, interrupter.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testCanInterruptRingtoneNonInsistentBeep() throws Exception {
- NotificationChannel ringtoneChannel =
- new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
- ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
- new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
- NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, false);
-
- mService.buzzBeepBlinkLocked(ringtoneNotification);
- verifyBeepUnlooped();
-
- NotificationRecord interrupter = getBeepyOtherNotification();
- mService.buzzBeepBlinkLocked(interrupter);
-
- verifyBeep(2);
-
- assertTrue(interrupter.isInterruptive());
- }
-
- @Test
- public void testCanInterruptRingtoneNonInsistentBuzz() {
- NotificationChannel ringtoneChannel =
- new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
- ringtoneChannel.setSound(null,
- new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
- ringtoneChannel.enableVibration(true);
- NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, false);
-
- mService.buzzBeepBlinkLocked(ringtoneNotification);
- verifyVibrate();
-
- NotificationRecord interrupter = getBuzzyOtherNotification();
- mService.buzzBeepBlinkLocked(interrupter);
-
- verifyVibrate(2);
-
- assertTrue(interrupter.isInterruptive());
- }
-
- @Test
- public void testRingtoneInsistentBeep_doesNotBlockFutureSoundsOnceStopped() throws Exception {
- NotificationChannel ringtoneChannel =
- new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
- ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
- new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
- NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
-
- mService.buzzBeepBlinkLocked(ringtoneNotification);
- verifyBeepLooped();
-
- mService.clearSoundLocked();
-
- NotificationRecord interrupter = getBeepyOtherNotification();
- mService.buzzBeepBlinkLocked(interrupter);
-
- verifyBeep(2);
-
- assertTrue(interrupter.isInterruptive());
- }
-
- @Test
- public void testRingtoneInsistentBuzz_doesNotBlockFutureSoundsOnceStopped() {
- NotificationChannel ringtoneChannel =
- new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
- ringtoneChannel.setSound(null,
- new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
- ringtoneChannel.enableVibration(true);
- NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
-
- mService.buzzBeepBlinkLocked(ringtoneNotification);
- verifyVibrateLooped();
-
- mService.clearVibrateLocked();
-
- NotificationRecord interrupter = getBuzzyOtherNotification();
- mService.buzzBeepBlinkLocked(interrupter);
-
- verifyVibrate(2);
-
- assertTrue(interrupter.isInterruptive());
- }
-
- @Test
- public void testCanInterruptNonRingtoneInsistentBeep() throws Exception {
- NotificationChannel fakeRingtoneChannel =
- new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
- NotificationRecord ringtoneNotification = getCallRecord(1, fakeRingtoneChannel, true);
-
- mService.buzzBeepBlinkLocked(ringtoneNotification);
- verifyBeepLooped();
-
- NotificationRecord interrupter = getBeepyOtherNotification();
- mService.buzzBeepBlinkLocked(interrupter);
-
- verifyBeep(2);
-
- assertTrue(interrupter.isInterruptive());
- }
-
- @Test
- public void testCanInterruptNonRingtoneInsistentBuzz() {
- NotificationChannel fakeRingtoneChannel =
- new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
- fakeRingtoneChannel.enableVibration(true);
- fakeRingtoneChannel.setSound(null,
- new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION).build());
- NotificationRecord ringtoneNotification = getCallRecord(1, fakeRingtoneChannel, true);
-
- mService.buzzBeepBlinkLocked(ringtoneNotification);
-
- NotificationRecord interrupter = getBuzzyOtherNotification();
- mService.buzzBeepBlinkLocked(interrupter);
-
- verifyVibrate(2);
-
- assertTrue(interrupter.isInterruptive());
- }
-
- @Test
- public void testBubbleSuppressedNotificationDoesntMakeSound() {
- Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
- mock(PendingIntent.class), mock(Icon.class))
- .build();
-
- NotificationRecord record = getBuzzyNotification();
- metadata.setFlags(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
- record.getNotification().setBubbleMetadata(metadata);
- record.setAllowBubble(true);
- record.getNotification().flags |= FLAG_BUBBLE;
- record.isUpdate = true;
- record.setInterruptive(false);
-
- mService.buzzBeepBlinkLocked(record);
- verifyNeverVibrate();
- }
-
- @Test
- public void testOverflowBubbleSuppressedNotificationDoesntMakeSound() {
- Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
- mock(PendingIntent.class), mock(Icon.class))
- .build();
-
- NotificationRecord record = getBuzzyNotification();
- metadata.setFlags(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
- record.getNotification().setBubbleMetadata(metadata);
- record.setFlagBubbleRemoved(true);
- record.setAllowBubble(true);
- record.isUpdate = true;
- record.setInterruptive(false);
-
- mService.buzzBeepBlinkLocked(record);
- verifyNeverVibrate();
- }
-
- @Test
- public void testBubbleUpdateMakesSound() {
- Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
- mock(PendingIntent.class), mock(Icon.class))
- .build();
-
- NotificationRecord record = getBuzzyNotification();
- record.getNotification().setBubbleMetadata(metadata);
- record.setAllowBubble(true);
- record.getNotification().flags |= FLAG_BUBBLE;
- record.isUpdate = true;
- record.setInterruptive(true);
-
- mService.buzzBeepBlinkLocked(record);
- verifyVibrate(1);
- }
-
- @Test
- public void testNewBubbleSuppressedNotifMakesSound() {
- Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
- mock(PendingIntent.class), mock(Icon.class))
- .build();
-
- NotificationRecord record = getBuzzyNotification();
- metadata.setFlags(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
- record.getNotification().setBubbleMetadata(metadata);
- record.setAllowBubble(true);
- record.getNotification().flags |= FLAG_BUBBLE;
- record.isUpdate = false;
- record.setInterruptive(true);
-
- mService.buzzBeepBlinkLocked(record);
- verifyVibrate(1);
- }
-
- @Test
- public void testStartFlashNotificationEvent_receiveBeepyNotification() throws Exception {
- NotificationRecord r = getBeepyNotification();
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyBeepUnlooped();
- verifyNeverVibrate();
- verify(mAccessibilityService).startFlashNotificationEvent(any(), anyInt(),
- eq(r.getSbn().getPackageName()));
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testStartFlashNotificationEvent_receiveBuzzyNotification() throws Exception {
- NotificationRecord r = getBuzzyNotification();
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyNeverBeep();
- verifyVibrate();
- verify(mAccessibilityService).startFlashNotificationEvent(any(), anyInt(),
- eq(r.getSbn().getPackageName()));
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testStartFlashNotificationEvent_receiveBuzzyBeepyNotification() throws Exception {
- NotificationRecord r = getBuzzyBeepyNotification();
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyBeepUnlooped();
- verifyDelayedVibrate(r.getVibration());
- verify(mAccessibilityService).startFlashNotificationEvent(any(), anyInt(),
- eq(r.getSbn().getPackageName()));
- assertTrue(r.isInterruptive());
- assertNotEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- @Test
- public void testStartFlashNotificationEvent_receiveBuzzyBeepyNotification_ringerModeSilent()
- throws Exception {
- NotificationRecord r = getBuzzyBeepyNotification();
- when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
- when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
- when(mAudioManager.shouldNotificationSoundPlay(any())).thenReturn(false);
-
- mService.buzzBeepBlinkLocked(r);
-
- verifyNeverBeep();
- verifyNeverVibrate();
- verify(mAccessibilityService).startFlashNotificationEvent(any(), anyInt(),
- eq(r.getSbn().getPackageName()));
- assertFalse(r.isInterruptive());
- assertEquals(-1, r.getLastAudiblyAlertedMs());
- }
-
- static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
- private final int mRepeatIndex;
-
- VibrateRepeatMatcher(int repeatIndex) {
- mRepeatIndex = repeatIndex;
- }
-
- @Override
- public boolean matches(VibrationEffect actual) {
- if (actual instanceof VibrationEffect.Composed
- && ((VibrationEffect.Composed) actual).getRepeatIndex() == mRepeatIndex) {
- return true;
- }
- // All non-waveform effects are essentially one shots.
- return mRepeatIndex == -1;
- }
-
- @Override
- public String toString() {
- return "repeatIndex=" + mRepeatIndex;
- }
- }
-}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 05b6c907069b..e5c42082ab97 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -20,10 +20,12 @@ import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
+import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
import static com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT;
import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE;
+import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled;
import static com.google.common.truth.Truth.assertThat;
@@ -1803,7 +1805,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
@Test
public void testInfoIsPermittedForProfile_notProfile() {
- when(mUserProfiles.isProfileUser(anyInt())).thenReturn(false);
+ when(mUserProfiles.isProfileUser(anyInt(), any(Context.class))).thenReturn(false);
IInterface service = mock(IInterface.class);
when(service.asBinder()).thenReturn(mock(IBinder.class));
@@ -1817,7 +1819,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
@Test
public void testInfoIsPermittedForProfile_profileAndDpmAllows() {
- when(mUserProfiles.isProfileUser(anyInt())).thenReturn(true);
+ when(mUserProfiles.isProfileUser(anyInt(), any(Context.class))).thenReturn(true);
when(mDpm.isNotificationListenerServicePermitted(anyString(), anyInt())).thenReturn(true);
IInterface service = mock(IInterface.class);
@@ -1833,7 +1835,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
@Test
public void testInfoIsPermittedForProfile_profileAndDpmDenies() {
- when(mUserProfiles.isProfileUser(anyInt())).thenReturn(true);
+ when(mUserProfiles.isProfileUser(anyInt(), any(Context.class))).thenReturn(true);
when(mDpm.isNotificationListenerServicePermitted(anyString(), anyInt())).thenReturn(false);
IInterface service = mock(IInterface.class);
@@ -1853,20 +1855,29 @@ public class ManagedServicesTest extends UiServiceTestCase {
UserInfo profile = new UserInfo(ActivityManager.getCurrentUser(), "current", 0);
profile.userType = USER_TYPE_FULL_SECONDARY;
users.add(profile);
- UserInfo managed = new UserInfo(12, "12", 0);
+ UserInfo managed = new UserInfo(12, "12", UserInfo.FLAG_PROFILE);
managed.userType = USER_TYPE_PROFILE_MANAGED;
users.add(managed);
- UserInfo clone = new UserInfo(13, "13", 0);
+ UserInfo clone = new UserInfo(13, "13", UserInfo.FLAG_PROFILE);
clone.userType = USER_TYPE_PROFILE_CLONE;
users.add(clone);
+ UserInfo privateProfile = new UserInfo(14, "14", UserInfo.FLAG_PROFILE);
+ if (privateSpaceFlagsEnabled()) {
+ privateProfile.userType = USER_TYPE_PROFILE_PRIVATE;
+ users.add(privateProfile);
+ }
when(mUm.getProfiles(ActivityManager.getCurrentUser())).thenReturn(users);
+ when(mUm.getProfileParent(anyInt())).thenReturn(new UserInfo(0, "primary", 0));
ManagedServices.UserProfiles profiles = new ManagedServices.UserProfiles();
profiles.updateCache(mContext);
- assertFalse(profiles.isProfileUser(ActivityManager.getCurrentUser()));
- assertTrue(profiles.isProfileUser(12));
- assertTrue(profiles.isProfileUser(13));
+ assertFalse(profiles.isProfileUser(ActivityManager.getCurrentUser(), mContext));
+ assertTrue(profiles.isProfileUser(12, mContext));
+ assertTrue(profiles.isProfileUser(13, mContext));
+ if (privateSpaceFlagsEnabled()) {
+ assertTrue(profiles.isProfileUser(14, mContext));
+ }
}
@Test
@@ -2015,7 +2026,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
@Test
public void isComponentEnabledForCurrentProfiles_profileUserId() {
final int profileUserId = 10;
- when(mUserProfiles.isProfileUser(profileUserId)).thenReturn(true);
+ when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true);
// Only approve for parent user (0)
mService.addApprovedList("pkg1/cmp1:pkg2/cmp2:pkg3/cmp3", 0, true);
@@ -2028,7 +2039,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
@Test
public void isComponentEnabledForCurrentProfiles_profileUserId_NAS() {
final int profileUserId = 10;
- when(mUserProfiles.isProfileUser(profileUserId)).thenReturn(true);
+ when(mUserProfiles.isProfileUser(profileUserId, mContext)).thenReturn(true);
// Do not rebind for parent users (NAS use-case)
ManagedServices service = spy(mService);
when(service.allowRebindForParentUser()).thenReturn(false);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index a1c24f1f27bf..acac63cd19f8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -210,7 +210,6 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
assertTrue(mAccessibilityManager.isEnabled());
// TODO (b/291907312): remove feature flag
- mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
// Disable feature flags by default. Tests should enable as needed.
mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS,
Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS, Flags.FLAG_VIBRATE_WHILE_UNLOCKED);
@@ -2486,6 +2485,17 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
}
}
+ @Test
+ public void testSoundResetsRankingTime() throws Exception {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_UPDATE_RANKING_TIME);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ initAttentionHelper(flagResolver);
+
+ NotificationRecord r = getBuzzyBeepyNotification();
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ assertThat(r.getRankingTimeMs()).isEqualTo(r.getSbn().getPostTime());
+ }
+
static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
private final int mRepeatIndex;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 06a4ac932e8e..ef879eeec112 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -24,6 +24,7 @@ import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_
import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.SHOW_IMMEDIATELY;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
+import static android.app.Flags.FLAG_UPDATE_RANKING_TIME;
import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP;
import static android.app.Notification.EXTRA_PICTURE;
import static android.app.Notification.EXTRA_PICTURE_ICON;
@@ -81,6 +82,7 @@ import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
+import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.service.notification.Adjustment.KEY_CONTEXTUAL_ACTIONS;
@@ -101,7 +103,6 @@ import static android.service.notification.NotificationListenerService.Ranking.U
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
-
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
@@ -111,11 +112,11 @@ import static com.android.server.notification.NotificationManagerService.DEFAULT
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
-
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -124,7 +125,6 @@ import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
-
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.isNull;
@@ -132,27 +132,7 @@ import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.inOrder;
-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.timeout;
-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 static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
+import static org.mockito.Mockito.*;
import android.Manifest;
import android.annotation.Nullable;
@@ -207,7 +187,6 @@ import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.media.AudioManager;
-import android.media.IRingtonePlayer;
import android.media.session.MediaSession;
import android.net.Uri;
import android.os.Binder;
@@ -246,7 +225,6 @@ import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -260,10 +238,8 @@ import android.util.AtomicFile;
import android.util.Pair;
import android.util.Xml;
import android.widget.RemoteViews;
-
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-
import com.android.internal.R;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.config.sysui.TestableFlagResolver;
@@ -294,13 +270,10 @@ import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.quota.MultiRateLimiter;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
-
import com.google.android.collect.Lists;
import com.google.common.collect.ImmutableList;
-
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -439,6 +412,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private NotificationChannel mTestNotificationChannel = new NotificationChannel(
TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+ NotificationChannel mSilentChannel = new NotificationChannel("low", "low", IMPORTANCE_LOW);
+
private static final int NOTIFICATION_LOCATION_UNKNOWN = 0;
private static final String VALID_CONVO_SHORTCUT_ID = "shortcut";
@@ -494,6 +469,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Mock
StatusBarManagerInternal mStatusBar;
+ @Mock
+ NotificationAttentionHelper mAttentionHelper;
+
private NotificationManagerService.WorkerHandler mWorkerHandler;
private class TestableToastCallback extends ITransientNotification.Stub {
@@ -661,7 +639,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// TODO (b/291907312): remove feature flag
// NOTE: Prefer using the @EnableFlag annotation where possible. Do not add any android.app
// flags here.
- mSetFlagsRule.disableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER,
+ mSetFlagsRule.disableFlags(
Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE);
initNMS();
@@ -695,11 +673,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
mAppUsageStats, mDevicePolicyManager, mUgm, mUgmInternal,
mAppOpsManager, mUm, mHistoryManager, mStatsManager,
- mock(TelephonyManager.class),
mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
mTelecomManager, mLogger, mTestFlagResolver, mPermissionManager,
mPowerManager, mPostNotificationTrackerFactory);
+ mService.setAttentionHelper(mAttentionHelper);
+
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
@@ -715,13 +694,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(mHistoryManager).onBootPhaseAppsCanStart();
}
- // TODO b/291907312: remove feature flag
- if (Flags.refactorAttentionHelper()) {
- mService.mAttentionHelper.setAudioManager(mAudioManager);
- } else {
- mService.setAudioManager(mAudioManager);
- }
-
mStrongAuthTracker = mService.new StrongAuthTrackerFake(mContext);
mService.setStrongAuthTracker(mStrongAuthTracker);
@@ -793,14 +765,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService = mService.getBinderService();
mInternalService = mService.getInternalService();
- mBinderService.createNotificationChannels(
- PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
- mBinderService.createNotificationChannels(
- PKG_P, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
- mBinderService.createNotificationChannels(
- PKG_O, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
+ mBinderService.createNotificationChannels(PKG, new ParceledListSlice(
+ Arrays.asList(mTestNotificationChannel, mSilentChannel)));
+ mBinderService.createNotificationChannels(PKG_P, new ParceledListSlice(
+ Arrays.asList(mTestNotificationChannel, mSilentChannel)));
+ mBinderService.createNotificationChannels(PKG_O, new ParceledListSlice(
+ Arrays.asList(mTestNotificationChannel, mSilentChannel)));
assertNotNull(mBinderService.getNotificationChannel(
PKG, mContext.getUserId(), PKG, TEST_CHANNEL_ID));
+ assertNotNull(mBinderService.getNotificationChannel(
+ PKG, mContext.getUserId(), PKG, mSilentChannel.getId()));
clearInvocations(mRankingHandler);
when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
@@ -1041,11 +1015,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
int userId) {
+ return generateNotificationRecord(channel, id, userId, "foo");
+ }
+
+ private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
+ int userId, String title) {
if (channel == null) {
channel = mTestNotificationChannel;
}
Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
- .setContentTitle("foo")
+ .setContentTitle(title)
.setSmallIcon(android.R.drawable.sym_def_app_icon);
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, "tag", mUid, 0,
nb.build(), new UserHandle(userId), null, 0);
@@ -1811,23 +1790,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testEnqueueNotificationWithTag_WritesExpectedLogs_NAHRefactor() throws Exception {
- // TODO b/291907312: remove feature flag
- mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
- // Cleanup NMS before re-initializing
- if (mService != null) {
- try {
- mService.onDestroy();
- } catch (IllegalStateException | IllegalArgumentException e) {
- // can throw if a broadcast receiver was never registered
- }
- }
- initNMS();
-
- testEnqueueNotificationWithTag_WritesExpectedLogs();
- }
-
- @Test
public void testEnqueueNotificationWithTag_LogsOnMajorUpdates() throws Exception {
final String tag = "testEnqueueNotificationWithTag_LogsOnMajorUpdates";
Notification original = new Notification.Builder(mContext,
@@ -5619,8 +5581,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
+ "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />"
+ "</dnd_apps>"
+ "</notification-policy>";
- UserInfo ui = new UserInfo();
- ui.id = 10;
+ UserInfo ui = new UserInfo(10, "Clone", UserInfo.FLAG_PROFILE);
ui.userType = USER_TYPE_PROFILE_CLONE;
when(mUmInternal.getUserInfo(10)).thenReturn(ui);
mService.readPolicyXml(
@@ -5646,8 +5607,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
+ "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />"
+ "</dnd_apps>"
+ "</notification-policy>";
- UserInfo ui = new UserInfo();
- ui.id = 10;
+ UserInfo ui = new UserInfo(10, "Work", UserInfo.FLAG_PROFILE);
ui.userType = USER_TYPE_PROFILE_MANAGED;
when(mUmInternal.getUserInfo(10)).thenReturn(ui);
mService.readPolicyXml(
@@ -5660,6 +5620,34 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testReadPolicyXml_doesNotRestoreManagedServicesForPrivateUser() throws Exception {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
+ final String policyXml = "<notification-policy version=\"1\">"
+ + "<ranking></ranking>"
+ + "<enabled_listeners>"
+ + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />"
+ + "</enabled_listeners>"
+ + "<enabled_assistants>"
+ + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />"
+ + "</enabled_assistants>"
+ + "<dnd_apps>"
+ + "<service_listing approved=\"test\" user=\"10\" primary=\"true\" />"
+ + "</dnd_apps>"
+ + "</notification-policy>";
+ UserInfo ui = new UserInfo(10, "Private", UserInfo.FLAG_PROFILE);
+ ui.userType = USER_TYPE_PROFILE_PRIVATE;
+ when(mUmInternal.getUserInfo(10)).thenReturn(ui);
+ mService.readPolicyXml(
+ new BufferedInputStream(new ByteArrayInputStream(policyXml.getBytes())),
+ true,
+ 10);
+ verify(mListeners, never()).readXml(any(), any(), eq(true), eq(10));
+ verify(mConditionProviders, never()).readXml(any(), any(), eq(true), eq(10));
+ verify(mAssistants, never()).readXml(any(), any(), eq(true), eq(10));
+ }
+
+ @Test
public void testReadPolicyXml_restoresManagedServicesForNonManagedUser() throws Exception {
final String policyXml = "<notification-policy version=\"1\">"
+ "<ranking></ranking>"
@@ -10105,13 +10093,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testOnBubbleMetadataChangedToSuppressNotification_soundStopped()
throws RemoteException {
- IRingtonePlayer mockPlayer = mock(IRingtonePlayer.class);
- when(mAudioManager.getRingtonePlayer()).thenReturn(mockPlayer);
- // Set up volume to be above 0, and for AudioManager to signal playback should happen,
- // for the sound to actually play
- when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
- when(mAudioManager.shouldNotificationSoundPlay(any(android.media.AudioAttributes.class)))
- .thenReturn(true);
setUpPrefsForBubbles(PKG, mUid,
true /* global */,
@@ -10130,25 +10111,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
waitForIdle();
// Check audio is stopped
- verify(mockPlayer).stopAsync();
- }
-
- @Test
- public void testOnBubbleMetadataChangedToSuppressNotification_soundStopped_NAHRefactor()
- throws Exception {
- // TODO b/291907312: remove feature flag
- mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
- // Cleanup NMS before re-initializing
- if (mService != null) {
- try {
- mService.onDestroy();
- } catch (IllegalStateException | IllegalArgumentException e) {
- // can throw if a broadcast receiver was never registered
- }
- }
- initNMS();
-
- testOnBubbleMetadataChangedToSuppressNotification_soundStopped();
+ verify(mAttentionHelper).clearEffectsLocked(nr.getKey());
}
@Test
@@ -14775,6 +14738,110 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(listener, never()).onCallNotificationRemoved(anyString(), any());
}
+ @Test
+ @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ public void rankingTime_newNotification_noisy_matchesSbn() throws Exception {
+ NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, mUserId);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag0",
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ NotificationRecord posted = mService.mNotificationList.get(0);
+ long originalPostTime = posted.getSbn().getPostTime();
+ assertThat(posted.getRankingTimeMs()).isEqualTo(originalPostTime);
+ }
+
+ @Test
+ @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ public void rankingTime_newNotification_silent_matchesSbn() throws Exception {
+ NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
+ NotificationRecord nr = generateNotificationRecord(low, mUserId);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag0",
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ NotificationRecord posted = mService.mNotificationList.get(0);
+ long originalPostTime = posted.getSbn().getPostTime();
+ assertThat(posted.getRankingTimeMs()).isEqualTo(originalPostTime);
+ }
+
+ @Test
+ @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ public void rankingTime_updatedNotification_silentSameText_originalPostTime() throws Exception {
+ NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
+ NotificationRecord nr = generateNotificationRecord(low, mUserId);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag0",
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+ NotificationRecord posted = mService.mNotificationList.get(0);
+ long originalPostTime = posted.getSbn().getPostTime();
+ assertThat(posted.getRankingTimeMs()).isEqualTo(originalPostTime);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag0",
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+ assertThat(mService.mNotificationList.get(0).getRankingTimeMs())
+ .isEqualTo(originalPostTime);
+ }
+
+ @Test
+ @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ public void rankingTime_updatedNotification_silentNewText_newPostTime() throws Exception {
+ NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
+ NotificationRecord nr = generateNotificationRecord(low, 0, mUserId);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag0",
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+ NotificationRecord posted = mService.mNotificationList.get(0);
+ long originalPostTime = posted.getSbn().getPostTime();
+ assertThat(posted.getRankingTimeMs()).isEqualTo(originalPostTime);
+
+ NotificationRecord nrUpdate = generateNotificationRecord(low, 0, mUserId, "bar");
+ // no attention helper mocked behavior needed because this does not make noise
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag0",
+ nrUpdate.getSbn().getId(), nrUpdate.getSbn().getNotification(),
+ nrUpdate.getSbn().getUserId());
+ waitForIdle();
+
+ posted = mService.mNotificationList.get(0);
+ assertThat(posted.getRankingTimeMs()).isGreaterThan(originalPostTime);
+ assertThat(posted.getRankingTimeMs()).isEqualTo(posted.getSbn().getPostTime());
+ }
+
+ @Test
+ @EnableFlags(FLAG_UPDATE_RANKING_TIME)
+ public void rankingTime_updatedNotification_noisySameText_newPostTime() throws Exception {
+ NotificationChannel low = new NotificationChannel("low", "low", IMPORTANCE_LOW);
+ NotificationRecord nr = generateNotificationRecord(low, mUserId);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag0",
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+ NotificationRecord posted = mService.mNotificationList.get(0);
+ long originalPostTime = posted.getSbn().getPostTime();
+ assertThat(posted.getRankingTimeMs()).isEqualTo(originalPostTime);
+
+ NotificationRecord nrUpdate = generateNotificationRecord(mTestNotificationChannel, mUserId);
+ when(mAttentionHelper.buzzBeepBlinkLocked(any(), any())).thenAnswer(new Answer<Object>() {
+ public Object answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ ((NotificationRecord) args[0]).resetRankingTime();
+ return 2; // beep
+ }
+ });
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag0",
+ nrUpdate.getSbn().getId(), nrUpdate.getSbn().getNotification(),
+ nrUpdate.getSbn().getUserId());
+ waitForIdle();
+ posted = mService.mNotificationList.get(0);
+ assertThat(posted.getRankingTimeMs()).isGreaterThan(originalPostTime);
+ assertThat(posted.getRankingTimeMs()).isEqualTo(posted.getSbn().getPostTime());
+ }
+
private NotificationRecord createAndPostCallStyleNotification(String packageName,
UserHandle userHandle, String testName) throws Exception {
Person person = new Person.Builder().setName("caller").build();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 70910b108fe9..c1bb3e7408fc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -164,7 +164,7 @@ public class RoleObserverTest extends UiServiceTestCase {
mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class),
mock(UriGrantsManagerInternal.class),
mock(AppOpsManager.class), mUm, mock(NotificationHistoryManager.class),
- mock(StatsManager.class), mock(TelephonyManager.class),
+ mock(StatsManager.class),
mock(ActivityManagerInternal.class),
mock(MultiRateLimiter.class), mock(PermissionHelper.class),
mock(UsageStatsManagerInternal.class), mock(TelecomManager.class),
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 0b76154d73d0..19ce217e581c 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -251,9 +251,9 @@ public class VibrationThreadTest {
VibrationEffect effect = VibrationEffect.createWaveform(
new long[]{5, 5, 5}, new int[]{1, 1, 1}, -1);
- CompletableFuture<Void> mRequestVibrationParamsFuture = CompletableFuture.runAsync(() -> {
- mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
- });
+ mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
+ CompletableFuture<Void> mRequestVibrationParamsFuture = CompletableFuture.completedFuture(
+ null);
long vibrationId = startThreadAndDispatcher(effect, mRequestVibrationParamsFuture,
USAGE_RINGTONE);
waitForCompletion();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 5861d88924e0..185677f966a4 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1589,7 +1589,8 @@ public class VibratorManagerServiceTest {
assertEquals(1f, ((PrimitiveSegment) segments.get(2)).getScale(), 1e-5);
verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.7f);
verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.4f);
- verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 1f);
+ verify(mVibratorFrameworkStatsLoggerMock,
+ timeout(TEST_TIMEOUT_MILLIS)).logVibrationAdaptiveHapticScale(UID, 1f);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index ff7129c50459..018600641853 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -64,8 +64,10 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -2368,9 +2370,7 @@ public class TransitionTests extends WindowTestsBase {
assertTrue(transitA.isCollecting());
// finish collecting A
- transitA.start();
- transitA.setAllReady();
- mSyncEngine.tryFinishForTest(transitA.getSyncId());
+ tryFinishTransitionSyncSet(transitA);
waitUntilHandlersIdle();
assertTrue(transitA.isPlaying());
@@ -2476,6 +2476,36 @@ public class TransitionTests extends WindowTestsBase {
}
@Test
+ public void testDeferredMoveTaskToBack() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = activity.getTask();
+ registerTestTransitionPlayer();
+ final TransitionController controller = mWm.mRoot.mTransitionController;
+ mSyncEngine = createTestBLASTSyncEngine();
+ controller.setSyncEngine(mSyncEngine);
+ final Transition transition = createTestTransition(TRANSIT_CHANGE, controller);
+ controller.moveToCollecting(transition);
+ task.moveTaskToBack(task);
+ // Actual action will be deferred by current transition.
+ verify(task, never()).moveToBack(any(), any());
+
+ tryFinishTransitionSyncSet(transition);
+ waitUntilHandlersIdle();
+ // Continue to move task to back after the transition is done.
+ verify(task).moveToBack(any(), any());
+ final Transition moveBackTransition = controller.getCollectingTransition();
+ assertNotNull(moveBackTransition);
+ moveBackTransition.abort();
+
+ // The move-to-back can be collected in to a collecting OPEN transition.
+ clearInvocations(task);
+ final Transition transition2 = createTestTransition(TRANSIT_OPEN, controller);
+ controller.moveToCollecting(transition2);
+ task.moveTaskToBack(task);
+ verify(task).moveToBack(any(), any());
+ }
+
+ @Test
public void testNoSyncFlagIfOneTrack() {
final TransitionController controller = mAtm.getTransitionController();
final TestTransitionPlayer player = registerTestTransitionPlayer();
@@ -2492,17 +2522,11 @@ public class TransitionTests extends WindowTestsBase {
controller.startCollectOrQueue(transitC, (deferred) -> {});
// Verify that, as-long as there is <= 1 track, we won't get a SYNC flag
- transitA.start();
- transitA.setAllReady();
- mSyncEngine.tryFinishForTest(transitA.getSyncId());
+ tryFinishTransitionSyncSet(transitA);
assertTrue((player.mLastReady.getFlags() & FLAG_SYNC) == 0);
- transitB.start();
- transitB.setAllReady();
- mSyncEngine.tryFinishForTest(transitB.getSyncId());
+ tryFinishTransitionSyncSet(transitB);
assertTrue((player.mLastReady.getFlags() & FLAG_SYNC) == 0);
- transitC.start();
- transitC.setAllReady();
- mSyncEngine.tryFinishForTest(transitC.getSyncId());
+ tryFinishTransitionSyncSet(transitC);
assertTrue((player.mLastReady.getFlags() & FLAG_SYNC) == 0);
}
@@ -2642,6 +2666,12 @@ public class TransitionTests extends WindowTestsBase {
assertEquals("reason1", condition1.mAlternate);
}
+ private void tryFinishTransitionSyncSet(Transition transition) {
+ transition.setAllReady();
+ transition.start();
+ mSyncEngine.tryFinishForTest(transition.getSyncId());
+ }
+
private static void makeTaskOrganized(Task... tasks) {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
for (Task t : tasks) {