Merge changes from topic "revert-20691780-cherrypick-modernize_alt_bouncer-2av1xvqe7q-EXOZAWOBTI"
* changes:
Revert "Modernize alternate bouncer logic"
Revert "Remove ScrimController from BiometricUnlockController"
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index 830031e..85b762d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -91,12 +91,23 @@
}
final int priority = job.getEffectivePriority();
if (mThermalStatus >= HIGHER_PRIORITY_THRESHOLD) {
- // For moderate throttling, only let expedited jobs and high priority regular jobs that
- // haven't been running for a long time run.
- return !job.shouldTreatAsExpeditedJob()
- && !(priority == JobInfo.PRIORITY_HIGH
- && mService.isCurrentlyRunningLocked(job)
- && !mService.isJobInOvertimeLocked(job));
+ // For moderate throttling:
+ // Only let expedited & user-initiated jobs run if:
+ // 1. They haven't previously run
+ // 2. They're already running and aren't yet in overtime
+ // Only let high priority jobs run if:
+ // They are already running and aren't yet in overtime
+ // Don't let any other job run.
+ if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiated()) {
+ return job.getNumPreviousAttempts() > 0
+ || (mService.isCurrentlyRunningLocked(job)
+ && mService.isJobInOvertimeLocked(job));
+ }
+ if (priority == JobInfo.PRIORITY_HIGH) {
+ return !mService.isCurrentlyRunningLocked(job)
+ || mService.isJobInOvertimeLocked(job);
+ }
+ return true;
}
if (mThermalStatus >= LOW_PRIORITY_THRESHOLD) {
// For light throttling, throttle all min priority jobs and all low priority jobs that
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index de0f752..411d157 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -458,8 +458,7 @@
&& WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
// Add onBackPressed as default back behavior.
mDefaultBackCallback = this::onBackPressed;
- getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mDefaultBackCallback);
+ getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
mDefaultBackCallback = null;
}
}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt
index 79e3d3d..00532f4 100644
--- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt
@@ -18,12 +18,17 @@
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Typography
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
+import com.android.systemui.compose.theme.typography.TypeScaleTokens
+import com.android.systemui.compose.theme.typography.TypefaceNames
+import com.android.systemui.compose.theme.typography.TypefaceTokens
+import com.android.systemui.compose.theme.typography.TypographyTokens
+import com.android.systemui.compose.theme.typography.systemUITypography
/** The Material 3 theme that should wrap all SystemUI Composables. */
@Composable
@@ -33,7 +38,7 @@
) {
val context = LocalContext.current
- // TODO(b/230605885): Define our typography and color scheme.
+ // TODO(b/230605885): Define our color scheme.
val colorScheme =
if (isDarkTheme) {
dynamicDarkColorScheme(context)
@@ -41,7 +46,11 @@
dynamicLightColorScheme(context)
}
val androidColorScheme = AndroidColorScheme(context)
- val typography = Typography()
+ val typefaceNames = remember(context) { TypefaceNames.get(context) }
+ val typography =
+ remember(typefaceNames) {
+ systemUITypography(TypographyTokens(TypeScaleTokens(TypefaceTokens(typefaceNames))))
+ }
MaterialTheme(colorScheme, typography = typography) {
CompositionLocalProvider(
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/SystemUITypography.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/SystemUITypography.kt
new file mode 100644
index 0000000..365f4bb
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/SystemUITypography.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.systemui.compose.theme.typography
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Typography
+
+/**
+ * The SystemUI typography.
+ *
+ * Do not use directly and call [MaterialTheme.typography] instead to access the different text
+ * styles.
+ */
+internal fun systemUITypography(typographyTokens: TypographyTokens): Typography {
+ return Typography(
+ displayLarge = typographyTokens.displayLarge,
+ displayMedium = typographyTokens.displayMedium,
+ displaySmall = typographyTokens.displaySmall,
+ headlineLarge = typographyTokens.headlineLarge,
+ headlineMedium = typographyTokens.headlineMedium,
+ headlineSmall = typographyTokens.headlineSmall,
+ titleLarge = typographyTokens.titleLarge,
+ titleMedium = typographyTokens.titleMedium,
+ titleSmall = typographyTokens.titleSmall,
+ bodyLarge = typographyTokens.bodyLarge,
+ bodyMedium = typographyTokens.bodyMedium,
+ bodySmall = typographyTokens.bodySmall,
+ labelLarge = typographyTokens.labelLarge,
+ labelMedium = typographyTokens.labelMedium,
+ labelSmall = typographyTokens.labelSmall,
+ )
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypeScaleTokens.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypeScaleTokens.kt
new file mode 100644
index 0000000..537ba0b
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypeScaleTokens.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.systemui.compose.theme.typography
+
+import androidx.compose.ui.unit.sp
+
+internal class TypeScaleTokens(typefaceTokens: TypefaceTokens) {
+ val bodyLargeFont = typefaceTokens.plain
+ val bodyLargeLineHeight = 24.0.sp
+ val bodyLargeSize = 16.sp
+ val bodyLargeTracking = 0.0.sp
+ val bodyLargeWeight = TypefaceTokens.WeightRegular
+ val bodyMediumFont = typefaceTokens.plain
+ val bodyMediumLineHeight = 20.0.sp
+ val bodyMediumSize = 14.sp
+ val bodyMediumTracking = 0.0.sp
+ val bodyMediumWeight = TypefaceTokens.WeightRegular
+ val bodySmallFont = typefaceTokens.plain
+ val bodySmallLineHeight = 16.0.sp
+ val bodySmallSize = 12.sp
+ val bodySmallTracking = 0.1.sp
+ val bodySmallWeight = TypefaceTokens.WeightRegular
+ val displayLargeFont = typefaceTokens.brand
+ val displayLargeLineHeight = 64.0.sp
+ val displayLargeSize = 57.sp
+ val displayLargeTracking = 0.0.sp
+ val displayLargeWeight = TypefaceTokens.WeightRegular
+ val displayMediumFont = typefaceTokens.brand
+ val displayMediumLineHeight = 52.0.sp
+ val displayMediumSize = 45.sp
+ val displayMediumTracking = 0.0.sp
+ val displayMediumWeight = TypefaceTokens.WeightRegular
+ val displaySmallFont = typefaceTokens.brand
+ val displaySmallLineHeight = 44.0.sp
+ val displaySmallSize = 36.sp
+ val displaySmallTracking = 0.0.sp
+ val displaySmallWeight = TypefaceTokens.WeightRegular
+ val headlineLargeFont = typefaceTokens.brand
+ val headlineLargeLineHeight = 40.0.sp
+ val headlineLargeSize = 32.sp
+ val headlineLargeTracking = 0.0.sp
+ val headlineLargeWeight = TypefaceTokens.WeightRegular
+ val headlineMediumFont = typefaceTokens.brand
+ val headlineMediumLineHeight = 36.0.sp
+ val headlineMediumSize = 28.sp
+ val headlineMediumTracking = 0.0.sp
+ val headlineMediumWeight = TypefaceTokens.WeightRegular
+ val headlineSmallFont = typefaceTokens.brand
+ val headlineSmallLineHeight = 32.0.sp
+ val headlineSmallSize = 24.sp
+ val headlineSmallTracking = 0.0.sp
+ val headlineSmallWeight = TypefaceTokens.WeightRegular
+ val labelLargeFont = typefaceTokens.plain
+ val labelLargeLineHeight = 20.0.sp
+ val labelLargeSize = 14.sp
+ val labelLargeTracking = 0.0.sp
+ val labelLargeWeight = TypefaceTokens.WeightMedium
+ val labelMediumFont = typefaceTokens.plain
+ val labelMediumLineHeight = 16.0.sp
+ val labelMediumSize = 12.sp
+ val labelMediumTracking = 0.1.sp
+ val labelMediumWeight = TypefaceTokens.WeightMedium
+ val labelSmallFont = typefaceTokens.plain
+ val labelSmallLineHeight = 16.0.sp
+ val labelSmallSize = 11.sp
+ val labelSmallTracking = 0.1.sp
+ val labelSmallWeight = TypefaceTokens.WeightMedium
+ val titleLargeFont = typefaceTokens.brand
+ val titleLargeLineHeight = 28.0.sp
+ val titleLargeSize = 22.sp
+ val titleLargeTracking = 0.0.sp
+ val titleLargeWeight = TypefaceTokens.WeightRegular
+ val titleMediumFont = typefaceTokens.plain
+ val titleMediumLineHeight = 24.0.sp
+ val titleMediumSize = 16.sp
+ val titleMediumTracking = 0.0.sp
+ val titleMediumWeight = TypefaceTokens.WeightMedium
+ val titleSmallFont = typefaceTokens.plain
+ val titleSmallLineHeight = 20.0.sp
+ val titleSmallSize = 14.sp
+ val titleSmallTracking = 0.0.sp
+ val titleSmallWeight = TypefaceTokens.WeightMedium
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypefaceTokens.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypefaceTokens.kt
new file mode 100644
index 0000000..f3d554f
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypefaceTokens.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalTextApi::class)
+
+package com.android.systemui.compose.theme.typography
+
+import android.content.Context
+import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.font.DeviceFontFamilyName
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+
+internal class TypefaceTokens(typefaceNames: TypefaceNames) {
+ companion object {
+ val WeightMedium = FontWeight.Medium
+ val WeightRegular = FontWeight.Normal
+ }
+
+ private val brandFont = DeviceFontFamilyName(typefaceNames.brand)
+ private val plainFont = DeviceFontFamilyName(typefaceNames.plain)
+
+ val brand =
+ FontFamily(
+ Font(brandFont, weight = WeightMedium),
+ Font(brandFont, weight = WeightRegular),
+ )
+ val plain =
+ FontFamily(
+ Font(plainFont, weight = WeightMedium),
+ Font(plainFont, weight = WeightRegular),
+ )
+}
+
+internal data class TypefaceNames
+private constructor(
+ val brand: String,
+ val plain: String,
+) {
+ private enum class Config(val configName: String, val default: String) {
+ Brand("config_headlineFontFamily", "sans-serif"),
+ Plain("config_bodyFontFamily", "sans-serif"),
+ }
+
+ companion object {
+ fun get(context: Context): TypefaceNames {
+ return TypefaceNames(
+ brand = getTypefaceName(context, Config.Brand),
+ plain = getTypefaceName(context, Config.Plain),
+ )
+ }
+
+ private fun getTypefaceName(context: Context, config: Config): String {
+ return context
+ .getString(context.resources.getIdentifier(config.configName, "string", "android"))
+ .takeIf { it.isNotEmpty() }
+ ?: config.default
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypographyTokens.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypographyTokens.kt
new file mode 100644
index 0000000..55f3d1f
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/typography/TypographyTokens.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.systemui.compose.theme.typography
+
+import androidx.compose.ui.text.TextStyle
+
+internal class TypographyTokens(typeScaleTokens: TypeScaleTokens) {
+ val bodyLarge =
+ TextStyle(
+ fontFamily = typeScaleTokens.bodyLargeFont,
+ fontWeight = typeScaleTokens.bodyLargeWeight,
+ fontSize = typeScaleTokens.bodyLargeSize,
+ lineHeight = typeScaleTokens.bodyLargeLineHeight,
+ letterSpacing = typeScaleTokens.bodyLargeTracking,
+ )
+ val bodyMedium =
+ TextStyle(
+ fontFamily = typeScaleTokens.bodyMediumFont,
+ fontWeight = typeScaleTokens.bodyMediumWeight,
+ fontSize = typeScaleTokens.bodyMediumSize,
+ lineHeight = typeScaleTokens.bodyMediumLineHeight,
+ letterSpacing = typeScaleTokens.bodyMediumTracking,
+ )
+ val bodySmall =
+ TextStyle(
+ fontFamily = typeScaleTokens.bodySmallFont,
+ fontWeight = typeScaleTokens.bodySmallWeight,
+ fontSize = typeScaleTokens.bodySmallSize,
+ lineHeight = typeScaleTokens.bodySmallLineHeight,
+ letterSpacing = typeScaleTokens.bodySmallTracking,
+ )
+ val displayLarge =
+ TextStyle(
+ fontFamily = typeScaleTokens.displayLargeFont,
+ fontWeight = typeScaleTokens.displayLargeWeight,
+ fontSize = typeScaleTokens.displayLargeSize,
+ lineHeight = typeScaleTokens.displayLargeLineHeight,
+ letterSpacing = typeScaleTokens.displayLargeTracking,
+ )
+ val displayMedium =
+ TextStyle(
+ fontFamily = typeScaleTokens.displayMediumFont,
+ fontWeight = typeScaleTokens.displayMediumWeight,
+ fontSize = typeScaleTokens.displayMediumSize,
+ lineHeight = typeScaleTokens.displayMediumLineHeight,
+ letterSpacing = typeScaleTokens.displayMediumTracking,
+ )
+ val displaySmall =
+ TextStyle(
+ fontFamily = typeScaleTokens.displaySmallFont,
+ fontWeight = typeScaleTokens.displaySmallWeight,
+ fontSize = typeScaleTokens.displaySmallSize,
+ lineHeight = typeScaleTokens.displaySmallLineHeight,
+ letterSpacing = typeScaleTokens.displaySmallTracking,
+ )
+ val headlineLarge =
+ TextStyle(
+ fontFamily = typeScaleTokens.headlineLargeFont,
+ fontWeight = typeScaleTokens.headlineLargeWeight,
+ fontSize = typeScaleTokens.headlineLargeSize,
+ lineHeight = typeScaleTokens.headlineLargeLineHeight,
+ letterSpacing = typeScaleTokens.headlineLargeTracking,
+ )
+ val headlineMedium =
+ TextStyle(
+ fontFamily = typeScaleTokens.headlineMediumFont,
+ fontWeight = typeScaleTokens.headlineMediumWeight,
+ fontSize = typeScaleTokens.headlineMediumSize,
+ lineHeight = typeScaleTokens.headlineMediumLineHeight,
+ letterSpacing = typeScaleTokens.headlineMediumTracking,
+ )
+ val headlineSmall =
+ TextStyle(
+ fontFamily = typeScaleTokens.headlineSmallFont,
+ fontWeight = typeScaleTokens.headlineSmallWeight,
+ fontSize = typeScaleTokens.headlineSmallSize,
+ lineHeight = typeScaleTokens.headlineSmallLineHeight,
+ letterSpacing = typeScaleTokens.headlineSmallTracking,
+ )
+ val labelLarge =
+ TextStyle(
+ fontFamily = typeScaleTokens.labelLargeFont,
+ fontWeight = typeScaleTokens.labelLargeWeight,
+ fontSize = typeScaleTokens.labelLargeSize,
+ lineHeight = typeScaleTokens.labelLargeLineHeight,
+ letterSpacing = typeScaleTokens.labelLargeTracking,
+ )
+ val labelMedium =
+ TextStyle(
+ fontFamily = typeScaleTokens.labelMediumFont,
+ fontWeight = typeScaleTokens.labelMediumWeight,
+ fontSize = typeScaleTokens.labelMediumSize,
+ lineHeight = typeScaleTokens.labelMediumLineHeight,
+ letterSpacing = typeScaleTokens.labelMediumTracking,
+ )
+ val labelSmall =
+ TextStyle(
+ fontFamily = typeScaleTokens.labelSmallFont,
+ fontWeight = typeScaleTokens.labelSmallWeight,
+ fontSize = typeScaleTokens.labelSmallSize,
+ lineHeight = typeScaleTokens.labelSmallLineHeight,
+ letterSpacing = typeScaleTokens.labelSmallTracking,
+ )
+ val titleLarge =
+ TextStyle(
+ fontFamily = typeScaleTokens.titleLargeFont,
+ fontWeight = typeScaleTokens.titleLargeWeight,
+ fontSize = typeScaleTokens.titleLargeSize,
+ lineHeight = typeScaleTokens.titleLargeLineHeight,
+ letterSpacing = typeScaleTokens.titleLargeTracking,
+ )
+ val titleMedium =
+ TextStyle(
+ fontFamily = typeScaleTokens.titleMediumFont,
+ fontWeight = typeScaleTokens.titleMediumWeight,
+ fontSize = typeScaleTokens.titleMediumSize,
+ lineHeight = typeScaleTokens.titleMediumLineHeight,
+ letterSpacing = typeScaleTokens.titleMediumTracking,
+ )
+ val titleSmall =
+ TextStyle(
+ fontFamily = typeScaleTokens.titleSmallFont,
+ fontWeight = typeScaleTokens.titleSmallWeight,
+ fontSize = typeScaleTokens.titleSmallSize,
+ lineHeight = typeScaleTokens.titleSmallLineHeight,
+ letterSpacing = typeScaleTokens.titleSmallTracking,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 87e9d56..8f38e58 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -15,6 +15,7 @@
*/
package com.android.keyguard
+import android.app.WallpaperManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -100,9 +101,13 @@
private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
private fun updateColors() {
+
if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) {
- smallClockIsDark = smallRegionSampler!!.currentRegionDarkness().isDark
- largeClockIsDark = largeRegionSampler!!.currentRegionDarkness().isDark
+ val wallpaperManager = WallpaperManager.getInstance(context)
+ if (!wallpaperManager.lockScreenWallpaperExists()) {
+ smallClockIsDark = smallRegionSampler!!.currentRegionDarkness().isDark
+ largeClockIsDark = largeRegionSampler!!.currentRegionDarkness().isDark
+ }
} else {
val isLightTheme = TypedValue()
context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 8b4b30c..3644d42 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -36,6 +36,8 @@
import android.app.role.RoleManager;
import android.app.smartspace.SmartspaceManager;
import android.app.trust.TrustManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -616,4 +618,16 @@
static CameraManager provideCameraManager(Context context) {
return context.getSystemService(CameraManager.class);
}
+
+ @Provides
+ @Singleton
+ static BluetoothManager provideBluetoothManager(Context context) {
+ return context.getSystemService(BluetoothManager.class);
+ }
+
+ @Provides
+ @Singleton
+ static BluetoothAdapter provideBluetoothAdapter(BluetoothManager bluetoothManager) {
+ return bluetoothManager.getAdapter();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 6dc4f5c..68f4dbe 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -125,10 +125,10 @@
default void init() {
// Initialize components that have no direct tie to the dagger dependency graph,
// but are critical to this component's operation
- // TODO(b/205034537): I think this is a good idea?
getSysUIUnfoldComponent().ifPresent(c -> {
c.getUnfoldLightRevealOverlayAnimation().init();
c.getUnfoldTransitionWallpaperController().init();
+ c.getUnfoldHapticsPlayer();
});
getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init());
// No init method needed, just needs to be gotten so that it's created.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ff3714f..4d0edc5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -251,7 +251,7 @@
// 801 - region sampling
// TODO(b/254512848): Tracking Bug
- val REGION_SAMPLING = unreleasedFlag(801, "region_sampling")
+ val REGION_SAMPLING = unreleasedFlag(801, "region_sampling", teamfood = true)
// 802 - wallpaper rendering
// TODO(b/254512923): Tracking Bug
@@ -430,6 +430,7 @@
// 2300 - stylus
@JvmField val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used")
+ @JvmField val ENABLE_STYLUS_CHARGING_UI = unreleasedFlag(2301, "enable_stylus_charging_ui")
// 2400 - performance tools and debugging info
// TODO(b/238923086): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index ac7c70b..f60d7ea 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -55,6 +55,7 @@
import android.view.WindowInsetsController.Behavior;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.view.AppearanceRegion;
@@ -125,7 +126,7 @@
private final DisplayManager mDisplayManager;
private Context mWindowContext;
private ScreenPinningNotify mScreenPinningNotify;
- private int mNavigationMode;
+ private int mNavigationMode = -1;
private final Consumer<Rect> mPipListener;
/**
@@ -217,8 +218,7 @@
parseCurrentSysuiState();
mCommandQueue.addCallback(this);
mOverviewProxyService.addCallback(this);
- mEdgeBackGestureHandler.onNavigationModeChanged(
- mNavigationModeController.addListener(this));
+ onNavigationModeChanged(mNavigationModeController.addListener(this));
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
mNavBarHelper.init();
mEdgeBackGestureHandler.onNavBarAttached();
@@ -492,6 +492,11 @@
!QuickStepContract.isGesturalMode(mNavigationMode));
}
+ @VisibleForTesting
+ int getNavigationMode() {
+ return mNavigationMode;
+ }
+
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("TaskbarDelegate (displayId=" + mDisplayId + "):");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 8bb2d46..56d3b7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.lockscreen
import android.app.PendingIntent
+import android.app.WallpaperManager
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
@@ -390,7 +391,8 @@
}
private fun updateTextColorFromWallpaper() {
- if (!regionSamplingEnabled) {
+ val wallpaperManager = WallpaperManager.getInstance(context)
+ if (!regionSamplingEnabled || wallpaperManager.lockScreenWallpaperExists()) {
val wallpaperTextColor =
Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor)
smartspaceViews.forEach { it.setPrimaryTextColor(wallpaperTextColor) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt
new file mode 100644
index 0000000..d9e3f8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiDebug.kt
@@ -0,0 +1,16 @@
+package com.android.systemui.statusbar.notification.fsi
+
+class FsiDebug {
+
+ companion object {
+ private const val debugTag = "FsiDebug"
+ private const val debug = true
+
+ fun log(s: Any) {
+ if (!debug) {
+ return
+ }
+ android.util.Log.d(debugTag, "$s")
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
new file mode 100644
index 0000000..3e111e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -0,0 +1,199 @@
+/*
+ * 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.systemui.stylus
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.hardware.input.InputManager
+import android.os.Handler
+import android.util.ArrayMap
+import android.util.Log
+import android.view.InputDevice
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import java.util.concurrent.CopyOnWriteArrayList
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * A class which keeps track of InputDevice events related to stylus devices, and notifies
+ * registered callbacks of stylus events.
+ */
+@SysUISingleton
+class StylusManager
+@Inject
+constructor(
+ private val inputManager: InputManager,
+ private val bluetoothAdapter: BluetoothAdapter,
+ @Background private val handler: Handler,
+ @Background private val executor: Executor,
+) : InputManager.InputDeviceListener, BluetoothAdapter.OnMetadataChangedListener {
+
+ private val stylusCallbacks: CopyOnWriteArrayList<StylusCallback> = CopyOnWriteArrayList()
+ private val stylusBatteryCallbacks: CopyOnWriteArrayList<StylusBatteryCallback> =
+ CopyOnWriteArrayList()
+ // This map should only be accessed on the handler
+ private val inputDeviceAddressMap: MutableMap<Int, String?> = ArrayMap()
+
+ /**
+ * Starts listening to InputManager InputDevice events. Will also load the InputManager snapshot
+ * at time of starting.
+ */
+ fun startListener() {
+ addExistingStylusToMap()
+ inputManager.registerInputDeviceListener(this, handler)
+ }
+
+ /** Registers a StylusCallback to listen to stylus events. */
+ fun registerCallback(callback: StylusCallback) {
+ stylusCallbacks.add(callback)
+ }
+
+ /** Unregisters a StylusCallback. If StylusCallback is not registered, is a no-op. */
+ fun unregisterCallback(callback: StylusCallback) {
+ stylusCallbacks.remove(callback)
+ }
+
+ fun registerBatteryCallback(callback: StylusBatteryCallback) {
+ stylusBatteryCallbacks.add(callback)
+ }
+
+ fun unregisterBatteryCallback(callback: StylusBatteryCallback) {
+ stylusBatteryCallbacks.remove(callback)
+ }
+
+ override fun onInputDeviceAdded(deviceId: Int) {
+ val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
+ if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
+
+ // TODO(b/257936830): get address once input api available
+ val btAddress: String? = null
+ inputDeviceAddressMap[deviceId] = btAddress
+ executeStylusCallbacks { cb -> cb.onStylusAdded(deviceId) }
+
+ if (btAddress != null) {
+ onStylusBluetoothConnected(btAddress)
+ executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, btAddress) }
+ }
+ }
+
+ override fun onInputDeviceChanged(deviceId: Int) {
+ val device: InputDevice = inputManager.getInputDevice(deviceId) ?: return
+ if (!device.supportsSource(InputDevice.SOURCE_STYLUS)) return
+
+ // TODO(b/257936830): get address once input api available
+ val currAddress: String? = null
+ val prevAddress: String? = inputDeviceAddressMap[deviceId]
+ inputDeviceAddressMap[deviceId] = currAddress
+
+ if (prevAddress == null && currAddress != null) {
+ onStylusBluetoothConnected(currAddress)
+ executeStylusCallbacks { cb -> cb.onStylusBluetoothConnected(deviceId, currAddress) }
+ }
+
+ if (prevAddress != null && currAddress == null) {
+ onStylusBluetoothDisconnected(prevAddress)
+ executeStylusCallbacks { cb -> cb.onStylusBluetoothDisconnected(deviceId, prevAddress) }
+ }
+ }
+
+ override fun onInputDeviceRemoved(deviceId: Int) {
+ if (!inputDeviceAddressMap.contains(deviceId)) return
+
+ val btAddress: String? = inputDeviceAddressMap[deviceId]
+ inputDeviceAddressMap.remove(deviceId)
+ if (btAddress != null) {
+ onStylusBluetoothDisconnected(btAddress)
+ executeStylusCallbacks { cb -> cb.onStylusBluetoothDisconnected(deviceId, btAddress) }
+ }
+ executeStylusCallbacks { cb -> cb.onStylusRemoved(deviceId) }
+ }
+
+ override fun onMetadataChanged(device: BluetoothDevice, key: Int, value: ByteArray?) {
+ handler.post executeMetadataChanged@{
+ if (key != BluetoothDevice.METADATA_MAIN_CHARGING || value == null)
+ return@executeMetadataChanged
+
+ val inputDeviceId: Int =
+ inputDeviceAddressMap.filterValues { it == device.address }.keys.firstOrNull()
+ ?: return@executeMetadataChanged
+
+ val isCharging = String(value) == "true"
+
+ executeStylusBatteryCallbacks { cb ->
+ cb.onStylusBluetoothChargingStateChanged(inputDeviceId, device, isCharging)
+ }
+ }
+ }
+
+ private fun onStylusBluetoothConnected(btAddress: String) {
+ val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(btAddress) ?: return
+ try {
+ bluetoothAdapter.addOnMetadataChangedListener(device, executor, this)
+ } catch (e: IllegalArgumentException) {
+ Log.e(TAG, "$e: Metadata listener already registered for device. Ignoring.")
+ }
+ }
+
+ private fun onStylusBluetoothDisconnected(btAddress: String) {
+ val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(btAddress) ?: return
+ try {
+ bluetoothAdapter.removeOnMetadataChangedListener(device, this)
+ } catch (e: IllegalArgumentException) {
+ Log.e(TAG, "$e: Metadata listener does not exist for device. Ignoring.")
+ }
+ }
+
+ private fun executeStylusCallbacks(run: (cb: StylusCallback) -> Unit) {
+ stylusCallbacks.forEach(run)
+ }
+
+ private fun executeStylusBatteryCallbacks(run: (cb: StylusBatteryCallback) -> Unit) {
+ stylusBatteryCallbacks.forEach(run)
+ }
+
+ private fun addExistingStylusToMap() {
+ for (deviceId: Int in inputManager.inputDeviceIds) {
+ val device: InputDevice = inputManager.getInputDevice(deviceId) ?: continue
+ if (device.supportsSource(InputDevice.SOURCE_STYLUS)) {
+ // TODO(b/257936830): get address once input api available
+ inputDeviceAddressMap[deviceId] = null
+ }
+ }
+ }
+
+ /** Callback interface to receive events from the StylusManager. */
+ interface StylusCallback {
+ fun onStylusAdded(deviceId: Int) {}
+ fun onStylusRemoved(deviceId: Int) {}
+ fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {}
+ fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) {}
+ }
+
+ /** Callback interface to receive stylus battery events from the StylusManager. */
+ interface StylusBatteryCallback {
+ fun onStylusBluetoothChargingStateChanged(
+ inputDeviceId: Int,
+ btDevice: BluetoothDevice,
+ isCharging: Boolean
+ ) {}
+ }
+
+ companion object {
+ private val TAG = StylusManager::class.simpleName.orEmpty()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 13ac39c..209d93f 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -92,5 +92,7 @@
fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController
+ fun getUnfoldHapticsPlayer(): UnfoldHapticsPlayer
+
fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
new file mode 100644
index 0000000..7726d09
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
@@ -0,0 +1,93 @@
+package com.android.systemui.unfold
+
+import android.os.SystemProperties
+import android.os.VibrationEffect
+import android.os.Vibrator
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import javax.inject.Inject
+
+/**
+ * Class that plays a haptics effect during unfolding a foldable device
+ */
+@SysUIUnfoldScope
+class UnfoldHapticsPlayer
+@Inject
+constructor(
+ unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+ private val vibrator: Vibrator?
+) : TransitionProgressListener {
+
+ init {
+ if (vibrator != null) {
+ // We don't need to remove the callback because we should listen to it
+ // the whole time when SystemUI process is alive
+ unfoldTransitionProgressProvider.addCallback(this)
+ }
+ }
+
+ private var lastTransitionProgress = TRANSITION_PROGRESS_FULL_OPEN
+
+ override fun onTransitionStarted() {
+ lastTransitionProgress = TRANSITION_PROGRESS_CLOSED
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ lastTransitionProgress = progress
+ }
+
+ override fun onTransitionFinishing() {
+ // Run haptics only if the animation is long enough to notice
+ if (lastTransitionProgress < TRANSITION_NOTICEABLE_THRESHOLD) {
+ playHaptics()
+ }
+ }
+
+ override fun onTransitionFinished() {
+ lastTransitionProgress = TRANSITION_PROGRESS_FULL_OPEN
+ }
+
+ private fun playHaptics() {
+ vibrator?.vibrate(effect)
+ }
+
+ private val hapticsScale: Float
+ get() {
+ val intensityString = SystemProperties.get("persist.unfold.haptics_scale", "0.1")
+ return intensityString.toFloatOrNull() ?: 0.1f
+ }
+
+ private val hapticsScaleTick: Float
+ get() {
+ val intensityString =
+ SystemProperties.get("persist.unfold.haptics_scale_end_tick", "0.6")
+ return intensityString.toFloatOrNull() ?: 0.6f
+ }
+
+ private val primitivesCount: Int
+ get() {
+ val count = SystemProperties.get("persist.unfold.primitives_count", "18")
+ return count.toIntOrNull() ?: 18
+ }
+
+ private val effect: VibrationEffect by lazy {
+ val composition =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0F, 0)
+
+ repeat(primitivesCount) {
+ composition.addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ hapticsScale,
+ 0
+ )
+ }
+
+ composition
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, hapticsScaleTick)
+ .compose()
+ }
+}
+
+private const val TRANSITION_PROGRESS_CLOSED = 0f
+private const val TRANSITION_PROGRESS_FULL_OPEN = 1f
+private const val TRANSITION_NOTICEABLE_THRESHOLD = 0.9f
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
new file mode 100644
index 0000000..1742c69
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
@@ -0,0 +1,93 @@
+package com.android.systemui.navigationbar
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.model.SysUiState
+import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler
+import com.android.systemui.recents.OverviewProxyService
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.phone.AutoHideController
+import com.android.systemui.statusbar.phone.LightBarController
+import com.android.systemui.statusbar.phone.LightBarTransitionsController
+import com.android.wm.shell.back.BackAnimation
+import com.android.wm.shell.pip.Pip
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Optional
+
+@SmallTest
+class TaskbarDelegateTest : SysuiTestCase() {
+ val DISPLAY_ID = 0;
+ val MODE_GESTURE = 0;
+ val MODE_THREE_BUTTON = 1;
+
+ private lateinit var mTaskbarDelegate: TaskbarDelegate
+ @Mock
+ lateinit var mEdgeBackGestureHandlerFactory : EdgeBackGestureHandler.Factory
+ @Mock
+ lateinit var mEdgeBackGestureHandler : EdgeBackGestureHandler
+ @Mock
+ lateinit var mLightBarControllerFactory : LightBarTransitionsController.Factory
+ @Mock
+ lateinit var mLightBarTransitionController: LightBarTransitionsController
+ @Mock
+ lateinit var mCommandQueue: CommandQueue
+ @Mock
+ lateinit var mOverviewProxyService: OverviewProxyService
+ @Mock
+ lateinit var mNavBarHelper: NavBarHelper
+ @Mock
+ lateinit var mNavigationModeController: NavigationModeController
+ @Mock
+ lateinit var mSysUiState: SysUiState
+ @Mock
+ lateinit var mDumpManager: DumpManager
+ @Mock
+ lateinit var mAutoHideController: AutoHideController
+ @Mock
+ lateinit var mLightBarController: LightBarController
+ @Mock
+ lateinit var mOptionalPip: Optional<Pip>
+ @Mock
+ lateinit var mBackAnimation: BackAnimation
+ @Mock
+ lateinit var mCurrentSysUiState: NavBarHelper.CurrentSysuiState
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ `when`(mEdgeBackGestureHandlerFactory.create(context)).thenReturn(mEdgeBackGestureHandler)
+ `when`(mLightBarControllerFactory.create(any())).thenReturn(mLightBarTransitionController)
+ `when`(mNavBarHelper.currentSysuiState).thenReturn(mCurrentSysUiState)
+ `when`(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState)
+ mTaskbarDelegate = TaskbarDelegate(context, mEdgeBackGestureHandlerFactory,
+ mLightBarControllerFactory)
+ mTaskbarDelegate.setDependencies(mCommandQueue, mOverviewProxyService, mNavBarHelper,
+ mNavigationModeController, mSysUiState, mDumpManager, mAutoHideController,
+ mLightBarController, mOptionalPip, mBackAnimation)
+ }
+
+ @Test
+ fun navigationModeInitialized() {
+ `when`(mNavigationModeController.addListener(any())).thenReturn(MODE_THREE_BUTTON)
+ assert(mTaskbarDelegate.navigationMode == -1)
+ mTaskbarDelegate.init(DISPLAY_ID)
+ assert(mTaskbarDelegate.navigationMode == MODE_THREE_BUTTON)
+ }
+
+ @Test
+ fun navigationModeInitialized_notifyEdgeBackHandler() {
+ `when`(mNavigationModeController.addListener(any())).thenReturn(MODE_GESTURE)
+ mTaskbarDelegate.init(DISPLAY_ID)
+ verify(mEdgeBackGestureHandler, times(1)).onNavigationModeChanged(MODE_GESTURE)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
new file mode 100644
index 0000000..58b5560
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
@@ -0,0 +1,338 @@
+/*
+ * 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.systemui.stylus
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.hardware.input.InputManager
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.view.InputDevice
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@Ignore("b/257936830 until bt APIs")
+class StylusManagerTest : SysuiTestCase() {
+ @Mock lateinit var inputManager: InputManager
+
+ @Mock lateinit var stylusDevice: InputDevice
+
+ @Mock lateinit var btStylusDevice: InputDevice
+
+ @Mock lateinit var otherDevice: InputDevice
+
+ @Mock lateinit var bluetoothAdapter: BluetoothAdapter
+
+ @Mock lateinit var bluetoothDevice: BluetoothDevice
+
+ @Mock lateinit var handler: Handler
+
+ @Mock lateinit var stylusCallback: StylusManager.StylusCallback
+
+ @Mock lateinit var otherStylusCallback: StylusManager.StylusCallback
+
+ @Mock lateinit var stylusBatteryCallback: StylusManager.StylusBatteryCallback
+
+ @Mock lateinit var otherStylusBatteryCallback: StylusManager.StylusBatteryCallback
+
+ private lateinit var stylusManager: StylusManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(handler.post(any())).thenAnswer {
+ (it.arguments[0] as Runnable).run()
+ true
+ }
+
+ stylusManager = StylusManager(inputManager, bluetoothAdapter, handler, EXECUTOR)
+
+ stylusManager.registerCallback(stylusCallback)
+
+ stylusManager.registerBatteryCallback(stylusBatteryCallback)
+
+ whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false)
+ whenever(stylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
+ whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
+
+ // whenever(stylusDevice.bluetoothAddress).thenReturn(null)
+ // whenever(btStylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)
+
+ whenever(inputManager.getInputDevice(OTHER_DEVICE_ID)).thenReturn(otherDevice)
+ whenever(inputManager.getInputDevice(STYLUS_DEVICE_ID)).thenReturn(stylusDevice)
+ whenever(inputManager.getInputDevice(BT_STYLUS_DEVICE_ID)).thenReturn(btStylusDevice)
+ whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(STYLUS_DEVICE_ID))
+
+ whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(bluetoothDevice)
+ whenever(bluetoothDevice.address).thenReturn(STYLUS_BT_ADDRESS)
+ }
+
+ @Test
+ fun startListener_registersInputDeviceListener() {
+ stylusManager.startListener()
+
+ verify(inputManager, times(1)).registerInputDeviceListener(any(), any())
+ }
+
+ @Test
+ fun onInputDeviceAdded_multipleRegisteredCallbacks_callsAll() {
+ stylusManager.registerCallback(otherStylusCallback)
+
+ stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+ verify(stylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID)
+ verifyNoMoreInteractions(stylusCallback)
+ verify(otherStylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID)
+ verifyNoMoreInteractions(otherStylusCallback)
+ }
+
+ @Test
+ fun onInputDeviceAdded_stylus_callsCallbacksOnStylusAdded() {
+ stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+ verify(stylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID)
+ verifyNoMoreInteractions(stylusCallback)
+ }
+
+ @Test
+ fun onInputDeviceAdded_btStylus_callsCallbacksWithAddress() {
+ stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+ inOrder(stylusCallback).let {
+ it.verify(stylusCallback, times(1)).onStylusAdded(BT_STYLUS_DEVICE_ID)
+ it.verify(stylusCallback, times(1))
+ .onStylusBluetoothConnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
+ }
+ }
+
+ @Test
+ fun onInputDeviceAdded_notStylus_doesNotCallCallbacks() {
+ stylusManager.onInputDeviceAdded(OTHER_DEVICE_ID)
+
+ verifyNoMoreInteractions(stylusCallback)
+ }
+
+ @Test
+ fun onInputDeviceChanged_multipleRegisteredCallbacks_callsAll() {
+ stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+ // whenever(stylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)
+ stylusManager.registerCallback(otherStylusCallback)
+
+ stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)
+
+ verify(stylusCallback, times(1))
+ .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
+ verify(otherStylusCallback, times(1))
+ .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
+ }
+
+ @Test
+ fun onInputDeviceChanged_stylusNewBtConnection_callsCallbacks() {
+ stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+ // whenever(stylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)
+
+ stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)
+
+ verify(stylusCallback, times(1))
+ .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
+ }
+
+ @Test
+ fun onInputDeviceChanged_stylusLostBtConnection_callsCallbacks() {
+ stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+ // whenever(btStylusDevice.bluetoothAddress).thenReturn(null)
+
+ stylusManager.onInputDeviceChanged(BT_STYLUS_DEVICE_ID)
+
+ verify(stylusCallback, times(1))
+ .onStylusBluetoothDisconnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
+ }
+
+ @Test
+ fun onInputDeviceChanged_btConnection_stylusAlreadyBtConnected_onlyCallsListenersOnce() {
+ stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+ stylusManager.onInputDeviceChanged(BT_STYLUS_DEVICE_ID)
+
+ verify(stylusCallback, times(1))
+ .onStylusBluetoothConnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
+ }
+
+ @Test
+ fun onInputDeviceChanged_noBtConnection_stylusNeverBtConnected_doesNotCallCallbacks() {
+ stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+ stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)
+
+ verify(stylusCallback, never()).onStylusBluetoothDisconnected(any(), any())
+ }
+
+ @Test
+ fun onInputDeviceRemoved_multipleRegisteredCallbacks_callsAll() {
+ stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+ stylusManager.registerCallback(otherStylusCallback)
+
+ stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+ verify(stylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID)
+ verify(otherStylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID)
+ }
+
+ @Test
+ fun onInputDeviceRemoved_stylus_callsCallbacks() {
+ stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
+
+ stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)
+
+ verify(stylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID)
+ verify(stylusCallback, never()).onStylusBluetoothDisconnected(any(), any())
+ }
+
+ @Test
+ fun onInputDeviceRemoved_btStylus_callsCallbacks() {
+ stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+ stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID)
+
+ inOrder(stylusCallback).let {
+ it.verify(stylusCallback, times(1))
+ .onStylusBluetoothDisconnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
+ it.verify(stylusCallback, times(1)).onStylusRemoved(BT_STYLUS_DEVICE_ID)
+ }
+ }
+
+ @Test
+ fun onStylusBluetoothConnected_registersMetadataListener() {
+ stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+ verify(bluetoothAdapter, times(1)).addOnMetadataChangedListener(any(), any(), any())
+ }
+
+ @Test
+ fun onStylusBluetoothConnected_noBluetoothDevice_doesNotRegisterMetadataListener() {
+ whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(null)
+
+ stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+ verify(bluetoothAdapter, never()).addOnMetadataChangedListener(any(), any(), any())
+ }
+
+ @Test
+ fun onStylusBluetoothDisconnected_unregistersMetadataListener() {
+ stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+ stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID)
+
+ verify(bluetoothAdapter, times(1)).removeOnMetadataChangedListener(any(), any())
+ }
+
+ @Test
+ fun onMetadataChanged_multipleRegisteredBatteryCallbacks_executesAll() {
+ stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+ stylusManager.registerBatteryCallback(otherStylusBatteryCallback)
+
+ stylusManager.onMetadataChanged(
+ bluetoothDevice,
+ BluetoothDevice.METADATA_MAIN_CHARGING,
+ "true".toByteArray()
+ )
+
+ verify(stylusBatteryCallback, times(1))
+ .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true)
+ verify(otherStylusBatteryCallback, times(1))
+ .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true)
+ }
+
+ @Test
+ fun onMetadataChanged_chargingStateTrue_executesBatteryCallbacks() {
+ stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+ stylusManager.onMetadataChanged(
+ bluetoothDevice,
+ BluetoothDevice.METADATA_MAIN_CHARGING,
+ "true".toByteArray()
+ )
+
+ verify(stylusBatteryCallback, times(1))
+ .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true)
+ }
+
+ @Test
+ fun onMetadataChanged_chargingStateFalse_executesBatteryCallbacks() {
+ stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+ stylusManager.onMetadataChanged(
+ bluetoothDevice,
+ BluetoothDevice.METADATA_MAIN_CHARGING,
+ "false".toByteArray()
+ )
+
+ verify(stylusBatteryCallback, times(1))
+ .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, false)
+ }
+
+ @Test
+ fun onMetadataChanged_chargingStateNoDevice_doesNotExecuteBatteryCallbacks() {
+ stylusManager.onMetadataChanged(
+ bluetoothDevice,
+ BluetoothDevice.METADATA_MAIN_CHARGING,
+ "true".toByteArray()
+ )
+
+ verifyNoMoreInteractions(stylusBatteryCallback)
+ }
+
+ @Test
+ fun onMetadataChanged_notChargingState_doesNotExecuteBatteryCallbacks() {
+ stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
+
+ stylusManager.onMetadataChanged(
+ bluetoothDevice,
+ BluetoothDevice.METADATA_DEVICE_TYPE,
+ "true".toByteArray()
+ )
+
+ verify(stylusBatteryCallback, never())
+ .onStylusBluetoothChargingStateChanged(any(), any(), any())
+ }
+
+ companion object {
+ private val EXECUTOR = Executor { r -> r.run() }
+
+ private const val OTHER_DEVICE_ID = 0
+ private const val STYLUS_DEVICE_ID = 1
+ private const val BT_STYLUS_DEVICE_ID = 2
+
+ private const val STYLUS_BT_ADDRESS = "SOME:ADDRESS"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
index 03fd624..abbdab0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
@@ -69,6 +69,22 @@
}
@Test
+ fun testUnfold_emitsFinishingEvent() {
+ runOnMainThreadWithInterval(
+ { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
+ { foldStateProvider.sendHingeAngleUpdate(10f) },
+ { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
+ { foldStateProvider.sendHingeAngleUpdate(90f) },
+ { foldStateProvider.sendHingeAngleUpdate(180f) },
+ { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
+ )
+
+ with(listener.ensureTransitionFinished()) {
+ assertHasSingleFinishingEvent()
+ }
+ }
+
+ @Test
fun testUnfold_screenAvailableOnlyAfterFullUnfold_emitsIncreasingTransitionEvents() {
runOnMainThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
@@ -157,6 +173,12 @@
currentRecording!!.addProgress(progress)
}
+ override fun onTransitionFinishing() {
+ assertWithMessage("Received transition finishing event when it's not started")
+ .that(currentRecording).isNotNull()
+ currentRecording!!.onFinishing()
+ }
+
override fun onTransitionFinished() {
assertWithMessage("Received transition finish event when it's not started")
.that(currentRecording).isNotNull()
@@ -171,6 +193,7 @@
class UnfoldTransitionRecording {
private val progressHistory: MutableList<Float> = arrayListOf()
+ private var finishingInvocations: Int = 0
fun addProgress(progress: Float) {
assertThat(progress).isAtMost(1.0f)
@@ -179,6 +202,10 @@
progressHistory += progress
}
+ fun onFinishing() {
+ finishingInvocations++
+ }
+
fun assertIncreasingProgress() {
assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
assertThat(progressHistory).isInOrder()
@@ -206,6 +233,11 @@
.isInOrder(Comparator.reverseOrder<Float>())
assertThat(progressHistory.last()).isEqualTo(0.0f)
}
+
+ fun assertHasSingleFinishingEvent() {
+ assertWithMessage("onTransitionFinishing callback should be invoked exactly " +
+ "one time").that(finishingInvocations).isEqualTo(1)
+ }
}
private companion object {
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index 108295b..180b611 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -33,6 +33,7 @@
"dagger2",
"jsr330",
],
+ kotlincflags: ["-Xjvm-default=enable"],
java_version: "1.8",
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
index 7117aaf..fee485d 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
@@ -34,8 +34,28 @@
fun destroy()
interface TransitionProgressListener {
+ /** Called when transition is started */
+ @JvmDefault
fun onTransitionStarted() {}
- fun onTransitionFinished() {}
+
+ /**
+ * Called whenever transition progress is updated, [progress] is a value of the animation
+ * where 0 is fully folded, 1 is fully unfolded
+ */
+ @JvmDefault
fun onTransitionProgress(@FloatRange(from = 0.0, to = 1.0) progress: Float) {}
+
+ /**
+ * Called when the progress provider determined that the transition is about to finish soon.
+ *
+ * For example, in [PhysicsBasedUnfoldTransitionProgressProvider] this could happen when the
+ * animation is not tied to the hinge angle anymore and it is about to run fixed animation.
+ */
+ @JvmDefault
+ fun onTransitionFinishing() {}
+
+ /** Called when transition is completely finished */
+ @JvmDefault
+ fun onTransitionFinished() {}
}
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
index 4c85b05..fa59cb4 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
@@ -88,6 +88,7 @@
override fun onAnimationStart(animator: Animator) {
listeners.forEach { it.onTransitionStarted() }
+ listeners.forEach { it.onTransitionFinishing() }
}
override fun onAnimationEnd(animator: Animator) {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 52fb0a7..ecc029d 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -125,6 +125,10 @@
private fun cancelTransition(endValue: Float, animate: Boolean) {
if (isTransitionRunning && animate) {
+ if (endValue == 1.0f && !isAnimatedCancelRunning) {
+ listeners.forEach { it.onTransitionFinishing() }
+ }
+
isAnimatedCancelRunning = true
springAnimation.animateToFinalPosition(endValue)
} else {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
index 8491f83..b7bab3e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -110,6 +110,12 @@
lastTransitionProgress = progress
}
+ override fun onTransitionFinishing() {
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionFinishing() }
+ }
+ }
+
override fun onTransitionFinished() {
if (isReadyToHandleTransition) {
listeners.forEach { it.onTransitionFinished() }
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index ea09629..b822541 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -378,6 +378,10 @@
return false;
}
+ if (!dreamsEnabledForUser(ActivityManager.getCurrentUser())) {
+ return false;
+ }
+
if ((mWhenToDream & DREAM_ON_CHARGE) == DREAM_ON_CHARGE) {
return mIsCharging;
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 399b94c..7919e19 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1054,6 +1054,19 @@
return;
}
+ // Make sure the device locks. Unfortunately, this has the side-effect of briefly revealing
+ // the lock screen before the dream appears. Note that this locking behavior needs to
+ // happen regardless of whether we end up dreaming (below) or not.
+ // TODO(b/261662912): Find a better way to lock the device that doesn't result in jank.
+ lockNow(null);
+
+ // Don't dream if the user isn't user zero.
+ // TODO(b/261907079): Move this check to DreamManagerService#canStartDreamingInternal().
+ if (ActivityManager.getCurrentUser() != UserHandle.USER_SYSTEM) {
+ noDreamAction.run();
+ return;
+ }
+
final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) {
noDreamAction.run();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9cf1e2f..f725d1a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7672,6 +7672,10 @@
// This activity may relaunch or perform configuration change so once it has reported drawn,
// the screen can be unfrozen.
ensureActivityConfiguration(0 /* globalChanges */, !PRESERVE_WINDOWS);
+ if (mTransitionController.isCollecting(this)) {
+ // In case the task was changed from PiP but still keeps old transform.
+ task.resetSurfaceControlTransforms();
+ }
}
void setRequestedOrientation(int requestedOrientation) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d5802cf..c6dc24f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3392,6 +3392,9 @@
if (!controller.isCollecting(this)) {
controller.collect(this);
startAsyncRotationIfNeeded();
+ if (mFixedRotationLaunchingApp != null) {
+ setSeamlessTransitionForFixedRotation(controller.getCollectingTransition());
+ }
}
return;
}
@@ -3401,12 +3404,8 @@
mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
if (mFixedRotationLaunchingApp != null) {
// A fixed-rotation transition is done, then continue to start a seamless display
- // transition. And be fore the start transaction is applied, the non-app windows
- // need to keep in previous rotation to avoid showing inconsistent content.
- t.setSeamlessRotation(this);
- if (mAsyncRotationController != null) {
- mAsyncRotationController.keepAppearanceInPreviousRotation();
- }
+ // transition.
+ setSeamlessTransitionForFixedRotation(t);
} else if (isRotationChanging()) {
if (displayChange != null) {
final boolean seamless = mDisplayRotation.shouldRotateSeamlessly(
@@ -3425,6 +3424,15 @@
}
}
+ private void setSeamlessTransitionForFixedRotation(Transition t) {
+ t.setSeamlessRotation(this);
+ // Before the start transaction is applied, the non-app windows need to keep in previous
+ // rotation to avoid showing inconsistent content.
+ if (mAsyncRotationController != null) {
+ mAsyncRotationController.keepAppearanceInPreviousRotation();
+ }
+ }
+
/** If the display is in transition, there should be a screenshot covering it. */
@Override
boolean inTransition() {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0be4191..03e3e93 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1125,11 +1125,12 @@
if (inMultiWindowMode() || !hasChild()) return false;
if (intent != null) {
final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME;
+ if ((intent.getFlags() & returnHomeFlags) != returnHomeFlags) {
+ return false;
+ }
final Task task = getDisplayArea() != null ? getDisplayArea().getRootHomeTask() : null;
- final boolean isLockTaskModeViolation = task != null
- && mAtmService.getLockTaskController().isLockTaskModeViolation(task);
- return (intent.getFlags() & returnHomeFlags) == returnHomeFlags
- && !isLockTaskModeViolation;
+ return !(task != null
+ && mAtmService.getLockTaskController().isLockTaskModeViolation(task));
}
final Task bottomTask = getBottomMostTask();
return bottomTask != this && bottomTask.returnsToHomeRootTask();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0c2cc43..1d17cd4 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1032,10 +1032,11 @@
* @param dc The display this container is on after changes.
*/
void onDisplayChanged(DisplayContent dc) {
- if (mDisplayContent != null) {
+ if (mDisplayContent != null && mDisplayContent != dc) {
+ // Cancel any change transition queued-up for this container on the old display when
+ // this container is moved from the old display.
mDisplayContent.mClosingChangingContainers.remove(this);
if (mDisplayContent.mChangingContainers.remove(this)) {
- // Cancel any change transition queued-up for this container on the old display.
mSurfaceFreezer.unfreeze(getSyncTransaction());
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
index f88e18b..4942690 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
@@ -180,14 +180,47 @@
createJobBuilder(7).setExpedited(true).build());
final JobStatus ej = spy(createJobStatus("testIsJobRestricted",
createJobBuilder(8).setExpedited(true).build()));
+ final JobStatus ejRetried = spy(createJobStatus("testIsJobRestricted",
+ createJobBuilder(11).setExpedited(true).build()));
+ final JobStatus ejRunning = spy(createJobStatus("testIsJobRestricted",
+ createJobBuilder(12).setExpedited(true).build()));
+ final JobStatus ejRunningLong = spy(createJobStatus("testIsJobRestricted",
+ createJobBuilder(13).setExpedited(true).build()));
+ final JobStatus ui = spy(createJobStatus("testIsJobRestricted",
+ createJobBuilder(14).build()));
+ final JobStatus uiRetried = spy(createJobStatus("testIsJobRestricted",
+ createJobBuilder(15).build()));
+ final JobStatus uiRunning = spy(createJobStatus("testIsJobRestricted",
+ createJobBuilder(16).build()));
+ final JobStatus uiRunningLong = spy(createJobStatus("testIsJobRestricted",
+ createJobBuilder(17).build()));
when(ej.shouldTreatAsExpeditedJob()).thenReturn(true);
+ when(ejRetried.shouldTreatAsExpeditedJob()).thenReturn(true);
+ when(ejRunning.shouldTreatAsExpeditedJob()).thenReturn(true);
+ when(ejRunningLong.shouldTreatAsExpeditedJob()).thenReturn(true);
+ when(ui.shouldTreatAsUserInitiated()).thenReturn(true);
+ when(uiRetried.shouldTreatAsUserInitiated()).thenReturn(true);
+ when(uiRunning.shouldTreatAsUserInitiated()).thenReturn(true);
+ when(uiRunningLong.shouldTreatAsUserInitiated()).thenReturn(true);
+ when(ejRetried.getNumPreviousAttempts()).thenReturn(1);
+ when(uiRetried.getNumPreviousAttempts()).thenReturn(2);
when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunning)).thenReturn(true);
when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunning))
.thenReturn(true);
+ when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunningLong))
+ .thenReturn(true);
+ when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunningLong))
+ .thenReturn(true);
+ when(mJobSchedulerService.isCurrentlyRunningLocked(ejRunning)).thenReturn(true);
+ when(mJobSchedulerService.isCurrentlyRunningLocked(ejRunningLong)).thenReturn(true);
+ when(mJobSchedulerService.isCurrentlyRunningLocked(uiRunning)).thenReturn(true);
+ when(mJobSchedulerService.isCurrentlyRunningLocked(uiRunningLong)).thenReturn(true);
when(mJobSchedulerService.isJobInOvertimeLocked(jobLowPriorityRunningLong))
.thenReturn(true);
when(mJobSchedulerService.isJobInOvertimeLocked(jobHighPriorityRunningLong))
.thenReturn(true);
+ when(mJobSchedulerService.isJobInOvertimeLocked(ejRunningLong)).thenReturn(true);
+ when(mJobSchedulerService.isJobInOvertimeLocked(uiRunningLong)).thenReturn(true);
assertFalse(mThermalStatusRestriction.isJobRestricted(jobMinPriority));
assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriority));
@@ -199,6 +232,13 @@
assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
assertFalse(mThermalStatusRestriction.isJobRestricted(ej));
assertFalse(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(ejRetried));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(ui));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(uiRetried));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_LIGHT);
@@ -212,6 +252,13 @@
assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
assertFalse(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
assertFalse(mThermalStatusRestriction.isJobRestricted(ej));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(ejRetried));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(ui));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(uiRetried));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_MODERATE);
@@ -225,6 +272,13 @@
assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
assertFalse(mThermalStatusRestriction.isJobRestricted(ej));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(ejRetried));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(ui));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(uiRetried));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_SEVERE);
@@ -238,6 +292,13 @@
assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
assertTrue(mThermalStatusRestriction.isJobRestricted(ej));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(ejRetried));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunning));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(ui));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(uiRetried));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunning));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_CRITICAL);
@@ -251,6 +312,13 @@
assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong));
assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded));
assertTrue(mThermalStatusRestriction.isJobRestricted(ej));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(ejRetried));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunning));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(ui));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(uiRetried));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunning));
+ assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
}
private JobInfo.Builder createJobBuilder(int jobId) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 50fcafc..98a28cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -869,6 +869,28 @@
}
@Test
+ public void testOnDisplayChanged_cleanupChanging() {
+ final Task task = createTask(mDisplayContent);
+ spyOn(task.mSurfaceFreezer);
+ mDisplayContent.mChangingContainers.add(task);
+
+ // Don't remove the changing transition of this window when it is still the old display.
+ // This happens on display info changed.
+ task.onDisplayChanged(mDisplayContent);
+
+ assertTrue(mDisplayContent.mChangingContainers.contains(task));
+ verify(task.mSurfaceFreezer, never()).unfreeze(any());
+
+ // Remove the changing transition of this window when it is moved or reparented from the old
+ // display.
+ final DisplayContent newDc = createNewDisplay();
+ task.onDisplayChanged(newDc);
+
+ assertFalse(mDisplayContent.mChangingContainers.contains(task));
+ verify(task.mSurfaceFreezer).unfreeze(any());
+ }
+
+ @Test
public void testHandleCompleteDeferredRemoval() {
final DisplayContent displayContent = createNewDisplay();
// Do not reparent activity to default display when removing the display.