diff options
25 files changed, 370 insertions, 211 deletions
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java index 5e2eceb23789..dee49350d93e 100644 --- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java @@ -177,7 +177,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * <code>1</code> would return the work profile {@link ProfileDescriptor}.</li> * </ul> */ - abstract ProfileDescriptor getItem(int pageIndex); + public abstract ProfileDescriptor getItem(int pageIndex); /** * Returns the number of {@link ProfileDescriptor} objects. @@ -438,8 +438,8 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { && isQuietModeEnabled(mWorkProfileUserHandle)); } - protected class ProfileDescriptor { - final ViewGroup rootView; + public static class ProfileDescriptor { + public final ViewGroup rootView; private final ViewGroup mEmptyStateView; ProfileDescriptor(ViewGroup rootView) { this.rootView = rootView; diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java index 7beb059fb648..8197e265ca29 100644 --- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java @@ -94,7 +94,7 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd } @Override - ChooserProfileDescriptor getItem(int pageIndex) { + public ChooserProfileDescriptor getItem(int pageIndex) { return mItems[pageIndex]; } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index ac15f11ee989..7534d2960b7c 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -2623,13 +2623,13 @@ public class ResolverActivity extends Activity implements * An a11y delegate that expands resolver drawer when gesture navigation reaches a partially * invisible target in the list. */ - private static class AppListAccessibilityDelegate extends View.AccessibilityDelegate { + public static class AppListAccessibilityDelegate extends View.AccessibilityDelegate { private final ResolverDrawerLayout mDrawer; @Nullable private final View mBottomBar; private final Rect mRect = new Rect(); - private AppListAccessibilityDelegate(ResolverDrawerLayout drawer) { + public AppListAccessibilityDelegate(ResolverDrawerLayout drawer) { mDrawer = drawer; mBottomBar = mDrawer.findViewById(R.id.button_bar_container); } diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java index 767791263673..031f9d3168bf 100644 --- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java @@ -79,7 +79,7 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA } @Override - ResolverProfileDescriptor getItem(int pageIndex) { + public ResolverProfileDescriptor getItem(int pageIndex) { return mItems[pageIndex]; } diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index b6c39c67954d..16e94f8737d7 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1015,6 +1015,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.java" }, + "-1152771606": { + "message": "Content Recording: Display %d was already recording, but pause capture since the task is in PIP", + "level": "VERBOSE", + "group": "WM_DEBUG_CONTENT_RECORDING", + "at": "com\/android\/server\/wm\/ContentRecorder.java" + }, "-1145384901": { "message": "shouldWaitAnimatingExit: isTransition: %s", "level": "DEBUG", @@ -2323,6 +2329,12 @@ "group": "WM_DEBUG_SYNC_ENGINE", "at": "com\/android\/server\/wm\/WindowState.java" }, + "1877956": { + "message": "Content Recording: Display %d should start recording, but don't yet since the task is in PIP", + "level": "VERBOSE", + "group": "WM_DEBUG_CONTENT_RECORDING", + "at": "com\/android\/server\/wm\/ContentRecorder.java" + }, "3593205": { "message": "commitVisibility: %s: visible=%b mVisibleRequested=%b", "level": "VERBOSE", diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index 0000aa4cdfd8..2eacaaf28bba 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -327,17 +327,13 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { return features; } - // We will transform the feature bounds to the Activity window, so using the rotation - // from the same source (WindowConfiguration) to make sure they are synchronized. - final int rotation = windowConfiguration.getDisplayRotation(); - for (CommonFoldingFeature baseFeature : storedFeatures) { Integer state = convertToExtensionState(baseFeature.getState()); if (state == null) { continue; } Rect featureRect = baseFeature.getRect(); - rotateRectToDisplayRotation(displayId, rotation, featureRect); + rotateRectToDisplayRotation(displayId, featureRect); transformToWindowSpaceRect(windowConfiguration, featureRect); if (isZero(featureRect)) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java index 15a329bd9509..5bfb0ebdcaa8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java @@ -120,12 +120,10 @@ class SampleSidecarImpl extends StubSidecar { } List<SidecarDisplayFeature> features = new ArrayList<>(); - final int rotation = activity.getResources().getConfiguration().windowConfiguration - .getDisplayRotation(); for (CommonFoldingFeature baseFeature : mStoredFeatures) { SidecarDisplayFeature feature = new SidecarDisplayFeature(); Rect featureRect = baseFeature.getRect(); - rotateRectToDisplayRotation(displayId, rotation, featureRect); + rotateRectToDisplayRotation(displayId, featureRect); transformToWindowSpaceRect(activity, featureRect); feature.setRect(featureRect); feature.setType(baseFeature.getType()); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java index 6b193fc53935..9e2611f392a3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java @@ -16,6 +16,8 @@ package androidx.window.util; +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; @@ -23,14 +25,12 @@ import android.app.WindowConfiguration; import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; -import android.util.RotationUtils; import android.view.DisplayInfo; import android.view.Surface; import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.UiContext; -import androidx.annotation.VisibleForTesting; /** * Util class for both Sidecar and Extensions. @@ -44,41 +44,47 @@ public final class ExtensionHelper { /** * Rotates the input rectangle specified in default display orientation to the current display * rotation. - * - * @param displayId the display id. - * @param rotation the target rotation relative to the default display orientation. - * @param inOutRect the input/output Rect as specified in the default display orientation. */ - public static void rotateRectToDisplayRotation( - int displayId, @Surface.Rotation int rotation, @NonNull Rect inOutRect) { - final DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance(); - final DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId); + public static void rotateRectToDisplayRotation(int displayId, Rect inOutRect) { + DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance(); + DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId); + int rotation = displayInfo.rotation; - rotateRectToDisplayRotation(displayInfo, rotation, inOutRect); + boolean isSideRotation = rotation == ROTATION_90 || rotation == ROTATION_270; + int displayWidth = isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth; + int displayHeight = isSideRotation ? displayInfo.logicalWidth : displayInfo.logicalHeight; + + inOutRect.intersect(0, 0, displayWidth, displayHeight); + + rotateBounds(inOutRect, displayWidth, displayHeight, rotation); } - @VisibleForTesting - static void rotateRectToDisplayRotation(@NonNull DisplayInfo displayInfo, - @Surface.Rotation int rotation, @NonNull Rect inOutRect) { - // The inOutRect is specified in the default display orientation, so here we need to get - // the display width and height in the default orientation to perform the intersection and - // rotation. - final boolean isSideRotation = - displayInfo.rotation == ROTATION_90 || displayInfo.rotation == ROTATION_270; - final int baseDisplayWidth = - isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth; - final int baseDisplayHeight = - isSideRotation ? displayInfo.logicalWidth : displayInfo.logicalHeight; - - final boolean success = inOutRect.intersect(0, 0, baseDisplayWidth, baseDisplayHeight); - if (!success) { - throw new IllegalArgumentException("inOutRect must intersect with the display." - + " inOutRect: " + inOutRect - + ", baseDisplayWidth: " + baseDisplayWidth - + ", baseDisplayHeight: " + baseDisplayHeight); + /** + * Rotates the input rectangle within parent bounds for a given delta. + */ + private static void rotateBounds(Rect inOutRect, int parentWidth, int parentHeight, + @Surface.Rotation int delta) { + int origLeft = inOutRect.left; + switch (delta) { + case ROTATION_0: + return; + case ROTATION_90: + inOutRect.left = inOutRect.top; + inOutRect.top = parentWidth - inOutRect.right; + inOutRect.right = inOutRect.bottom; + inOutRect.bottom = parentWidth - origLeft; + return; + case ROTATION_180: + inOutRect.left = parentWidth - inOutRect.right; + inOutRect.right = parentWidth - origLeft; + return; + case ROTATION_270: + inOutRect.left = parentHeight - inOutRect.bottom; + inOutRect.bottom = inOutRect.right; + inOutRect.right = parentHeight - inOutRect.top; + inOutRect.top = origLeft; + return; } - - RotationUtils.rotateBounds(inOutRect, baseDisplayWidth, baseDisplayHeight, rotation); } /** Transforms rectangle from absolute coordinate space to the window coordinate space. */ diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java index a0590dc2c832..d189ae2cf72e 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java @@ -29,7 +29,7 @@ import org.junit.Test; import org.junit.runner.RunWith; /** - * Test class for {@link WindowExtensions}. + * Test class for {@link WindowExtensionsTest}. * * Build/Install/Run: * atest WMJetpackUnitTests:WindowExtensionsTest diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java deleted file mode 100644 index ae783de228fb..000000000000 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.window.util; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; - -import android.graphics.Rect; -import android.platform.test.annotations.Presubmit; -import android.view.DisplayInfo; -import android.view.Surface; - -import androidx.annotation.NonNull; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Test class for {@link ExtensionHelper}. - * - * Build/Install/Run: - * atest WMJetpackUnitTests:ExtensionHelperTest - */ -@Presubmit -@SmallTest -@RunWith(AndroidJUnit4.class) -public class ExtensionHelperTest { - - private static final int MOCK_DISPLAY_HEIGHT = 1000; - private static final int MOCK_DISPLAY_WIDTH = 2000; - private static final int MOCK_FEATURE_LEFT = 100; - private static final int MOCK_FEATURE_RIGHT = 200; - - private static final int[] ROTATIONS = { - Surface.ROTATION_0, - Surface.ROTATION_90, - Surface.ROTATION_180, - Surface.ROTATION_270 - }; - - private static final DisplayInfo[] MOCK_DISPLAY_INFOS = { - getMockDisplayInfo(Surface.ROTATION_0), - getMockDisplayInfo(Surface.ROTATION_90), - getMockDisplayInfo(Surface.ROTATION_180), - getMockDisplayInfo(Surface.ROTATION_270), - }; - - @Test - public void testRotateRectToDisplayRotation() { - for (int rotation : ROTATIONS) { - final Rect expectedResult = getExpectedFeatureRectAfterRotation(rotation); - // The method should return correctly rotated Rect even if the requested rotation value - // differs from the rotation in DisplayInfo. This is because the WindowConfiguration is - // not always synced with DisplayInfo. - for (DisplayInfo displayInfo : MOCK_DISPLAY_INFOS) { - final Rect rect = getMockFeatureRect(); - ExtensionHelper.rotateRectToDisplayRotation(displayInfo, rotation, rect); - assertEquals( - "Result Rect should equal to expected for rotation: " + rotation - + "; displayInfo: " + displayInfo, - expectedResult, rect); - } - } - } - - @Test - public void testRotateRectToDisplayRotation_invalidInputRect() { - final Rect invalidRect = new Rect( - MOCK_DISPLAY_WIDTH + 10, 0, MOCK_DISPLAY_WIDTH + 10, MOCK_DISPLAY_HEIGHT); - assertThrows(IllegalArgumentException.class, - () -> ExtensionHelper.rotateRectToDisplayRotation( - MOCK_DISPLAY_INFOS[0], ROTATIONS[0], invalidRect)); - } - - - @NonNull - private static DisplayInfo getMockDisplayInfo(@Surface.Rotation int rotation) { - final DisplayInfo displayInfo = new DisplayInfo(); - displayInfo.rotation = rotation; - if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) { - displayInfo.logicalWidth = MOCK_DISPLAY_WIDTH; - displayInfo.logicalHeight = MOCK_DISPLAY_HEIGHT; - } else { - displayInfo.logicalWidth = MOCK_DISPLAY_HEIGHT; - displayInfo.logicalHeight = MOCK_DISPLAY_WIDTH; - } - return displayInfo; - } - - @NonNull - private static Rect getMockFeatureRect() { - return new Rect(MOCK_FEATURE_LEFT, 0, MOCK_FEATURE_RIGHT, MOCK_DISPLAY_HEIGHT); - } - - @NonNull - private static Rect getExpectedFeatureRectAfterRotation(@Surface.Rotation int rotation) { - switch (rotation) { - case Surface.ROTATION_0: - return new Rect( - MOCK_FEATURE_LEFT, 0, MOCK_FEATURE_RIGHT, MOCK_DISPLAY_HEIGHT); - case Surface.ROTATION_90: - return new Rect(0, MOCK_DISPLAY_WIDTH - MOCK_FEATURE_RIGHT, - MOCK_DISPLAY_HEIGHT, MOCK_DISPLAY_WIDTH - MOCK_FEATURE_LEFT); - case Surface.ROTATION_180: - return new Rect(MOCK_DISPLAY_WIDTH - MOCK_FEATURE_RIGHT, 0, - MOCK_DISPLAY_WIDTH - MOCK_FEATURE_LEFT, MOCK_DISPLAY_HEIGHT); - case Surface.ROTATION_270: - return new Rect(0, MOCK_FEATURE_LEFT, MOCK_DISPLAY_HEIGHT, - MOCK_FEATURE_RIGHT); - default: - throw new IllegalArgumentException("Unknown rotation value: " + rotation); - } - } -} diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index b708fc27448d..285fc5f1f332 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -6,6 +6,7 @@ dsandler@android.com aaliomer@google.com aaronjli@google.com +achalke@google.com acul@google.com adamcohen@google.com aioana@google.com @@ -70,6 +71,7 @@ omarmt@google.com patmanning@google.com peanutbutter@google.com peskal@google.com +petrcermak@google.com pinyaoting@google.com pixel@google.com pomini@google.com @@ -81,13 +83,17 @@ santie@google.com shanh@google.com snoeberger@google.com steell@google.com +stevenckng@google.com stwu@google.com syeonlee@google.com sunnygoyal@google.com thiruram@google.com +tkachenkoi@google.com tracyzhou@google.com tsuji@google.com twickham@google.com +vadimt@google.com +vanjan@google.com victortulias@google.com winsonc@google.com wleshner@google.com diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 42cb739dba6a..b6c50f7603c7 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -119,7 +119,7 @@ object Flags { // TODO(b/292213543): Tracking Bug @JvmField val NOTIFICATION_GROUP_EXPANSION_CHANGE = - unreleasedFlag("notification_group_expansion_change", teamfood = true) + unreleasedFlag("notification_group_expansion_change") // 200 - keyguard/lockscreen // ** Flag retired ** diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt index 053c9b56ef96..60fd10492628 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt @@ -29,7 +29,10 @@ import android.os.Bundle import android.os.IBinder import android.os.ResultReceiver import android.os.UserHandle +import android.util.Log +import android.view.View import android.view.ViewGroup +import android.view.accessibility.AccessibilityEvent import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry @@ -40,6 +43,9 @@ import com.android.internal.app.ChooserActivity import com.android.internal.app.ResolverListController import com.android.internal.app.chooser.NotSelectableTargetInfo import com.android.internal.app.chooser.TargetInfo +import com.android.internal.widget.RecyclerView +import com.android.internal.widget.RecyclerViewAccessibilityDelegate +import com.android.internal.widget.ResolverDrawerLayout import com.android.systemui.R import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController @@ -105,6 +111,10 @@ class MediaProjectionAppSelectorActivity( super.onCreate(bundle) controller.init() + // we override AppList's AccessibilityDelegate set in ResolverActivity.onCreate because in + // our case this delegate must extend RecyclerViewAccessibilityDelegate, otherwise + // RecyclerView scrolling is broken + setAppListAccessibilityDelegate() } override fun onStart() { @@ -277,6 +287,8 @@ class MediaProjectionAppSelectorActivity( recentsViewController.createView(parent) companion object { + const val TAG = "MediaProjectionAppSelectorActivity" + /** * When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra the activity will * send the [CaptureRegion] to the result receiver instead of returning media projection @@ -313,4 +325,42 @@ class MediaProjectionAppSelectorActivity( putExtra(EXTRA_SELECTED_PROFILE, selectedProfile) } } + + private fun setAppListAccessibilityDelegate() { + val rdl = requireViewById<ResolverDrawerLayout>(com.android.internal.R.id.contentPanel) + for (i in 0 until mMultiProfilePagerAdapter.count) { + val list = + mMultiProfilePagerAdapter + .getItem(i) + .rootView + .findViewById<View>(com.android.internal.R.id.resolver_list) + if (list == null || list !is RecyclerView) { + Log.wtf(TAG, "MediaProjection only supports RecyclerView") + } else { + list.accessibilityDelegate = RecyclerViewExpandingAccessibilityDelegate(rdl, list) + } + } + } + + /** + * An a11y delegate propagating all a11y events to [AppListAccessibilityDelegate] so that it can + * expand drawer when needed. It needs to extend [RecyclerViewAccessibilityDelegate] because + * that superclass handles RecyclerView scrolling while using a11y services. + */ + private class RecyclerViewExpandingAccessibilityDelegate( + rdl: ResolverDrawerLayout, + view: RecyclerView + ) : RecyclerViewAccessibilityDelegate(view) { + + private val delegate = AppListAccessibilityDelegate(rdl) + + override fun onRequestSendAccessibilityEvent( + host: ViewGroup, + child: View, + event: AccessibilityEvent + ): Boolean { + super.onRequestSendAccessibilityEvent(host, child, event) + return delegate.onRequestSendAccessibilityEvent(host, child, event) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java index 51eb9f7220fc..c24e9dcea29d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java @@ -36,7 +36,7 @@ import javax.inject.Inject; @SysUISingleton public class NotifInflationErrorManager { - Set<NotificationEntry> mErroredNotifs = new ArraySet<>(); + Set<String> mErroredNotifs = new ArraySet<>(); List<NotifInflationErrorListener> mListeners = new ArrayList<>(); @Inject @@ -48,7 +48,7 @@ public class NotifInflationErrorManager { * @param e the exception encountered while inflating */ public void setInflationError(NotificationEntry entry, Exception e) { - mErroredNotifs.add(entry); + mErroredNotifs.add(entry.getKey()); for (int i = 0; i < mListeners.size(); i++) { mListeners.get(i).onNotifInflationError(entry, e); } @@ -58,8 +58,8 @@ public class NotifInflationErrorManager { * Notification inflated successfully and is no longer errored out. */ public void clearInflationError(NotificationEntry entry) { - if (mErroredNotifs.contains(entry)) { - mErroredNotifs.remove(entry); + if (mErroredNotifs.contains(entry.getKey())) { + mErroredNotifs.remove(entry.getKey()); for (int i = 0; i < mListeners.size(); i++) { mListeners.get(i).onNotifInflationErrorCleared(entry); } @@ -70,7 +70,7 @@ public class NotifInflationErrorManager { * Whether or not the notification encountered an exception while inflating. */ public boolean hasInflationError(@NonNull NotificationEntry entry) { - return mErroredNotifs.contains(entry); + return mErroredNotifs.contains(entry.getKey()); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt new file mode 100644 index 000000000000..e38adeb0fcd9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager.NotifInflationErrorListener +import com.android.systemui.util.mockito.any +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class NotifInflationErrorManagerTest : SysuiTestCase() { + private lateinit var manager: NotifInflationErrorManager + + private val listener1 = mock(NotifInflationErrorListener::class.java) + private val listener2 = mock(NotifInflationErrorListener::class.java) + + private val foo: NotificationEntry = NotificationEntryBuilder().setPkg("foo").build() + private val bar: NotificationEntry = NotificationEntryBuilder().setPkg("bar").build() + private val baz: NotificationEntry = NotificationEntryBuilder().setPkg("baz").build() + + private val fooException = Exception("foo") + private val barException = Exception("bar") + + @Before + fun setUp() { + // Reset manager instance before each test. + manager = NotifInflationErrorManager() + } + + @Test + fun testTracksInflationErrors() { + manager.setInflationError(foo, fooException) + manager.setInflationError(bar, barException) + + assertThat(manager.hasInflationError(foo)).isTrue() + assertThat(manager.hasInflationError(bar)).isTrue() + assertThat(manager.hasInflationError(baz)).isFalse() + + manager.clearInflationError(bar) + + assertThat(manager.hasInflationError(bar)).isFalse() + } + + @Test + fun testNotifiesListeners() { + manager.addInflationErrorListener(listener1) + manager.setInflationError(foo, fooException) + + verify(listener1).onNotifInflationError(foo, fooException) + + manager.addInflationErrorListener(listener2) + manager.setInflationError(bar, barException) + + verify(listener1).onNotifInflationError(bar, barException) + verify(listener2).onNotifInflationError(bar, barException) + + manager.clearInflationError(foo) + + verify(listener1).onNotifInflationErrorCleared(foo) + verify(listener2).onNotifInflationErrorCleared(foo) + } + + @Test + fun testClearUnknownEntry() { + manager.addInflationErrorListener(listener1) + manager.clearInflationError(foo) + + verify(listener1, never()).onNotifInflationErrorCleared(any()) + } +} diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 5ebc1c055b50..95b6c2cef963 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -334,9 +334,8 @@ public class SoundDoseHelper { SAFE_MEDIA_VOLUME_UNINITIALIZED); mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES, SAFE_MEDIA_VOLUME_UNINITIALIZED); - // TODO(b/278265907): enable A2DP when we can distinguish A2DP headsets - // mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - // SAFE_MEDIA_VOLUME_UNINITIALIZED); + mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + SAFE_MEDIA_VOLUME_UNINITIALIZED); } float getOutputRs2UpperBound() { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index 51a938558e57..4502e5d0c4b6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -128,7 +128,11 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements vibrateSuccess(); try { - getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric); + if (getListener() != null) { + getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric); + } else { + Slog.e(TAG, "Listener is null!"); + } mCallback.onClientFinished(this, true /* success */); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when sending onDetected", e); diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java index aa99dab8f007..7b613874e25e 100644 --- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java +++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java @@ -235,6 +235,7 @@ final class HandwritingModeController { @Nullable HandwritingSession startHandwritingSession( int requestId, int imePid, int imeUid, IBinder focusedWindowToken) { + clearPendingHandwritingDelegation(); if (mHandwritingSurface == null) { Slog.e(TAG, "Cannot start handwriting session: Handwriting was not initialized."); return null; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index aff20eea9aa3..7aea63255dc8 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -3453,13 +3453,19 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override public void startStylusHandwriting(IInputMethodClient client) { + startStylusHandwriting(client, false /* usesDelegation */); + } + + private void startStylusHandwriting(IInputMethodClient client, boolean usesDelegation) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.startStylusHandwriting"); try { ImeTracing.getInstance().triggerManagerServiceDump( "InputMethodManagerService#startStylusHandwriting"); int uid = Binder.getCallingUid(); synchronized (ImfLock.class) { - mHwController.clearPendingHandwritingDelegation(); + if (!usesDelegation) { + mHwController.clearPendingHandwritingDelegation(); + } if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting", null /* statsToken */)) { return; @@ -3541,7 +3547,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return false; } - startStylusHandwriting(client); + startStylusHandwriting(client, true /* usesDelegation */); return true; } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 309a9c0e0372..5b25e890aaa2 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -321,9 +321,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (DEBUG) { Slog.d(TAG, "publish system wallpaper changed!"); } - if (localSync != null) { - localSync.complete(); - } notifyWallpaperChanged(wallpaper); } }; @@ -331,7 +328,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // If this was the system wallpaper, rebind... bindWallpaperComponentLocked(mImageWallpaper, true, false, wallpaper, callback); - notifyColorsWhich |= FLAG_SYSTEM; + notifyColorsWhich |= wallpaper.mWhich; } if (lockWallpaperChanged) { @@ -345,9 +342,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (DEBUG) { Slog.d(TAG, "publish lock wallpaper changed!"); } - if (localSync != null) { - localSync.complete(); - } notifyWallpaperChanged(wallpaper); } }; @@ -372,9 +366,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } saveSettingsLocked(wallpaper.userId); - // Notify the client immediately if only lockscreen wallpaper changed. - if (lockWallpaperChanged && !sysWallpaperChanged) { - notifyWallpaperChanged(wallpaper); + if ((sysWallpaperChanged || lockWallpaperChanged) && localSync != null) { + localSync.complete(); } } @@ -1383,7 +1376,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub lockWp.connection.mWallpaper = lockWp; mOriginalSystem.mWhich = FLAG_LOCK; updateEngineFlags(mOriginalSystem); - notifyWallpaperColorsChanged(lockWp, FLAG_LOCK); } else { // Failed rename, use current system wp for both if (DEBUG) { @@ -1403,7 +1395,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub updateEngineFlags(mOriginalSystem); mLockWallpaperMap.put(mNewWallpaper.userId, mOriginalSystem); mLastLockWallpaper = mOriginalSystem; - notifyWallpaperColorsChanged(mOriginalSystem, FLAG_LOCK); } } else if (mNewWallpaper.mWhich == FLAG_LOCK) { // New wp is lock only, so old system+lock is now system only @@ -1417,10 +1408,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } } - - synchronized (mLock) { - saveSettingsLocked(mNewWallpaper.userId); - } + saveSettingsLocked(mNewWallpaper.userId); if (DEBUG) { Slog.v(TAG, "--- wallpaper changed --"); @@ -3300,7 +3288,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (DEBUG) { Slog.d(TAG, "publish system wallpaper changed!"); } - liveSync.complete(); } }; @@ -3356,6 +3343,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } mLockWallpaperMap.remove(newWallpaper.userId); } + if (liveSync != null) liveSync.complete(); } } finally { Binder.restoreCallingIdentity(ident); @@ -3474,6 +3462,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } // Has the component changed? if (!force && changingToSame(componentName, wallpaper)) { + try { + if (reply != null) reply.sendResult(null); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to send callback", e); + } return true; } diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index f0e4149a9159..7cd07d6f3a5f 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -151,6 +151,20 @@ final class ContentRecorder implements WindowContainerListener { return; } + // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are + // inaccurate. + if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) { + final Task capturedTask = mRecordedWindowContainer.asTask(); + if (capturedTask.inPinnedWindowingMode()) { + ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, + "Content Recording: Display %d was already recording, but " + + "pause capture since the task is in PIP", + mDisplayContent.getDisplayId()); + pauseRecording(); + return; + } + } + ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, "Content Recording: Display %d was already recording, so apply " + "transformations if necessary", @@ -292,6 +306,17 @@ final class ContentRecorder implements WindowContainerListener { return; } + // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are inaccurate. + if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) { + if (mRecordedWindowContainer.asTask().inPinnedWindowingMode()) { + ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, + "Content Recording: Display %d should start recording, but " + + "don't yet since the task is in PIP", + mDisplayContent.getDisplayId()); + return; + } + } + final Point surfaceSize = fetchSurfaceSizeIfPresent(); if (surfaceSize == null) { ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, @@ -305,9 +330,6 @@ final class ContentRecorder implements WindowContainerListener { + "state %d", mDisplayContent.getDisplayId(), mDisplayContent.getDisplayInfo().state); - // TODO(b/274790702): Do not start recording if waiting for consent - for now, - // go ahead. - // Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture. mRecordedSurface = SurfaceControl.mirrorSurface( mRecordedWindowContainer.getSurfaceControl()); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 75458302586d..034299659dd5 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -1147,7 +1147,7 @@ class TransitionController { Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */); } else if (!animatingState && mAnimatingState) { t.setEarlyWakeupEnd(); - mAtm.mWindowManager.requestTraversal(); + mAtm.mWindowManager.scheduleAnimationLocked(); mSnapshotController.setPause(false); mAnimatingState = false; Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java index 20d5f93a2c07..78d3a9dd9f9e 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java @@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; @@ -147,6 +148,24 @@ public class FingerprintDetectClientTest { verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue())); } + @Test + public void testWhenListenerIsNull() { + final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mHalSessionCallback); + final FingerprintDetectClient client = new FingerprintDetectClient(mContext, () -> aidl, + mToken, 6 /* requestId */, null /* listener */, + new FingerprintAuthenticateOptions.Builder() + .setUserId(2) + .setSensorId(1) + .setOpPackageName("a-test") + .build(), + mBiometricLogger, mBiometricContext, + mUdfpsOverlayController, true /* isStrongBiometric */); + client.start(mCallback); + client.onInteractionDetected(); + + verify(mCallback).onClientFinished(eq(client), anyBoolean()); + } + private FingerprintDetectClient createClient() throws RemoteException { return createClient(200 /* version */); } diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index f158ce14549f..9d2b34cc6ba5 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -84,6 +84,7 @@ import android.view.Display; import android.view.InputDevice; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; import com.android.internal.app.IBatteryStats; import com.android.internal.util.FrameworkStatsLog; @@ -813,6 +814,7 @@ public class VibratorManagerServiceTest { eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString()); } + @FlakyTest @Test public void vibrate_withOngoingRepeatingVibration_ignoresEffect() throws Exception { mockVibrators(1); @@ -899,6 +901,7 @@ public class VibratorManagerServiceTest { cancelVibrate(service); // Clean up repeating effect. } + @FlakyTest @Test public void vibrate_withNewSameImportanceVibrationButOngoingIsRepeating_ignoreNewVibration() throws Exception { @@ -952,6 +955,7 @@ public class VibratorManagerServiceTest { cancelVibrate(service); // Clean up repeating effect. } + @FlakyTest @Test public void vibrate_withNewUnknownUsageVibrationAndNotRepeating_ignoreNewVibration() throws Exception { @@ -1687,6 +1691,7 @@ public class VibratorManagerServiceTest { cancelVibrate(service); // Clean up long effect. } + @FlakyTest @Test public void onExternalVibration_withNewSameImportanceButRepeating_cancelsOngoingVibration() throws Exception { diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java index aa2b93506f8e..b8f6cb8b5eba 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -360,6 +363,39 @@ public class ContentRecorderTests extends WindowTestsBase { } @Test + public void testTaskWindowingModeChanged_pip_stopsRecording() { + // WHEN a recording is ongoing. + mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mContentRecorder.setContentRecordingSession(mTaskSession); + mContentRecorder.updateRecording(); + assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); + + // WHEN a configuration change arrives, and the task is now pinned. + mTask.setWindowingMode(WINDOWING_MODE_PINNED); + Configuration configuration = mTask.getConfiguration(); + mTask.onConfigurationChanged(configuration); + + // THEN recording is paused. + assertThat(mContentRecorder.isCurrentlyRecording()).isFalse(); + } + + @Test + public void testTaskWindowingModeChanged_fullscreen_startsRecording() { + // WHEN a recording is ongoing. + mTask.setWindowingMode(WINDOWING_MODE_PINNED); + mContentRecorder.setContentRecordingSession(mTaskSession); + mContentRecorder.updateRecording(); + assertThat(mContentRecorder.isCurrentlyRecording()).isFalse(); + + // WHEN the task is now fullscreen. + mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + mContentRecorder.updateRecording(); + + // THEN recording is started. + assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); + } + + @Test public void testStartRecording_notifiesCallback_taskSession() { // WHEN a recording is ongoing. mContentRecorder.setContentRecordingSession(mTaskSession); @@ -384,6 +420,45 @@ public class ContentRecorderTests extends WindowTestsBase { } @Test + public void testStartRecording_taskInPIP_recordingNotStarted() { + // GIVEN a task is in PIP. + mContentRecorder.setContentRecordingSession(mTaskSession); + mTask.setWindowingMode(WINDOWING_MODE_PINNED); + + // WHEN a recording tries to start. + mContentRecorder.updateRecording(); + + // THEN recording does not start. + assertThat(mContentRecorder.isCurrentlyRecording()).isFalse(); + } + + @Test + public void testStartRecording_taskInSplit_recordingStarted() { + // GIVEN a task is in PIP. + mContentRecorder.setContentRecordingSession(mTaskSession); + mTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + + // WHEN a recording tries to start. + mContentRecorder.updateRecording(); + + // THEN recording does not start. + assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); + } + + @Test + public void testStartRecording_taskInFullscreen_recordingStarted() { + // GIVEN a task is in PIP. + mContentRecorder.setContentRecordingSession(mTaskSession); + mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + // WHEN a recording tries to start. + mContentRecorder.updateRecording(); + + // THEN recording does not start. + assertThat(mContentRecorder.isCurrentlyRecording()).isTrue(); + } + + @Test public void testOnVisibleRequestedChanged_notifiesCallback() { // WHEN a recording is ongoing. mContentRecorder.setContentRecordingSession(mTaskSession); |