Default to using SplitSelectSource drawable if TaskView icon drawable is null
* Alternative solution could be to set
onTaskViewVisibilityChanged(true) for the taskView that is about
to be dismissed so it loads it's taskIcon/thumbnail back from the cache
* However, that does still leave us open to race conditions (even though
we can be reasonably confident the icon is probably in the cache)
* Also made other changes to allow already public fields on some classes
to be mockable for unit testing
Fixes: 275267738
Test: Tested with fullscreen task at end of overview,
GroupedTaskView at end of overview,
Initiating split from home,
Initiating split from overview actions,
Initiating split from overview app icon
Change-Id: Ic9059c93c07b90f61c9f418d5d36d6ba201ff96a
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 5e22703..7318298 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -639,7 +639,7 @@
PendingAnimation anim = new PendingAnimation(TABLET_HOME_TO_SPLIT.getDuration());
RectF startingTaskRect = new RectF();
final FloatingTaskView floatingTaskView = FloatingTaskView.getFloatingTaskView(this,
- source.view, null /* thumbnail */, source.drawable, startingTaskRect);
+ source.getView(), null /* thumbnail */, source.getDrawable(), startingTaskRect);
floatingTaskView.setAlpha(1);
floatingTaskView.addStagingAnimation(anim, startingTaskRect, tempRect,
false /* fadeWithThumbnail */, true /* isStagedTask */);
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 6dd67de..b76fe5c 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -24,6 +24,7 @@
import com.android.launcher3.DeviceProfile
import com.android.launcher3.anim.PendingAnimation
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
+import com.android.quickstep.views.IconView
import com.android.quickstep.views.TaskThumbnailView
import com.android.quickstep.views.TaskView
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
@@ -52,21 +53,22 @@
* depending on the state of the surface from which the split was initiated
*/
fun getFirstAnimInitViews(taskViewSupplier: Supplier<TaskView>,
- splitSelectSourceSupplier: Supplier<SplitSelectSource>)
+ splitSelectSourceSupplier: Supplier<SplitSelectSource?>)
: SplitAnimInitProps {
+ val splitSelectSource = splitSelectSourceSupplier.get()
if (!splitSelectStateController.isAnimateCurrentTaskDismissal) {
// Initiating from home
- val splitSelectSource = splitSelectSourceSupplier.get()
- return SplitAnimInitProps(splitSelectSource.view, originalBitmap = null,
+ return SplitAnimInitProps(splitSelectSource!!.view, originalBitmap = null,
splitSelectSource.drawable, fadeWithThumbnail = false, isStagedTask = true,
iconView = null)
} else if (splitSelectStateController.isDismissingFromSplitPair) {
// Initiating split from overview, but on a split pair
val taskView = taskViewSupplier.get()
for (container : TaskIdAttributeContainer in taskView.taskIdAttributeContainers) {
- if (container.task.key.id == splitSelectStateController.initialTaskId) {
+ if (container.task.getKey().getId() == splitSelectStateController.initialTaskId) {
+ val drawable = getDrawable(container.iconView, splitSelectSource)
return SplitAnimInitProps(container.thumbnailView,
- container.thumbnailView.thumbnail, container.iconView.drawable!!,
+ container.thumbnailView.thumbnail, drawable!!,
fadeWithThumbnail = true, isStagedTask = true,
iconView = container.iconView
)
@@ -77,14 +79,28 @@
} else {
// Initiating split from overview on fullscreen task TaskView
val taskView = taskViewSupplier.get()
+ val drawable = getDrawable(taskView.iconView, splitSelectSource)
return SplitAnimInitProps(taskView.thumbnail, taskView.thumbnail.thumbnail,
- taskView.iconView.drawable!!, fadeWithThumbnail = true, isStagedTask = true,
+ drawable!!, fadeWithThumbnail = true, isStagedTask = true,
taskView.iconView
)
}
}
/**
+ * Returns the drawable that's provided in iconView, however if that
+ * is null it falls back to the drawable that's in splitSelectSource.
+ * TaskView's icon drawable can be null if the TaskView is scrolled far enough off screen
+ * @return [Drawable]
+ */
+ fun getDrawable(iconView: IconView, splitSelectSource: SplitSelectSource?) : Drawable? {
+ if (iconView.drawable == null && splitSelectSource != null) {
+ return splitSelectSource.drawable
+ }
+ return iconView.drawable
+ }
+
+ /**
* When selecting first app from split pair, second app's thumbnail remains. This animates
* the second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying
* it with [TaskThumbnailView]'s splashView. Adds animations to the provided builder.
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
new file mode 100644
index 0000000..7e07b81
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.quickstep.util
+
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.quickstep.views.GroupedTaskView
+import com.android.quickstep.views.IconView
+import com.android.quickstep.views.TaskThumbnailView
+import com.android.quickstep.views.TaskView
+import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
+import com.android.systemui.shared.recents.model.Task
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class SplitAnimationControllerTest {
+
+ private val taskId = 9
+
+ @Mock lateinit var mockSplitSelectStateController: SplitSelectStateController
+ // TaskView
+ @Mock lateinit var mockTaskView: TaskView
+ @Mock lateinit var mockThumbnailView: TaskThumbnailView
+ @Mock lateinit var mockBitmap: Bitmap
+ @Mock lateinit var mockIconView: IconView
+ @Mock lateinit var mockTaskViewDrawable: Drawable
+ // GroupedTaskView
+ @Mock lateinit var mockGroupedTaskView: GroupedTaskView
+ @Mock lateinit var mockTask: Task
+ @Mock lateinit var mockTaskKey: Task.TaskKey
+ @Mock lateinit var mockTaskIdAttributeContainer: TaskIdAttributeContainer
+
+ // SplitSelectSource
+ @Mock lateinit var splitSelectSource: SplitConfigurationOptions.SplitSelectSource
+ @Mock lateinit var mockSplitSourceDrawable: Drawable
+ @Mock lateinit var mockSplitSourceView: View
+
+ lateinit var splitAnimationController: SplitAnimationController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(mockTaskView.thumbnail).thenReturn(mockThumbnailView)
+ whenever(mockThumbnailView.thumbnail).thenReturn(mockBitmap)
+ whenever(mockTaskView.iconView).thenReturn(mockIconView)
+ whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable)
+
+ whenever(splitSelectSource.drawable).thenReturn(mockSplitSourceDrawable)
+ whenever(splitSelectSource.view).thenReturn(mockSplitSourceView)
+
+ splitAnimationController = SplitAnimationController(mockSplitSelectStateController)
+ }
+
+ @Test
+ fun getFirstAnimInitViews_nullTaskViewIcon_useSplitSourceIcon() {
+ // Hit fullscreen task dismissal state
+ whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(true)
+ whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(false)
+
+ // Missing taskView icon
+ whenever(mockIconView.drawable).thenReturn(null)
+
+ val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
+ splitAnimationController.getFirstAnimInitViews(
+ { mockTaskView }, { splitSelectSource })
+
+ assertEquals("Did not fallback to use splitSource icon drawable",
+ mockSplitSourceDrawable, splitAnimInitProps.iconDrawable)
+ }
+
+ @Test
+ fun getFirstAnimInitViews_validTaskViewIcon_useTaskViewIcon() {
+ // Hit fullscreen task dismissal state
+ whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(true)
+ whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(false)
+
+ val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
+ splitAnimationController.getFirstAnimInitViews(
+ { mockTaskView }, { splitSelectSource })
+
+ assertEquals("Did not use taskView icon drawable", mockTaskViewDrawable,
+ splitAnimInitProps.iconDrawable)
+ }
+
+ @Test
+ fun getFirstAnimInitViews_validTaskViewNullSplitSource_useTaskViewIcon() {
+ // Hit fullscreen task dismissal state
+ whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(true)
+ whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(false)
+
+ // Set split source to null
+ whenever(splitSelectSource.drawable).thenReturn(null)
+
+ val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
+ splitAnimationController.getFirstAnimInitViews(
+ { mockTaskView }, { splitSelectSource })
+
+ assertEquals("Did not use taskView icon drawable", mockTaskViewDrawable,
+ splitAnimInitProps.iconDrawable)
+ }
+
+ @Test
+ fun getFirstAnimInitViews_nullTaskViewValidSplitSource_noTaskDismissal() {
+ // Hit initiating split from home
+ whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(false)
+ whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(false)
+
+ val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
+ splitAnimationController.getFirstAnimInitViews(
+ { mockTaskView }, { splitSelectSource })
+
+ assertEquals("Did not use splitSource icon drawable", mockSplitSourceDrawable,
+ splitAnimInitProps.iconDrawable)
+ }
+
+ @Test
+ fun getFirstAnimInitViews_nullTaskViewValidSplitSource_groupedTaskView() {
+ // Hit groupedTaskView dismissal
+ whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(true)
+ whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(true)
+
+ // Remove icon view from GroupedTaskView
+ whenever(mockIconView.drawable).thenReturn(null)
+
+ whenever(mockTaskIdAttributeContainer.task).thenReturn(mockTask)
+ whenever(mockTaskIdAttributeContainer.iconView).thenReturn(mockIconView)
+ whenever(mockTaskIdAttributeContainer.thumbnailView).thenReturn(mockThumbnailView)
+ whenever(mockTask.getKey()).thenReturn(mockTaskKey)
+ whenever(mockTaskKey.getId()).thenReturn(taskId)
+ whenever(mockSplitSelectStateController.initialTaskId).thenReturn(taskId)
+ whenever(mockGroupedTaskView.taskIdAttributeContainers)
+ .thenReturn(Array(1) { mockTaskIdAttributeContainer })
+ val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
+ splitAnimationController.getFirstAnimInitViews(
+ { mockGroupedTaskView }, { splitSelectSource })
+
+ assertEquals("Did not use splitSource icon drawable", mockSplitSourceDrawable,
+ splitAnimInitProps.iconDrawable)
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 8c5e782..1ae43d0 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -200,8 +200,8 @@
/** Keep in sync w/ ActivityTaskManager#INVALID_TASK_ID (unreference-able) */
private static final int INVALID_TASK_ID = -1;
- public final View view;
- public final Drawable drawable;
+ private View view;
+ private Drawable drawable;
public final Intent intent;
public final SplitPositionOption position;
public final ItemInfo itemInfo;
@@ -224,5 +224,13 @@
this.itemInfo = itemInfo;
this.splitEvent = splitEvent;
}
+
+ public Drawable getDrawable() {
+ return drawable;
+ }
+
+ public View getView() {
+ return view;
+ }
}
}