diff options
27 files changed, 629 insertions, 117 deletions
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 2a0d4acbe76f..8e01b86b2fff 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2916,6 +2916,16 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: This device is capable of launching apps in automotive display + * compatibility mode. + * @hide + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAR_DISPLAY_COMPATIBILITY = + "android.software.car.display_compatibility"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature(String, int)}: If this feature is supported, the device supports * {@link android.security.identity.IdentityCredentialStore} implemented in secure hardware * at the given feature version. 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/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index cba28f3a596f..b45bcb405557 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -3612,6 +3612,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, @@ -5201,6 +5206,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/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 4b8b43134e6c..b4e27d7f3f4e 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -1415,11 +1415,11 @@ public class ClipboardService extends SystemService { .getDrawable(R.drawable.ic_safety_protection); toastToShow = Toast.makeCustomToastWithIcon(toastContext, UiThread.get().getLooper(), message, - Toast.LENGTH_SHORT, safetyProtectionIcon); + Toast.LENGTH_LONG, safetyProtectionIcon); } else { toastToShow = Toast.makeText( toastContext, UiThread.get().getLooper(), message, - Toast.LENGTH_SHORT); + Toast.LENGTH_LONG); } toastToShow.show(); } 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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 8df1d7553a33..cf67ef399092 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -3383,8 +3383,19 @@ public class NotificationManagerService extends SystemService { null /* options */); record = getToastRecord(callingUid, callingPid, pkg, isSystemToast, token, text, callback, duration, windowToken, displayId, textCallback); - mToastQueue.add(record); - index = mToastQueue.size() - 1; + + // Insert system toasts at the front of the queue + int systemToastInsertIdx = mToastQueue.size(); + if (isSystemToast) { + systemToastInsertIdx = getInsertIndexForSystemToastLocked(); + } + if (systemToastInsertIdx < mToastQueue.size()) { + index = systemToastInsertIdx; + mToastQueue.add(index, record); + } else { + mToastQueue.add(record); + index = mToastQueue.size() - 1; + } keepProcessAliveForToastIfNeededLocked(callingPid); } // If it's at index 0, it's the current toast. It doesn't matter if it's @@ -3400,6 +3411,23 @@ public class NotificationManagerService extends SystemService { } } + @GuardedBy("mToastQueue") + private int getInsertIndexForSystemToastLocked() { + // If there are other system toasts: insert after the last one + int idx = 0; + for (ToastRecord r : mToastQueue) { + if (idx == 0 && mIsCurrentToastShown) { + idx++; + continue; + } + if (!r.isSystemToast) { + return idx; + } + idx++; + } + return idx; + } + private boolean checkCanEnqueueToast(String pkg, int callingUid, int displayId, boolean isAppRenderedToast, boolean isSystemToast) { final boolean isPackageSuspended = isPackagePaused(pkg); @@ -5744,6 +5772,10 @@ public class NotificationManagerService extends SystemService { Objects.requireNonNull(user); verifyPrivilegedListener(token, user, false); + + final NotificationChannel originalChannel = mPreferencesHelper.getNotificationChannel( + pkg, getUidForPackageAndUser(pkg, user), channel.getId(), true); + verifyPrivilegedListenerUriPermission(Binder.getCallingUid(), channel, originalChannel); updateNotificationChannelInt(pkg, getUidForPackageAndUser(pkg, user), channel, true); } @@ -5835,6 +5867,24 @@ public class NotificationManagerService extends SystemService { } } + private void verifyPrivilegedListenerUriPermission(int sourceUid, + @NonNull NotificationChannel updateChannel, + @Nullable NotificationChannel originalChannel) { + // Check that the NLS has the required permissions to access the channel + final Uri soundUri = updateChannel.getSound(); + final Uri originalSoundUri = + (originalChannel != null) ? originalChannel.getSound() : null; + if (soundUri != null && !Objects.equals(originalSoundUri, soundUri)) { + Binder.withCleanCallingIdentity(() -> { + mUgmInternal.checkGrantUriPermission(sourceUid, null, + ContentProvider.getUriWithoutUserId(soundUri), + Intent.FLAG_GRANT_READ_URI_PERMISSION, + ContentProvider.getUserIdFromUri(soundUri, + UserHandle.getUserId(sourceUid))); + }); + } + } + private int getUidForPackageAndUser(String pkg, UserHandle user) throws RemoteException { int uid = INVALID_UID; final long identity = Binder.clearCallingIdentity(); 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 c4506d59c138..8f1e96840fac 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3089,8 +3089,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/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/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 14480185ad6c..59b20bba7390 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -3593,6 +3593,69 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testUpdateNotificationChannelFromPrivilegedListener_noSoundUriPermission() + throws Exception { + mService.setPreferencesHelper(mPreferencesHelper); + when(mCompanionMgr.getAssociations(PKG, mUserId)) + .thenReturn(singletonList(mock(AssociationInfo.class))); + when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(mTestNotificationChannel); + + final Uri soundUri = Uri.parse("content://media/test/sound/uri"); + final NotificationChannel updatedNotificationChannel = new NotificationChannel( + TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); + updatedNotificationChannel.setSound(soundUri, + updatedNotificationChannel.getAudioAttributes()); + + doThrow(new SecurityException("no access")).when(mUgmInternal) + .checkGrantUriPermission(eq(Process.myUid()), any(), eq(soundUri), + anyInt(), eq(Process.myUserHandle().getIdentifier())); + + assertThrows(SecurityException.class, + () -> mBinderService.updateNotificationChannelFromPrivilegedListener(null, PKG, + Process.myUserHandle(), updatedNotificationChannel)); + + verify(mPreferencesHelper, never()).updateNotificationChannel( + anyString(), anyInt(), any(), anyBoolean(), anyInt(), anyBoolean()); + + verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG), + eq(Process.myUserHandle()), eq(mTestNotificationChannel), + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); + } + + @Test + public void testUpdateNotificationChannelFromPrivilegedListener_noSoundUriPermission_sameSound() + throws Exception { + mService.setPreferencesHelper(mPreferencesHelper); + when(mCompanionMgr.getAssociations(PKG, mUserId)) + .thenReturn(singletonList(mock(AssociationInfo.class))); + when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(mTestNotificationChannel); + + final Uri soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; + final NotificationChannel updatedNotificationChannel = new NotificationChannel( + TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); + updatedNotificationChannel.setSound(soundUri, + updatedNotificationChannel.getAudioAttributes()); + + doThrow(new SecurityException("no access")).when(mUgmInternal) + .checkGrantUriPermission(eq(Process.myUid()), any(), eq(soundUri), + anyInt(), eq(Process.myUserHandle().getIdentifier())); + + mBinderService.updateNotificationChannelFromPrivilegedListener( + null, PKG, Process.myUserHandle(), updatedNotificationChannel); + + verify(mPreferencesHelper, times(1)).updateNotificationChannel( + anyString(), anyInt(), any(), anyBoolean(), anyInt(), anyBoolean()); + + verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG), + eq(Process.myUserHandle()), eq(mTestNotificationChannel), + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); + } + + @Test public void testGetNotificationChannelFromPrivilegedListener_cdm_success() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); when(mCompanionMgr.getAssociations(PKG, mUserId)) @@ -7599,6 +7662,74 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(NotificationManagerService.MAX_PACKAGE_TOASTS, mService.mToastQueue.size()); } + @Test + public void testPrioritizeSystemToasts() throws Exception { + // Insert non-system toasts + final String testPackage = "testPackageName"; + assertEquals(0, mService.mToastQueue.size()); + mService.isSystemUid = false; + mService.isSystemAppId = false; + setToastRateIsWithinQuota(true); + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false); + + // package is not suspended + when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId)) + .thenReturn(false); + + INotificationManager nmService = (INotificationManager) mService.mService; + + // Enqueue maximum number of toasts for test package + for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS; i++) { + enqueueTextToast(testPackage, "Text"); + } + + // Enqueue system toast + final String testPackageSystem = "testPackageNameSystem"; + mService.isSystemUid = true; + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem, false); + when(mPackageManager.isPackageSuspendedForUser(testPackageSystem, mUserId)) + .thenReturn(false); + + enqueueToast(testPackageSystem, new TestableToastCallback()); + + // System toast is inserted at the front of the queue, behind current showing toast + assertEquals(testPackageSystem, mService.mToastQueue.get(1).pkg); + } + + @Test + public void testPrioritizeSystemToasts_enqueueAfterExistingSystemToast() throws Exception { + // Insert system toasts + final String testPackageSystem1 = "testPackageNameSystem1"; + assertEquals(0, mService.mToastQueue.size()); + mService.isSystemUid = true; + setToastRateIsWithinQuota(true); + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem1, false); + + // package is not suspended + when(mPackageManager.isPackageSuspendedForUser(testPackageSystem1, mUserId)) + .thenReturn(false); + + INotificationManager nmService = (INotificationManager) mService.mService; + + // Enqueue maximum number of toasts for test package + for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_TOASTS; i++) { + enqueueTextToast(testPackageSystem1, "Text"); + } + + // Enqueue another system toast + final String testPackageSystem2 = "testPackageNameSystem2"; + mService.isSystemUid = true; + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackageSystem2, false); + when(mPackageManager.isPackageSuspendedForUser(testPackageSystem2, mUserId)) + .thenReturn(false); + + enqueueToast(testPackageSystem2, new TestableToastCallback()); + + // System toast is inserted at the back of the queue, after the other system toasts + assertEquals(testPackageSystem2, + mService.mToastQueue.get(mService.mToastQueue.size() - 1).pkg); + } + private void setAppInForegroundForToasts(int uid, boolean inForeground) { int importance = (inForeground) ? IMPORTANCE_FOREGROUND : IMPORTANCE_NONE; when(mActivityManager.getUidImportance(mUid)).thenReturn(importance); 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 diff --git a/tests/BootImageProfileTest/AndroidTest.xml b/tests/BootImageProfileTest/AndroidTest.xml index 7e97fa3a8ff1..50f323c59d27 100644 --- a/tests/BootImageProfileTest/AndroidTest.xml +++ b/tests/BootImageProfileTest/AndroidTest.xml @@ -14,6 +14,7 @@ limitations under the License. --> <configuration description="Config for BootImageProfileTest"> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> <!-- do not use DeviceSetup#set-property because it reboots the device b/136200738. furthermore the changes in /data/local.prop don't actually seem to get picked up. --> diff --git a/tests/Input/AndroidTest.xml b/tests/Input/AndroidTest.xml index c62db1ea5ca9..13b5f0d99ba8 100644 --- a/tests/Input/AndroidTest.xml +++ b/tests/Input/AndroidTest.xml @@ -4,6 +4,7 @@ --> <configuration description="Runs Input Tests"> <option name="test-tag" value="InputTests" /> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on" /> |