diff options
author | Linux Build Service Account <lnxbuild@localhost> | 2024-06-07 05:30:56 -0700 |
---|---|---|
committer | Linux Build Service Account <lnxbuild@localhost> | 2024-06-07 05:30:56 -0700 |
commit | f000abea9c9533eaac20ab020632b013eee86977 (patch) | |
tree | 4295f128afff8fb2da1549c5fc59987956beea63 | |
parent | 48e0991410c9dc87260a8c0971f86131b6849109 (diff) | |
parent | 95a6b8d970fd1aa5333e75f190859fb4753b638f (diff) |
Merge 95a6b8d970fd1aa5333e75f190859fb4753b638f on remote branch
Change-Id: I9b6dd4cefb74a078e09262e57306625b41e0ba06
30 files changed, 701 insertions, 145 deletions
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 3cb5c60259eb..2b84b25b92e2 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -424,6 +424,8 @@ public class ZygoteProcess { throw new ZygoteStartFailedEx("Embedded newlines not allowed"); } else if (arg.indexOf('\r') >= 0) { throw new ZygoteStartFailedEx("Embedded carriage returns not allowed"); + } else if (arg.indexOf('\u0000') >= 0) { + throw new ZygoteStartFailedEx("Embedded nulls not allowed"); } } @@ -959,6 +961,14 @@ public class ZygoteProcess { return true; } + for (/* NonNull */ String s : mApiDenylistExemptions) { + // indexOf() is intrinsified and faster than contains(). + if (s.indexOf('\n') >= 0 || s.indexOf('\r') >= 0 || s.indexOf('\u0000') >= 0) { + Slog.e(LOG_TAG, "Failed to set API denylist exemptions: Bad character"); + mApiDenylistExemptions = Collections.emptyList(); + return false; + } + } try { state.mZygoteOutputWriter.write(Integer.toString(mApiDenylistExemptions.size() + 1)); state.mZygoteOutputWriter.newLine(); diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt index 2883210805d3..2359f6ad3b43 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt @@ -18,16 +18,10 @@ package com.android.systemui.media.controls.ui import android.content.Context import android.content.res.Configuration -import android.database.ContentObserver -import android.net.Uri -import android.os.Handler -import android.os.UserHandle -import android.provider.Settings import android.view.View import android.view.ViewGroup import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.dagger.MediaModule.KEYGUARD import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState @@ -36,7 +30,6 @@ import com.android.systemui.statusbar.notification.stack.MediaContainerView import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.LargeScreenUtils -import com.android.systemui.util.settings.SecureSettings import javax.inject.Inject import javax.inject.Named @@ -52,8 +45,6 @@ constructor( private val bypassController: KeyguardBypassController, private val statusBarStateController: SysuiStatusBarStateController, private val context: Context, - private val secureSettings: SecureSettings, - @Main private val handler: Handler, configurationController: ConfigurationController, ) { @@ -77,26 +68,6 @@ constructor( } ) - val settingsObserver: ContentObserver = - object : ContentObserver(handler) { - override fun onChange(selfChange: Boolean, uri: Uri?) { - if (uri == lockScreenMediaPlayerUri) { - allowMediaPlayerOnLockScreen = - secureSettings.getBoolForUser( - Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, - true, - UserHandle.USER_CURRENT - ) - refreshMediaPosition() - } - } - } - secureSettings.registerContentObserverForUser( - Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, - settingsObserver, - UserHandle.USER_ALL - ) - // First let's set the desired state that we want for this host mediaHost.expansion = MediaHostState.EXPANDED mediaHost.showsOnlyActiveMedia = true @@ -142,16 +113,6 @@ constructor( private set private var splitShadeContainer: ViewGroup? = null - /** Track the media player setting status on lock screen. */ - private var allowMediaPlayerOnLockScreen: Boolean = - secureSettings.getBoolForUser( - Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, - true, - UserHandle.USER_CURRENT - ) - private val lockScreenMediaPlayerUri = - secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN) - /** * Attaches media container in single pane mode, situated at the top of the notifications list */ @@ -211,7 +172,6 @@ constructor( mediaHost.visible && !bypassController.bypassEnabled && keyguardOrUserSwitcher && - allowMediaPlayerOnLockScreen && shouldBeVisibleForSplitShade() if (visible) { showMediaPlayer() diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt index fe8ebafdf9b4..6fcfa71c2f5d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt @@ -104,7 +104,7 @@ constructor( ) { /** Track the media player setting status on lock screen. */ - private var allowMediaPlayerOnLockScreen: Boolean = true + private var allowMediaPlayerOnLockScreen: Boolean = getMediaLockScreenSetting() private val lockScreenMediaPlayerUri = secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN) @@ -461,6 +461,7 @@ constructor( mediaCarouselController.logSmartspaceImpression(qsExpanded) } updateUserVisibility() + mediaCarouselController.updateHostVisibility() } override fun onDozeAmountChanged(linear: Float, eased: Float) { @@ -533,7 +534,6 @@ constructor( mediaCarouselController.updateHostVisibility = { mediaHosts.forEach { it?.updateViewVisibility() } } - panelEventsEvents.addShadeStateEventsListener( object : ShadeStateEventsListener { override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) { @@ -547,12 +547,8 @@ constructor( object : ContentObserver(handler) { override fun onChange(selfChange: Boolean, uri: Uri?) { if (uri == lockScreenMediaPlayerUri) { - allowMediaPlayerOnLockScreen = - secureSettings.getBoolForUser( - Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, - true, - UserHandle.USER_CURRENT - ) + allowMediaPlayerOnLockScreen = getMediaLockScreenSetting() + mediaCarouselController.updateHostVisibility() } } } @@ -563,6 +559,14 @@ constructor( ) } + private fun getMediaLockScreenSetting(): Boolean { + return secureSettings.getBoolForUser( + Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, + true, + UserHandle.USER_CURRENT + ) + } + private fun updateConfiguration() { distanceForFullShadeTransition = context.resources.getDimensionPixelSize( @@ -602,6 +606,13 @@ constructor( mediaCarouselController.closeGuts() } + /** Return true if the carousel should be hidden because lockscreen is currently visible */ + fun isLockedAndHidden(): Boolean { + return !allowMediaPlayerOnLockScreen && + (statusbarState == StatusBarState.SHADE_LOCKED || + statusbarState == StatusBarState.KEYGUARD) + } + private fun createUniqueObjectHost(): UniqueObjectHostView { val viewHost = UniqueObjectHostView(context) viewHost.addOnAttachStateChangeListener( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt index be570b4a1119..26580e54cd62 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt @@ -199,7 +199,9 @@ constructor( */ fun updateViewVisibility() { state.visible = - if (showsOnlyActiveMedia) { + if (mediaHierarchyManager.isLockedAndHidden()) { + false + } else if (showsOnlyActiveMedia) { mediaDataManager.hasActiveMediaOrRecommendation() } else { mediaDataManager.hasAnyMediaOrRecommendation() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 9f397fe9ac0c..85708394b09c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -40,6 +40,7 @@ import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.os.Trace; +import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.AttributeSet; @@ -371,7 +372,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mUseIncreasedHeadsUpHeight; private float mTranslationWhenRemoved; private boolean mWasChildInGroupWhenRemoved; - private NotificationInlineImageResolver mImageResolver; + private final NotificationInlineImageResolver mImageResolver; @Nullable private OnExpansionChangedListener mExpansionChangedListener; @Nullable @@ -1630,13 +1631,41 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** - * Constructs an ExpandableNotificationRow. - * @param context context passed to image resolver + * Constructs an ExpandableNotificationRow. Used by layout inflation. + * + * @param context passed to image resolver * @param attrs attributes used to initialize parent view */ public ExpandableNotificationRow(Context context, AttributeSet attrs) { - super(context, attrs); - mImageResolver = new NotificationInlineImageResolver(context, + this(context, attrs, context); + Log.wtf(TAG, "This constructor shouldn't be called"); + } + + /** + * Constructs an ExpandableNotificationRow. Used by layout inflation (with a custom {@code + * AsyncLayoutFactory} in {@link RowInflaterTask}. + * + * @param context context context of the view + * @param attrs attributes used to initialize parent view + * @param entry notification that the row will be associated to (determines the user for the + * ImageResolver) + */ + public ExpandableNotificationRow(Context context, AttributeSet attrs, NotificationEntry entry) { + this(context, attrs, userContextForEntry(context, entry)); + } + + private static Context userContextForEntry(Context base, NotificationEntry entry) { + if (base.getUserId() == entry.getSbn().getNormalizedUserId()) { + return base; + } + return base.createContextAsUser( + UserHandle.of(entry.getSbn().getNormalizedUserId()), /* flags= */ 0); + } + + private ExpandableNotificationRow(Context sysUiContext, AttributeSet attrs, + Context userContext) { + super(sysUiContext, attrs); + mImageResolver = new NotificationInlineImageResolver(userContext, new NotificationInlineImageCache()); float radius = getResources().getDimension(R.dimen.notification_corner_radius_small); mSmallRoundness = radius / getMaxRadius(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java index c620f448b3b7..3e932aa616b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java @@ -66,7 +66,7 @@ public class NotificationInlineImageResolver implements ImageResolver { * @param imageCache The implementation of internal cache. */ public NotificationInlineImageResolver(Context context, ImageCache imageCache) { - mContext = context.getApplicationContext(); + mContext = context; mImageCache = imageCache; if (mImageCache != null) { @@ -76,6 +76,11 @@ public class NotificationInlineImageResolver implements ImageResolver { updateMaxImageSizes(); } + @VisibleForTesting + public Context getContext() { + return mContext; + } + /** * Check if this resolver has its internal cache implementation. * @return True if has its internal cache, false otherwise. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java index 6feffe654630..b2fe1d82945b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java @@ -17,10 +17,15 @@ package com.android.systemui.statusbar.notification.row; import android.content.Context; +import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.asynclayoutinflater.view.AsyncLayoutFactory; import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import com.android.systemui.R; @@ -55,12 +60,39 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf mInflateOrigin = new Throwable("inflate requested here"); } mListener = listener; - AsyncLayoutInflater inflater = new AsyncLayoutInflater(context); + AsyncLayoutInflater inflater = new AsyncLayoutInflater(context, + new RowAsyncLayoutInflater(entry)); mEntry = entry; entry.setInflationTask(this); inflater.inflate(R.layout.status_bar_notification_row, parent, this); } + @VisibleForTesting + static class RowAsyncLayoutInflater implements AsyncLayoutFactory { + private final NotificationEntry mEntry; + + RowAsyncLayoutInflater(NotificationEntry entry) { + mEntry = entry; + } + + @Nullable + @Override + public View onCreateView(@Nullable View parent, @NonNull String name, + @NonNull Context context, @NonNull AttributeSet attrs) { + if (name.equals(ExpandableNotificationRow.class.getName())) { + return new ExpandableNotificationRow(context, attrs, mEntry); + } + return null; + } + + @Nullable + @Override + public View onCreateView(@NonNull String name, @NonNull Context context, + @NonNull AttributeSet attrs) { + return null; + } + } + @Override public void abort() { mCancelled = true; diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt index 91b0245be8d3..c1cf7e07aee2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.media.controls.ui -import android.provider.Settings import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -32,8 +31,6 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.settings.FakeSettings -import com.android.systemui.utils.os.FakeHandler import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertTrue import org.junit.Before @@ -60,10 +57,7 @@ class KeyguardMediaControllerTest : SysuiTestCase() { private val mediaContainerView: MediaContainerView = MediaContainerView(context, null) private val hostView = UniqueObjectHostView(context) - private val settings = FakeSettings() private lateinit var keyguardMediaController: KeyguardMediaController - private lateinit var testableLooper: TestableLooper - private lateinit var fakeHandler: FakeHandler private lateinit var statusBarStateListener: StatusBarStateController.StateListener @Before @@ -79,16 +73,12 @@ class KeyguardMediaControllerTest : SysuiTestCase() { whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) whenever(mediaHost.hostView).thenReturn(hostView) hostView.layoutParams = FrameLayout.LayoutParams(100, 100) - testableLooper = TestableLooper.get(this) - fakeHandler = FakeHandler(testableLooper.looper) keyguardMediaController = KeyguardMediaController( mediaHost, bypassController, statusBarStateController, context, - settings, - fakeHandler, configurationController, ) keyguardMediaController.attachSinglePaneContainer(mediaContainerView) @@ -118,24 +108,6 @@ class KeyguardMediaControllerTest : SysuiTestCase() { } @Test - fun testHiddenOnKeyguard_whenMediaOnLockScreenDisabled() { - settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 0) - - keyguardMediaController.refreshMediaPosition() - - assertThat(mediaContainerView.visibility).isEqualTo(GONE) - } - - @Test - fun testAvailableOnKeyguard_whenMediaOnLockScreenEnabled() { - settings.putInt(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, 1) - - keyguardMediaController.refreshMediaPosition() - - assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) - } - - @Test fun testActivatesSplitShadeContainerInSplitShadeMode() { val splitShadeContainer = FrameLayout(context) keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt index 2ce236d4ba89..69bf97f6545f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt @@ -124,6 +124,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture()) verify(statusBarStateController).addCallback(statusBarCallback.capture()) verify(dreamOverlayStateController).addCallback(dreamOverlayCallback.capture()) + whenever(mediaCarouselController.updateHostVisibility).thenReturn({}) setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN, LOCKSCREEN_TOP) setupHost(qsHost, MediaHierarchyManager.LOCATION_QS, QS_TOP) setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP) @@ -485,6 +486,55 @@ class MediaHierarchyManagerTest : SysuiTestCase() { assertThat(mediaCarouselScrollHandler.visibleToUser).isFalse() } + @Test + fun keyguardState_allowedOnLockscreen_updateVisibility() { + settings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, true) + clearInvocations(mediaCarouselController) + + statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD) + statusBarCallback.value.onStateChanged(StatusBarState.KEYGUARD) + + verify(mediaCarouselController).updateHostVisibility + assertThat(mediaHierarchyManager.isLockedAndHidden()).isFalse() + } + + @Test + fun keyguardState_notAllowedOnLockscreen_updateVisibility() { + settings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, false) + clearInvocations(mediaCarouselController) + + statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD) + statusBarCallback.value.onStateChanged(StatusBarState.KEYGUARD) + + verify(mediaCarouselController).updateHostVisibility + assertThat(mediaHierarchyManager.isLockedAndHidden()).isTrue() + } + + @Test + fun keyguardGone_updateVisibility() { + settings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, false) + clearInvocations(mediaCarouselController) + + statusBarCallback.value.onStatePreChange(StatusBarState.KEYGUARD, StatusBarState.SHADE) + statusBarCallback.value.onStateChanged(StatusBarState.SHADE) + + verify(mediaCarouselController).updateHostVisibility + assertThat(mediaHierarchyManager.isLockedAndHidden()).isFalse() + } + + @Test + fun lockscreenSettingChanged_updateVisibility() { + settings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, true) + statusBarCallback.value.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD) + statusBarCallback.value.onStateChanged(StatusBarState.KEYGUARD) + clearInvocations(mediaCarouselController) + + settings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, false) + + verify(mediaCarouselController).updateHostVisibility + assertThat(mediaHierarchyManager.isLockedAndHidden()).isTrue() + } + private fun enableSplitShade() { context .getOrCreateTestableResources() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 4c83194783ab..35211ea2174e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -20,6 +20,8 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL; +import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.PKG; +import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.USER_HANDLE; import static com.google.common.truth.Truth.assertThat; @@ -40,10 +42,12 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.NotificationChannel; +import android.content.Context; import android.graphics.Color; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; +import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -56,6 +60,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.internal.widget.CachingIconView; import com.android.systemui.SysuiTestCase; +import com.android.systemui.SysuiTestableContext; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; @@ -733,6 +738,26 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { verify(lowPriVectorDrawable, times(1)).start(); } + @Test + public void imageResolver_sameNotificationUser_usesContext() throws Exception { + ExpandableNotificationRow row = mNotificationTestHelper.createRow(PKG, + USER_HANDLE.getUid(1234), USER_HANDLE); + + assertThat(row.getImageResolver().getContext()).isSameInstanceAs(mContext); + } + + @Test + public void imageResolver_differentNotificationUser_createsUserContext() throws Exception { + UserHandle user = new UserHandle(33); + Context userContext = new SysuiTestableContext(mContext); + mContext.prepareCreateContextAsUser(user, userContext); + + ExpandableNotificationRow row = mNotificationTestHelper.createRow(PKG, + user.getUid(1234), user); + + assertThat(row.getImageResolver().getContext()).isSameInstanceAs(userContext); + } + private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable, Drawable rightIconDrawable) { ImageView iconView = mock(ImageView.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 813bae893569..b4be92e86ff0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -526,14 +526,6 @@ public class NotificationTestHelper { @InflationFlag int extraInflationFlags, int importance) throws Exception { - LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( - mContext.LAYOUT_INFLATER_SERVICE); - mRow = (ExpandableNotificationRow) inflater.inflate( - R.layout.status_bar_notification_row, - null /* root */, - false /* attachToRoot */); - ExpandableNotificationRow row = mRow; - final NotificationChannel channel = new NotificationChannel( notification.getChannelId(), @@ -553,6 +545,15 @@ public class NotificationTestHelper { .setChannel(channel) .build(); + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry)); + mRow = (ExpandableNotificationRow) inflater.inflate( + R.layout.status_bar_notification_row, + null /* root */, + false /* attachToRoot */); + ExpandableNotificationRow row = mRow; + entry.setRow(row); mIconManager.createIcons(entry); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java index 5ff57aad9f5d..aa58b89f4676 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java @@ -14,6 +14,7 @@ package com.android.systemui; +import android.annotation.NonNull; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -29,12 +30,15 @@ import android.view.Display; import com.android.internal.annotations.GuardedBy; +import java.util.HashMap; +import java.util.Map; import java.util.Set; public class SysuiTestableContext extends TestableContext { @GuardedBy("mRegisteredReceivers") private final Set<BroadcastReceiver> mRegisteredReceivers = new ArraySet<>(); + private final Map<UserHandle, Context> mContextForUser = new HashMap<>(); public SysuiTestableContext(Context base) { super(base); @@ -152,4 +156,22 @@ public class SysuiTestableContext extends TestableContext { } super.unregisterReceiver(receiver); } + + /** + * Sets a Context object that will be returned as the result of {@link #createContextAsUser} + * for a specific {@code user}. + */ + public void prepareCreateContextAsUser(UserHandle user, Context context) { + mContextForUser.put(user, context); + } + + @Override + @NonNull + public Context createContextAsUser(UserHandle user, int flags) { + Context userContext = mContextForUser.get(user); + if (userContext != null) { + return userContext; + } + return super.createContextAsUser(user, flags); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 531227947ba0..7bb7c4a81a7d 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2398,10 +2398,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.mComponentNameToServiceMap; boolean isUnlockingOrUnlocked = mUmi.isUserUnlockingOrUnlocked(userState.mUserId); + // Store the list of installed services. + mTempComponentNameSet.clear(); for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) { AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i); ComponentName componentName = ComponentName.unflattenFromString( installedService.getId()); + mTempComponentNameSet.add(componentName); AccessibilityServiceConnection service = componentNameToServiceMap.get(componentName); @@ -2461,6 +2464,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub audioManager.setAccessibilityServiceUids(mTempIntArray); } mActivityTaskManagerService.setAccessibilityServiceUids(mTempIntArray); + // If any services have been removed, remove them from the enabled list and the touch + // exploration granted list. + boolean anyServiceRemoved = + userState.mEnabledServices.removeIf((comp) -> !mTempComponentNameSet.contains(comp)) + || userState.mTouchExplorationGrantedServices.removeIf( + (comp) -> !mTempComponentNameSet.contains(comp)); + if (anyServiceRemoved) { + // Update the enabled services setting. + persistComponentNamesToSettingLocked( + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + userState.mEnabledServices, + userState.mUserId); + // Update the touch exploration granted services setting. + persistComponentNamesToSettingLocked( + Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, + userState.mTouchExplorationGrantedServices, + userState.mUserId); + } + mTempComponentNameSet.clear(); updateAccessibilityEnabledSettingLocked(userState); } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 63a607c8d0d4..7be2dea3c2f6 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -32,8 +32,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManagerInternal; import android.content.ComponentName; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.graphics.Rect; import android.metrics.LogMaker; @@ -250,6 +252,31 @@ final class AutofillManagerServiceImpl @Override // from PerUserSystemService protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) throws NameNotFoundException { + final List<ResolveInfo> resolveInfos = + getContext().getPackageManager().queryIntentServicesAsUser( + new Intent(AutofillService.SERVICE_INTERFACE), + // The MATCH_INSTANT flag is added because curret autofill CTS module is + // defined in one apk, which makes the test autofill service installed in a + // instant app when the CTS tests are running in instant app mode. + // TODO: Remove MATCH_INSTANT flag after completing refactoring the CTS module + // to make the test autofill service a separate apk. + PackageManager.GET_META_DATA | PackageManager.MATCH_INSTANT, + mUserId); + boolean serviceHasAutofillIntentFilter = false; + for (ResolveInfo resolveInfo : resolveInfos) { + final ServiceInfo serviceInfo = resolveInfo.serviceInfo; + if (serviceInfo.getComponentName().equals(serviceComponent)) { + serviceHasAutofillIntentFilter = true; + break; + } + } + if (!serviceHasAutofillIntentFilter) { + Slog.w(TAG, + "Autofill service from '" + serviceComponent.getPackageName() + "' does" + + "not have intent filter " + AutofillService.SERVICE_INTERFACE); + throw new SecurityException("Service does not declare intent filter " + + AutofillService.SERVICE_INTERFACE); + } mInfo = new AutofillServiceInfo(getContext(), serviceComponent, mUserId); return mInfo.getServiceInfo(); } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 5c36a6b07392..2194a0863238 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -83,9 +83,7 @@ import android.os.ParcelFileDescriptor; import android.os.PowerWhitelistManager; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.os.ResultReceiver; import android.os.ServiceManager; -import android.os.ShellCallback; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; @@ -931,13 +929,14 @@ public class CompanionDeviceManagerService extends SystemService { } @Override - public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, - String[] args, ShellCallback callback, ResultReceiver resultReceiver) - throws RemoteException { - new CompanionDeviceShellCommand(CompanionDeviceManagerService.this, mAssociationStore, - mDevicePresenceMonitor, mTransportManager, mSystemDataTransferRequestStore, - mAssociationRequestsProcessor) - .exec(this, in, out, err, args, callback, resultReceiver); + public int handleShellCommand(@NonNull ParcelFileDescriptor in, + @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, + @NonNull String[] args) { + return new CompanionDeviceShellCommand(CompanionDeviceManagerService.this, + mAssociationStore, mDevicePresenceMonitor, mTransportManager, + mSystemDataTransferRequestStore, mAssociationRequestsProcessor) + .exec(this, in.getFileDescriptor(), out.getFileDescriptor(), + err.getFileDescriptor(), args); } @Override diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 0a222d731bc2..6af252edee91 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -3618,6 +3618,11 @@ public class AccountManagerService // Strip auth token from result. result.remove(AccountManager.KEY_AUTHTOKEN); + if (!checkKeyIntent(Binder.getCallingUid(), result)) { + onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, + "invalid intent in bundle returned"); + return; + } if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, @@ -5208,6 +5213,11 @@ public class AccountManagerService } else { if (mStripAuthTokenFromResult) { result.remove(AccountManager.KEY_AUTHTOKEN); + if (!checkKeyIntent(Binder.getCallingUid(), result)) { + onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, + "invalid intent in bundle returned"); + return; + } } if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, getClass().getSimpleName() diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 12fc263e1c8c..98b288cd7676 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -939,6 +939,23 @@ abstract public class ManagedServices { return false; } + protected boolean isPackageOrComponentAllowedWithPermission(ComponentName component, + int userId) { + if (!(isPackageOrComponentAllowed(component.flattenToString(), userId) + || isPackageOrComponentAllowed(component.getPackageName(), userId))) { + return false; + } + return componentHasBindPermission(component, userId); + } + + private boolean componentHasBindPermission(ComponentName component, int userId) { + ServiceInfo info = getServiceInfo(component, userId); + if (info == null) { + return false; + } + return mConfig.bindPermission.equals(info.permission); + } + boolean isPackageOrComponentUserSet(String pkgOrComponent, int userId) { synchronized (mApproved) { ArraySet<String> services = mUserSetServices.get(userId); @@ -1000,6 +1017,7 @@ abstract public class ManagedServices { for (int uid : uidList) { if (isPackageAllowed(pkgName, UserHandle.getUserId(uid))) { anyServicesInvolved = true; + trimApprovedListsForInvalidServices(pkgName, UserHandle.getUserId(uid)); } } } @@ -1132,8 +1150,7 @@ abstract public class ManagedServices { synchronized (mMutex) { if (enabled) { - if (isPackageOrComponentAllowed(component.flattenToString(), userId) - || isPackageOrComponentAllowed(component.getPackageName(), userId)) { + if (isPackageOrComponentAllowedWithPermission(component, userId)) { registerServiceLocked(component, userId); } else { Slog.d(TAG, component + " no longer has permission to be bound"); @@ -1252,6 +1269,33 @@ abstract public class ManagedServices { return removed; } + private void trimApprovedListsForInvalidServices(String packageName, int userId) { + synchronized (mApproved) { + final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(userId); + if (approvedByType == null) { + return; + } + for (int i = 0; i < approvedByType.size(); i++) { + final ArraySet<String> approved = approvedByType.valueAt(i); + for (int j = approved.size() - 1; j >= 0; j--) { + final String approvedPackageOrComponent = approved.valueAt(j); + if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) { + final ComponentName component = ComponentName.unflattenFromString( + approvedPackageOrComponent); + if (component != null && !componentHasBindPermission(component, userId)) { + approved.removeAt(j); + if (DEBUG) { + Slog.v(TAG, "Removing " + approvedPackageOrComponent + + " from approved list; no bind permission found " + + mConfig.bindPermission); + } + } + } + } + } + } + } + protected String getPackageName(String packageOrComponent) { final ComponentName component = ComponentName.unflattenFromString(packageOrComponent); if (component != null) { @@ -1488,8 +1532,7 @@ abstract public class ManagedServices { void reregisterService(final ComponentName cn, final int userId) { // If rebinding a package that died, ensure it still has permission // after the rebind delay - if (isPackageOrComponentAllowed(cn.getPackageName(), userId) - || isPackageOrComponentAllowed(cn.flattenToString(), userId)) { + if (isPackageOrComponentAllowedWithPermission(cn, userId)) { registerService(cn, userId); } } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index f72e8ab353bc..7312b4282d74 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -248,6 +248,15 @@ final class InstallPackageHelper { */ final private HashMap<String, String> mPackagesToBeDisabled = new HashMap<>(); + /** + * Tracks packages that need to be disabled for QSPA enabled taregts. + * List of packages path on the file system. + */ + final private List<String> mPackagesPathToBeDisabledForQSPA = new ArrayList<String>(); + final private boolean mQspaEnabled = SystemProperties.getBoolean( + "ro.vendor.config.qspa.apps", false); + + // TODO(b/198166813): remove PMS dependency InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) { mPm = pm; @@ -265,6 +274,18 @@ final class InstallPackageHelper { mViewCompiler = pm.mInjector.getViewCompiler(); mSharedLibraries = pm.mInjector.getSharedLibrariesImpl(); mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper(); + + if (mQspaEnabled) { + mPackagesPathToBeDisabledForQSPA.add("/system_ext/priv-app/SystemUI"); + mPackagesPathToBeDisabledForQSPA.add("/system_ext/priv-app/Launcher3QuickStepGo"); + mPackagesPathToBeDisabledForQSPA.add("/system_ext/priv-app/Launcher3Go"); + mPackagesPathToBeDisabledForQSPA.add("/system/app/PrintSpooler"); + mPackagesPathToBeDisabledForQSPA.add("/system/priv-app/StatementService"); + mPackagesPathToBeDisabledForQSPA.add("/product/app/Calendar"); + } else { + mPackagesPathToBeDisabledForQSPA.add("/system_ext/app/HeadlessLauncher"); + } + } InstallPackageHelper(PackageManagerService pm) { @@ -3767,6 +3788,15 @@ final class InstallPackageHelper { cacher.cleanCachedResult(file); } + + if (mPackagesPathToBeDisabledForQSPA != null && + mPackagesPathToBeDisabledForQSPA.contains(file.toString())) { + // Ignore entries contained in {@link #mPackagesPathToBeDisabledForQSPA} + Slog.d(TAG, "QSPA is " + mQspaEnabled + " ignoring package for install : " + file); + continue; + } + + if (mPackagesToBeDisabled.values() != null && (mPackagesToBeDisabled.values().contains(file.toString()) || mPackagesToBeDisabled.values().stream().anyMatch(file.toString()::contains))) { diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 8456927b2cdf..00da2439ced8 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -33,6 +33,7 @@ import android.app.appsearch.SearchResult; import android.app.appsearch.SearchResults; import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaRequest; +import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; @@ -47,6 +48,7 @@ import android.graphics.drawable.Icon; import android.os.Binder; import android.os.PersistableBundle; import android.os.StrictMode; +import android.os.SystemClock; import android.text.format.Formatter; import android.util.ArrayMap; import android.util.ArraySet; @@ -160,6 +162,9 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String KEY_BITMAPS = "bitmaps"; private static final String KEY_BITMAP_BYTES = "bitmapBytes"; + @VisibleForTesting + public static final int REPORT_USAGE_BUFFER_SIZE = 3; + private final Executor mExecutor; /** @@ -196,6 +201,9 @@ class ShortcutPackage extends ShortcutPackageItem { private long mLastKnownForegroundElapsedTime; @GuardedBy("mLock") + private List<Long> mLastReportedTime = new ArrayList<>(); + + @GuardedBy("mLock") private boolean mIsAppSearchSchemaUpToDate; private ShortcutPackage(ShortcutUser shortcutUser, @@ -1677,6 +1685,30 @@ class ShortcutPackage extends ShortcutPackageItem { return condition[0]; } + void reportShortcutUsed(@NonNull final UsageStatsManagerInternal usageStatsManagerInternal, + @NonNull final String shortcutId) { + synchronized (mLock) { + final long currentTS = SystemClock.elapsedRealtime(); + final ShortcutService s = mShortcutUser.mService; + if (mLastReportedTime.isEmpty() + || mLastReportedTime.size() < REPORT_USAGE_BUFFER_SIZE) { + mLastReportedTime.add(currentTS); + } else if (currentTS - mLastReportedTime.get(0) > s.mSaveDelayMillis) { + mLastReportedTime.remove(0); + mLastReportedTime.add(currentTS); + } else { + return; + } + final long token = s.injectClearCallingIdentity(); + try { + usageStatsManagerInternal.reportShortcutUsage(getPackageName(), shortcutId, + getUser().getUserId()); + } finally { + s.injectRestoreCallingIdentity(token); + } + } + } + public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) { pw.println(); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index c6aba2ab9cbe..77ec06878e23 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -369,7 +369,7 @@ public class ShortcutService extends IShortcutService.Stub { private CompressFormat mIconPersistFormat; private int mIconPersistQuality; - private int mSaveDelayMillis; + int mSaveDelayMillis; private final IPackageManager mIPackageManager; private final PackageManagerInternal mPackageManagerInternal; @@ -2282,7 +2282,7 @@ public class ShortcutService extends IShortcutService.Stub { packageShortcutsChanged(ps, changedShortcuts, removedShortcuts); - reportShortcutUsedInternal(packageName, shortcut.getId(), userId); + ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcut.getId()); verifyStates(); } @@ -2686,25 +2686,17 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d", shortcutId, packageName, userId)); } + final ShortcutPackage ps; synchronized (mLock) { throwIfUserLockedL(userId); - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); + ps = getPackageShortcutsForPublisherLocked(packageName, userId); if (ps.findShortcutById(shortcutId) == null) { Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s", packageName, shortcutId)); return; } } - reportShortcutUsedInternal(packageName, shortcutId, userId); - } - - private void reportShortcutUsedInternal(String packageName, String shortcutId, int userId) { - final long token = injectClearCallingIdentity(); - try { - mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId); - } finally { - injectRestoreCallingIdentity(token); - } + ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcutId); } @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 2499529f0fc0..a79f912747cd 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -695,6 +695,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt // TODO: switch this back to SecurityException Slog.wtf(TAG, "Not allowed to modify non-dynamic permission " + permName); + return; } mRegistry.removePermission(permName); } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index c19ad4d60e3e..1eb2d5c686e8 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3096,8 +3096,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return false; } if (doAnimation) { - mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false); - if (!isAnimating(TRANSITION | PARENTS)) { + // If a hide animation is applied, then let onAnimationFinished + // -> checkPolicyVisibilityChange hide the window. Otherwise make doAnimation false + // to commit invisible immediately. + if (!mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false /* isEntrance */)) { doAnimation = false; } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 6f810688d37b..26dd4806588b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3388,6 +3388,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (shouldMigrateToDevicePolicyEngine()) { migratePoliciesToDevicePolicyEngine(); } + + maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked(); } maybeStartSecurityLogMonitorOnActivityManagerReady(); break; @@ -13494,27 +13496,47 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { CallerIdentity caller, EnforcingAdmin admin, String key, boolean enabled, boolean parent) { synchronized (getLockObject()) { + + int ownerType; if (isDeviceOwner(caller)) { - if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_DEVICE_OWNER, key)) { - setGlobalUserRestrictionInternal(admin, key, enabled); - } else { - setLocalUserRestrictionInternal(admin, key, enabled, caller.getUserId()); - } + ownerType = OWNER_TYPE_DEVICE_OWNER; + } else if (isProfileOwnerOfOrganizationOwnedDevice(caller)) { + ownerType = OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE; } else if (isProfileOwner(caller)) { - if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_PROFILE_OWNER, key) - || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller) - && UserRestrictionsUtils.isGlobal( - OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, key))) { - setGlobalUserRestrictionInternal(admin, key, enabled); - } else { - int affectedUserId = parent - ? getProfileParentId(caller.getUserId()) : caller.getUserId(); - setLocalUserRestrictionInternal(admin, key, enabled, affectedUserId); - } + ownerType = OWNER_TYPE_PROFILE_OWNER; } else { throw new IllegalStateException("Non-DO/Non-PO cannot set restriction " + key + " while targetSdkVersion is less than UPSIDE_DOWN_CAKE"); } + setBackwardCompatibleUserRestrictionLocked(ownerType, admin, caller.getUserId(), key, + enabled, parent); + } + } + + private void setBackwardCompatibleUserRestrictionLocked( + int ownerType, EnforcingAdmin admin, int userId, String key, boolean enabled, + boolean parent) { + if (ownerType == OWNER_TYPE_DEVICE_OWNER) { + if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_DEVICE_OWNER, key)) { + setGlobalUserRestrictionInternal(admin, key, enabled); + } else { + setLocalUserRestrictionInternal(admin, key, enabled, userId); + } + } else if (ownerType == OWNER_TYPE_PROFILE_OWNER + || ownerType == OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE) { + if (UserRestrictionsUtils.isGlobal(OWNER_TYPE_PROFILE_OWNER, key) + || (parent && ownerType == OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE + && UserRestrictionsUtils.isGlobal( + OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, key))) { + setGlobalUserRestrictionInternal(admin, key, enabled); + } else { + int affectedUserId = parent + ? getProfileParentId(userId) : userId; + setLocalUserRestrictionInternal(admin, key, enabled, affectedUserId); + } + } else { + throw new IllegalStateException("Non-DO/Non-PO cannot set restriction " + key + + " while targetSdkVersion is less than UPSIDE_DOWN_CAKE"); } } @@ -24147,11 +24169,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkCallAuthorization( hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); return mInjector.binderWithCleanCallingIdentity(() -> { - boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins(); - if (!canForceMigration && !shouldMigrateToDevicePolicyEngine()) { - return false; + synchronized (getLockObject()) { + boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins(); + if (!canForceMigration && !shouldMigrateToDevicePolicyEngine()) { + return false; + } + boolean migrated = migratePoliciesToDevicePolicyEngine(); + migrated &= migratePoliciesPostUpgradeToDevicePolicyEngineLocked(); + return migrated; } - return migratePoliciesToDevicePolicyEngine(); }); } @@ -24181,6 +24207,31 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } /** + * [b/318497672] Migrate policies that weren't migrated properly in the initial migration on + * update from Android T to Android U + */ + private void maybeMigratePoliciesPostUpgradeToDevicePolicyEngineLocked() { + if (!mOwners.isMigratedToPolicyEngine() || mOwners.isMigratedPostUpdate()) { + return; + } + migratePoliciesPostUpgradeToDevicePolicyEngineLocked(); + mOwners.markPostUpgradeMigration(); + } + + private boolean migratePoliciesPostUpgradeToDevicePolicyEngineLocked() { + try { + migrateScreenCapturePolicyLocked(); + migrateLockTaskPolicyLocked(); + migrateUserRestrictionsLocked(); + return true; + } catch (Exception e) { + Slogf.e(LOG_TAG, e, "Error occurred during post upgrade migration to the device " + + "policy engine."); + return false; + } + } + + /** * @return {@code true} if policies were migrated successfully, {@code false} otherwise. */ private boolean migratePoliciesToDevicePolicyEngine() { @@ -24193,7 +24244,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { migrateAutoTimezonePolicy(); migratePermissionGrantStatePolicies(); } - migrateScreenCapturePolicyLocked(); migratePermittedInputMethodsPolicyLocked(); migrateAccountManagementDisabledPolicyLocked(); migrateUserControlDisabledPackagesLocked(); @@ -24266,14 +24316,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void migrateScreenCapturePolicyLocked() { Binder.withCleanCallingIdentity(() -> { - if (mPolicyCache.getScreenCaptureDisallowedUser() == UserHandle.USER_NULL) { - return; - } ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); if (admin != null && ((isDeviceOwner(admin) && admin.disableScreenCapture) || (admin.getParentActiveAdmin() != null && admin.getParentActiveAdmin().disableScreenCapture))) { + EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin( admin.info.getComponent(), admin.getUserHandle().getIdentifier(), @@ -24302,6 +24350,48 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }); } + private void migrateLockTaskPolicyLocked() { + Binder.withCleanCallingIdentity(() -> { + ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + if (deviceOwner != null) { + int doUserId = deviceOwner.getUserHandle().getIdentifier(); + DevicePolicyData policies = getUserData(doUserId); + List<String> packages = policies.mLockTaskPackages; + int features = policies.mLockTaskFeatures; + // TODO: find out about persistent preferred activities + if (!packages.isEmpty()) { + setLockTaskPolicyInPolicyEngine(deviceOwner, doUserId, packages, features); + } + } + + for (int userId : mUserManagerInternal.getUserIds()) { + ActiveAdmin profileOwner = getProfileOwnerLocked(userId); + if (profileOwner != null && canDPCManagedUserUseLockTaskLocked(userId)) { + DevicePolicyData policies = getUserData(userId); + List<String> packages = policies.mLockTaskPackages; + int features = policies.mLockTaskFeatures; + if (!packages.isEmpty()) { + setLockTaskPolicyInPolicyEngine(profileOwner, userId, packages, features); + } + } + } + }); + } + + private void setLockTaskPolicyInPolicyEngine( + ActiveAdmin admin, int userId, List<String> packages, int features) { + EnforcingAdmin enforcingAdmin = + EnforcingAdmin.createEnterpriseEnforcingAdmin( + admin.info.getComponent(), + userId, + admin); + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.LOCK_TASK, + enforcingAdmin, + new LockTaskPolicy(new HashSet<>(packages), features), + userId); + } + private void migratePermittedInputMethodsPolicyLocked() { Binder.withCleanCallingIdentity(() -> { List<UserInfo> users = mUserManager.getUsers(); @@ -24394,6 +24484,42 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }); } + private void migrateUserRestrictionsLocked() { + Binder.withCleanCallingIdentity(() -> { + List<UserInfo> users = mUserManager.getUsers(); + for (UserInfo userInfo : users) { + ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userInfo.id); + if (admin == null) continue; + ComponentName adminComponent = admin.info.getComponent(); + int userId = userInfo.id; + EnforcingAdmin enforcingAdmin = EnforcingAdmin.createEnterpriseEnforcingAdmin( + adminComponent, + userId, + admin); + int ownerType; + if (isDeviceOwner(admin)) { + ownerType = OWNER_TYPE_DEVICE_OWNER; + } else if (isProfileOwnerOfOrganizationOwnedDevice(adminComponent, userId)) { + ownerType = OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE; + } else if (isProfileOwner(adminComponent, userId)) { + ownerType = OWNER_TYPE_PROFILE_OWNER; + } else { + throw new IllegalStateException("Invalid DO/PO state"); + } + + for (final String restriction : admin.ensureUserRestrictions().keySet()) { + setBackwardCompatibleUserRestrictionLocked(ownerType, enforcingAdmin, userId, + restriction, /* enabled */ true, /* parent */ false); + } + for (final String restriction : admin.getParentActiveAdmin() + .ensureUserRestrictions().keySet()) { + setBackwardCompatibleUserRestrictionLocked(ownerType, enforcingAdmin, userId, + restriction, /* enabled */ true, /* parent */ true); + } + } + }); + } + private List<PackageInfo> getInstalledPackagesOnUser(int userId) { return mInjector.binderWithCleanCallingIdentity(() -> mContext.getPackageManager().getInstalledPackagesAsUser( diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index bb275e45b55a..c842e29e3c70 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -616,6 +616,19 @@ class Owners { } } + void markPostUpgradeMigration() { + synchronized (mData) { + mData.mPoliciesMigratedPostUpdate = true; + mData.writeDeviceOwner(); + } + } + + boolean isMigratedPostUpdate() { + synchronized (mData) { + return mData.mPoliciesMigratedPostUpdate; + } + } + @GuardedBy("mData") void pushToAppOpsLocked() { if (!mSystemReady) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java index 37d4f95cac29..608ae140450e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java @@ -87,6 +87,8 @@ class OwnersData { private static final String ATTR_MIGRATED_TO_POLICY_ENGINE = "migratedToPolicyEngine"; + private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade"; + // Internal state for the device owner package. OwnerInfo mDeviceOwner; int mDeviceOwnerUserId = UserHandle.USER_NULL; @@ -114,6 +116,8 @@ class OwnersData { boolean mMigratedToPolicyEngine = false; + boolean mPoliciesMigratedPostUpdate = false; + OwnersData(PolicyPathProvider pathProvider) { mPathProvider = pathProvider; } @@ -397,6 +401,7 @@ class OwnersData { out.startTag(null, TAG_POLICY_ENGINE_MIGRATION); out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine); + out.attributeBoolean(null, ATTR_MIGRATED_POST_UPGRADE, mPoliciesMigratedPostUpdate); out.endTag(null, TAG_POLICY_ENGINE_MIGRATION); } @@ -457,6 +462,8 @@ class OwnersData { case TAG_POLICY_ENGINE_MIGRATION: mMigratedToPolicyEngine = parser.getAttributeBoolean( null, ATTR_MIGRATED_TO_POLICY_ENGINE, false); + mPoliciesMigratedPostUpdate = parser.getAttributeBoolean( + null, ATTR_MIGRATED_POST_UPGRADE, false); break; default: Slog.e(TAG, "Unexpected tag: " + tag); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java index b539a76b521c..5eda84ba7629 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java @@ -146,6 +146,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { @SmallTest @Test + @Ignore("b/277916462") public void testCompMigrationUnAffiliated_skipped() throws Exception { prepareAdmin1AsDo(); prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID); @@ -217,6 +218,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { @SmallTest @Test + @Ignore("b/277916462") public void testCompMigration_keepSuspendedAppsWhenDpcIsRPlus() throws Exception { prepareAdmin1AsDo(); prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R); @@ -250,6 +252,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { @SmallTest @Test + @Ignore("b/277916462") public void testCompMigration_unsuspendAppsWhenDpcNotRPlus() throws Exception { prepareAdmin1AsDo(); prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 7c1845fcd54e..77e3a988790f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -404,8 +404,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { public void testPushDynamicShortcut() { // Change the max number of shortcuts. - mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5"); - + mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5," + + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1"); setCaller(CALLING_PACKAGE_1, USER_0); final ShortcutInfo s1 = makeShortcut("s1"); @@ -543,6 +543,57 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_0)); } + public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled() + throws InterruptedException { + mService.updateConfigurationLocked( + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500"); + + // Verify calls to UsageStatsManagerInternal#reportShortcutUsage are throttled. + setCaller(CALLING_PACKAGE_1, USER_0); + for (int i = 0; i < ShortcutPackage.REPORT_USAGE_BUFFER_SIZE; i++) { + final ShortcutInfo si = makeShortcut("s" + i); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_1), eq("s1"), eq(USER_0)); + Mockito.reset(mMockUsageStatsManagerInternal); + for (int i = ShortcutPackage.REPORT_USAGE_BUFFER_SIZE; i <= 10; i++) { + final ShortcutInfo si = makeShortcut("s" + i); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage( + any(), any(), anyInt()); + + // Verify pkg2 isn't blocked by pkg1, but consecutive calls from pkg2 are throttled as well. + setCaller(CALLING_PACKAGE_2, USER_0); + for (int i = 0; i < ShortcutPackage.REPORT_USAGE_BUFFER_SIZE; i++) { + final ShortcutInfo si = makeShortcut("s" + i); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_2), eq("s1"), eq(USER_0)); + Mockito.reset(mMockUsageStatsManagerInternal); + for (int i = ShortcutPackage.REPORT_USAGE_BUFFER_SIZE; i <= 10; i++) { + final ShortcutInfo si = makeShortcut("s" + i); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage( + any(), any(), anyInt()); + + Mockito.reset(mMockUsageStatsManagerInternal); + // Let time passes which resets the throttle + Thread.sleep(505); + // Verify UsageStatsManagerInternal#reportShortcutUsed can be called again + setCaller(CALLING_PACKAGE_1, USER_0); + mManager.pushDynamicShortcut(makeShortcut("s10")); + setCaller(CALLING_PACKAGE_2, USER_0); + mManager.pushDynamicShortcut(makeShortcut("s10")); + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_1), any(), eq(USER_0)); + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_2), any(), eq(USER_0)); + } + public void testUnlimitedCalls() { setCaller(CALLING_PACKAGE_1, USER_0); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 1cfaf7c83584..27c981aba85c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -2175,6 +2175,8 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { public void testReportShortcutUsed() { mRunningUsers.put(USER_10, true); + mService.updateConfigurationLocked( + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1"); runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { reset(mMockUsageStatsManagerInternal); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 541739d50024..4ef3a6eb12ab 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -36,6 +36,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -883,6 +884,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a", 0, true); service.reregisterService(cn, 0); @@ -913,6 +915,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a", 0, false); service.reregisterService(cn, 0); @@ -943,6 +946,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a/a", 0, true); service.reregisterService(cn, 0); @@ -973,6 +977,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a/a", 0, false); service.reregisterService(cn, 0); @@ -1130,6 +1135,58 @@ public class ManagedServicesTest extends UiServiceTestCase { } @Test + public void testUpgradeAppNoPermissionNoRebind() throws Exception { + Context context = spy(getContext()); + doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any()); + + ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, + mIpm, + APPROVAL_BY_COMPONENT); + + List<String> packages = new ArrayList<>(); + packages.add("package"); + addExpectedServices(service, packages, 0); + + final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1"); + final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2"); + + // Both components are approved initially + mExpectedPrimaryComponentNames.clear(); + mExpectedPrimaryPackages.clear(); + mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2"); + mExpectedSecondaryComponentNames.clear(); + mExpectedSecondaryPackages.clear(); + + loadXml(service); + + //Component package/C1 loses bind permission + when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer( + (Answer<ServiceInfo>) invocation -> { + ComponentName invocationCn = invocation.getArgument(0); + if (invocationCn != null) { + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.packageName = invocationCn.getPackageName(); + serviceInfo.name = invocationCn.getClassName(); + if (invocationCn.equals(unapprovedComponent)) { + serviceInfo.permission = "none"; + } else { + serviceInfo.permission = service.getConfig().bindPermission; + } + serviceInfo.metaData = null; + return serviceInfo; + } + return null; + } + ); + + // Trigger package update + service.onPackagesChanged(false, new String[]{"package"}, new int[]{0}); + + assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent)); + assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent)); + } + + @Test public void testSetPackageOrComponentEnabled() throws Exception { for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) { ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 0ddd3135506e..7fb5c9687796 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -229,6 +229,26 @@ public class WindowStateTests extends WindowTestsBase { assertTrue(window.isOnScreen()); window.hide(false /* doAnimation */, false /* requestAnim */); assertFalse(window.isOnScreen()); + + // Verifies that a window without animation can be hidden even if its parent is animating. + window.show(false /* doAnimation */, false /* requestAnim */); + assertTrue(window.isVisibleByPolicy()); + window.getParent().startAnimation(mTransaction, mock(AnimationAdapter.class), + false /* hidden */, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION); + window.mAttrs.windowAnimations = 0; + window.hide(true /* doAnimation */, true /* requestAnim */); + assertFalse(window.isSelfAnimating(0, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION)); + assertFalse(window.isVisibleByPolicy()); + assertFalse(window.isOnScreen()); + + // Verifies that a window with animation can be hidden after the hide animation is finished. + window.show(false /* doAnimation */, false /* requestAnim */); + window.mAttrs.windowAnimations = android.R.style.Animation_Dialog; + window.hide(true /* doAnimation */, true /* requestAnim */); + assertTrue(window.isSelfAnimating(0, SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION)); + assertTrue(window.isVisibleByPolicy()); + window.cancelAnimation(); + assertFalse(window.isVisibleByPolicy()); } @Test |