diff options
41 files changed, 977 insertions, 541 deletions
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java index 625d7cb66900..c7237ea35c2a 100644 --- a/core/java/android/content/res/FontScaleConverterFactory.java +++ b/core/java/android/content/res/FontScaleConverterFactory.java @@ -58,6 +58,16 @@ public class FontScaleConverterFactory { synchronized (LOOKUP_TABLES_WRITE_LOCK) { putInto( sLookupTables, + /* scaleKey= */ 1.05f, + new FontScaleConverterImpl( + /* fromSp= */ + new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, + /* toDp= */ + new float[] { 8.4f, 10.5f, 12.6f, 14.8f, 18.6f, 20.6f, 24.4f, 30f, 100}) + ); + + putInto( + sLookupTables, /* scaleKey= */ 1.1f, new FontScaleConverterImpl( /* fromSp= */ @@ -78,6 +88,16 @@ public class FontScaleConverterFactory { putInto( sLookupTables, + /* scaleKey= */ 1.2f, + new FontScaleConverterImpl( + /* fromSp= */ + new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, + /* toDp= */ + new float[] { 9.6f, 12f, 14.4f, 17.2f, 20.4f, 22.4f, 25.6f, 30f, 100}) + ); + + putInto( + sLookupTables, /* scaleKey= */ 1.3f, new FontScaleConverterImpl( /* fromSp= */ @@ -117,7 +137,7 @@ public class FontScaleConverterFactory { ); } - sMinScaleBeforeCurvesApplied = getScaleFromKey(sLookupTables.keyAt(0)) - 0.02f; + sMinScaleBeforeCurvesApplied = getScaleFromKey(sLookupTables.keyAt(0)) - 0.01f; if (sMinScaleBeforeCurvesApplied <= 1.0f) { throw new IllegalStateException( "You should only apply non-linear scaling to font scales > 1" diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 44fa1863afc1..a8a0c5b8bdf7 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -160,3 +160,14 @@ flag { description: "Feature flag for showing error message when user tries stylus handwriting on a text field which doesn't support it" bug: "297962571" } + +flag { + name: "fix_font_update_failure" + namespace: "text" + description: "There was a bug of updating system font from Android 13 to 14. This flag for fixing the migration failure." + is_fixed_read_only: true + bug: "331717791" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 6bd273bc1aeb..4f5544172edf 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -28,6 +28,7 @@ import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER; +import static com.android.internal.app.ResolverActivity.EXTRA_RESTRICT_TO_SINGLE_USER; import static com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE; import android.annotation.Nullable; @@ -190,7 +191,7 @@ public class IntentForwarderActivity extends Activity { .thenApplyAsync(targetResolveInfo -> { if (isResolverActivityResolveInfo(targetResolveInfo)) { launchResolverActivityWithCorrectTab(intentReceived, className, newIntent, - callingUserId, targetUserId); + callingUserId, targetUserId, false); // When switching to the personal profile, automatically start the activity } else if (className.equals(FORWARD_INTENT_TO_PARENT)) { startActivityAsCaller(newIntent, targetUserId); @@ -218,7 +219,7 @@ public class IntentForwarderActivity extends Activity { .thenAcceptAsync(targetResolveInfo -> { if (isResolverActivityResolveInfo(targetResolveInfo)) { launchResolverActivityWithCorrectTab(intentReceived, className, newIntent, - callingUserId, targetUserId); + callingUserId, targetUserId, true); } else { maybeShowUserConsentMiniResolverPrivate(targetResolveInfo, newIntent, targetUserId); @@ -490,7 +491,7 @@ public class IntentForwarderActivity extends Activity { } private void launchResolverActivityWithCorrectTab(Intent intentReceived, String className, - Intent newIntent, int callingUserId, int targetUserId) { + Intent newIntent, int callingUserId, int targetUserId, boolean singleTabOnly) { // When showing the intent resolver, instead of forwarding to the other profile, // we launch it in the current user and select the other tab. This fixes b/155874820. // @@ -505,6 +506,9 @@ public class IntentForwarderActivity extends Activity { sanitizeIntent(intentReceived); intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile); intentReceived.putExtra(EXTRA_CALLING_USER, UserHandle.of(callingUserId)); + if (singleTabOnly) { + intentReceived.putExtra(EXTRA_RESTRICT_TO_SINGLE_USER, true); + } startActivityAsCaller(intentReceived, null, false, userId); finish(); } diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 593bdf0812aa..163f32e022e6 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -110,3 +110,6 @@ per-file android_database_SQLite* = file:/SQLITE_OWNERS # PerformanceHintManager per-file android_os_PerformanceHintManager.cpp = file:/ADPF_OWNERS + +# IF Tools +per-file android_tracing_Perfetto* = file:platform/development:/tools/winscope/OWNERS diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt index a2a5433eca24..c7d5825733ae 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt @@ -175,10 +175,12 @@ class FontScaleConverterFactoryTest { assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(-1f)).isFalse() assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0.85f)).isFalse() assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.02f)).isFalse() + assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.05f)).isTrue() assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isTrue() assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.15f)).isTrue() assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.1499999f)) .isTrue() + assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.2f)).isTrue() assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.5f)).isTrue() assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(2f)).isTrue() assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(3f)).isTrue() diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt index b94989d98e97..12e395db8396 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt @@ -24,6 +24,7 @@ import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.helpers.WindowUtils import android.tools.traces.parsers.toFlickerComponent +import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions @@ -143,6 +144,10 @@ class FromSplitScreenAutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) : } } + @FlakyTest(bugId = 293133362) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 4e6d3cbb60cf..9b1e4b7a633b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -254,8 +254,6 @@ public abstract class InfoMediaManager { protected abstract List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName); protected final void rebuildDeviceList() { - mMediaDevices.clear(); - mCurrentConnectedDevice = null; buildAvailableRoutes(); } @@ -524,6 +522,7 @@ public abstract class InfoMediaManager { // MediaRoute2Info.getType was made public on API 34, but exists since API 30. @SuppressWarnings("NewApi") private synchronized void buildAvailableRoutes() { + mMediaDevices.clear(); RoutingSessionInfo activeSession = getActiveRoutingSession(); for (MediaRoute2Info route : getAvailableRoutes(activeSession)) { @@ -533,6 +532,12 @@ public abstract class InfoMediaManager { } addMediaDevice(route, activeSession); } + + // In practice, mMediaDevices should always have at least one route. + if (!mMediaDevices.isEmpty()) { + // First device on the list is always the first selected route. + mCurrentConnectedDevice = mMediaDevices.get(0); + } } private synchronized List<MediaRoute2Info> getAvailableRoutes( @@ -643,9 +648,6 @@ public abstract class InfoMediaManager { if (mediaDevice != null) { if (activeSession.getSelectedRoutes().contains(route.getId())) { mediaDevice.setState(STATE_SELECTED); - if (mCurrentConnectedDevice == null) { - mCurrentConnectedDevice = mediaDevice; - } } mMediaDevices.add(mediaDevice); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index f8dcec7315b2..d7938670c598 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -110,6 +110,17 @@ public class InfoMediaManagerTest { .setAddress("00:00:00:00:00:00") .build(); + private static final RoutingSessionInfo TEST_REMOTE_ROUTING_SESSION = + new RoutingSessionInfo.Builder("FAKE_REMOTE_ROUTING_SESSION_ID", TEST_PACKAGE_NAME) + .addSelectedRoute(TEST_ID_1) + .build(); + + private static final MediaRoute2Info TEST_REMOTE_ROUTE = + new MediaRoute2Info.Builder(TEST_ID_1, "REMOTE_ROUTE") + .setSystemRoute(true) + .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO) + .build(); + @Mock private MediaRouter2Manager mRouterManager; @Mock @@ -151,7 +162,10 @@ public class InfoMediaManagerTest { RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class); mInfoMediaManager.mRouterManager = mRouterManager; // Since test is running in Robolectric, return a fake session to avoid NPE. - when(mRouterManager.getRoutingSessions(anyString())).thenReturn(List.of(sessionInfo)); + when(mRouterManager.getRoutingSessions(anyString())) + .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION)); + when(mRouterManager.getSelectedRoutes(any())) + .thenReturn(List.of(TEST_SELECTED_SYSTEM_ROUTE)); mInfoMediaManager.startScan(); mInfoMediaManager.stopScan(); @@ -191,52 +205,27 @@ public class InfoMediaManagerTest { @Test public void onSessionReleased_shouldUpdateConnectedDevice() { - final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>(); - final RoutingSessionInfo sessionInfo1 = mock(RoutingSessionInfo.class); - routingSessionInfos.add(sessionInfo1); - final RoutingSessionInfo sessionInfo2 = mock(RoutingSessionInfo.class); - routingSessionInfos.add(sessionInfo2); - - final List<String> selectedRoutesSession1 = new ArrayList<>(); - selectedRoutesSession1.add(TEST_ID_1); - when(sessionInfo1.getSelectedRoutes()).thenReturn(selectedRoutesSession1); - - final List<String> selectedRoutesSession2 = new ArrayList<>(); - selectedRoutesSession2.add(TEST_ID_2); - when(sessionInfo2.getSelectedRoutes()).thenReturn(selectedRoutesSession2); - - mShadowRouter2Manager.setRoutingSessions(routingSessionInfos); - - final MediaRoute2Info info1 = mock(MediaRoute2Info.class); - when(info1.getId()).thenReturn(TEST_ID_1); - when(info1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); - - final MediaRoute2Info info2 = mock(MediaRoute2Info.class); - when(info2.getId()).thenReturn(TEST_ID_2); - when(info2.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); - - final List<MediaRoute2Info> routes = new ArrayList<>(); - routes.add(info1); - routes.add(info2); - mShadowRouter2Manager.setAllRoutes(routes); - mShadowRouter2Manager.setTransferableRoutes(routes); + mInfoMediaManager.mRouterManager = mRouterManager; - final MediaDevice mediaDevice1 = mInfoMediaManager.findMediaDevice(TEST_ID_1); - assertThat(mediaDevice1).isNull(); - final MediaDevice mediaDevice2 = mInfoMediaManager.findMediaDevice(TEST_ID_2); - assertThat(mediaDevice2).isNull(); + // Active routing session is last one in list. + when(mRouterManager.getRoutingSessions(anyString())) + .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION, TEST_REMOTE_ROUTING_SESSION)); + when(mRouterManager.getSelectedRoutes(TEST_SYSTEM_ROUTING_SESSION)) + .thenReturn(List.of(TEST_SELECTED_SYSTEM_ROUTE)); + when(mRouterManager.getSelectedRoutes(TEST_REMOTE_ROUTING_SESSION)) + .thenReturn(List.of(TEST_REMOTE_ROUTE)); mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated(); - final MediaDevice infoDevice1 = mInfoMediaManager.mMediaDevices.get(0); - assertThat(infoDevice1.getId()).isEqualTo(TEST_ID_1); - final MediaDevice infoDevice2 = mInfoMediaManager.mMediaDevices.get(1); - assertThat(infoDevice2.getId()).isEqualTo(TEST_ID_2); - // The active routing session is the last one in the list, which maps to infoDevice2. - assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice2); + MediaDevice remoteDevice = mInfoMediaManager.findMediaDevice(TEST_REMOTE_ROUTE.getId()); + assertThat(remoteDevice).isNotNull(); + assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(remoteDevice); - routingSessionInfos.remove(sessionInfo2); - mInfoMediaManager.mMediaRouterCallback.onSessionReleased(sessionInfo2); - assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice1); + when(mRouterManager.getRoutingSessions(anyString())) + .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION)); + mInfoMediaManager.mMediaRouterCallback.onSessionReleased(TEST_REMOTE_ROUTING_SESSION); + MediaDevice systemRoute = mInfoMediaManager.findMediaDevice(TEST_SYSTEM_ROUTE_ID); + assertThat(systemRoute).isNotNull(); + assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(systemRoute); } @Test @@ -794,18 +783,16 @@ public class InfoMediaManagerTest { @Test public void onSessionUpdated_shouldDispatchDeviceListAdded() { - final MediaRoute2Info info = mock(MediaRoute2Info.class); - when(info.getId()).thenReturn(TEST_ID); - when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); - when(info.isSystemRoute()).thenReturn(true); - - final List<MediaRoute2Info> routes = new ArrayList<>(); - routes.add(info); - mShadowRouter2Manager.setAllRoutes(routes); + mInfoMediaManager.mRouterManager = mRouterManager; + // Since test is running in Robolectric, return a fake session to avoid NPE. + when(mRouterManager.getRoutingSessions(anyString())) + .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION)); + when(mRouterManager.getSelectedRoutes(any())) + .thenReturn(List.of(TEST_SELECTED_SYSTEM_ROUTE)); mInfoMediaManager.registerCallback(mCallback); - mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(mock(RoutingSessionInfo.class)); + mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(TEST_SYSTEM_ROUTING_SESSION); verify(mCallback).onDeviceListAdded(any()); } @@ -871,7 +858,7 @@ public class InfoMediaManagerTest { } @Test - public void addMediaDevice_deviceIncludedInSelectedDevices_shouldSetAsCurrentConnected() { + public void onRoutesUpdated_setsFirstSelectedRouteAsCurrentConnectedDevice() { final CachedBluetoothDeviceManager cachedBluetoothDeviceManager = mock(CachedBluetoothDeviceManager.class); @@ -886,14 +873,14 @@ public class InfoMediaManagerTest { when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)) .thenReturn(List.of(selectedBtSession)); + when(mRouterManager.getSelectedRoutes(any())).thenReturn(List.of(TEST_BLUETOOTH_ROUTE)); when(mLocalBluetoothManager.getCachedDeviceManager()) .thenReturn(cachedBluetoothDeviceManager); when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class))) .thenReturn(cachedDevice); mInfoMediaManager.mRouterManager = mRouterManager; - mInfoMediaManager.mMediaDevices.clear(); - mInfoMediaManager.addMediaDevice(TEST_BLUETOOTH_ROUTE, selectedBtSession); + mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated(); MediaDevice device = mInfoMediaManager.mMediaDevices.get(0); diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 112d9646175f..f6616dbfe11a 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -26,6 +26,14 @@ flag { } flag { + name: "refactor_keyguard_dismiss_intent" + namespace: "systemui" + description: "Update how keyguard dismiss intents are stored." + bug: "275069969" +} + +flag { + name: "notification_heads_up_cycling" namespace: "systemui" description: "Heads-up notification cycling animation for the Notification Avalanche feature." diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index d31064ae23b3..19d3f599ef31 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -17,10 +17,10 @@ package com.android.systemui.volume.panel.component.volume.ui.composable import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size -import androidx.compose.material3.IconButton -import androidx.compose.material3.IconButtonColors import androidx.compose.material3.LocalContentColor import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -32,7 +32,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.ProgressBarRangeInfo import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription @@ -130,24 +129,20 @@ private fun SliderIcon( isTappable: Boolean, modifier: Modifier = Modifier ) { - if (isTappable) { - IconButton( - modifier = modifier, - onClick = onIconTapped, - colors = - IconButtonColors( - contentColor = LocalContentColor.current, - containerColor = Color.Transparent, - disabledContentColor = LocalContentColor.current, - disabledContainerColor = Color.Transparent, - ), - content = { Icon(modifier = Modifier.size(24.dp), icon = icon) }, - ) - } else { - Box( - modifier = modifier, - contentAlignment = Alignment.Center, - content = { Icon(modifier = Modifier.size(24.dp), icon = icon) }, - ) - } + val boxModifier = + if (isTappable) { + modifier.clickable( + onClick = onIconTapped, + interactionSource = null, + indication = null + ) + } else { + modifier + } + .fillMaxSize() + Box( + modifier = boxModifier, + contentAlignment = Alignment.Center, + content = { Icon(modifier = Modifier.size(24.dp), icon = icon) }, + ) } diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml index 1777bdf92786..dabfe9df6d2e 100644 --- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml @@ -39,7 +39,6 @@ android:layout_height="match_parent"> android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.8" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:srcCompat="@tools:sample/avatars" /> @@ -60,11 +59,12 @@ android:layout_height="match_parent"> android:id="@+id/scrollView" android:layout_width="0dp" android:layout_height="0dp" - android:fillViewport="true" - android:padding="24dp" - app:layout_constrainedHeight="true" - app:layout_constrainedWidth="true" - app:layout_constraintBottom_toTopOf="@+id/buttonBarrier" + android:paddingBottom="16dp" + android:paddingLeft="24dp" + android:paddingRight="12dp" + android:paddingTop="24dp" + android:fadeScrollbars="false" + app:layout_constraintBottom_toTopOf="@+id/button_bar" app:layout_constraintEnd_toStartOf="@+id/midGuideline" app:layout_constraintStart_toStartOf="@id/leftGuideline" app:layout_constraintTop_toTopOf="@+id/topGuideline"> @@ -91,7 +91,7 @@ android:layout_height="match_parent"> android:layout_width="0dp" android:layout_height="wrap_content" android:textAlignment="viewStart" - android:paddingLeft="8dp" + android:paddingLeft="16dp" app:layout_constraintBottom_toBottomOf="@+id/logo" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/logo" @@ -119,7 +119,7 @@ android:layout_height="match_parent"> style="@style/TextAppearance.AuthCredential.Subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="12dp" + android:layout_marginTop="16dp" android:gravity="@integer/biometric_dialog_text_gravity" android:paddingHorizontal="0dp" android:textAlignment="viewStart" @@ -133,6 +133,7 @@ android:layout_height="match_parent"> android:id="@+id/customized_view_container" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginTop="24dp" android:gravity="center_vertical" android:orientation="vertical" android:visibility="gone" @@ -148,6 +149,7 @@ android:layout_height="match_parent"> style="@style/TextAppearance.AuthCredential.Description" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="24dp" android:gravity="@integer/biometric_dialog_text_gravity" android:paddingHorizontal="0dp" android:textAlignment="viewStart" @@ -179,7 +181,7 @@ android:layout_height="match_parent"> android:fadingEdge="horizontal" android:gravity="center_horizontal" android:scrollHorizontally="true" - app:layout_constraintBottom_toTopOf="@+id/buttonBarrier" + app:layout_constraintBottom_toTopOf="@+id/button_bar" app:layout_constraintEnd_toEndOf="@+id/biometric_icon" app:layout_constraintStart_toStartOf="@+id/biometric_icon" app:layout_constraintTop_toBottomOf="@+id/biometric_icon" @@ -189,7 +191,7 @@ android:layout_height="match_parent"> android:id="@+id/button_bar" layout="@layout/biometric_prompt_button_bar" android:layout_width="0dp" - android:layout_height="0dp" + android:layout_height="wrap_content" app:layout_constraintBottom_toTopOf="@id/bottomGuideline" app:layout_constraintEnd_toEndOf="@id/scrollView" app:layout_constraintStart_toStartOf="@id/scrollView" @@ -204,14 +206,6 @@ android:layout_height="match_parent"> app:barrierDirection="top" app:constraint_referenced_ids="scrollView" /> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/buttonBarrier" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierAllowsGoneWidgets="false" - app:barrierDirection="top" - app:constraint_referenced_ids="button_bar" /> - <androidx.constraintlayout.widget.Guideline android:id="@+id/leftGuideline" android:layout_width="wrap_content" @@ -238,13 +232,13 @@ android:layout_height="match_parent"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" - app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" /> + app:layout_constraintGuide_end="40dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/topGuideline" android:layout_width="0dp" android:layout_height="0dp" android:orientation="horizontal" - app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" /> + app:layout_constraintGuide_begin="0dp" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml index 8b886a7fdffb..240ababc7da4 100644 --- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml @@ -29,28 +29,31 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/rightGuideline" app:layout_constraintStart_toStartOf="@+id/leftGuideline" - app:layout_constraintTop_toTopOf="@+id/topBarrier" /> + app:layout_constraintTop_toTopOf="@+id/topBarrier" + app:layout_constraintWidth_max="640dp" /> <include - layout="@layout/biometric_prompt_button_bar" android:id="@+id/button_bar" + layout="@layout/biometric_prompt_button_bar" android:layout_width="0dp" - android:layout_height="match_parent" - app:layout_constraintBottom_toTopOf="@id/bottomGuideline" + android:layout_height="wrap_content" + android:layout_marginBottom="40dp" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/panel" - app:layout_constraintStart_toStartOf="@id/panel"/> + app:layout_constraintStart_toStartOf="@id/panel" /> <ScrollView android:id="@+id/scrollView" android:layout_width="0dp" android:layout_height="wrap_content" + android:fadeScrollbars="false" android:fillViewport="true" - android:paddingBottom="36dp" - android:paddingHorizontal="24dp" + android:paddingBottom="32dp" + android:paddingHorizontal="32dp" android:paddingTop="24dp" app:layout_constrainedHeight="true" app:layout_constrainedWidth="true" - app:layout_constraintBottom_toTopOf="@+id/biometric_icon" + app:layout_constraintBottom_toTopOf="@+id/scrollBarrier" app:layout_constraintEnd_toEndOf="@id/panel" app:layout_constraintStart_toStartOf="@id/panel" app:layout_constraintTop_toTopOf="@+id/topGuideline" @@ -63,10 +66,10 @@ <ImageView android:id="@+id/logo" - android:contentDescription="@string/biometric_dialog_logo" android:layout_width="@dimen/biometric_prompt_logo_size" android:layout_height="@dimen/biometric_prompt_logo_size" android:layout_gravity="center" + android:contentDescription="@string/biometric_dialog_logo" android:scaleType="fitXY" android:visibility="visible" app:layout_constraintBottom_toTopOf="@+id/logo_description" @@ -79,6 +82,7 @@ style="@style/TextAppearance.AuthCredential.LogoDescription" android:layout_width="match_parent" android:layout_height="wrap_content" + android:paddingTop="16dp" app:layout_constraintBottom_toTopOf="@+id/title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -114,6 +118,7 @@ android:layout_height="wrap_content" android:gravity="center_vertical" android:orientation="vertical" + android:paddingTop="24dp" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" @@ -126,6 +131,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="@integer/biometric_dialog_text_gravity" + android:paddingTop="16dp" + android:textAlignment="viewStart" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -153,7 +160,7 @@ android:fadingEdge="horizontal" android:gravity="center_horizontal" android:scrollHorizontally="true" - app:layout_constraintBottom_toTopOf="@+id/buttonBarrier" + app:layout_constraintBottom_toTopOf="@+id/button_bar" app:layout_constraintEnd_toEndOf="@+id/panel" app:layout_constraintStart_toStartOf="@+id/panel" app:layout_constraintTop_toBottomOf="@+id/biometric_icon" @@ -172,12 +179,12 @@ <!-- Try Again Button --> <androidx.constraintlayout.widget.Barrier - android:id="@+id/buttonBarrier" + android:id="@+id/scrollBarrier" android:layout_width="wrap_content" android:layout_height="wrap_content" app:barrierAllowsGoneWidgets="false" app:barrierDirection="top" - app:constraint_referenced_ids="button_bar" /> + app:constraint_referenced_ids="biometric_icon, button_bar" /> <!-- Guidelines for setting panel border --> <androidx.constraintlayout.widget.Guideline @@ -199,14 +206,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" - app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" /> + app:layout_constraintGuide_end="40dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/topGuideline" android:layout_width="0dp" android:layout_height="0dp" android:orientation="horizontal" - app:layout_constraintGuide_percent="0.25171" /> + app:layout_constraintGuide_begin="56dp" /> <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper android:id="@+id/biometric_icon" @@ -216,7 +223,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="0.8" + app:layout_constraintVertical_bias="1.0" tools:srcCompat="@tools:sample/avatars" /> <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper diff --git a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml index 810c7433e4ad..4d2310a2a6ca 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml @@ -17,12 +17,13 @@ <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:theme="@style/Theme.SystemUI.Dialog" xmlns:app="http://schemas.android.com/apk/res-auto"> <!-- Negative Button, reserved for app --> <Button android:id="@+id/button_negative" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + style="@style/Widget.Dialog.Button.BorderButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" @@ -36,7 +37,7 @@ <!-- Cancel Button, replaces negative button when biometric is accepted --> <Button android:id="@+id/button_cancel" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + style="@style/Widget.Dialog.Button.BorderButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" @@ -49,7 +50,7 @@ <!-- "Use Credential" Button, replaces if device credential is allowed --> <Button android:id="@+id/button_use_credential" - style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored" + style="@style/Widget.Dialog.Button.BorderButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" @@ -61,7 +62,7 @@ <!-- Positive Button --> <Button android:id="@+id/button_confirm" - style="@*android:style/Widget.DeviceDefault.Button.Colored" + style="@style/Widget.Dialog.Button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" @@ -76,7 +77,7 @@ <!-- Try Again Button --> <Button android:id="@+id/button_try_again" - style="@*android:style/Widget.DeviceDefault.Button.Colored" + style="@style/Widget.Dialog.Button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml index 74bf318465b6..4aa609207312 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml @@ -35,24 +35,26 @@ android:id="@+id/button_bar" layout="@layout/biometric_prompt_button_bar" android:layout_width="0dp" - android:layout_height="match_parent" - app:layout_constraintBottom_toTopOf="@id/bottomGuideline" + android:layout_height="wrap_content" + android:layout_marginBottom="40dp" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@id/panel" app:layout_constraintStart_toStartOf="@id/panel" /> <ScrollView android:id="@+id/scrollView" - android:layout_width="match_parent" + android:layout_width="0dp" android:layout_height="wrap_content" android:fillViewport="true" + android:fadeScrollbars="false" android:paddingBottom="36dp" android:paddingHorizontal="24dp" android:paddingTop="24dp" app:layout_constrainedHeight="true" app:layout_constrainedWidth="true" - app:layout_constraintBottom_toTopOf="@+id/biometric_icon" - app:layout_constraintEnd_toEndOf="@id/rightGuideline" - app:layout_constraintStart_toStartOf="@id/leftGuideline" + app:layout_constraintBottom_toTopOf="@+id/scrollBarrier" + app:layout_constraintEnd_toEndOf="@id/panel" + app:layout_constraintStart_toStartOf="@id/panel" app:layout_constraintTop_toTopOf="@+id/topGuideline" app:layout_constraintVertical_bias="1.0"> @@ -79,6 +81,7 @@ style="@style/TextAppearance.AuthCredential.LogoDescription" android:layout_width="match_parent" android:layout_height="wrap_content" + android:paddingTop="8dp" app:layout_constraintBottom_toTopOf="@+id/title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -114,8 +117,8 @@ android:layout_height="wrap_content" android:gravity="center_vertical" android:orientation="vertical" - android:visibility="gone" android:paddingTop="24dp" + android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -127,7 +130,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="@integer/biometric_dialog_text_gravity" - android:paddingTop="24dp" + android:textAlignment="viewStart" + android:paddingTop="16dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -154,7 +158,7 @@ android:fadingEdge="horizontal" android:gravity="center_horizontal" android:scrollHorizontally="true" - app:layout_constraintBottom_toTopOf="@+id/buttonBarrier" + app:layout_constraintBottom_toTopOf="@+id/button_bar" app:layout_constraintEnd_toEndOf="@+id/panel" app:layout_constraintStart_toStartOf="@+id/panel" app:layout_constraintTop_toBottomOf="@+id/biometric_icon" @@ -169,12 +173,12 @@ app:constraint_referenced_ids="scrollView" /> <androidx.constraintlayout.widget.Barrier - android:id="@+id/buttonBarrier" + android:id="@+id/scrollBarrier" android:layout_width="wrap_content" android:layout_height="wrap_content" app:barrierAllowsGoneWidgets="false" app:barrierDirection="top" - app:constraint_referenced_ids="button_bar" /> + app:constraint_referenced_ids="biometric_icon, button_bar" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/leftGuideline" @@ -195,14 +199,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" - app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" /> + app:layout_constraintGuide_end="40dp" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/topGuideline" android:layout_width="0dp" android:layout_height="0dp" android:orientation="horizontal" - app:layout_constraintGuide_percent="0.25" /> + app:layout_constraintGuide_begin="119dp" /> <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper android:id="@+id/biometric_icon" @@ -212,7 +216,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="0.8" + app:layout_constraintVertical_bias="1.0" tools:srcCompat="@tools:sample/avatars" /> <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index b95ee56e9e48..b0b548295b71 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1091,7 +1091,7 @@ <dimen name="remote_input_history_extra_height">60dp</dimen> <!-- Biometric Dialog values --> - <dimen name="biometric_dialog_face_icon_size">64dp</dimen> + <dimen name="biometric_dialog_face_icon_size">54dp</dimen> <dimen name="biometric_dialog_fingerprint_icon_width">80dp</dimen> <dimen name="biometric_dialog_fingerprint_icon_height">80dp</dimen> <dimen name="biometric_dialog_button_negative_max_width">160dp</dimen> @@ -1109,6 +1109,22 @@ <dimen name="biometric_dialog_width">240dp</dimen> <dimen name="biometric_dialog_height">240dp</dimen> + <!-- Dimensions for biometric prompt panel padding --> + <dimen name="biometric_prompt_small_horizontal_guideline_padding">344dp</dimen> + <dimen name="biometric_prompt_udfps_horizontal_guideline_padding">114dp</dimen> + <dimen name="biometric_prompt_udfps_mid_guideline_padding">409dp</dimen> + <dimen name="biometric_prompt_medium_horizontal_guideline_padding">640dp</dimen> + <dimen name="biometric_prompt_medium_mid_guideline_padding">330dp</dimen> + + <!-- Dimensions for biometric prompt icon padding --> + <dimen name="biometric_prompt_portrait_small_bottom_padding">60dp</dimen> + <dimen name="biometric_prompt_portrait_medium_bottom_padding">160dp</dimen> + <dimen name="biometric_prompt_portrait_large_screen_bottom_padding">176dp</dimen> + <dimen name="biometric_prompt_landscape_small_bottom_padding">192dp</dimen> + <dimen name="biometric_prompt_landscape_small_horizontal_padding">145dp</dimen> + <dimen name="biometric_prompt_landscape_medium_bottom_padding">148dp</dimen> + <dimen name="biometric_prompt_landscape_medium_horizontal_padding">125dp</dimen> + <!-- Dimensions for biometric prompt custom content view. --> <dimen name="biometric_prompt_logo_size">32dp</dimen> <dimen name="biometric_prompt_content_corner_radius">28dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 244603908c65..44cbdbc2c75b 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1971,7 +1971,7 @@ <!-- Content description for the clear search button in shortcut search list. [CHAR LIMIT=NONE] --> <string name="keyboard_shortcut_clear_text">Clear search query</string> <!-- The title for keyboard shortcut search list [CHAR LIMIT=25] --> - <string name="keyboard_shortcut_search_list_title">Shortcuts</string> + <string name="keyboard_shortcut_search_list_title">Keyboard Shortcuts</string> <!-- The hint for keyboard shortcut search list [CHAR LIMIT=25] --> <string name="keyboard_shortcut_search_list_hint">Search shortcuts</string> <!-- The description for no shortcuts results [CHAR LIMIT=25] --> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index b2ade4fa1e8a..76d46ed9889f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -288,12 +288,14 @@ object BiometricViewBinder { // set padding launch { viewModel.promptPadding.collect { promptPadding -> - view.setPadding( - promptPadding.left, - promptPadding.top, - promptPadding.right, - promptPadding.bottom - ) + if (!constraintBp()) { + view.setPadding( + promptPadding.left, + promptPadding.top, + promptPadding.right, + promptPadding.bottom + ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index e3c0cba42e2d..f380746105ed 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -20,7 +20,6 @@ import android.animation.Animator import android.animation.AnimatorSet import android.animation.ValueAnimator import android.graphics.Outline -import android.graphics.Rect import android.transition.AutoTransition import android.transition.TransitionManager import android.util.TypedValue @@ -47,17 +46,14 @@ import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.ui.viewmodel.PromptPosition import com.android.systemui.biometrics.ui.viewmodel.PromptSize import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel -import com.android.systemui.biometrics.ui.viewmodel.isBottom import com.android.systemui.biometrics.ui.viewmodel.isLarge import com.android.systemui.biometrics.ui.viewmodel.isLeft import com.android.systemui.biometrics.ui.viewmodel.isMedium import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall -import com.android.systemui.biometrics.ui.viewmodel.isRight import com.android.systemui.biometrics.ui.viewmodel.isSmall -import com.android.systemui.biometrics.ui.viewmodel.isTop import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R -import kotlin.math.min +import kotlin.math.abs import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @@ -97,8 +93,6 @@ object BiometricViewSizeBinder { if (constraintBp()) { val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline) val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline) - val bottomGuideline = view.requireViewById<Guideline>(R.id.bottomGuideline) - val topGuideline = view.requireViewById<Guideline>(R.id.topGuideline) val midGuideline = view.findViewById<Guideline>(R.id.midGuideline) val iconHolderView = view.requireViewById<View>(R.id.biometric_icon) @@ -121,165 +115,12 @@ object BiometricViewSizeBinder { val largeConstraintSet = ConstraintSet() largeConstraintSet.clone(mediumConstraintSet) + largeConstraintSet.constrainMaxWidth(R.id.panel, view.width) // TODO: Investigate better way to handle 180 rotations val flipConstraintSet = ConstraintSet() - flipConstraintSet.clone(mediumConstraintSet) - flipConstraintSet.connect( - R.id.scrollView, - ConstraintSet.START, - R.id.midGuideline, - ConstraintSet.START - ) - flipConstraintSet.connect( - R.id.scrollView, - ConstraintSet.END, - R.id.rightGuideline, - ConstraintSet.END - ) - flipConstraintSet.setHorizontalBias(R.id.biometric_icon, .2f) - - // Round the panel outline - panelView.outlineProvider = - object : ViewOutlineProvider() { - override fun getOutline(view: View, outline: Outline) { - outline.setRoundRect( - 0, - 0, - view.width, - view.height + cornerRadiusPx, - cornerRadiusPx.toFloat() - ) - } - } view.doOnLayout { - val windowBounds = windowManager.maximumWindowMetrics.bounds - val bottomInset = - windowManager.maximumWindowMetrics.windowInsets - .getInsets(WindowInsets.Type.navigationBars()) - .bottom - - // TODO: Move to viewmodel - fun measureBounds(position: PromptPosition) { - val density = windowManager.currentWindowMetrics.density - val width = min((640 * density).toInt(), windowBounds.width()) - - var left = -1 - var right = -1 - var bottom = -1 - var mid = -1 - - when { - position.isTop -> { - // Round bottom corners - panelView.outlineProvider = - object : ViewOutlineProvider() { - override fun getOutline(view: View, outline: Outline) { - outline.setRoundRect( - 0, - -cornerRadiusPx, - view.width, - view.height, - cornerRadiusPx.toFloat() - ) - } - } - left = windowBounds.centerX() - width / 2 + viewModel.promptMargin - right = windowBounds.centerX() - width / 2 + viewModel.promptMargin - bottom = iconHolderView.centerY() * 2 - iconHolderView.centerY() / 4 - } - position.isBottom -> { - // Round top corners - panelView.outlineProvider = - object : ViewOutlineProvider() { - override fun getOutline(view: View, outline: Outline) { - outline.setRoundRect( - 0, - 0, - view.width, - view.height + cornerRadiusPx, - cornerRadiusPx.toFloat() - ) - } - } - - left = windowBounds.centerX() - width / 2 - right = windowBounds.centerX() - width / 2 - bottom = if (view.isLandscape()) bottomInset else 0 - } - position.isLeft -> { - // Round right corners - panelView.outlineProvider = - object : ViewOutlineProvider() { - override fun getOutline(view: View, outline: Outline) { - outline.setRoundRect( - -cornerRadiusPx, - 0, - view.width, - view.height, - cornerRadiusPx.toFloat() - ) - } - } - - left = 0 - mid = (windowBounds.width() * .85).toInt() / 2 - right = windowBounds.width() - (windowBounds.width() * .85).toInt() - bottom = if (view.isLandscape()) bottomInset else 0 - } - position.isRight -> { - // Round left corners - panelView.outlineProvider = - object : ViewOutlineProvider() { - override fun getOutline(view: View, outline: Outline) { - outline.setRoundRect( - 0, - 0, - view.width + cornerRadiusPx, - view.height, - cornerRadiusPx.toFloat() - ) - } - } - - left = windowBounds.width() - (windowBounds.width() * .85).toInt() - right = 0 - bottom = if (view.isLandscape()) bottomInset else 0 - mid = windowBounds.width() - (windowBounds.width() * .85).toInt() / 2 - } - } - - val bounds = Rect(left, mid, right, bottom) - if (bounds.shouldAdjustLeftGuideline()) { - leftGuideline.setGuidelineBegin(bounds.left) - smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left) - mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left) - } - if (bounds.shouldAdjustRightGuideline()) { - rightGuideline.setGuidelineEnd(bounds.right) - smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right) - mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right) - } - if (bounds.shouldAdjustBottomGuideline()) { - bottomGuideline.setGuidelineEnd(bounds.bottom) - smallConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom) - mediumConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom) - } - - if (position.isBottom) { - topGuideline.setGuidelinePercent(.25f) - mediumConstraintSet.setGuidelinePercent(topGuideline.id, .25f) - } else { - topGuideline.setGuidelinePercent(0f) - mediumConstraintSet.setGuidelinePercent(topGuideline.id, 0f) - } - - if (mid != -1 && midGuideline != null) { - midGuideline.setGuidelineBegin(mid) - } - } - fun setVisibilities(size: PromptSize) { viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) } largeConstraintSet.setVisibility(iconHolderView.id, View.GONE) @@ -297,36 +138,151 @@ object BiometricViewSizeBinder { } } + fun roundCorners(size: PromptSize, position: PromptPosition) { + var left = 0 + var top = 0 + var right = 0 + var bottom = 0 + when (size) { + PromptSize.SMALL, + PromptSize.MEDIUM -> + when (position) { + PromptPosition.Right -> { + left = 0 + top = 0 + right = view.width + cornerRadiusPx + bottom = view.height + } + PromptPosition.Left -> { + left = -cornerRadiusPx + top = 0 + right = view.width + bottom = view.height + } + PromptPosition.Top -> { + left = 0 + top = -cornerRadiusPx + right = panelView.width + bottom = view.height + } + PromptPosition.Bottom -> { + left = 0 + top = 0 + right = panelView.width + bottom = view.height + cornerRadiusPx + } + } + PromptSize.LARGE -> { + left = 0 + top = 0 + right = view.width + bottom = view.height + } + } + + // Round the panel outline + panelView.outlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect( + left, + top, + right, + bottom, + cornerRadiusPx.toFloat() + ) + } + } + } + view.repeatWhenAttached { var currentSize: PromptSize? = null lifecycleScope.launch { + viewModel.guidelineBounds.collect { bounds -> + if (bounds.left >= 0) { + mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left) + smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left) + } else if (bounds.left < 0) { + mediumConstraintSet.setGuidelineEnd( + leftGuideline.id, + abs(bounds.left) + ) + smallConstraintSet.setGuidelineEnd( + leftGuideline.id, + abs(bounds.left) + ) + } + + if (bounds.right >= 0) { + mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right) + smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right) + } else if (bounds.right < 0) { + mediumConstraintSet.setGuidelineBegin( + rightGuideline.id, + abs(bounds.right) + ) + smallConstraintSet.setGuidelineBegin( + rightGuideline.id, + abs(bounds.right) + ) + } + + if (midGuideline != null) { + if (bounds.bottom >= 0) { + midGuideline.setGuidelineEnd(bounds.bottom) + mediumConstraintSet.setGuidelineEnd( + midGuideline.id, + bounds.bottom + ) + } else if (bounds.bottom < 0) { + midGuideline.setGuidelineBegin(abs(bounds.bottom)) + mediumConstraintSet.setGuidelineBegin( + midGuideline.id, + abs(bounds.bottom) + ) + } + } + } + } + + lifecycleScope.launch { combine(viewModel.position, viewModel.size, ::Pair).collect { (position, size) -> view.doOnAttach { + setVisibilities(size) + if (position.isLeft) { - flipConstraintSet.applyTo(view) - } else if (position.isRight) { - mediumConstraintSet.applyTo(view) + if (size.isSmall) { + flipConstraintSet.clone(smallConstraintSet) + } else { + flipConstraintSet.clone(mediumConstraintSet) + } + + // Move all content to other panel + flipConstraintSet.connect( + R.id.scrollView, + ConstraintSet.START, + R.id.midGuideline, + ConstraintSet.START + ) + flipConstraintSet.connect( + R.id.scrollView, + ConstraintSet.END, + R.id.rightGuideline, + ConstraintSet.END + ) } - measureBounds(position) - setVisibilities(size) + roundCorners(size, position) + when { size.isSmall -> { - val ratio = - if (view.isLandscape()) { - (windowBounds.height() - - bottomInset - - viewModel.promptMargin) - .toFloat() / windowBounds.height() - } else { - (windowBounds.height() - viewModel.promptMargin) - .toFloat() / windowBounds.height() - } - smallConstraintSet.setVerticalBias(iconHolderView.id, ratio) - - smallConstraintSet.applyTo(view as ConstraintLayout?) + if (position.isLeft) { + flipConstraintSet.applyTo(view) + } else { + smallConstraintSet.applyTo(view) + } } size.isMedium && currentSize.isSmall -> { val autoTransition = AutoTransition() @@ -338,7 +294,19 @@ object BiometricViewSizeBinder { view, autoTransition ) - mediumConstraintSet.applyTo(view) + + if (position.isLeft) { + flipConstraintSet.applyTo(view) + } else { + mediumConstraintSet.applyTo(view) + } + } + size.isMedium -> { + if (position.isLeft) { + flipConstraintSet.applyTo(view) + } else { + mediumConstraintSet.applyTo(view) + } } size.isLarge -> { val autoTransition = AutoTransition() @@ -551,20 +519,6 @@ private fun View.showContentOrHide(forceHide: Boolean = false) { } } -private fun View.centerX(): Int { - return (x + width / 2).toInt() -} - -private fun View.centerY(): Int { - return (y + height / 2).toInt() -} - -private fun Rect.shouldAdjustLeftGuideline(): Boolean = left != -1 - -private fun Rect.shouldAdjustRightGuideline(): Boolean = right != -1 - -private fun Rect.shouldAdjustBottomGuideline(): Boolean = bottom != -1 - private fun View.asVerticalAnimator( duration: Long, toY: Float, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt index d8265c7d40e8..66b7d7afb8c4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt @@ -37,7 +37,6 @@ import com.android.systemui.util.kotlin.Utils.Companion.toQuint import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch /** Sub-binder for [BiometricPromptLayout.iconView]. */ @@ -127,14 +126,22 @@ object PromptIconViewBinder { if (constraintBp() && position != Rect()) { val iconParams = iconView.layoutParams as ConstraintLayout.LayoutParams - if (position.left != -1) { + if (position.left != 0) { iconParams.endToEnd = ConstraintSet.UNSET iconParams.leftMargin = position.left } - if (position.top != -1) { + if (position.top != 0) { iconParams.bottomToBottom = ConstraintSet.UNSET iconParams.topMargin = position.top } + if (position.right != 0) { + iconParams.startToStart = ConstraintSet.UNSET + iconParams.rightMargin = position.right + } + if (position.bottom != 0) { + iconParams.topToTop = ConstraintSet.UNSET + iconParams.bottomMargin = position.bottom + } iconView.layoutParams = iconParams } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt index d0c140b353e2..8dbed5f2e323 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt @@ -94,25 +94,76 @@ constructor( params.naturalDisplayHeight, rotation.ordinal ) - rotatedBounds + Rect( + rotatedBounds.left, + rotatedBounds.top, + params.logicalDisplayWidth - rotatedBounds.right, + params.logicalDisplayHeight - rotatedBounds.bottom + ) } .distinctUntilChanged() val iconPosition: Flow<Rect> = - combine(udfpsSensorBounds, promptViewModel.size, promptViewModel.modalities) { - sensorBounds, - size, - modalities -> - // If not Udfps, icon does not change from default layout position - if (!modalities.hasUdfps) { - Rect() // Empty rect, don't offset from default position - } else if (size.isSmall) { - // When small with Udfps, only set horizontal position - Rect(sensorBounds.left, -1, sensorBounds.right, -1) - } else { - sensorBounds + combine( + udfpsSensorBounds, + promptViewModel.size, + promptViewModel.position, + promptViewModel.modalities + ) { sensorBounds, size, position, modalities -> + when (position) { + PromptPosition.Bottom -> + if (size.isSmall) { + Rect(0, 0, 0, promptViewModel.portraitSmallBottomPadding) + } else if (size.isMedium && modalities.hasUdfps) { + Rect(0, 0, 0, sensorBounds.bottom) + } else if (size.isMedium) { + Rect(0, 0, 0, promptViewModel.portraitMediumBottomPadding) + } else { + // Large screen + Rect(0, 0, 0, promptViewModel.portraitLargeScreenBottomPadding) + } + PromptPosition.Right -> + if (size.isSmall || modalities.hasFaceOnly) { + Rect( + 0, + 0, + promptViewModel.landscapeSmallHorizontalPadding, + promptViewModel.landscapeSmallBottomPadding + ) + } else if (size.isMedium && modalities.hasUdfps) { + Rect(0, 0, sensorBounds.right, sensorBounds.bottom) + } else { + // SFPS + Rect( + 0, + 0, + promptViewModel.landscapeMediumHorizontalPadding, + promptViewModel.landscapeMediumBottomPadding + ) + } + PromptPosition.Left -> + if (size.isSmall || modalities.hasFaceOnly) { + Rect( + promptViewModel.landscapeSmallHorizontalPadding, + 0, + 0, + promptViewModel.landscapeSmallBottomPadding + ) + } else if (size.isMedium && modalities.hasUdfps) { + Rect(sensorBounds.left, 0, 0, sensorBounds.bottom) + } else { + // SFPS + Rect( + promptViewModel.landscapeMediumHorizontalPadding, + 0, + 0, + promptViewModel.landscapeMediumBottomPadding + ) + } + PromptPosition.Top -> Rect() + } } - } + .distinctUntilChanged() /** Whether an error message is currently being shown. */ val showingError = promptViewModel.showingError @@ -162,10 +213,11 @@ constructor( val iconSize: Flow<Pair<Int, Int>> = combine( + promptViewModel.position, activeAuthType, promptViewModel.fingerprintSensorWidth, promptViewModel.fingerprintSensorHeight, - ) { activeAuthType, fingerprintSensorWidth, fingerprintSensorHeight -> + ) { _, activeAuthType, fingerprintSensorWidth, fingerprintSensorHeight -> if (activeAuthType == AuthType.Face) { Pair(promptViewModel.faceIconWidth, promptViewModel.faceIconHeight) } else { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index fbd87fd3a9f5..21ebff4d0b71 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -86,6 +86,36 @@ constructor( val faceIconHeight: Int = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size) + /** Padding for placing icons */ + val portraitSmallBottomPadding = + context.resources.getDimensionPixelSize( + R.dimen.biometric_prompt_portrait_small_bottom_padding + ) + val portraitMediumBottomPadding = + context.resources.getDimensionPixelSize( + R.dimen.biometric_prompt_portrait_medium_bottom_padding + ) + val portraitLargeScreenBottomPadding = + context.resources.getDimensionPixelSize( + R.dimen.biometric_prompt_portrait_large_screen_bottom_padding + ) + val landscapeSmallBottomPadding = + context.resources.getDimensionPixelSize( + R.dimen.biometric_prompt_landscape_small_bottom_padding + ) + val landscapeSmallHorizontalPadding = + context.resources.getDimensionPixelSize( + R.dimen.biometric_prompt_landscape_small_horizontal_padding + ) + val landscapeMediumBottomPadding = + context.resources.getDimensionPixelSize( + R.dimen.biometric_prompt_landscape_medium_bottom_padding + ) + val landscapeMediumHorizontalPadding = + context.resources.getDimensionPixelSize( + R.dimen.biometric_prompt_landscape_medium_horizontal_padding + ) + val fingerprintSensorWidth: Flow<Int> = combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams -> @@ -111,9 +141,6 @@ constructor( /** Hint for talkback directional guidance */ val accessibilityHint: Flow<String> = _accessibilityHint.asSharedFlow() - val promptMargin: Int = - context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding) - private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false) /** If the user is currently authenticating (i.e. at least one biometric is scanning). */ @@ -205,6 +232,66 @@ constructor( } .distinctUntilChanged() + /** Prompt panel size padding */ + private val smallHorizontalGuidelinePadding = + context.resources.getDimensionPixelSize( + R.dimen.biometric_prompt_small_horizontal_guideline_padding + ) + private val udfpsHorizontalGuidelinePadding = + context.resources.getDimensionPixelSize( + R.dimen.biometric_prompt_udfps_horizontal_guideline_padding + ) + private val udfpsMidGuidelinePadding = + context.resources.getDimensionPixelSize( + R.dimen.biometric_prompt_udfps_mid_guideline_padding + ) + private val mediumHorizontalGuidelinePadding = + context.resources.getDimensionPixelSize( + R.dimen.biometric_prompt_medium_horizontal_guideline_padding + ) + private val mediumMidGuidelinePadding = + context.resources.getDimensionPixelSize( + R.dimen.biometric_prompt_medium_mid_guideline_padding + ) + + /** + * Rect for positioning prompt guidelines (left, top, right, mid) + * + * Negative values are used to signify that guideline measuring should be flipped, measuring + * from opposite side of the screen + */ + val guidelineBounds: Flow<Rect> = + combine(size, position, modalities) { size, position, modalities -> + if (position.isBottom) { + Rect(0, 0, 0, 0) + } else if (position.isRight) { + if (size.isSmall) { + Rect(-smallHorizontalGuidelinePadding, 0, 0, 0) + } else if (modalities.hasUdfps) { + Rect(udfpsHorizontalGuidelinePadding, 0, 0, udfpsMidGuidelinePadding) + } else if (modalities.isEmpty) { + // TODO: Temporary fix until no biometric landscape layout is added + Rect(-mediumHorizontalGuidelinePadding, 0, 0, 6) + } else { + Rect(-mediumHorizontalGuidelinePadding, 0, 0, mediumMidGuidelinePadding) + } + } else if (position.isLeft) { + if (size.isSmall) { + Rect(0, 0, -smallHorizontalGuidelinePadding, 0) + } else if (modalities.hasUdfps) { + Rect(0, 0, udfpsHorizontalGuidelinePadding, -udfpsMidGuidelinePadding) + } else if (modalities.isEmpty) { + // TODO: Temporary fix until no biometric landscape layout is added + Rect(0, 0, -mediumHorizontalGuidelinePadding, -6) + } else { + Rect(0, 0, -mediumHorizontalGuidelinePadding, -mediumMidGuidelinePadding) + } + } else { + Rect() + } + } + .distinctUntilChanged() + /** * If the API caller or the user's personal preferences require explicit confirmation after * successful authentication. Confirmation always required when in explicit flow. @@ -424,7 +511,7 @@ constructor( isAuthenticated, promptSelectorInteractor.isCredentialAllowed, ) { size, _, authState, credentialAllowed -> - size.isNotSmall && authState.isNotAuthenticated && credentialAllowed + size.isMedium && authState.isNotAuthenticated && credentialAllowed } private val history = PromptHistoryImpl() diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt index cfda75c7851a..b72b1f35595a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt @@ -28,6 +28,7 @@ import android.view.WindowManager import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY import com.airbnb.lottie.model.KeyPath +import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor @@ -155,17 +156,19 @@ constructor( -> val topLeft = Point(sensorLocation.left, sensorLocation.top) - if (sensorLocation.isSensorVerticalInDefaultOrientation) { - if (displayRotation == DisplayRotation.ROTATION_0) { - topLeft.x -= bounds!!.width() - } else if (displayRotation == DisplayRotation.ROTATION_270) { - topLeft.y -= bounds!!.height() - } - } else { - if (displayRotation == DisplayRotation.ROTATION_180) { - topLeft.y -= bounds!!.height() - } else if (displayRotation == DisplayRotation.ROTATION_270) { - topLeft.x -= bounds!!.width() + if (!constraintBp()) { + if (sensorLocation.isSensorVerticalInDefaultOrientation) { + if (displayRotation == DisplayRotation.ROTATION_0) { + topLeft.x -= bounds!!.width() + } else if (displayRotation == DisplayRotation.ROTATION_270) { + topLeft.y -= bounds!!.height() + } + } else { + if (displayRotation == DisplayRotation.ROTATION_180) { + topLeft.y -= bounds!!.height() + } else if (displayRotation == DisplayRotation.ROTATION_270) { + topLeft.x -= bounds!!.width() + } } } defaultOverlayViewParams.apply { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt index 3a19780c7017..a09d58ac381b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt @@ -21,15 +21,21 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION +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.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R +import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge /** * Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to @@ -43,6 +49,8 @@ constructor( deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, configurationInteractor: ConfigurationInteractor, animationFlow: KeyguardTransitionAnimationFlow, + keyguardInteractor: KeyguardInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, ) : DeviceEntryIconTransition { private val transitionAnimation = @@ -74,11 +82,28 @@ constructor( /** Lockscreen views alpha */ val lockscreenAlpha: Flow<Float> = - transitionAnimation.sharedFlow( - startTime = 233.milliseconds, - duration = 250.milliseconds, - onStep = { it }, - name = "OCCLUDED->LOCKSCREEN: lockscreenAlpha", + merge( + transitionAnimation.sharedFlow( + startTime = 233.milliseconds, + duration = 250.milliseconds, + onStep = { it }, + name = "OCCLUDED->LOCKSCREEN: lockscreenAlpha", + ), + // Required to fix a bug where the shade expands while lockscreenAlpha=1f, due to a call + // to setOccluded(false) triggering a reset() call in KeyguardViewMediator. The + // permanent solution is to only expand the shade once the keyguard transition from + // OCCLUDED starts, but that requires more refactoring of expansion amounts. For now, + // emit alpha = 0f for OCCLUDED -> LOCKSCREEN whenever isOccluded flips from true to + // false while currentState == OCCLUDED, so that alpha = 0f when that expansion occurs. + // TODO(b/332946323): Remove this once it's no longer needed. + keyguardInteractor.isKeyguardOccluded + .pairwise() + .filter { (wasOccluded, isOccluded) -> + wasOccluded && + !isOccluded && + keyguardTransitionInteractor.getCurrentState() == KeyguardState.OCCLUDED + } + .map { 0f } ) val deviceEntryBackgroundViewAlpha: Flow<Float> = 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 1acb20322c14..238a76eb7400 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 @@ -33,6 +33,7 @@ import com.airbnb.lottie.model.KeyPath import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.settingslib.Utils +import com.android.systemui.Flags.FLAG_CONSTRAINT_BP import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository @@ -351,6 +352,7 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() { @Test fun updatesOverlayViewParams_onDisplayRotationChange_xAlignedSensor() { testScope.runTest { + mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP) setupTestConfiguration( DeviceConfig.X_ALIGNED, rotation = DisplayRotation.ROTATION_0, @@ -392,6 +394,7 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() { @Test fun updatesOverlayViewParams_onDisplayRotationChange_yAlignedSensor() { testScope.runTest { + mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP) setupTestConfiguration( DeviceConfig.Y_ALIGNED, rotation = DisplayRotation.ROTATION_0, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index 695d3b2c6c78..ca403e0addec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -19,6 +19,7 @@ package com.android.systemui.media.dialog; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -47,6 +48,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.media.LocalMediaManager; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastSender; @@ -127,6 +129,12 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager, mKeyguardManager, mFlags, mUserTracker); + // Using a fake package will cause routing operations to fail, so we intercept + // scanning-related operations. + mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class); + doNothing().when(mMediaOutputController.mLocalMediaManager).startScan(); + doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan(); + mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender, mMediaOutputController); mMediaOutputBaseDialogImpl.onCreate(new Bundle()); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt index e6651a44236f..f86e9b7216ce 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt @@ -20,6 +20,8 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -30,5 +32,7 @@ var Kosmos.occludedToLockscreenTransitionViewModel by Fixture { deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, configurationInteractor = configurationInteractor, animationFlow = keyguardTransitionAnimationFlow, + keyguardInteractor = keyguardInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt index 1ce26109ed04..0de76c802faf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt @@ -35,7 +35,6 @@ import com.android.systemui.plugins.qs.QSFactory import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl import com.android.systemui.qs.footer.foregroundServicesRepository import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel -import com.android.systemui.qs.tiles.di.NewQSTileFactory import com.android.systemui.security.data.repository.securityRepository import com.android.systemui.settings.userTracker import com.android.systemui.statusbar.policy.deviceProvisionedController @@ -49,7 +48,6 @@ val Kosmos.qsEventLogger: QsEventLoggerFake by Fixture { QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake) } -var Kosmos.newQSTileFactory by Fixture<NewQSTileFactory>() var Kosmos.qsTileFactory by Fixture<QSFactory>() val Kosmos.fgsManagerController by Fixture { FakeFgsManagerController() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt index 9ef44c4b9085..b870039982f1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt @@ -21,7 +21,6 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.qs.external.customTileStatePersister import com.android.systemui.qs.external.tileLifecycleManagerFactory -import com.android.systemui.qs.newQSTileFactory import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository import com.android.systemui.qs.pipeline.data.repository.minimumTilesRepository @@ -29,6 +28,7 @@ import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository import com.android.systemui.qs.pipeline.shared.logging.qsLogger import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository import com.android.systemui.qs.qsTileFactory +import com.android.systemui.qs.tiles.di.newQSTileFactory import com.android.systemui.settings.userTracker import com.android.systemui.user.data.repository.userRepository diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt new file mode 100644 index 000000000000..5c4b39081143 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.di + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider +import com.android.systemui.qs.tiles.viewmodel.qsTileViewModelAdaperFactory +import com.android.systemui.util.mockito.mock +import javax.inject.Provider +import org.mockito.Mockito + +var Kosmos.newFactoryTileMap by Kosmos.Fixture { emptyMap<String, Provider<QSTileViewModel>>() } + +val Kosmos.newQSTileFactory by + Kosmos.Fixture { + NewQSTileFactory( + qSTileConfigProvider, + qsTileViewModelAdaperFactory, + newFactoryTileMap, + mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)), + mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)), + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderKosmos.kt new file mode 100644 index 000000000000..1d579797bcb3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.viewmodel + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeQSTileConfigProvider by Kosmos.Fixture { FakeQSTileConfigProvider() } +var Kosmos.qSTileConfigProvider: QSTileConfigProvider by Kosmos.Fixture { fakeQSTileConfigProvider } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt new file mode 100644 index 000000000000..a90876551d20 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.viewmodel + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.util.mockito.mock + +val Kosmos.qsTileViewModelAdaperFactory by + Kosmos.Fixture { + object : QSTileViewModelAdapter.Factory { + override fun create(qsTileViewModel: QSTileViewModel): QSTileViewModelAdapter { + return QSTileViewModelAdapter( + applicationCoroutineScope, + mock(), + qsTileViewModel, + ) + } + } + } diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java index 9d04682f2374..ea240c75452d 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -196,7 +196,12 @@ final class UpdatableFontDir { File signatureFile = new File(dir, FONT_SIGNATURE_FILE); if (!signatureFile.exists()) { Slog.i(TAG, "The signature file is missing."); - return; + if (com.android.text.flags.Flags.fixFontUpdateFailure()) { + return; + } else { + FileUtils.deleteContentsAndDir(dir); + continue; + } } byte[] signature; try { @@ -221,33 +226,39 @@ final class UpdatableFontDir { FontFileInfo fontFileInfo = validateFontFile(fontFile, signature); if (fontConfig == null) { - // Use preinstalled font config for checking revision number. - fontConfig = mConfigSupplier.apply(Collections.emptyMap()); + if (com.android.text.flags.Flags.fixFontUpdateFailure()) { + // Use preinstalled font config for checking revision number. + fontConfig = mConfigSupplier.apply(Collections.emptyMap()); + } else { + fontConfig = getSystemFontConfig(); + } } addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, true /* deleteOldFile */); } - // Treat as error if post script name of font family was not installed. - for (int i = 0; i < config.fontFamilies.size(); ++i) { - FontUpdateRequest.Family family = config.fontFamilies.get(i); - for (int j = 0; j < family.getFonts().size(); ++j) { - FontUpdateRequest.Font font = family.getFonts().get(j); - if (mFontFileInfoMap.containsKey(font.getPostScriptName())) { - continue; + if (com.android.text.flags.Flags.fixFontUpdateFailure()) { + // Treat as error if post script name of font family was not installed. + for (int i = 0; i < config.fontFamilies.size(); ++i) { + FontUpdateRequest.Family family = config.fontFamilies.get(i); + for (int j = 0; j < family.getFonts().size(); ++j) { + FontUpdateRequest.Font font = family.getFonts().get(j); + if (mFontFileInfoMap.containsKey(font.getPostScriptName())) { + continue; + } + + if (fontConfig == null) { + fontConfig = mConfigSupplier.apply(Collections.emptyMap()); + } + + if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) { + continue; + } + + Slog.e(TAG, "Unknown font that has PostScript name " + + font.getPostScriptName() + " is requested in FontFamily " + + family.getName()); + return; } - - if (fontConfig == null) { - fontConfig = mConfigSupplier.apply(Collections.emptyMap()); - } - - if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) { - continue; - } - - Slog.e(TAG, "Unknown font that has PostScript name " - + font.getPostScriptName() + " is requested in FontFamily " - + family.getName()); - return; } } @@ -262,7 +273,9 @@ final class UpdatableFontDir { mFontFileInfoMap.clear(); mLastModifiedMillis = 0; FileUtils.deleteContents(mFilesDir); - mConfigFile.delete(); + if (com.android.text.flags.Flags.fixFontUpdateFailure()) { + mConfigFile.delete(); + } } } } diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index 6fc9d9a8ce1d..ad54efcd11d3 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -32,8 +32,9 @@ import android.util.IndentingPrintWriter; import android.util.proto.ProtoOutputStream; import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.Locale; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; @@ -42,10 +43,11 @@ import java.util.concurrent.atomic.AtomicInteger; * The base class for all vibrations. */ abstract class Vibration { - private static final SimpleDateFormat DEBUG_TIME_FORMAT = - new SimpleDateFormat("HH:mm:ss.SSS"); - private static final SimpleDateFormat DEBUG_DATE_TIME_FORMAT = - new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); + private static final DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern( + "HH:mm:ss.SSS").withZone(ZoneId.systemDefault()); + private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern( + "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault()); + // Used to generate globally unique vibration ids. private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback @@ -240,10 +242,12 @@ abstract class Vibration { @Override public String toString() { - return "createTime: " + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)) - + ", startTime: " + DEBUG_DATE_TIME_FORMAT.format(new Date(mStartTime)) - + ", endTime: " - + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime))) + return "createTime: " + DEBUG_DATE_TIME_FORMATTER.format( + Instant.ofEpochMilli(mCreateTime)) + + ", startTime: " + DEBUG_DATE_TIME_FORMATTER.format( + Instant.ofEpochMilli(mStartTime)) + + ", endTime: " + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMATTER.format( + Instant.ofEpochMilli(mEndTime))) + ", durationMs: " + mDurationMs + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) + ", playedEffect: " + mPlayedEffect @@ -267,12 +271,14 @@ abstract class Vibration { boolean isExternalVibration = mPlayedEffect == null; String timingsStr = String.format(Locale.ROOT, "%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s", - DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)), + DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mCreateTime)), isExternalVibration ? "external" : "effect", mStatus.name().toLowerCase(Locale.ROOT), mDurationMs, - mStartTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mStartTime)), - mEndTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mEndTime))); + mStartTime == 0 ? "" + : DEBUG_TIME_FORMATTER.format(Instant.ofEpochMilli(mStartTime)), + mEndTime == 0 ? "" + : DEBUG_TIME_FORMATTER.format(Instant.ofEpochMilli(mEndTime))); String paramStr = String.format(Locale.ROOT, " | scale: %8s (adaptive=%.2f) | flags: %4s | usage: %s", VibrationScaler.scaleLevelToString(mScaleLevel), mAdaptiveScale, @@ -307,10 +313,12 @@ abstract class Vibration { pw.increaseIndent(); pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT)); pw.println("durationMs = " + mDurationMs); - pw.println("createTime = " + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime))); - pw.println("startTime = " + DEBUG_DATE_TIME_FORMAT.format(new Date(mStartTime))); - pw.println("endTime = " - + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime)))); + pw.println("createTime = " + DEBUG_DATE_TIME_FORMATTER.format( + Instant.ofEpochMilli(mCreateTime))); + pw.println("startTime = " + DEBUG_DATE_TIME_FORMATTER.format( + Instant.ofEpochMilli(mStartTime))); + pw.println("endTime = " + (mEndTime == 0 ? null + : DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mEndTime)))); pw.println("playedEffect = " + mPlayedEffect); pw.println("originalEffect = " + mOriginalEffect); pw.println("scale = " + VibrationScaler.scaleLevelToString(mScaleLevel)); diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java index 10317c997581..b33fa6f56a23 100644 --- a/services/core/java/com/android/server/vibrator/VibratorControlService.java +++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java @@ -51,8 +51,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import java.io.PrintWriter; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.Locale; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -66,8 +67,8 @@ final class VibratorControlService extends IVibratorControlService.Stub { private static final int UNRECOGNIZED_VIBRATION_TYPE = -1; private static final int NO_SCALE = -1; - private static final SimpleDateFormat DEBUG_DATE_TIME_FORMAT = - new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); + private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern( + "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault()); private final VibrationParamsRecords mVibrationParamsRecords; private final VibratorControllerHolder mVibratorControllerHolder; @@ -567,7 +568,7 @@ final class VibratorControlService extends IVibratorControlService.Stub { public void dump(IndentingPrintWriter pw) { String line = String.format(Locale.ROOT, "%s | %6s | scale: %5s | typesMask: %6s | usages: %s", - DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)), + DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mCreateTime)), mOperation.name().toLowerCase(Locale.ROOT), (mScale == NO_SCALE) ? "" : String.format(Locale.ROOT, "%.2f", mScale), Long.toBinaryString(mTypesMask), createVibrationUsagesString()); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java index 5175b74116f4..6905802809c3 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java @@ -16,6 +16,7 @@ package com.android.server.wallpaper; +import static android.app.WallpaperManager.ORIENTATION_UNKNOWN; import static android.app.WallpaperManager.getOrientation; import static android.app.WallpaperManager.getRotatedOrientation; import static android.view.Display.DEFAULT_DISPLAY; @@ -86,7 +87,7 @@ public class WallpaperCropper { public interface WallpaperCropUtils { /** - * Equivalent to {@link #getCrop(Point, Point, SparseArray, boolean)} + * Equivalent to {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)} */ Rect getCrop(Point displaySize, Point bitmapSize, SparseArray<Rect> suggestedCrops, boolean rtl); @@ -120,16 +121,23 @@ public class WallpaperCropper { public Rect getCrop(Point displaySize, Point bitmapSize, SparseArray<Rect> suggestedCrops, boolean rtl) { - // Case 1: if no crops are provided, center align the full image + int orientation = getOrientation(displaySize); + + // Case 1: if no crops are provided, show the full image (from the left, or right if RTL). if (suggestedCrops == null || suggestedCrops.size() == 0) { - Rect crop = new Rect(0, 0, displaySize.x, displaySize.y); - float scale = Math.min( - ((float) bitmapSize.x) / displaySize.x, - ((float) bitmapSize.y) / displaySize.y); - crop.scale(scale); - crop.offset((bitmapSize.x - crop.width()) / 2, - (bitmapSize.y - crop.height()) / 2); - return crop; + Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); + + // The first exception is if the device is a foldable and we're on the folded screen. + // In that case, show the center of what's on the unfolded screen. + int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation); + if (unfoldedOrientation != ORIENTATION_UNKNOWN) { + // Let the system know that we're showing the full image on the unfolded screen + SparseArray<Rect> newSuggestedCrops = new SparseArray<>(); + newSuggestedCrops.put(unfoldedOrientation, crop); + // This will fall into "Case 4" of this function and center the folded screen + return getCrop(displaySize, bitmapSize, newSuggestedCrops, rtl); + } + return getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD); } // If any suggested crop is invalid, fallback to case 1 @@ -142,8 +150,6 @@ public class WallpaperCropper { } } - int orientation = getOrientation(displaySize); - // Case 2: if the orientation exists in the suggested crops, adjust the suggested crop Rect suggestedCrop = suggestedCrops.get(orientation); if (suggestedCrop != null) { @@ -168,11 +174,21 @@ public class WallpaperCropper { suggestedCrop = suggestedCrops.get(unfoldedOrientation); suggestedDisplaySize = defaultDisplaySizes.get(unfoldedOrientation); if (suggestedCrop != null) { - // only keep the visible part (without parallax) + // compute the visible part (without parallax) of the unfolded screen Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl); - return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE); + // compute the folded crop, at the center of the crop of the unfolded screen + Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE); + // if we removed some width, add it back to add a parallax effect + if (res.width() < adjustedCrop.width()) { + if (rtl) res.left = Math.min(res.left, adjustedCrop.left); + else res.right = Math.max(res.right, adjustedCrop.right); + // use getAdjustedCrop(parallax=true) to make sure we don't exceed MAX_PARALLAX + res = getAdjustedCrop(res, bitmapSize, displaySize, true, rtl, ADD); + } + return res; } + // Case 5: if the device is a foldable, if we're looking for an unfolded orientation and // have the suggested crop of the relative folded orientation, reuse it by adding content. int foldedOrientation = mWallpaperDisplayHelper.getFoldedOrientation(orientation); @@ -274,11 +290,8 @@ public class WallpaperCropper { if (additionalWidthForParallax > MAX_PARALLAX) { int widthToRemove = (int) Math.ceil( (additionalWidthForParallax - MAX_PARALLAX) * screenRatio * crop.height()); - if (rtl) { - adjustedCrop.left += widthToRemove; - } else { - adjustedCrop.right -= widthToRemove; - } + adjustedCrop.left += widthToRemove / 2; + adjustedCrop.right -= widthToRemove / 2 + widthToRemove % 2; } } else { // TODO (b/281648899) the third case is not always correct, fix that. @@ -366,6 +379,24 @@ public class WallpaperCropper { */ SparseArray<Rect> getDefaultCrops(SparseArray<Rect> suggestedCrops, Point bitmapSize) { + // If the suggested crops is single-element map with (ORIENTATION_UNKNOWN, cropHint), + // Crop the bitmap using the cropHint and compute the crops for cropped bitmap. + Rect cropHint = suggestedCrops.get(ORIENTATION_UNKNOWN); + if (cropHint != null) { + Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y); + if (suggestedCrops.size() != 1 || !bitmapRect.contains(cropHint)) { + Slog.w(TAG, "Couldn't get default crops from suggested crops " + suggestedCrops + + " for bitmap of size " + bitmapSize + "; ignoring suggested crops"); + return getDefaultCrops(new SparseArray<>(), bitmapSize); + } + Point cropSize = new Point(cropHint.width(), cropHint.height()); + SparseArray<Rect> relativeDefaultCrops = getDefaultCrops(new SparseArray<>(), cropSize); + for (int i = 0; i < relativeDefaultCrops.size(); i++) { + relativeDefaultCrops.valueAt(i).offset(cropHint.left, cropHint.top); + } + return relativeDefaultCrops; + } + SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes(); boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL; @@ -422,26 +453,74 @@ public class WallpaperCropper { } else { boolean needCrop = false; boolean needScale; - boolean multiCrop = multiCrop() && wallpaper.mSupportsMultiCrop; Point bitmapSize = new Point(options.outWidth, options.outHeight); + Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y); + if (multiCrop()) { + // Check that the suggested crops per screen orientation are all within the bitmap. + for (int i = 0; i < wallpaper.mCropHints.size(); i++) { + int orientation = wallpaper.mCropHints.keyAt(i); + Rect crop = wallpaper.mCropHints.valueAt(i); + if (crop.isEmpty() || !bitmapRect.contains(crop)) { + Slog.w(TAG, "Invalid crop " + crop + " for orientation " + orientation + + " and bitmap size " + bitmapSize + "; clearing suggested crops."); + wallpaper.mCropHints.clear(); + wallpaper.cropHint.set(bitmapRect); + break; + } + } + } final Rect cropHint; - if (multiCrop) { - SparseArray<Rect> defaultDisplayCrops = - getDefaultCrops(wallpaper.mCropHints, bitmapSize); - // adapt the entries in wallpaper.mCropHints for the actual display + + // A wallpaper with cropHints = Map.of(ORIENTATION_UNKNOWN, rect) is treated like + // a wallpaper with cropHints = null and cropHint = rect. + Rect tempCropHint = wallpaper.mCropHints.get(ORIENTATION_UNKNOWN); + if (multiCrop() && tempCropHint != null) { + wallpaper.cropHint.set(tempCropHint); + wallpaper.mCropHints.clear(); + } + if (multiCrop() && wallpaper.mCropHints.size() > 0) { + // Some suggested crops per screen orientation were provided, + // use them to compute the default crops for this device + SparseArray<Rect> defaultCrops = getDefaultCrops(wallpaper.mCropHints, bitmapSize); + // Adapt the provided crops to match the actual crops for the default display SparseArray<Rect> updatedCropHints = new SparseArray<>(); for (int i = 0; i < wallpaper.mCropHints.size(); i++) { int orientation = wallpaper.mCropHints.keyAt(i); - Rect defaultCrop = defaultDisplayCrops.get(orientation); + Rect defaultCrop = defaultCrops.get(orientation); if (defaultCrop != null) { updatedCropHints.put(orientation, defaultCrop); } } wallpaper.mCropHints = updatedCropHints; - cropHint = getTotalCrop(defaultDisplayCrops); + + // Finally, compute the cropHint based on the default crops + cropHint = getTotalCrop(defaultCrops); wallpaper.cropHint.set(cropHint); + if (DEBUG) { + Slog.d(TAG, "Generated default crops for wallpaper: " + defaultCrops + + " based on suggested crops: " + wallpaper.mCropHints); + } + } else if (multiCrop()) { + // No crops per screen orientation were provided, but an overall cropHint may be + // defined in wallpaper.cropHint. Compute the default crops for the sub-image + // defined by the cropHint, then recompute the cropHint based on the default crops. + // If the cropHint is empty or invalid, ignore it and use the full image. + if (wallpaper.cropHint.isEmpty()) wallpaper.cropHint.set(bitmapRect); + if (!bitmapRect.contains(wallpaper.cropHint)) { + Slog.w(TAG, "Ignoring wallpaper.cropHint = " + wallpaper.cropHint + + "; not within the bitmap of size " + bitmapSize); + wallpaper.cropHint.set(bitmapRect); + } + Point cropSize = new Point(wallpaper.cropHint.width(), wallpaper.cropHint.height()); + SparseArray<Rect> defaultCrops = getDefaultCrops(new SparseArray<>(), cropSize); + cropHint = getTotalCrop(defaultCrops); + cropHint.offset(wallpaper.cropHint.left, wallpaper.cropHint.top); + wallpaper.cropHint.set(cropHint); + if (DEBUG) { + Slog.d(TAG, "Generated default crops for wallpaper: " + defaultCrops); + } } else { cropHint = new Rect(wallpaper.cropHint); } @@ -455,11 +534,11 @@ public class WallpaperCropper { } // Empty crop means use the full image - if (cropHint.isEmpty()) { + if (!multiCrop() && cropHint.isEmpty()) { cropHint.left = cropHint.top = 0; cropHint.right = options.outWidth; cropHint.bottom = options.outHeight; - } else { + } else if (!multiCrop()) { // force the crop rect to lie within the measured bounds int dx = cropHint.right > options.outWidth ? options.outWidth - cropHint.right : 0; int dy = cropHint.bottom > options.outHeight @@ -473,19 +552,19 @@ public class WallpaperCropper { if (cropHint.top < 0) { cropHint.top = 0; } - - // Don't bother cropping if what we're left with is identity - needCrop = (options.outHeight > cropHint.height() - || options.outWidth > cropHint.width()); } + // Don't bother cropping if what we're left with is identity + needCrop = (options.outHeight > cropHint.height() + || options.outWidth > cropHint.width()); + // scale if the crop height winds up not matching the recommended metrics needScale = cropHint.height() > wpData.mHeight || cropHint.height() > GLHelper.getMaxTextureSize() || cropHint.width() > GLHelper.getMaxTextureSize(); //make sure screen aspect ratio is preserved if width is scaled under screen size - if (needScale && !multiCrop) { + if (needScale && !multiCrop()) { final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height(); final int newWidth = (int) (cropHint.width() * scaleByHeight); if (newWidth < displayInfo.logicalWidth) { @@ -543,7 +622,7 @@ public class WallpaperCropper { final Rect estimateCrop = new Rect(cropHint); estimateCrop.scale(1f / options.inSampleSize); float hRatio = (float) wpData.mHeight / estimateCrop.height(); - if (multiCrop) { + if (multiCrop()) { // make sure the crop height is at most the display largest dimension hRatio = (float) mWallpaperDisplayHelper.getDefaultDisplayLargestDimension() / estimateCrop.height(); @@ -557,7 +636,7 @@ public class WallpaperCropper { if (DEBUG) { Slog.w(TAG, "Invalid crop dimensions, trying to adjust."); } - if (multiCrop) { + if (multiCrop()) { // clear custom crop guidelines, fallback to system default wallpaper.mCropHints.clear(); generateCropInternal(wallpaper); @@ -618,7 +697,7 @@ public class WallpaperCropper { final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped, safeWidth, safeHeight, true); - if (multiCrop) { + if (multiCrop()) { wallpaper.mSampleSize = ((float) cropHint.height()) / finalCrop.getHeight(); } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java index 02594d2d8d22..b792f7909fc8 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperData.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java @@ -172,11 +172,6 @@ class WallpaperData { SparseArray<Rect> mCropHints = new SparseArray<>(); /** - * cropHints will be ignored if this flag is false - */ - boolean mSupportsMultiCrop; - - /** * The phone orientation when the wallpaper was set. Only relevant for image wallpapers */ int mOrientationWhenSet = ORIENTATION_UNKNOWN; @@ -204,7 +199,6 @@ class WallpaperData { if (source.mCropHints != null) { this.mCropHints = source.mCropHints.clone(); } - this.mSupportsMultiCrop = source.mSupportsMultiCrop; this.allowBackup = source.allowBackup; this.primaryColors = source.primaryColors; this.mWallpaperDimAmount = source.mWallpaperDimAmount; diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java index 7f53ea372687..4aefb54889aa 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java @@ -324,10 +324,7 @@ public class WallpaperDataParser { getAttributeInt(parser, "totalCropTop", 0), getAttributeInt(parser, "totalCropRight", 0), getAttributeInt(parser, "totalCropBottom", 0)); - wallpaper.mSupportsMultiCrop = multiCrop() && ( - parser.getAttributeBoolean(null, "supportsMultiCrop", false) - || mImageWallpaper.equals(wallpaper.wallpaperComponent)); - if (wallpaper.mSupportsMultiCrop) { + if (multiCrop() && mImageWallpaper.equals(wallpaper.nextWallpaperComponent)) { wallpaper.mCropHints = new SparseArray<>(); for (Pair<Integer, String> pair: screenDimensionPairs()) { Rect cropHint = new Rect( @@ -342,16 +339,14 @@ public class WallpaperDataParser { } if (wallpaper.mCropHints.size() == 0 && totalCropHint.isEmpty()) { // migration case: the crops per screen orientation are not specified. - int orientation = legacyCropHint.width() < legacyCropHint.height() - ? WallpaperManager.PORTRAIT : WallpaperManager.LANDSCAPE; if (!legacyCropHint.isEmpty()) { - wallpaper.mCropHints.put(orientation, legacyCropHint); + wallpaper.cropHint.set(legacyCropHint); } } else { wallpaper.cropHint.set(totalCropHint); } wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f); - } else { + } else if (!multiCrop()) { wallpaper.cropHint.set(legacyCropHint); } final DisplayData wpData = mWallpaperDisplayHelper @@ -467,13 +462,12 @@ public class WallpaperDataParser { out.startTag(null, tag); out.attributeInt(null, "id", wallpaper.wallpaperId); - out.attributeBoolean(null, "supportsMultiCrop", wallpaper.mSupportsMultiCrop); - - if (multiCrop() && wallpaper.mSupportsMultiCrop) { + if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) { if (wallpaper.mCropHints == null) { Slog.e(TAG, "cropHints should not be null when saved"); wallpaper.mCropHints = new SparseArray<>(); } + Rect rectToPutInLegacyCrop = new Rect(wallpaper.cropHint); for (Pair<Integer, String> pair : screenDimensionPairs()) { Rect cropHint = wallpaper.mCropHints.get(pair.first); if (cropHint == null) continue; @@ -493,12 +487,14 @@ public class WallpaperDataParser { } } if (pair.first == orientationToPutInLegacyCrop) { - out.attributeInt(null, "cropLeft", cropHint.left); - out.attributeInt(null, "cropTop", cropHint.top); - out.attributeInt(null, "cropRight", cropHint.right); - out.attributeInt(null, "cropBottom", cropHint.bottom); + rectToPutInLegacyCrop.set(cropHint); } } + out.attributeInt(null, "cropLeft", rectToPutInLegacyCrop.left); + out.attributeInt(null, "cropTop", rectToPutInLegacyCrop.top); + out.attributeInt(null, "cropRight", rectToPutInLegacyCrop.right); + out.attributeInt(null, "cropBottom", rectToPutInLegacyCrop.bottom); + out.attributeInt(null, "totalCropLeft", wallpaper.cropHint.left); out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top); out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 885baf65013f..8c4c0de71781 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -803,7 +803,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub null /* options */); mWindowManagerInternal.setWallpaperShowWhenLocked( mToken, (wallpaper.mWhich & FLAG_LOCK) != 0); - if (multiCrop() && wallpaper.mSupportsMultiCrop) { + if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) { mWindowManagerInternal.setWallpaperCropHints(mToken, mWallpaperCropper.getRelativeCropHints(wallpaper)); } else { @@ -1917,11 +1917,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final ComponentName component; final int finalWhich; - if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) { - clearWallpaperBitmaps(lockWallpaper); - } - if ((which & FLAG_SYSTEM) > 0) { - clearWallpaperBitmaps(wallpaper); + // Clear any previous ImageWallpaper related fields + List<WallpaperData> toClear = new ArrayList<>(); + if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) toClear.add(lockWallpaper); + if ((which & FLAG_SYSTEM) > 0) toClear.add(wallpaper); + for (WallpaperData wallpaperToClear : toClear) { + clearWallpaperBitmaps(wallpaperToClear); + if (multiCrop()) { + wallpaperToClear.mCropHints.clear(); + wallpaperToClear.cropHint.set(0, 0, 0, 0); + wallpaperToClear.mSampleSize = 1; + } } // lock only case: set the system wallpaper component to both screens @@ -2225,7 +2231,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub checkPermission(READ_WALLPAPER_INTERNAL); WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId) : mWallpaperMap.get(userId); - if (wallpaper == null || !wallpaper.mSupportsMultiCrop) return null; + if (wallpaper == null || !mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + return null; + } SparseArray<Rect> relativeSuggestedCrops = mWallpaperCropper.getRelativeCropHints(wallpaper); Point croppedBitmapSize = new Point( @@ -2255,7 +2263,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub @Override public List<Rect> getFutureBitmapCrops(Point bitmapSize, List<Point> displaySizes, int[] screenOrientations, List<Rect> crops) { - SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN); + SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops); SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize); List<Rect> result = new ArrayList<>(); boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) @@ -2272,7 +2280,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub throw new UnsupportedOperationException( "This method should only be called with the multi crop flag enabled"); } - SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN); + SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops); SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize); return WallpaperCropper.getTotalCrop(defaultCrops); } @@ -2860,10 +2868,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return null; } - int currentOrientation = mWallpaperDisplayHelper.getDefaultDisplayCurrentOrientation(); - SparseArray<Rect> cropMap = !multiCrop() ? null - : getCropMap(screenOrientations, crops, currentOrientation); - Rect cropHint = multiCrop() || crops == null ? null : crops.get(0); + SparseArray<Rect> cropMap = !multiCrop() ? null : getCropMap(screenOrientations, crops); + Rect cropHint = multiCrop() || crops == null || crops.isEmpty() ? new Rect() : crops.get(0); final boolean fromForegroundApp = !multiCrop() ? false : isFromForegroundApp(callingPackage); @@ -2912,12 +2918,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper.setComplete = completion; wallpaper.fromForegroundApp = multiCrop() ? fromForegroundApp : isFromForegroundApp(callingPackage); - if (!multiCrop()) wallpaper.cropHint.set(cropHint); - if (multiCrop()) wallpaper.mSupportsMultiCrop = true; - if (multiCrop()) wallpaper.mCropHints = cropMap; + wallpaper.cropHint.set(cropHint); + if (multiCrop()) { + wallpaper.mCropHints = cropMap; + wallpaper.mSampleSize = 1f; + wallpaper.mOrientationWhenSet = + mWallpaperDisplayHelper.getDefaultDisplayCurrentOrientation(); + } wallpaper.allowBackup = allowBackup; wallpaper.mWallpaperDimAmount = getWallpaperDimAmount(); - wallpaper.mOrientationWhenSet = currentOrientation; } return pfd; } finally { @@ -2926,16 +2935,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private SparseArray<Rect> getCropMap(int[] screenOrientations, List<Rect> crops, - int currentOrientation) { + private SparseArray<Rect> getCropMap(int[] screenOrientations, List<Rect> crops) { if ((crops == null ^ screenOrientations == null) || (crops != null && crops.size() != screenOrientations.length)) { throw new IllegalArgumentException( "Illegal crops/orientations lists: must both be null, or both the same size"); } SparseArray<Rect> cropMap = new SparseArray<>(); - boolean unknown = false; - if (crops != null && crops.size() != 0) { + if (crops != null && !crops.isEmpty()) { for (int i = 0; i < crops.size(); i++) { Rect crop = crops.get(i); int width = crop.width(), height = crop.height(); @@ -2943,22 +2950,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub throw new IllegalArgumentException("Invalid crop rect supplied: " + crop); } int orientation = screenOrientations[i]; - if (orientation == ORIENTATION_UNKNOWN) { - if (currentOrientation == ORIENTATION_UNKNOWN) { - throw new IllegalArgumentException( - "Invalid orientation: " + ORIENTATION_UNKNOWN); - } - unknown = true; - orientation = currentOrientation; + if (orientation == ORIENTATION_UNKNOWN && cropMap.size() > 1) { + throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN" + + "screen orientation should only be used in a singleton map"); } cropMap.put(orientation, crop); } } - if (unknown && cropMap.size() > 1) { - throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN screen " - + "orientation should only be used in a singleton map (in which case it" - + "represents the current orientation of the default display)"); - } return cropMap; } @@ -2975,7 +2973,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData lockWP = new WallpaperData(userId, FLAG_LOCK); lockWP.wallpaperId = sysWP.wallpaperId; lockWP.cropHint.set(sysWP.cropHint); - lockWP.mSupportsMultiCrop = sysWP.mSupportsMultiCrop; if (sysWP.mCropHints != null) { lockWP.mCropHints = sysWP.mCropHints.clone(); } @@ -3096,7 +3093,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final long ident = Binder.clearCallingIdentity(); try { - newWallpaper.mSupportsMultiCrop = mImageWallpaper.equals(name); newWallpaper.imageWallpaperPending = false; newWallpaper.mWhich = which; newWallpaper.mSystemWasBoth = systemIsBoth; diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java index 7ecc7fd1b94b..29f3720a1828 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java @@ -235,12 +235,11 @@ public class WallpaperCropperTest { int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX)); Point expectedCropSize = new Point(expectedWidth, 1000); for (int mode: ALL_MODES) { - assertThat(WallpaperCropper.getAdjustedCrop( - crop, bitmapSize, displaySize, true, false, mode)) - .isEqualTo(leftOf(crop, expectedCropSize)); - assertThat(WallpaperCropper.getAdjustedCrop( - crop, bitmapSize, displaySize, true, true, mode)) - .isEqualTo(rightOf(crop, expectedCropSize)); + for (boolean rtl: List.of(false, true)) { + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, true, rtl, mode)) + .isEqualTo(centerOf(crop, expectedCropSize)); + } } } @@ -362,11 +361,13 @@ public class WallpaperCropperTest { } /** - * Test that {@link WallpaperCropper#getCrop} follows a simple centre-align strategy when - * no suggested crops are provided. + * Test that {@link WallpaperCropper#getCrop} uses the full image when no crops are provided. + * If the image has more width/height ratio than the screen, keep that width for parallax up + * to {@link WallpaperCropper#MAX_PARALLAX}. If the crop has less width/height ratio, remove the + * surplus height, on both sides to keep the wallpaper centered. */ @Test - public void testGetCrop_noSuggestedCrops_centersWallpaper() { + public void testGetCrop_noSuggestedCrops() { setUpWithDisplays(STANDARD_DISPLAY); Point bitmapSize = new Point(800, 1000); Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y); @@ -374,9 +375,11 @@ public class WallpaperCropperTest { List<Point> displaySizes = List.of( new Point(500, 1000), + new Point(200, 1000), new Point(1000, 500)); List<Point> expectedCropSizes = List.of( - new Point(500, 1000), + new Point(Math.min(800, (int) (500 * (1 + WallpaperCropper.MAX_PARALLAX))), 1000), + new Point(Math.min(800, (int) (200 * (1 + WallpaperCropper.MAX_PARALLAX))), 1000), new Point(800, 400)); for (int i = 0; i < displaySizes.size(); i++) { @@ -450,7 +453,8 @@ public class WallpaperCropperTest { /** * Test that {@link WallpaperCropper#getCrop}, when asked for a folded crop with a suggested * crop only for the relative unfolded orientation, creates the folded crop at the center of the - * unfolded crop, by removing content on two sides to match the folded screen dimensions. + * unfolded crop, by removing content on two sides to match the folded screen dimensions, and + * then adds some width for parallax. * <p> * To simplify, in this test case all crops have the same size as the display (no zoom) * and are at the center of the image. @@ -468,6 +472,7 @@ public class WallpaperCropperTest { int unfoldedTwo = getRotatedOrientation(unfoldedOne); Rect unfoldedCropOne = centerOf(bitmapRect, mDisplaySizes.get(unfoldedOne)); Rect unfoldedCropTwo = centerOf(bitmapRect, mDisplaySizes.get(unfoldedTwo)); + List<Rect> unfoldedCrops = List.of(unfoldedCropOne, unfoldedCropTwo); SparseArray<Rect> suggestedCrops = new SparseArray<>(); suggestedCrops.put(unfoldedOne, unfoldedCropOne); suggestedCrops.put(unfoldedTwo, unfoldedCropTwo); @@ -476,15 +481,28 @@ public class WallpaperCropperTest { int foldedTwo = getFoldedOrientation(unfoldedTwo); Point foldedDisplayOne = mDisplaySizes.get(foldedOne); Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo); + List<Point> foldedDisplays = List.of(foldedDisplayOne, foldedDisplayTwo); for (boolean rtl : List.of(false, true)) { - assertThat(mWallpaperCropper.getCrop( - foldedDisplayOne, bitmapSize, suggestedCrops, rtl)) - .isEqualTo(centerOf(unfoldedCropOne, foldedDisplayOne)); - - assertThat(mWallpaperCropper.getCrop( - foldedDisplayTwo, bitmapSize, suggestedCrops, rtl)) - .isEqualTo(centerOf(unfoldedCropTwo, foldedDisplayTwo)); + for (int i = 0; i < 2; i++) { + Rect unfoldedCrop = unfoldedCrops.get(i); + Point foldedDisplay = foldedDisplays.get(i); + Rect expectedCrop = centerOf(unfoldedCrop, foldedDisplay); + int maxParallax = (int) (WallpaperCropper.MAX_PARALLAX * unfoldedCrop.width()); + + // the expected behaviour is that we add width for parallax until we reach + // either MAX_PARALLAX or the edge of the crop for the unfolded screen. + if (rtl) { + expectedCrop.left = Math.max( + unfoldedCrop.left, expectedCrop.left - maxParallax); + } else { + expectedCrop.right = Math.min( + unfoldedCrop.right, unfoldedCrop.right + maxParallax); + } + assertThat(mWallpaperCropper.getCrop( + foldedDisplay, bitmapSize, suggestedCrops, rtl)) + .isEqualTo(expectedCrop); + } } } } diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java index fa892782f42e..44aa868716eb 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java @@ -30,6 +30,9 @@ import android.graphics.fonts.SystemFonts; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.system.Os; import android.text.FontConfig; import android.util.Xml; @@ -38,8 +41,11 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.text.flags.Flags; + import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParser; @@ -69,6 +75,9 @@ public final class UpdatableFontDirTest { private static final String LEGACY_FONTS_XML = "/system/etc/fonts.xml"; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + /** * A {@link UpdatableFontDir.FontFileParser} for testing. Instead of using real font files, * this test uses fake font files. A fake font file has its PostScript naem and revision as the @@ -1097,6 +1106,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1116,6 +1126,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1); @@ -1135,6 +1146,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf and bar.ttf installTestFontFile(2 /* numFonts */, 1 /* version */); @@ -1154,6 +1166,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf and bar.ttf installTestFontFile(2 /* numFonts */, 1 /* version */); @@ -1173,6 +1186,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1192,6 +1206,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureAllMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1211,6 +1226,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureAllMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1230,6 +1246,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void signatureAllMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1249,6 +1266,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1268,6 +1286,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1); @@ -1287,6 +1306,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf and bar.ttf installTestFontFile(2 /* numFonts */, 1 /* version */); @@ -1306,6 +1326,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf and bar.ttf installTestFontFile(2 /* numFonts */, 1 /* version */); @@ -1325,6 +1346,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1344,6 +1366,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontAllMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1363,6 +1386,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontAllMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1382,6 +1406,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontAllMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1401,6 +1426,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontDirAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1420,6 +1446,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontDirAllMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1439,6 +1466,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontDirAllMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1458,6 +1486,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void fontDirAllMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1477,6 +1506,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void dirContentAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1497,6 +1527,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void dirContentAllMissingCase_fontFamilyInstalled_fontInstallLater() { // Install font families, foo.ttf, bar.ttf. installTestFontFamilies(1 /* version */); @@ -1517,6 +1548,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void dirContentAllMissingCase_fontFileInstalled_fontFamilyInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); @@ -1537,6 +1569,7 @@ public final class UpdatableFontDirTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE) public void dirContentAllMissingCase_fontFileInstalled_fontFileInstallLater() { // Install font file, foo.ttf installTestFontFile(1 /* numFonts */, 1 /* version */); diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt index 8e210d455591..f1df8a68fb63 100644 --- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt +++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt @@ -123,7 +123,9 @@ class OpenAppFromLockscreenNotificationWithOverlayAppTest(flicker: LegacyFlicker @Test override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() - @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() + @FlakyTest(bugId = 227143265) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() @FlakyTest(bugId = 278227468) @Test |