diff options
Diffstat (limited to 'libs')
38 files changed, 510 insertions, 342 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 0a15d8468983..f6e92ef0e8ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -165,7 +165,8 @@ public class Bubble implements BubbleViewProvider { * Create a bubble with limited information based on given {@link ShortcutInfo}. * Note: Currently this is only being used when the bubble is persisted to disk. */ - Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo, + @VisibleForTesting(visibility = PRIVATE) + public Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo, final int desiredHeight, final int desiredHeightResId, @Nullable final String title, int taskId, @Nullable final String locus, Executor mainExecutor) { Objects.requireNonNull(key); @@ -188,7 +189,7 @@ public class Bubble implements BubbleViewProvider { } @VisibleForTesting(visibility = PRIVATE) - Bubble(@NonNull final BubbleEntry entry, + public Bubble(@NonNull final BubbleEntry entry, @Nullable final Bubbles.SuppressionChangedListener listener, final Bubbles.PendingIntentCanceledListener intentCancelListener, Executor mainExecutor) { @@ -718,7 +719,8 @@ public class Bubble implements BubbleViewProvider { private int getUid(final Context context) { if (mAppUid != -1) return mAppUid; - final PackageManager pm = context.getPackageManager(); + final PackageManager pm = BubbleController.getPackageManagerForUser(context, + mUser.getIdentifier()); if (pm == null) return -1; try { final ApplicationInfo info = pm.getApplicationInfo(mShortcutInfo.getPackage(), 0); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index dca598518432..4b037214fcdd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -48,6 +48,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; +import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.PointF; @@ -64,6 +65,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.Slog; +import android.util.SparseArray; import android.util.SparseSetArray; import android.view.View; import android.view.ViewGroup; @@ -144,6 +146,8 @@ public class BubbleController { // Tracks the id of the current (foreground) user. private int mCurrentUserId; + // Current profiles of the user (e.g. user with a workprofile) + private SparseArray<UserInfo> mCurrentProfiles; // Saves notification keys of active bubbles when users are switched. private final SparseSetArray<String> mSavedBubbleKeysPerUser; @@ -153,8 +157,8 @@ public class BubbleController { // Callback that updates BubbleOverflowActivity on data change. @Nullable private BubbleData.Listener mOverflowListener = null; - // Only load overflow data from disk once - private boolean mOverflowDataLoaded = false; + // Typically only load once & after user switches + private boolean mOverflowDataLoadNeeded = true; /** * When the shade status changes to SHADE (from anything but SHADE, like LOCKED) we'll select @@ -468,14 +472,31 @@ public class BubbleController { updateStack(); } - private void onUserChanged(int newUserId) { + /** Called when the current user changes. */ + @VisibleForTesting + public void onUserChanged(int newUserId) { saveBubbles(mCurrentUserId); + mCurrentUserId = newUserId; + mBubbleData.dismissAll(DISMISS_USER_CHANGED); + mBubbleData.clearOverflow(); + mOverflowDataLoadNeeded = true; + restoreBubbles(newUserId); - mCurrentUserId = newUserId; mBubbleData.setCurrentUserId(newUserId); } + /** Called when the profiles for the current user change. **/ + public void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) { + mCurrentProfiles = currentProfiles; + } + + /** Whether this userId belongs to the current user. */ + private boolean isCurrentProfile(int userId) { + return userId == UserHandle.USER_ALL + || (mCurrentProfiles != null && mCurrentProfiles.get(userId) != null); + } + /** * Sets whether to perform inflation on the same thread as the caller. This method should only * be used in tests, not in production. @@ -556,6 +577,7 @@ public class BubbleController { mWmLayoutParams.setTitle("Bubbles!"); mWmLayoutParams.packageName = mContext.getPackageName(); mWmLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + mWmLayoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; try { mAddedToWindowManager = true; @@ -639,7 +661,7 @@ public class BubbleController { }); }); // Finally, remove the entries for this user now that bubbles are restored. - mSavedBubbleKeysPerUser.remove(mCurrentUserId); + mSavedBubbleKeysPerUser.remove(userId); } private void updateForThemeChanges() { @@ -804,12 +826,12 @@ public class BubbleController { * Fills the overflow bubbles by loading them from disk. */ void loadOverflowBubblesFromDisk() { - if (!mBubbleData.getOverflowBubbles().isEmpty() || mOverflowDataLoaded) { + if (!mBubbleData.getOverflowBubbles().isEmpty() && !mOverflowDataLoadNeeded) { // we don't need to load overflow bubbles from disk if it is already in memory return; } - mOverflowDataLoaded = true; - mDataRepository.loadBubbles((bubbles) -> { + mOverflowDataLoadNeeded = false; + mDataRepository.loadBubbles(mCurrentUserId, (bubbles) -> { bubbles.forEach(bubble -> { if (mBubbleData.hasAnyBubbleWithKey(bubble.getKey())) { // if the bubble is already active, there's no need to push it to overflow @@ -911,6 +933,12 @@ public class BubbleController { Pair<BubbleEntry, Boolean> entryData = entryDataByKey.get(key); BubbleEntry entry = entryData.first; boolean shouldBubbleUp = entryData.second; + + if (entry != null && !isCurrentProfile( + entry.getStatusBarNotification().getUser().getIdentifier())) { + return; + } + rankingMap.getRanking(key, mTmpRanking); boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key); if (isActiveBubble && !mTmpRanking.canBubble()) { @@ -1428,6 +1456,13 @@ public class BubbleController { } @Override + public void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) { + mMainExecutor.execute(() -> { + BubbleController.this.onCurrentProfilesChanged(currentProfiles); + }); + } + + @Override public void onConfigChanged(Configuration newConfig) { mMainExecutor.execute(() -> { BubbleController.this.onConfigChanged(newConfig); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index f6e6b8f3b700..8434d668e153 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -510,7 +510,8 @@ public class BubbleData { || reason == Bubbles.DISMISS_NO_LONGER_BUBBLE || reason == Bubbles.DISMISS_BLOCKED || reason == Bubbles.DISMISS_SHORTCUT_REMOVED - || reason == Bubbles.DISMISS_PACKAGE_REMOVED)) { + || reason == Bubbles.DISMISS_PACKAGE_REMOVED + || reason == Bubbles.DISMISS_USER_CHANGED)) { Bubble b = getOverflowBubbleWithKey(key); if (DEBUG_BUBBLE_DATA) { @@ -642,6 +643,16 @@ public class BubbleData { } } + /** + * Removes all bubbles from the overflow, called when the user changes. + */ + public void clearOverflow() { + while (!mOverflowBubbles.isEmpty()) { + doRemove(mOverflowBubbles.get(0).getKey(), Bubbles.DISMISS_USER_CHANGED); + } + dispatchPendingChanges(); + } + private void dispatchPendingChanges() { if (mListener != null && mStateChange.anythingChanged()) { mListener.applyUpdate(mStateChange); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt index bfacd1cfe90e..9d9e442affd3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt @@ -58,7 +58,8 @@ internal class BubbleDataRepository( */ fun addBubbles(@UserIdInt userId: Int, bubbles: List<Bubble>) { if (DEBUG) Log.d(TAG, "adding ${bubbles.size} bubbles") - val entities = transform(userId, bubbles).also(volatileRepository::addBubbles) + val entities = transform(bubbles).also { + b -> volatileRepository.addBubbles(userId, b) } if (entities.isNotEmpty()) persistToDisk() } @@ -67,14 +68,15 @@ internal class BubbleDataRepository( */ fun removeBubbles(@UserIdInt userId: Int, bubbles: List<Bubble>) { if (DEBUG) Log.d(TAG, "removing ${bubbles.size} bubbles") - val entities = transform(userId, bubbles).also(volatileRepository::removeBubbles) + val entities = transform(bubbles).also { + b -> volatileRepository.removeBubbles(userId, b) } if (entities.isNotEmpty()) persistToDisk() } - private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> { + private fun transform(bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> BubbleEntity( - userId, + b.user.identifier, b.packageName, b.metadataShortcutId ?: return@mapNotNull null, b.key, @@ -116,10 +118,11 @@ internal class BubbleDataRepository( /** * Load bubbles from disk. * @param cb The callback to be run after the bubbles are loaded. This callback is always made - * on the main thread of the hosting process. + * on the main thread of the hosting process. The callback is only run if there are + * bubbles. */ @SuppressLint("WrongConstant") - fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch { + fun loadBubbles(userId: Int, cb: (List<Bubble>) -> Unit) = ioScope.launch { /** * Load BubbleEntity from disk. * e.g. @@ -129,8 +132,9 @@ internal class BubbleDataRepository( * BubbleEntity(0, "com.example.messenger", "id-1") * ] */ - val entities = persistentRepository.readFromDisk() - volatileRepository.addBubbles(entities) + val entitiesByUser = persistentRepository.readFromDisk() + val entities = entitiesByUser.get(userId) ?: return@launch + volatileRepository.addBubbles(userId, entities) /** * Extract userId/packageName from these entities. * e.g. @@ -139,9 +143,10 @@ internal class BubbleDataRepository( * ] */ val shortcutKeys = entities.map { ShortcutKey(it.userId, it.packageName) }.toSet() + /** - * Retrieve shortcuts with given userId/packageName combination, then construct a mapping - * from the userId/packageName pair to a list of associated ShortcutInfo. + * Retrieve shortcuts with given userId/packageName combination, then construct a + * mapping from the userId/packageName pair to a list of associated ShortcutInfo. * e.g. * { * ShortcutKey(0, "com.example.messenger") -> [ @@ -161,21 +166,23 @@ internal class BubbleDataRepository( .setQueryFlags(SHORTCUT_QUERY_FLAG), UserHandle.of(key.userId)) ?: emptyList() }.groupBy { ShortcutKey(it.userId, it.`package`) } - // For each entity loaded from xml, find the corresponding ShortcutInfo then convert them - // into Bubble. + // For each entity loaded from xml, find the corresponding ShortcutInfo then convert + // them into Bubble. val bubbles = entities.mapNotNull { entity -> shortcutMap[ShortcutKey(entity.userId, entity.packageName)] ?.firstOrNull { shortcutInfo -> entity.shortcutId == shortcutInfo.id } - ?.let { shortcutInfo -> Bubble( - entity.key, - shortcutInfo, - entity.desiredHeight, - entity.desiredHeightResId, - entity.title, - entity.taskId, - entity.locus, - mainExecutor - ) } + ?.let { shortcutInfo -> + Bubble( + entity.key, + shortcutInfo, + entity.desiredHeight, + entity.desiredHeightResId, + entity.title, + entity.taskId, + entity.locus, + mainExecutor + ) + } } mainExecutor.execute { cb(bubbles) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java index fe3f9ef6aa5f..e64ed6a0836c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java @@ -29,6 +29,8 @@ import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import androidx.annotation.VisibleForTesting; + import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.ShadowGenerator; @@ -39,11 +41,12 @@ import com.android.wm.shell.R; * We are not using Launcher's IconFactory because bubbles only runs on the UI thread, * so there is no need to manage a pool across multiple threads. */ +@VisibleForTesting public class BubbleIconFactory extends BaseIconFactory { private int mBadgeSize; - protected BubbleIconFactory(Context context) { + public BubbleIconFactory(Context context) { super(context, context.getResources().getConfiguration().densityDpi, context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size)); mBadgeSize = mContext.getResources().getDimensionPixelSize( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index c5a712e271e4..fc53ef26dbd9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -39,6 +39,7 @@ import android.util.PathParser; import android.view.LayoutInflater; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.launcher3.icons.BitmapInfo; @@ -118,7 +119,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask /** * Info necessary to render a bubble. */ - static class BubbleViewInfo { + @VisibleForTesting + public static class BubbleViewInfo { BadgedImageView imageView; BubbleExpandedView expandedView; ShortcutInfo shortcutInfo; @@ -129,8 +131,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask Path dotPath; Bubble.FlyoutMessage flyoutMessage; + @VisibleForTesting @Nullable - static BubbleViewInfo populate(Context c, BubbleController controller, + public static BubbleViewInfo populate(Context c, BubbleController controller, BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b, boolean skipInflation) { BubbleViewInfo info = new BubbleViewInfo(); @@ -152,7 +155,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask } // App name & app icon - PackageManager pm = c.getPackageManager(); + PackageManager pm = BubbleController.getPackageManagerForUser(c, + b.getUser().getIdentifier()); ApplicationInfo appInfo; Drawable badgedIcon; Drawable appIcon; @@ -217,10 +221,16 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) { Objects.requireNonNull(context); if (icon == null) return null; - if (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { - context.grantUriPermission(context.getPackageName(), - icon.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION); + try { + if (icon.getType() == Icon.TYPE_URI + || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + context.grantUriPermission(context.getPackageName(), + icon.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + return icon.loadDrawable(context); + } catch (Exception e) { + Log.w(TAG, "loadSenderAvatar failed: " + e.getMessage()); + return null; } - return icon.loadDrawable(context); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 1bfb61929297..a93ce01dfc7b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -21,12 +21,14 @@ import static java.lang.annotation.ElementType.LOCAL_VARIABLE; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.content.pm.UserInfo; import android.content.res.Configuration; import android.os.Bundle; import android.os.Looper; import android.service.notification.NotificationListenerService.RankingMap; import android.util.ArraySet; import android.util.Pair; +import android.util.SparseArray; import android.view.View; import androidx.annotation.IntDef; @@ -214,6 +216,13 @@ public interface Bubbles { void onUserChanged(int newUserId); /** + * Called when the current user profiles change. + * + * @param currentProfiles the user infos for the current profile. + */ + void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles); + + /** * Called when config changed. * * @param newConfig the new config. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepository.kt index 66a75af7d64c..130790a04160 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepository.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.bubbles.storage import android.content.Context import android.util.AtomicFile import android.util.Log +import android.util.SparseArray import java.io.File import java.io.FileOutputStream import java.io.IOException @@ -27,8 +28,8 @@ class BubblePersistentRepository(context: Context) { private val bubbleFile: AtomicFile = AtomicFile(File(context.filesDir, "overflow_bubbles.xml"), "overflow-bubbles") - fun persistsToDisk(bubbles: List<BubbleEntity>): Boolean { - if (DEBUG) Log.d(TAG, "persisting ${bubbles.size} bubbles") + fun persistsToDisk(bubbles: SparseArray<List<BubbleEntity>>): Boolean { + if (DEBUG) Log.d(TAG, "persisting ${bubbles.size()} bubbles") synchronized(bubbleFile) { val stream: FileOutputStream = try { bubbleFile.startWrite() } catch (e: IOException) { Log.e(TAG, "Failed to save bubble file", e) @@ -37,7 +38,7 @@ class BubblePersistentRepository(context: Context) { try { writeXml(stream, bubbles) bubbleFile.finishWrite(stream) - if (DEBUG) Log.d(TAG, "persisted ${bubbles.size} bubbles") + if (DEBUG) Log.d(TAG, "persisted ${bubbles.size()} bubbles") return true } catch (e: Exception) { Log.e(TAG, "Failed to save bubble file, restoring backup", e) @@ -47,13 +48,13 @@ class BubblePersistentRepository(context: Context) { return false } - fun readFromDisk(): List<BubbleEntity> { + fun readFromDisk(): SparseArray<List<BubbleEntity>> { synchronized(bubbleFile) { - if (!bubbleFile.exists()) return emptyList() + if (!bubbleFile.exists()) return SparseArray() try { return bubbleFile.openRead().use(::readXml) } catch (e: Throwable) { Log.e(TAG, "Failed to open bubble file", e) } - return emptyList() + return SparseArray() } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt index 7f0b165bdc25..a5267d8be9fe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.bubbles.storage import android.content.pm.LauncherApps import android.os.UserHandle +import android.util.SparseArray import com.android.internal.annotations.VisibleForTesting import com.android.wm.shell.bubbles.ShortcutKey @@ -27,10 +28,11 @@ private const val CAPACITY = 16 * manipulation. */ class BubbleVolatileRepository(private val launcherApps: LauncherApps) { + /** - * An ordered set of bubbles based on their natural ordering. + * Set of bubbles per user. Each set of bubbles is ordered by recency. */ - private var entities = mutableSetOf<BubbleEntity>() + private var entitiesByUser = SparseArray<MutableList<BubbleEntity>>() /** * The capacity of the cache. @@ -39,19 +41,43 @@ class BubbleVolatileRepository(private val launcherApps: LauncherApps) { var capacity = CAPACITY /** - * Returns a snapshot of all the bubbles. + * Returns a snapshot of all the bubbles, a map of the userId to bubble list. */ - val bubbles: List<BubbleEntity> + val bubbles: SparseArray<List<BubbleEntity>> @Synchronized - get() = entities.toList() + get() { + val map = SparseArray<List<BubbleEntity>>() + for (i in 0 until entitiesByUser.size()) { + val k = entitiesByUser.keyAt(i) + val v = entitiesByUser.valueAt(i) + map.put(k, v.toList()) + } + return map + } + + /** + * Returns the entity list of the provided user's bubbles or creates one if it doesn't exist. + */ + @Synchronized + fun getEntities(userId: Int): MutableList<BubbleEntity> { + val entities = entitiesByUser.get(userId) + return when (entities) { + null -> mutableListOf<BubbleEntity>().also { + entitiesByUser.put(userId, it) + } + else -> entities + } + } /** * Add the bubbles to memory and perform a de-duplication. In case a bubble already exists, * it will be moved to the last. */ @Synchronized - fun addBubbles(bubbles: List<BubbleEntity>) { + fun addBubbles(userId: Int, bubbles: List<BubbleEntity>) { if (bubbles.isEmpty()) return + // Get the list for this user + var entities = getEntities(userId) // Verify the size of given bubbles is within capacity, otherwise trim down to capacity val bubblesInRange = bubbles.takeLast(capacity) // To ensure natural ordering of the bubbles, removes bubbles which already exist @@ -61,16 +87,17 @@ class BubbleVolatileRepository(private val launcherApps: LauncherApps) { if (overflowCount > 0) { // Uncache ShortcutInfo of bubbles that will be removed due to capacity uncache(entities.take(overflowCount)) - entities = entities.drop(overflowCount).toMutableSet() + entities = entities.drop(overflowCount).toMutableList() } entities.addAll(bubblesInRange) + entitiesByUser.put(userId, entities) cache(uniqueBubbles) } @Synchronized - fun removeBubbles(bubbles: List<BubbleEntity>) = + fun removeBubbles(userId: Int, bubbles: List<BubbleEntity>) = uncache(bubbles.filter { b: BubbleEntity -> - entities.removeIf { e: BubbleEntity -> b.key == e.key } }) + getEntities(userId).removeIf { e: BubbleEntity -> b.key == e.key } }) private fun cache(bubbles: List<BubbleEntity>) { bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) -> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt index a74445bba1ab..f4fa1835b7a5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt @@ -16,6 +16,8 @@ package com.android.wm.shell.bubbles.storage import android.app.ActivityTaskManager.INVALID_TASK_ID +import android.os.UserHandle +import android.util.SparseArray import android.util.Xml import com.android.internal.util.FastXmlSerializer import com.android.internal.util.XmlUtils @@ -26,8 +28,8 @@ import java.io.InputStream import java.io.OutputStream import java.nio.charset.StandardCharsets -// TODO: handle version changes gracefully -private const val CURRENT_VERSION = 1 +// If this number increases, consider bubbles might be restored even with differences in XML. +private const val CURRENT_VERSION = 2 private const val TAG_BUBBLES = "bs" private const val ATTR_VERSION = "v" @@ -46,13 +48,20 @@ private const val ATTR_LOCUS = "l" * Writes the bubbles in xml format into given output stream. */ @Throws(IOException::class) -fun writeXml(stream: OutputStream, bubbles: List<BubbleEntity>) { +fun writeXml(stream: OutputStream, bubbles: SparseArray<List<BubbleEntity>>) { val serializer: XmlSerializer = FastXmlSerializer() serializer.setOutput(stream, StandardCharsets.UTF_8.name()) serializer.startDocument(null, true) serializer.startTag(null, TAG_BUBBLES) serializer.attribute(null, ATTR_VERSION, CURRENT_VERSION.toString()) - bubbles.forEach { b -> writeXmlEntry(serializer, b) } + for (i in 0 until bubbles.size()) { + val k = bubbles.keyAt(i) + val v = bubbles.valueAt(i) + serializer.startTag(null, TAG_BUBBLES) + serializer.attribute(null, ATTR_USER_ID, k.toString()) + v.forEach { b -> writeXmlEntry(serializer, b) } + serializer.endTag(null, TAG_BUBBLES) + } serializer.endTag(null, TAG_BUBBLES) serializer.endDocument() } @@ -84,16 +93,39 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) { /** * Reads the bubbles from xml file. */ -fun readXml(stream: InputStream): List<BubbleEntity> { - val bubbles = mutableListOf<BubbleEntity>() +fun readXml(stream: InputStream): SparseArray<List<BubbleEntity>> { + val bubbles = SparseArray<List<BubbleEntity>>() val parser: XmlPullParser = Xml.newPullParser() parser.setInput(stream, StandardCharsets.UTF_8.name()) XmlUtils.beginDocument(parser, TAG_BUBBLES) - val version = parser.getAttributeWithName(ATTR_VERSION)?.toInt() - if (version != null && version == CURRENT_VERSION) { + val veryOuterDepth = parser.depth + val version = parser.getAttributeWithName(ATTR_VERSION)?.toInt() ?: return bubbles + if (version == CURRENT_VERSION) { + while (XmlUtils.nextElementWithin(parser, veryOuterDepth)) { + val uid = parser.getAttributeWithName(ATTR_USER_ID) ?: continue + val outerDepth = parser.depth + val userBubbles = mutableListOf<BubbleEntity>() + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + userBubbles.add(readXmlEntry(parser) ?: continue) + } + if (!userBubbles.isEmpty()) { + bubbles.put(uid.toInt(), userBubbles.toList()) + } + } + } else if (version == 1) { + // upgrade v1 to v2 format val outerDepth = parser.depth + val userBubbles = mutableListOf<BubbleEntity>() while (XmlUtils.nextElementWithin(parser, outerDepth)) { - bubbles.add(readXmlEntry(parser) ?: continue) + // We can't tell which profile the bubble was for, so we'll only copy the main users' + // bubbles on upgrade. + val b = readXmlEntry(parser) + if (b != null && b.userId == UserHandle.USER_SYSTEM) { + userBubbles.add(b) + } + } + if (!userBubbles.isEmpty()) { + bubbles.put(UserHandle.USER_SYSTEM, userBubbles.toList()) } } return bubbles diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java index e3594d0cd367..561dff0da6ca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java @@ -19,10 +19,14 @@ package com.android.wm.shell.pip; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityTaskManager; +import android.app.PictureInPictureUiState; import android.content.ComponentName; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; +import android.os.RemoteException; +import android.util.Log; import android.util.Size; import android.view.Display; @@ -185,7 +189,18 @@ public final class PipBoundsState { /** Dictate where PiP currently should be stashed, if at all. */ public void setStashed(@StashType int stashedState) { + if (mStashedState == stashedState) { + return; + } + mStashedState = stashedState; + try { + ActivityTaskManager.getService().onPictureInPictureStateChanged( + new PictureInPictureUiState(stashedState != STASH_TYPE_NONE /* isStashed */) + ); + } catch (RemoteException e) { + Log.e(TAG, "Unable to set alert PiP state change."); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java index 3af0ff0dfb36..97139626a3d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java @@ -21,6 +21,7 @@ import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import android.annotation.DrawableRes; import android.annotation.StringRes; +import android.annotation.SuppressLint; import android.app.PendingIntent; import android.app.RemoteAction; import android.content.BroadcastReceiver; @@ -210,13 +211,16 @@ public class PipMediaController { /** * Gets the set of media actions currently available. */ + // This is due to using PlaybackState#isActive, which is added in API 31. + // It can be removed when min_sdk of the app is set to 31 or greater. + @SuppressLint("NewApi") private List<RemoteAction> getMediaActions() { if (mMediaController == null || mMediaController.getPlaybackState() == null) { return Collections.emptyList(); } ArrayList<RemoteAction> mediaActions = new ArrayList<>(); - boolean isPlaying = mMediaController.getPlaybackState().isActiveState(); + boolean isPlaying = mMediaController.getPlaybackState().isActive(); long actions = mMediaController.getPlaybackState().getActions(); // Prev action diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java index 582ff2180c83..3dd97f565179 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java @@ -21,6 +21,7 @@ import android.content.res.Resources; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; +import android.os.SystemProperties; import android.view.SurfaceControl; import com.android.wm.shell.R; @@ -42,7 +43,8 @@ public class PipSurfaceTransactionHelper { public PipSurfaceTransactionHelper(Context context) { final Resources res = context.getResources(); - mEnableCornerRadius = res.getBoolean(R.bool.config_pipEnableRoundCorner); + mEnableCornerRadius = res.getBoolean(R.bool.config_pipEnableRoundCorner) + || SystemProperties.getBoolean("debug.sf.enable_hole_punch_pip", false); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 3c25a13e94eb..a57e8cdd0928 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -44,6 +44,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.SystemProperties; import android.os.UserHandle; import android.util.Log; import android.util.Pair; @@ -131,7 +132,8 @@ public class PipMenuView extends FrameLayout { inflate(context, R.layout.pip_menu, this); final boolean enableCornerRadius = mContext.getResources() - .getBoolean(R.bool.config_pipEnableRoundCorner); + .getBoolean(R.bool.config_pipEnableRoundCorner) + || SystemProperties.getBoolean("debug.sf.enable_hole_punch_pip", false); mBackgroundDrawable = enableCornerRadius ? mContext.getDrawable(R.drawable.pip_menu_background) : new ColorDrawable(Color.BLACK); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index f29d4f59493e..2a1fe6080cb1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -625,6 +625,7 @@ public class PipTouchHandler { } mMenuController.handlePointerEvent(cloneEvent); + cloneEvent.recycle(); } return true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java index a47483144fef..dd7e29451ffc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java @@ -50,6 +50,7 @@ public class TvPipNotificationController { // Referenced in com.android.systemui.util.NotificationChannels. public static final String NOTIFICATION_CHANNEL = "TVPIP"; private static final String NOTIFICATION_TAG = "TvPip"; + private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF"; private static final String ACTION_SHOW_PIP_MENU = "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU"; @@ -207,7 +208,8 @@ public class TvPipNotificationController { } private static PendingIntent createPendingIntent(Context context, String action) { - return PendingIntent.getBroadcast(context, 0, new Intent(action), + return PendingIntent.getBroadcast(context, 0, + new Intent(action).setPackage(context.getPackageName()), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); } @@ -223,7 +225,7 @@ public class TvPipNotificationController { void register() { if (mRegistered) return; - mContext.registerReceiverForAllUsers(this, mIntentFilter, null /* permission */, + mContext.registerReceiverForAllUsers(this, mIntentFilter, SYSTEMUI_PERMISSION, mMainHandler); mRegistered = true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 147f2e2ec846..f3ae0a1bf717 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -109,11 +109,12 @@ public class SplashscreenContentDrawer { * view on background thread so the view and the drawable can be create and pre-draw in * parallel. * + * @param emptyView Create a splash screen view without icon on it. * @param consumer Receiving the SplashScreenView object, which will also be executed * on splash screen thread. Note that the view can be null if failed. */ - void createContentView(Context context, int splashScreenResId, ActivityInfo info, - int taskId, Consumer<SplashScreenView> consumer) { + void createContentView(Context context, boolean emptyView, int splashScreenResId, + ActivityInfo info, int taskId, Consumer<SplashScreenView> consumer) { mSplashscreenWorkerHandler.post(() -> { SplashScreenView contentView; try { @@ -121,7 +122,11 @@ public class SplashscreenContentDrawer { context, splashScreenResId); if (contentView == null) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "makeSplashScreenContentView"); - contentView = makeSplashScreenContentView(context, info); + if (emptyView) { + contentView = makeEmptySplashScreenContentView(context); + } else { + contentView = makeSplashScreenContentView(context, info); + } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } catch (RuntimeException e) { @@ -190,6 +195,18 @@ public class SplashscreenContentDrawer { } } + private SplashScreenView makeEmptySplashScreenContentView(Context context) { + getWindowAttrs(context, mTmpAttrs); + final StartingWindowViewBuilder builder = new StartingWindowViewBuilder(); + final int themeBGColor = peekWindowBGColor(context); + final SplashScreenView view = builder + .setContext(context) + .setWindowBGColor(themeBGColor) + .build(); + view.setNotCopyable(); + return view; + } + private SplashScreenView makeSplashScreenContentView(Context context, ActivityInfo ai) { updateDensity(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 7037d18decbe..e4b28696bc4f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -317,46 +317,38 @@ public class StartingSurfaceDrawer { // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at // the same time the splash screen thread should be executing Session#relayout. Blocking the // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready. - final Runnable setViewSynchronized; - if (!emptyView) { - // Record whether create splash screen view success, notify to current thread after - // create splash screen view finished. - final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier(); - setViewSynchronized = () -> { - // waiting for setContentView before relayoutWindow - SplashScreenView contentView = viewSupplier.get(); - final StartingWindowRecord record = mStartingWindowRecords.get(taskId); - // if record == null, either the starting window added fail or removed already. - if (record != null) { - // if view == null then creation of content view was failed. - if (contentView != null) { - try { - win.setContentView(contentView); - contentView.cacheRootWindow(win); - } catch (RuntimeException e) { - Slog.w(TAG, "failed set content view to starting window " - + "at taskId: " + taskId, e); - contentView = null; - } + + // Record whether create splash screen view success, notify to current thread after + // create splash screen view finished. + final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier(); + final Runnable setViewSynchronized = () -> { + // waiting for setContentView before relayoutWindow + SplashScreenView contentView = viewSupplier.get(); + final StartingWindowRecord record = mStartingWindowRecords.get(taskId); + // if record == null, either the starting window added fail or removed already. + if (record != null) { + // if view == null then creation of content view was failed. + if (contentView != null) { + try { + win.setContentView(contentView); + contentView.cacheRootWindow(win); + } catch (RuntimeException e) { + Slog.w(TAG, "failed set content view to starting window " + + "at taskId: " + taskId, e); + contentView = null; } - record.setSplashScreenView(contentView); } - }; - mSplashscreenContentDrawer.createContentView(context, - splashscreenContentResId[0], activityInfo, taskId, viewSupplier::setView); - } else { - setViewSynchronized = null; - } + record.setSplashScreenView(contentView); + } + }; + mSplashscreenContentDrawer.createContentView(context, emptyView, + splashscreenContentResId[0], activityInfo, taskId, viewSupplier::setView); try { final View view = win.getDecorView(); final WindowManager wm = mContext.getSystemService(WindowManager.class); postAddWindow(taskId, appToken, view, wm, params); - // all done - if (emptyView) { - return; - } // We use the splash screen worker thread to create SplashScreenView while adding the // window, as otherwise Choreographer#doFrame might be delayed on this thread. // And since Choreographer#doFrame won't happen immediately after adding the window, if diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt index 6494f89997e5..91d51de3a872 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt @@ -74,12 +74,6 @@ class EnterSplitScreenDockActivity( @Test fun dockedStackDividerBecomesVisible() = testSpec.dockedStackDividerBecomesVisible() - @FlakyTest(bugId = 178531736) - @Test - // b/178531736 - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - @Presubmit @Test fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() @@ -88,12 +82,6 @@ class EnterSplitScreenDockActivity( @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - @FlakyTest(bugId = 178531736) - @Test - // b/178531736 - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - @Presubmit @Test fun appWindowIsVisible() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt index 9000f22fb03d..f975ed92e872 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt @@ -83,13 +83,6 @@ class EnterSplitScreenLaunchToSide( // b/169271943 fun dockedStackDividerBecomesVisible() = testSpec.dockedStackDividerBecomesVisible() - @FlakyTest(bugId = 178447631) - @Test - // TODO(b/178447631) Remove Splash Screen from white list when flicker lib - // add a wait for splash screen be gone - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - @Presubmit @Test fun appWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp.defaultWindowName) @@ -102,11 +95,6 @@ class EnterSplitScreenLaunchToSide( @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - @Presubmit - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt index 7d22d4dbe5ab..6bc9a5c5982c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt @@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.platform.test.annotations.Postsubmit import android.provider.Settings import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -97,25 +96,9 @@ class EnterSplitScreenNotSupportNonResizable( prevSupportNonResizableInMultiWindow) } - @FlakyTest(bugId = 178447631) - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - @Test fun dockedStackDividerIsInvisible() = testSpec.dockedStackDividerIsInvisible() - @Test - fun appWindowIsVisible() { - testSpec.assertWmEnd { - isInvisible(nonResizeableApp.defaultWindowName) - } - } - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt index 9b4a10389619..91ca7c1d2a72 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt @@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.platform.test.annotations.Postsubmit import android.provider.Settings import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -92,15 +91,6 @@ class EnterSplitScreenSupportNonResizable( prevSupportNonResizableInMultiWindow) } - @FlakyTest(bugId = 178447631) - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - @Test fun dockedStackDividerIsVisible() = testSpec.dockedStackDividerIsVisible() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt index 64cc85340a38..faf7aa75a1de 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt @@ -78,12 +78,6 @@ class ExitLegacySplitScreenFromBottom( @Test fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(DOCKED_STACK_DIVIDER) - @FlakyTest(bugId = 178447631) - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() { - super.visibleLayersShownMoreThanOneConsecutiveEntry() - } - @FlakyTest @Test fun appWindowBecomesInVisible() = @@ -97,11 +91,6 @@ class ExitLegacySplitScreenFromBottom( @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - @FlakyTest(bugId = 178447631) - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt index 2e115518721f..8845777dda6e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt @@ -82,11 +82,6 @@ class ExitPrimarySplitScreenShowSecondaryFullscreen( @Test fun layerBecomesInvisible() = testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName) - @FlakyTest(bugId = 178447631) - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - @FlakyTest @Test fun appWindowBecomesInVisible() = @@ -100,11 +95,6 @@ class ExitPrimarySplitScreenShowSecondaryFullscreen( @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - @FlakyTest(bugId = 178447631) - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt index 892384561eb2..968aff1ce572 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt @@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.platform.test.annotations.Postsubmit import android.provider.Settings import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -96,16 +95,6 @@ class LegacySplitScreenFromIntentNotSupportNonResizable( prevSupportNonResizableInMultiWindow) } - @FlakyTest(bugId = 178447631) - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @FlakyTest(bugId = 178447631) - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - @Test fun resizableAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt index 2f5e0bddd71f..8d206730b436 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt @@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.platform.test.annotations.Postsubmit import android.provider.Settings import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -94,16 +93,6 @@ class LegacySplitScreenFromIntentSupportNonResizable( prevSupportNonResizableInMultiWindow) } - @FlakyTest(bugId = 178447631) - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @FlakyTest(bugId = 178447631) - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - @Test fun nonResizableAppLayerBecomesVisible() = testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt index a42774d93b5b..4e291d9ef9f0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt @@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.platform.test.annotations.Postsubmit import android.provider.Settings import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -97,16 +96,6 @@ class LegacySplitScreenFromRecentNotSupportNonResizable( prevSupportNonResizableInMultiWindow) } - @FlakyTest(bugId = 178447631) - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @FlakyTest(bugId = 178447631) - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - @Test fun resizableAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(splitScreenApp.defaultWindowName) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt index 14f6deef6ff4..880dc5567d8a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt @@ -19,7 +19,6 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.platform.test.annotations.Postsubmit import android.provider.Settings import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -95,16 +94,6 @@ class LegacySplitScreenFromRecentSupportNonResizable( prevSupportNonResizableInMultiWindow) } - @FlakyTest(bugId = 178447631) - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @FlakyTest(bugId = 178447631) - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - @Test fun nonResizableAppLayerBecomesVisible() = testSpec.layerBecomesVisible(nonResizeableApp.defaultWindowName) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt index 08d5db0f9124..1e89a25c06df 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt @@ -17,13 +17,11 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.view.Surface -import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import org.junit.Test abstract class LegacySplitScreenRotateTransition( testSpec: FlickerTestParameter @@ -46,16 +44,4 @@ abstract class LegacySplitScreenRotateTransition( } } } - - @FlakyTest - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() { - super.visibleLayersShownMoreThanOneConsecutiveEntry() - } - - @FlakyTest - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt index 39f4ce298ff5..976668e60231 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt @@ -118,11 +118,6 @@ class LegacySplitScreenToLauncher( fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(testSpec.config.endRotation) - @FlakyTest - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt index e13056c36684..8684ba52bd52 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt @@ -18,9 +18,9 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.app.Instrumentation import android.content.Context -import android.platform.test.annotations.Presubmit import android.support.test.launcherhelper.LauncherStrategyFactory import android.view.Surface +import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter @@ -102,7 +102,7 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa } } - @Presubmit + @FlakyTest(bugId = 178447631) @Test open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { testSpec.assertWm { @@ -110,7 +110,7 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa } } - @Presubmit + @FlakyTest(bugId = 178447631) @Test open fun visibleLayersShownMoreThanOneConsecutiveEntry() { testSpec.assertLayers { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt index 7cf30ec116eb..69520c291522 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt @@ -65,11 +65,6 @@ class OpenAppToLegacySplitScreen( WindowManagerStateHelper.SPLASH_SCREEN_NAME, WindowManagerStateHelper.SNAPSHOT_WINDOW_NAME) - @FlakyTest(bugId = 178447631) - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - @FlakyTest @Test fun appWindowBecomesVisible() = testSpec.appWindowBecomesVisible(splitScreenApp.getPackage()) @@ -90,11 +85,6 @@ class OpenAppToLegacySplitScreen( @Test fun layerBecomesVisible() = testSpec.layerBecomesVisible(splitScreenApp.getPackage()) - @FlakyTest(bugId = 178447631) - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - @FlakyTest(bugId = 151179149) @Test fun focusChanges() = testSpec.focusChanges(splitScreenApp.`package`, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt index 33ade38d0373..ecbb887e2f9b 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt @@ -104,13 +104,6 @@ class ResizeLegacySplitScreen( @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry() - } - } - @FlakyTest(bugId = 156223549) @Test fun topAppWindowIsAlwaysVisible() { @@ -145,10 +138,6 @@ class ResizeLegacySplitScreen( testSpec.statusBarLayerRotatesScales(testSpec.config.endRotation) @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - @Test fun topAppLayerIsAlwaysVisible() { testSpec.assertLayers { this.isVisible(sSimpleActivity) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt index 2f064ac95204..0972cf2c032f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt @@ -18,8 +18,10 @@ package com.android.wm.shell.bubbles.storage import android.app.ActivityTaskManager.INVALID_TASK_ID import android.testing.AndroidTestingRunner +import android.util.SparseArray import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.bubbles.storage.BubbleXmlHelperTest.Companion.sparseArraysEqual import junit.framework.Assert.assertEquals import junit.framework.Assert.assertNotNull import junit.framework.Assert.assertTrue @@ -31,19 +33,32 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) class BubblePersistentRepositoryTest : ShellTestCase() { - private val bubbles = listOf( - // user, package, shortcut, notification key, height, res-height, title, taskId, locusId - BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0, null, 1, null), - BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428, "title", - 2, null), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0, null, - INVALID_TASK_ID, "key-3") + // user, package, shortcut, notification key, height, res-height, title, taskId, locusId + private val user0Bubbles = listOf( + BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1, null), + BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2, + null), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null, + INVALID_TASK_ID, null) ) + + private val user1Bubbles = listOf( + BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3, null), + BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4, + null), + BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null, + INVALID_TASK_ID, null) + ) + + private val bubbles = SparseArray<List<BubbleEntity>>() + private lateinit var repository: BubblePersistentRepository @Before fun setup() { repository = BubblePersistentRepository(mContext) + bubbles.put(0, user0Bubbles) + bubbles.put(1, user1Bubbles) } @Test @@ -51,9 +66,9 @@ class BubblePersistentRepositoryTest : ShellTestCase() { // Verify read before write doesn't cause FileNotFoundException val actual = repository.readFromDisk() assertNotNull(actual) - assertTrue(actual.isEmpty()) + assertEquals(actual.size(), 0) repository.persistsToDisk(bubbles) - assertEquals(bubbles, repository.readFromDisk()) + assertTrue(sparseArraysEqual(bubbles, repository.readFromDisk())) } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt index 03aa6c2eba12..bfdf5208bbf0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt @@ -21,32 +21,40 @@ import android.content.pm.LauncherApps import android.os.UserHandle import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import org.junit.Test import com.android.wm.shell.ShellTestCase import junit.framework.Assert.assertEquals import org.junit.Before -import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.reset @SmallTest @RunWith(AndroidTestingRunner::class) class BubbleVolatileRepositoryTest : ShellTestCase() { private val user0 = UserHandle.of(0) - private val user10 = UserHandle.of(10) + private val user10_managed = UserHandle.of(10) // In test, acts as workprofile of user0 + private val user11 = UserHandle.of(11) // user, package, shortcut, notification key, height, res-height, title, taskId, locusId - private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0, - null, 1, null) + private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", + "0key-1", 120, 0, null, 1, null) private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob", - "key-2", 0, 16537428, "title", 2, null) - private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0, - null, INVALID_TASK_ID, "key-3") + "10key-2", 0, 16537428, "title", 2, null) + private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", + "0key-3", 120, 0, null, INVALID_TASK_ID, null) - private val bubbles = listOf(bubble1, bubble2, bubble3) + private val bubble11 = BubbleEntity(11, "com.example.messenger", + "shortcut-1", "01key-1", 120, 0, null, 3) + private val bubble12 = BubbleEntity(11, "com.example.chat", "alice and bob", + "11key-2", 0, 16537428, "title", INVALID_TASK_ID) + + private val user0bubbles = listOf(bubble1, bubble2, bubble3) + private val user11bubbles = listOf(bubble11, bubble12) private lateinit var repository: BubbleVolatileRepository private lateinit var launcherApps: LauncherApps @@ -59,51 +67,74 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { @Test fun testAddBubbles() { - repository.addBubbles(bubbles) - assertEquals(bubbles, repository.bubbles) + repository.addBubbles(user0.identifier, user0bubbles) + repository.addBubbles(user11.identifier, user11bubbles) + + assertEquals(user0bubbles, repository.getEntities(user0.identifier).toList()) + assertEquals(user11bubbles, repository.getEntities(user11.identifier).toList()) + verify(launcherApps).cacheShortcuts(eq(PKG_MESSENGER), eq(listOf("shortcut-1", "shortcut-2")), eq(user0), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) verify(launcherApps).cacheShortcuts(eq(PKG_CHAT), - eq(listOf("alice and bob")), eq(user10), + eq(listOf("alice and bob")), eq(user10_managed), + eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) + + verify(launcherApps).cacheShortcuts(eq(PKG_MESSENGER), + eq(listOf("shortcut-1")), eq(user11), + eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) + verify(launcherApps).cacheShortcuts(eq(PKG_CHAT), + eq(listOf("alice and bob")), eq(user11), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) - repository.addBubbles(listOf(bubble1)) - assertEquals(listOf(bubble2, bubble3, bubble1), repository.bubbles) - verifyNoMoreInteractions(launcherApps) + repository.addBubbles(user0.identifier, listOf(bubble1)) + assertEquals(listOf(bubble2, bubble3, bubble1), repository.getEntities(user0.identifier)) + + repository.addBubbles(user11.identifier, listOf(bubble12)) + assertEquals(listOf(bubble11, bubble12), repository.getEntities(user11.identifier)) + + Mockito.verifyNoMoreInteractions(launcherApps) } @Test fun testRemoveBubbles() { - repository.addBubbles(bubbles) - assertEquals(bubbles, repository.bubbles) + repository.addBubbles(user0.identifier, user0bubbles) + repository.addBubbles(user11.identifier, user11bubbles) - repository.removeBubbles(listOf(bubble3)) - assertEquals(listOf(bubble1, bubble2), repository.bubbles) + repository.removeBubbles(user0.identifier, listOf(bubble3)) + assertEquals(listOf(bubble1, bubble2), repository.getEntities(user0.identifier).toList()) verify(launcherApps).uncacheShortcuts(eq(PKG_MESSENGER), eq(listOf("shortcut-2")), eq(user0), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) + + reset(launcherApps) + + repository.removeBubbles(user11.identifier, listOf(bubble12)) + assertEquals(listOf(bubble11), repository.getEntities(user11.identifier).toList()) + verify(launcherApps).uncacheShortcuts(eq(PKG_CHAT), + eq(listOf("alice and bob")), eq(user11), + eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) } @Test fun testAddAndRemoveBubblesWhenExceedingCapacity() { repository.capacity = 2 // push bubbles beyond capacity - repository.addBubbles(bubbles) + repository.addBubbles(user0.identifier, user0bubbles) // verify it is trim down to capacity - assertEquals(listOf(bubble2, bubble3), repository.bubbles) + assertEquals(listOf(bubble2, bubble3), repository.getEntities(user0.identifier).toList()) verify(launcherApps).cacheShortcuts(eq(PKG_MESSENGER), eq(listOf("shortcut-2")), eq(user0), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) verify(launcherApps).cacheShortcuts(eq(PKG_CHAT), - eq(listOf("alice and bob")), eq(user10), + eq(listOf("alice and bob")), eq(user10_managed), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) - repository.addBubbles(listOf(bubble1)) - // verify the oldest bubble is popped - assertEquals(listOf(bubble3, bubble1), repository.bubbles) + repository.addBubbles(user0.identifier, listOf(bubble1)) + // verify the oldest bubble is popped 2, 3 + assertEquals(listOf(bubble3, bubble1), repository.getEntities(user0.identifier).toList()) verify(launcherApps).uncacheShortcuts(eq(PKG_CHAT), - eq(listOf("alice and bob")), eq(user10), + eq(listOf("alice and bob")), eq(user10_managed), eq(LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)) } @@ -111,14 +142,14 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { fun testAddBubbleMatchesByKey() { val bubble = BubbleEntity(0, "com.example.pkg", "shortcut-id", "key", 120, 0, "title", 1, null) - repository.addBubbles(listOf(bubble)) - assertEquals(bubble, repository.bubbles.get(0)) + repository.addBubbles(user0.identifier, listOf(bubble)) + assertEquals(bubble, repository.getEntities(user0.identifier).get(0)) // Same key as first bubble but different entry val bubbleModified = BubbleEntity(0, "com.example.pkg", "shortcut-id", "key", 120, 0, - "different title", 2, null) - repository.addBubbles(listOf(bubbleModified)) - assertEquals(bubbleModified, repository.bubbles.get(0)) + "different title", 2) + repository.addBubbles(user0.identifier, listOf(bubbleModified)) + assertEquals(bubbleModified, repository.getEntities(user0.identifier).get(0)) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt index 8d719e7a7378..4ab9f87dbbf6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt @@ -18,10 +18,12 @@ package com.android.wm.shell.bubbles.storage import android.app.ActivityTaskManager.INVALID_TASK_ID import android.testing.AndroidTestingRunner +import android.util.SparseArray import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase import junit.framework.Assert.assertEquals import junit.framework.Assert.assertTrue +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import java.io.ByteArrayInputStream @@ -31,21 +33,65 @@ import java.io.ByteArrayOutputStream @RunWith(AndroidTestingRunner::class) class BubbleXmlHelperTest : ShellTestCase() { - private val bubbles = listOf( - // user, package, shortcut, notification key, height, res-height, title, taskId, locusId - BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0, null, 1), - BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428, "title", - 2, null), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0, null, + private val user0Bubbles = listOf( + BubbleEntity(0, "com.example.messenger", "shortcut-1", "0k1", 120, 0, null, 1), + BubbleEntity(10, "com.example.chat", "alice and bob", "0k2", 0, 16537428, "title", 2, + null), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "0k3", 120, 0, null, INVALID_TASK_ID, "l3") ) + private val user1Bubbles = listOf( + BubbleEntity(1, "com.example.messenger", "shortcut-1", "1k1", 120, 0, null, 3), + BubbleEntity(12, "com.example.chat", "alice and bob", "1k2", 0, 16537428, "title", 4, + null), + BubbleEntity(1, "com.example.messenger", "shortcut-2", "1k3", 120, 0, null, + INVALID_TASK_ID, "l4") + ) + + private val bubbles = SparseArray<List<BubbleEntity>>() + + // Checks that the contents of the two sparse arrays are the same. + companion object { + fun sparseArraysEqual( + one: SparseArray<List<BubbleEntity>>?, + two: SparseArray<List<BubbleEntity>>? + ): Boolean { + if (one == null && two == null) return true + if ((one == null) != (two == null)) return false + if (one!!.size() != two!!.size()) return false + for (i in 0 until one.size()) { + val k1 = one.keyAt(i) + val v1 = one.valueAt(i) + val k2 = two.keyAt(i) + val v2 = two.valueAt(i) + if (k1 != k2 && v1 != v2) { + return false + } + } + return true + } + } + + @Before + fun setup() { + bubbles.put(0, user0Bubbles) + bubbles.put(1, user1Bubbles) + } + @Test fun testWriteXml() { val expectedEntries = """ -<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" tid="1" /> -<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" tid="2" /> -<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" tid="-1" l="l3" /> +<bs uid="0"> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="0k1" h="120" hid="0" tid="1" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="0k2" h="0" hid="16537428" t="title" tid="2" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="0k3" h="120" hid="0" tid="-1" l="l3" /> +</bs> +<bs uid="1"> +<bb uid="1" pkg="com.example.messenger" sid="shortcut-1" key="1k1" h="120" hid="0" tid="3" /> +<bb uid="12" pkg="com.example.chat" sid="alice and bob" key="1k2" h="0" hid="16537428" t="title" tid="4" /> +<bb uid="1" pkg="com.example.messenger" sid="shortcut-2" key="1k3" h="120" hid="0" tid="-1" l="l4" /> +</bs> """.trimIndent() ByteArrayOutputStream().use { writeXml(it, bubbles) @@ -59,19 +105,26 @@ class BubbleXmlHelperTest : ShellTestCase() { fun testReadXml() { val src = """ <?xml version='1.0' encoding='utf-8' standalone='yes' ?> -<bs v="1"> -<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" tid="1" /> -<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" tid="2" /> -<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" tid="-1" l="l3" /> +<bs v="2"> +<bs uid="0"> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="0k1" h="120" hid="0" tid="1" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="0k2" h="0" hid="16537428" t="title" tid="2" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="0k3" h="120" hid="0" tid="-1" l="l3" /> +</bs> +<bs uid="1"> +<bb uid="1" pkg="com.example.messenger" sid="shortcut-1" key="1k1" h="120" hid="0" tid="3" /> +<bb uid="12" pkg="com.example.chat" sid="alice and bob" key="1k2" h="0" hid="16537428" t="title" tid="4" /> +<bb uid="1" pkg="com.example.messenger" sid="shortcut-2" key="1k3" h="120" hid="0" tid="-1" l="l4" /> +</bs> </bs> """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) - assertEquals("failed parsing bubbles from xml\n$src", bubbles, actual) + assertTrue("failed parsing bubbles from xml\n$src", sparseArraysEqual(bubbles, actual)) } - // TODO: We should handle upgrades gracefully but this is v1 + // V0 -> V1 happened prior to release / during dogfood so nothing is saved @Test - fun testUpgradeDropsPreviousData() { + fun testUpgradeFromV0DropsPreviousData() { val src = """ <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <bs> @@ -81,7 +134,7 @@ class BubbleXmlHelperTest : ShellTestCase() { </bs> """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) - assertEquals("failed parsing bubbles from xml\n$src", emptyList<BubbleEntity>(), actual) + assertEquals("failed parsing bubbles from xml\n$src", 0, actual.size()) } /** @@ -91,24 +144,25 @@ class BubbleXmlHelperTest : ShellTestCase() { */ @Test fun testReadXMLWithoutTaskId() { - val expectedBubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0, null, - INVALID_TASK_ID), - BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428, "title", - INVALID_TASK_ID), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0, null, - INVALID_TASK_ID) - ) + val expectedBubbles = SparseArray<List<BubbleEntity>>() + expectedBubbles.put(0, listOf( + BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0, + null, INVALID_TASK_ID), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0, + null, INVALID_TASK_ID)) + ) val src = """ <?xml version='1.0' encoding='utf-8' standalone='yes' ?> -<bs v="1"> +<bs v="2"> +<bs uid="0"> <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> -<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" /> <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> </bs> +</bs> """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) - assertEquals("failed parsing bubbles from xml\n$src", expectedBubbles, actual) + assertTrue("failed parsing bubbles from xml\n$src", + sparseArraysEqual(expectedBubbles, actual)) } /** @@ -117,23 +171,45 @@ class BubbleXmlHelperTest : ShellTestCase() { */ @Test fun testXMLWithoutLocusToLocus() { - val expectedBubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0, null, - INVALID_TASK_ID, null), - BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428, "title", - INVALID_TASK_ID, null), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0, null, - INVALID_TASK_ID, null) + val expectedBubbles = SparseArray<List<BubbleEntity>>() + expectedBubbles.put(0, listOf( + BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0, + null, INVALID_TASK_ID), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0, + null, INVALID_TASK_ID)) ) val src = """ <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <bs v="1"> <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> -<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" /> <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> </bs> """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) - assertEquals("failed parsing bubbles from xml\n$src", expectedBubbles, actual) + assertTrue("failed parsing bubbles from xml\n$src", + sparseArraysEqual(expectedBubbles, actual)) + } + + @Test + fun testUpgradeToV2SavesPreviousData() { + val src = """ + <?xml version='1.0' encoding='utf-8' standalone='yes' ?> + <bs v="1"> + <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> + <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" /> + <bb uid="2" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> + <bb uid="0" pkg="com.example.messenger" sid="shortcut-4" key="k4" h="0" hid="16537428" /> + </bs> + """.trimIndent() + val expectedBubbles = SparseArray<List<BubbleEntity>>() + expectedBubbles.put(0, listOf( + BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0, + null, INVALID_TASK_ID, null), + BubbleEntity(0, "com.example.messenger", "shortcut-4", "k4", 0, 16537428, + null, INVALID_TASK_ID, null)) + ) + val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) + assertTrue("failed parsing bubbles from xml\n$src", + sparseArraysEqual(expectedBubbles, actual)) } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java index dea24d3c2ec0..a6215d3347a8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java @@ -38,6 +38,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.function.Consumer; + /** * Tests for {@link PipBoundsState}. */ @@ -178,4 +180,20 @@ public class PipBoundsStateTest extends ShellTestCase { mPipBoundsState.setOverrideMinSize(new Size(15, 10)); assertEquals(10, mPipBoundsState.getOverrideMinEdgeSize()); } + + @Test + public void testSetBounds_updatesPipExclusionBounds() { + final Consumer<Rect> callback = mock(Consumer.class); + final Rect currentBounds = new Rect(10, 10, 20, 15); + final Rect newBounds = new Rect(50, 50, 100, 75); + mPipBoundsState.setBounds(currentBounds); + + mPipBoundsState.setPipExclusionBoundsChangeCallback(callback); + // Setting the listener immediately calls back with the current bounds. + verify(callback).accept(currentBounds); + + mPipBoundsState.setBounds(newBounds); + // Updating the bounds makes the listener call back back with the new rect. + verify(callback).accept(newBounds); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 5df391f919a7..9d7c82bb8550 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -19,6 +19,7 @@ package com.android.wm.shell.pip; import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; @@ -31,6 +32,7 @@ import android.app.ActivityManager; import android.app.PictureInPictureParams; import android.content.ComponentName; import android.content.pm.ActivityInfo; +import android.graphics.Rect; import android.os.RemoteException; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -222,6 +224,16 @@ public class PipTaskOrganizerTest extends ShellTestCase { assertEquals(minSize, mPipBoundsState.getOverrideMinSize()); } + @Test + public void onTaskVanished_clearsPipBounds() { + mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, + createPipParams(null)), null /* leash */); + mPipBoundsState.setBounds(new Rect(100, 100, 200, 150)); + + mSpiedPipTaskOrganizer.onTaskVanished(createTaskInfo(mComponent1, createPipParams(null))); + assertTrue(mPipBoundsState.getBounds().isEmpty()); + } + private void preparePipTaskOrg() { final DisplayInfo info = new DisplayInfo(); mPipBoundsState.setDisplayLayout(new DisplayLayout(info, |