summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJainam Shah <jainams@google.com>2023-12-09 01:11:33 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2023-12-09 01:11:33 +0000
commitf912a1cc7d94e9fbf86dd4e22737d0cbcb84c6b5 (patch)
treecba15b9bb32a9c643dbf312ef70bb1756afae4ab
parent98319d5eabab616a10f44d0df60a2ef0b6d46279 (diff)
parentcb22dc5ec2dd26ade4cdecd8ce68ae99fa897703 (diff)
downloadLauncher-f912a1cc7d94e9fbf86dd4e22737d0cbcb84c6b5.tar.gz
Merge "Add pin shortcut to Dock items" into main
-rw-r--r--docklib-util/Android.bp1
-rw-r--r--docklib-util/res/drawable/ic_dock_unpin.xml10
-rw-r--r--docklib-util/res/values/strings.xml1
-rw-r--r--docklib-util/src/com/android/car/dockutil/events/DockEventSenderHelper.java11
-rw-r--r--docklib-util/src/com/android/car/dockutil/shortcuts/PinShortcutItem.kt47
-rw-r--r--docklib-util/tests/Android.bp6
-rw-r--r--docklib-util/tests/src/com/android/car/dockutil/shortcuts/PinShortcutItemTest.kt43
-rw-r--r--docklib/res/values/dimens.xml3
-rw-r--r--docklib/src/com/android/car/docklib/data/DockAppItem.kt1
-rw-r--r--docklib/src/com/android/car/docklib/view/DockAdapter.kt60
-rw-r--r--docklib/src/com/android/car/docklib/view/DockItemLongClickListener.kt72
-rw-r--r--docklib/src/com/android/car/docklib/view/DockItemViewHolder.kt60
-rw-r--r--docklib/tests/src/com/android/car/docklib/data/DockAppItemTest.kt12
-rw-r--r--docklib/tests/src/com/android/car/docklib/view/DockAdapterTest.kt56
-rw-r--r--docklib/tests/src/com/android/car/docklib/view/DockItemLongClickListenerTest.kt89
-rw-r--r--libs/appgrid/lib/src/com/android/car/carlauncher/AppLauncherUtils.java27
16 files changed, 450 insertions, 49 deletions
diff --git a/docklib-util/Android.bp b/docklib-util/Android.bp
index 0aa1173e..d7e2f5a8 100644
--- a/docklib-util/Android.bp
+++ b/docklib-util/Android.bp
@@ -30,6 +30,7 @@ android_library {
static_libs: [
"androidx.lifecycle_lifecycle-extensions",
"com.google.android.material_material",
+ "car-ui-lib",
],
manifest: "AndroidManifest.xml",
diff --git a/docklib-util/res/drawable/ic_dock_unpin.xml b/docklib-util/res/drawable/ic_dock_unpin.xml
new file mode 100644
index 00000000..ea3a2912
--- /dev/null
+++ b/docklib-util/res/drawable/ic_dock_unpin.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M336,680L480,536L624,680L680,624L536,480L680,336L624,280L480,424L336,280L280,336L424,480L280,624L336,680ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
+</vector>
diff --git a/docklib-util/res/values/strings.xml b/docklib-util/res/values/strings.xml
index 38b2ca56..2c24df71 100644
--- a/docklib-util/res/values/strings.xml
+++ b/docklib-util/res/values/strings.xml
@@ -18,4 +18,5 @@
<resources>
<!-- todo(b/314817575): update the string to final value -->
<string name="dock_pin_shortcut_label">Pin to the dock</string>
+ <string name="dock_unpin_shortcut_label">Unpin from the dock</string>
</resources>
diff --git a/docklib-util/src/com/android/car/dockutil/events/DockEventSenderHelper.java b/docklib-util/src/com/android/car/dockutil/events/DockEventSenderHelper.java
index 3353617b..4f8b52f6 100644
--- a/docklib-util/src/com/android/car/dockutil/events/DockEventSenderHelper.java
+++ b/docklib-util/src/com/android/car/dockutil/events/DockEventSenderHelper.java
@@ -64,13 +64,20 @@ public class DockEventSenderHelper {
}
/**
- * Used to send unpin event to the dock. Generally used when an app should be unpinned from the
- * dock.
+ * @see #sendUnpinEvent(ComponentName)
*/
public void sendUnpinEvent(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
sendEventBroadcast(DockEvent.UNPIN, taskInfo);
}
+ /**
+ * Used to send unpin event to the dock. Generally used when an app should be unpinned from the
+ * dock.
+ */
+ public void sendUnpinEvent(@NonNull ComponentName componentName) {
+ sendEventBroadcast(DockEvent.UNPIN, componentName);
+ }
+
@VisibleForTesting
void sendEventBroadcast(@NonNull DockEvent event,
@NonNull ActivityManager.RunningTaskInfo taskInfo) {
diff --git a/docklib-util/src/com/android/car/dockutil/shortcuts/PinShortcutItem.kt b/docklib-util/src/com/android/car/dockutil/shortcuts/PinShortcutItem.kt
new file mode 100644
index 00000000..6aba1579
--- /dev/null
+++ b/docklib-util/src/com/android/car/dockutil/shortcuts/PinShortcutItem.kt
@@ -0,0 +1,47 @@
+package com.android.car.dockutil.shortcuts
+
+import android.content.res.Resources
+import com.android.car.dockutil.R
+import com.android.car.ui.shortcutspopup.CarUiShortcutsPopup
+
+/**
+ * {@link CarUiShortcutsPopup.ShortcutItem} to pin or unpin an app to the dock.
+ * @param isItemPinned if the app is pinned to the dock
+ * @param pinItemClickDelegate {@link Runnable} to pin the app to the dock
+ * @param unpinItemClickDelegate {@link Runnable} to unpin the app to the dock
+ */
+class PinShortcutItem(
+ private val resources: Resources,
+ private val isItemPinned: Boolean,
+ private val pinItemClickDelegate: Runnable,
+ private val unpinItemClickDelegate: Runnable
+) : CarUiShortcutsPopup.ShortcutItem {
+
+ override fun data(): CarUiShortcutsPopup.ItemData {
+ return if (isItemPinned) {
+ CarUiShortcutsPopup.ItemData(
+ R.drawable.ic_dock_unpin, // leftDrawable
+ resources.getString(R.string.dock_unpin_shortcut_label) // shortcutName
+ )
+ } else {
+ CarUiShortcutsPopup.ItemData(
+ R.drawable.ic_dock_pin, // leftDrawable
+ resources.getString(R.string.dock_pin_shortcut_label) // shortcutName
+ )
+ }
+ }
+
+ override fun onClick(): Boolean {
+ // todo(b/314835197): fix pinning/opening media apps
+ if (isItemPinned) {
+ unpinItemClickDelegate.run()
+ } else {
+ pinItemClickDelegate.run()
+ }
+ return true
+ }
+
+ override fun isEnabled(): Boolean {
+ return true
+ }
+}
diff --git a/docklib-util/tests/Android.bp b/docklib-util/tests/Android.bp
index 91339856..6883e975 100644
--- a/docklib-util/tests/Android.bp
+++ b/docklib-util/tests/Android.bp
@@ -21,7 +21,10 @@ package {
android_test {
name: "CarDockUtilLibTests",
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
libs: [
"android.test.base",
@@ -35,6 +38,7 @@ android_test {
"androidx.test.runner",
"androidx.test.ext.junit",
"mockito-target-extended",
+ "mockito-kotlin2",
"truth",
"CarDockUtilLib",
],
diff --git a/docklib-util/tests/src/com/android/car/dockutil/shortcuts/PinShortcutItemTest.kt b/docklib-util/tests/src/com/android/car/dockutil/shortcuts/PinShortcutItemTest.kt
new file mode 100644
index 00000000..d3498aee
--- /dev/null
+++ b/docklib-util/tests/src/com/android/car/dockutil/shortcuts/PinShortcutItemTest.kt
@@ -0,0 +1,43 @@
+package com.android.car.dockutil.shortcuts
+
+import android.content.res.Resources
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidJUnit4::class)
+class PinShortcutItemTest {
+ private val resourcesMock = mock<Resources> {}
+ private val pinItemClickDelegateMock = mock<Runnable> {}
+ private val unpinItemClickDelegateMock = mock<Runnable> {}
+
+ @Test
+ fun onClick_pinnedItem_runUnpinDelegate() {
+ val pinShortcutItem = PinShortcutItem(
+ resourcesMock,
+ isItemPinned = true,
+ pinItemClickDelegateMock,
+ unpinItemClickDelegateMock
+ )
+
+ pinShortcutItem.onClick()
+
+ verify(unpinItemClickDelegateMock).run()
+ }
+
+ @Test
+ fun onClick_unpinnedItem_runPinDelegate() {
+ val pinShortcutItem = PinShortcutItem(
+ resourcesMock,
+ isItemPinned = false,
+ pinItemClickDelegateMock,
+ unpinItemClickDelegateMock
+ )
+
+ pinShortcutItem.onClick()
+
+ verify(pinItemClickDelegateMock).run()
+ }
+}
diff --git a/docklib/res/values/dimens.xml b/docklib/res/values/dimens.xml
index 3005a475..d38260fa 100644
--- a/docklib/res/values/dimens.xml
+++ b/docklib/res/values/dimens.xml
@@ -21,7 +21,8 @@
<dimen name="dock_item_size">72dp</dimen>
<dimen name="dock_item_spacing">2dp</dimen>
- <dimen name="icon_stroke_width">6dp</dimen>
+ <dimen name="static_icon_stroke_width">0dp</dimen>
+ <dimen name="dynamic_icon_stroke_width">6dp</dimen>
<dimen name="icon_stroke_width_excited">30dp</dimen>
</resources>
diff --git a/docklib/src/com/android/car/docklib/data/DockAppItem.kt b/docklib/src/com/android/car/docklib/data/DockAppItem.kt
index 90097287..3e679502 100644
--- a/docklib/src/com/android/car/docklib/data/DockAppItem.kt
+++ b/docklib/src/com/android/car/docklib/data/DockAppItem.kt
@@ -27,6 +27,7 @@ data class DockAppItem(
val icon: Drawable,
val isDistractionOptimized: Boolean,
) {
+ // todo(b/315210225): handle getting icon lazily
enum class Type(val value: String) {
DYNAMIC("DYNAMIC"),
STATIC("STATIC");
diff --git a/docklib/src/com/android/car/docklib/view/DockAdapter.kt b/docklib/src/com/android/car/docklib/view/DockAdapter.kt
index 294e3d41..5a5d746e 100644
--- a/docklib/src/com/android/car/docklib/view/DockAdapter.kt
+++ b/docklib/src/com/android/car/docklib/view/DockAdapter.kt
@@ -15,19 +15,47 @@ import com.android.car.docklib.R
import com.android.car.docklib.data.DockAppItem
import java.util.function.Consumer
+/**
+ * [RecyclerView.Adapter] used to bind Dock items
+ * @param numItems maximum num of items present in the dock
+ * @param items initial list of items in the Dock
+ */
class DockAdapter(
private val numItems: Int,
private val intentDelegate: Consumer<Intent>,
private val userContext: Context,
+ private val items: Array<DockAppItem?> = arrayOfNulls(numItems)
) : RecyclerView.Adapter<DockItemViewHolder>() {
companion object {
private val DEBUG = Build.isDebuggable()
private const val TAG = "DockAdapter"
}
- private val items: Array<DockAppItem?> = arrayOfNulls(numItems)
private var carPackageManager: CarPackageManager? = null
+ enum class PayloadType {
+ CHANGE_SAME_ITEM_TYPE,
+ }
+
+ override fun onBindViewHolder(
+ viewHolder: DockItemViewHolder,
+ position: Int,
+ payloads: MutableList<Any>
+ ) {
+ if (payloads.isEmpty() ||
+ payloads.getOrNull(0) == null ||
+ payloads[0] !is PayloadType
+ ) {
+ return super.onBindViewHolder(viewHolder, position, payloads)
+ }
+ when (payloads[0]) {
+ PayloadType.CHANGE_SAME_ITEM_TYPE ->
+ items[position]?.let {
+ viewHolder.itemTypeChanged(it)
+ }
+ }
+ }
+
override fun onCreateViewHolder(parent: ViewGroup, p1: Int): DockItemViewHolder {
val view = LayoutInflater.from(parent.context).inflate(
R.layout.dock_app_item_view, // resource
@@ -53,9 +81,10 @@ class DockAdapter(
}
/**
- * Pin app to the given position
+ * Pin new app to the given position
*/
fun pinItemAt(position: Int, componentName: ComponentName) {
+ // todo(b/315222570): move to controller
if (!isValidPosition(position)) {
return
}
@@ -82,6 +111,33 @@ class DockAdapter(
}
/**
+ * Pin the DockItem at the given position. If the app is already pinned this call is a no-op.
+ */
+ fun pinItemAt(position: Int) {
+ // todo(b/315222570): move to controller
+ changeItemType(position, DockAppItem.Type.STATIC)
+ }
+
+ /**
+ * Unpin the DockItem at the given position. If the app is already unpinned this call is a
+ * no-op.
+ */
+ fun unpinItemAt(position: Int) {
+ // todo(b/315222570): move to controller
+ changeItemType(position, DockAppItem.Type.DYNAMIC)
+ }
+
+ private fun changeItemType(position: Int, newItemType: DockAppItem.Type) {
+ if (!isValidPosition(position) || items[position]?.type == newItemType) {
+ return
+ }
+ items[position]?.let {
+ items[position] = it.copy(type = newItemType)
+ notifyItemChanged(position, PayloadType.CHANGE_SAME_ITEM_TYPE)
+ }
+ }
+
+ /**
* Setter for CarPackageManager
*/
fun setCarPackageManager(carPackageManager: CarPackageManager) {
diff --git a/docklib/src/com/android/car/docklib/view/DockItemLongClickListener.kt b/docklib/src/com/android/car/docklib/view/DockItemLongClickListener.kt
new file mode 100644
index 00000000..2e65b743
--- /dev/null
+++ b/docklib/src/com/android/car/docklib/view/DockItemLongClickListener.kt
@@ -0,0 +1,72 @@
+package com.android.car.docklib.view
+
+import android.content.res.Resources
+import android.view.View
+import androidx.annotation.OpenForTesting
+import androidx.annotation.VisibleForTesting
+import com.android.car.docklib.data.DockAppItem
+import com.android.car.dockutil.shortcuts.PinShortcutItem
+import com.android.car.ui.shortcutspopup.CarUiShortcutsPopup
+
+/**
+ * {@link View.OnLongClickListener} for handling long clicks on dock item.
+ * It is responsible to create and show th popup window
+ *
+ * @param dockAppItem the {@link DockAppItem} to be used on long click.
+ * @param pinItemClickDelegate called when item should be pinned at that position
+ * @param unpinItemClickDelegate called when item should be unpinned at that position
+ */
+@OpenForTesting
+open class DockItemLongClickListener(
+ private var dockAppItem: DockAppItem,
+ private val pinItemClickDelegate: Runnable,
+ private val unpinItemClickDelegate: Runnable
+) : View.OnLongClickListener {
+ override fun onLongClick(view: View?): Boolean {
+ if (view == null) return false
+
+ createCarUiShortcutsPopupBuilder()
+ .addShortcut(
+ createPinShortcutItem(
+ view.context.resources,
+ isItemPinned = (dockAppItem.type == DockAppItem.Type.STATIC),
+ pinItemClickDelegate,
+ unpinItemClickDelegate
+ )
+ )
+ .build(view.context, view)
+ .show()
+ return true
+ }
+
+ /**
+ * Set the {@link DockAppItem} to be used on long click.
+ */
+ fun setDockAppItem(dockAppItem: DockAppItem) {
+ this.dockAppItem = dockAppItem
+ }
+
+ /**
+ * Need to be overridden in test.
+ */
+ @VisibleForTesting
+ @OpenForTesting
+ open fun createCarUiShortcutsPopupBuilder(): CarUiShortcutsPopup.Builder =
+ CarUiShortcutsPopup.Builder()
+
+ /**
+ * Need to be overridden in test.
+ */
+ @VisibleForTesting
+ fun createPinShortcutItem(
+ resources: Resources,
+ isItemPinned: Boolean,
+ pinItemClickDelegate: Runnable,
+ unpinItemClickDelegate: Runnable
+ ): PinShortcutItem = PinShortcutItem(
+ resources,
+ isItemPinned,
+ pinItemClickDelegate,
+ unpinItemClickDelegate
+ )
+}
diff --git a/docklib/src/com/android/car/docklib/view/DockItemViewHolder.kt b/docklib/src/com/android/car/docklib/view/DockItemViewHolder.kt
index 5be920a5..9750f369 100644
--- a/docklib/src/com/android/car/docklib/view/DockItemViewHolder.kt
+++ b/docklib/src/com/android/car/docklib/view/DockItemViewHolder.kt
@@ -19,21 +19,24 @@ class DockItemViewHolder(
private val intentDelegate: Consumer<Intent>,
) : RecyclerView.ViewHolder(itemView) {
- private val iconStrokeWidth: Int
- private val excitedIconStrokeWidth: Int
- private val iconPadding: Int
- private val excitedIconPadding: Int
+ companion object {
+ private const val DEFAULT_STROKE_WIDTH = 0f
+ }
+
+ private val staticIconStrokeWidth: Float
+ private val dynamicIconStrokeWidth: Float
+ private val excitedIconStrokeWidth: Float
private val iconStrokeColor: Int
private val excitedIconStrokeColor: Int
private val appIcon: ShapeableImageView
+ private var dockItemLongClickListener: DockItemLongClickListener? = null
+ private var iconStrokeWidth: Float = DEFAULT_STROKE_WIDTH
init {
- appIcon = itemView.requireViewById(R.id.dock_app_icon)
- iconStrokeWidth = itemView.resources.getDimensionPixelSize(R.dimen.icon_stroke_width)
- iconPadding = iconStrokeWidth / 2
- excitedIconStrokeWidth =
- itemView.resources.getDimensionPixelSize(R.dimen.icon_stroke_width_excited)
- excitedIconPadding = excitedIconStrokeWidth / 2
+ staticIconStrokeWidth = itemView.resources.getDimension(R.dimen.static_icon_stroke_width)
+ dynamicIconStrokeWidth = itemView.resources.getDimension(R.dimen.dynamic_icon_stroke_width)
+ excitedIconStrokeWidth = itemView.resources.getDimension(R.dimen.icon_stroke_width_excited)
+ // todo(b/314859977): iconStrokeColor should be decided by the app primary color
iconStrokeColor = itemView.resources.getColor(
R.color.icon_default_stroke_color,
null // theme
@@ -42,12 +45,15 @@ class DockItemViewHolder(
R.color.icon_excited_stroke_color,
null // theme
)
+ appIcon = itemView.requireViewById(R.id.dock_app_icon)
}
fun bind(dockAppItem: DockAppItem?) {
reset()
if (dockAppItem == null) return
+ itemTypeChanged(dockAppItem)
+
appIcon.contentDescription = dockAppItem.name
appIcon.setImageDrawable(dockAppItem.icon)
appIcon.setOnClickListener {
@@ -58,6 +64,14 @@ class DockItemViewHolder(
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intentDelegate.accept(intent)
}
+ dockItemLongClickListener = DockItemLongClickListener(
+ dockAppItem,
+ pinItemClickDelegate =
+ { (bindingAdapter as? DockAdapter)?.pinItemAt(bindingAdapterPosition) },
+ unpinItemClickDelegate =
+ { (bindingAdapter as? DockAdapter)?.unpinItemAt(bindingAdapterPosition) }
+ )
+ appIcon.onLongClickListener = dockItemLongClickListener
itemView.setOnDragListener(
DockDragListener(
@@ -83,8 +97,8 @@ class DockItemViewHolder(
override fun getDropLocation(): Point {
val iconLocation = appIcon.locationOnScreen
return Point(
- (iconLocation[0] + iconStrokeWidth),
- (iconLocation[1] + iconStrokeWidth)
+ (iconLocation[0] + iconStrokeWidth.toInt()),
+ (iconLocation[1] + iconStrokeWidth.toInt())
)
}
@@ -100,6 +114,17 @@ class DockItemViewHolder(
)
}
+ fun itemTypeChanged(dockAppItem: DockAppItem) {
+ iconStrokeWidth = when (dockAppItem.type) {
+ DockAppItem.Type.STATIC -> staticIconStrokeWidth
+ DockAppItem.Type.DYNAMIC -> dynamicIconStrokeWidth
+ }
+ appIcon.strokeWidth = iconStrokeWidth
+
+ appIcon.invalidate()
+ dockItemLongClickListener?.setDockAppItem(dockAppItem)
+ }
+
private fun pinNewItem(componentName: ComponentName) {
(bindingAdapter as? DockAdapter)?.pinItemAt(bindingAdapterPosition, componentName)
}
@@ -108,20 +133,21 @@ class DockItemViewHolder(
// todo(b/312737692): add animations
appIcon.strokeColor = ColorStateList.valueOf(excitedIconStrokeColor)
appIcon.setColorFilter(Color.argb(0.3f, 0f, 0f, 0f), PorterDuff.Mode.DARKEN)
- appIcon.strokeWidth = excitedIconStrokeWidth.toFloat()
- appIcon.setPadding(excitedIconStrokeWidth / 2)
+ appIcon.strokeWidth = excitedIconStrokeWidth
+ appIcon.setPadding(getPaddingFromStrokeWidth(excitedIconStrokeWidth))
appIcon.invalidate()
}
private fun resetAppIcon() {
appIcon.strokeColor = ColorStateList.valueOf(iconStrokeColor)
appIcon.colorFilter = null
- appIcon.strokeWidth = iconStrokeWidth.toFloat()
- appIcon.setPadding(iconStrokeWidth / 2)
+ appIcon.strokeWidth = iconStrokeWidth
+ appIcon.setPadding(getPaddingFromStrokeWidth(iconStrokeWidth))
appIcon.invalidate()
}
private fun reset() {
+ iconStrokeWidth = DEFAULT_STROKE_WIDTH
resetAppIcon()
appIcon.contentDescription = null
appIcon.setImageDrawable(null)
@@ -129,5 +155,7 @@ class DockItemViewHolder(
itemView.setOnDragListener(null)
}
+ private fun getPaddingFromStrokeWidth(strokeWidth: Float): Int = (strokeWidth / 2).toInt()
+
// TODO: b/301484526 Add animation when app icon is changed
}
diff --git a/docklib/tests/src/com/android/car/docklib/data/DockAppItemTest.kt b/docklib/tests/src/com/android/car/docklib/data/DockAppItemTest.kt
index 049a516a..2da0f3e3 100644
--- a/docklib/tests/src/com/android/car/docklib/data/DockAppItemTest.kt
+++ b/docklib/tests/src/com/android/car/docklib/data/DockAppItemTest.kt
@@ -22,8 +22,8 @@ import com.android.car.docklib.TestUtils
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when`
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class DockAppItemTest {
@@ -61,10 +61,10 @@ class DockAppItemTest {
@Test
fun compareAppItems_notEqual_differentIcons() {
- val icon1 = mock(Drawable::class.java)
- `when`(icon1.constantState).thenReturn(null)
- val icon2 = mock(Drawable::class.java)
- `when`(icon2.constantState).thenReturn(mock(Drawable.ConstantState::class.java))
+ val icon1 = mock<Drawable>()
+ whenever(icon1.constantState).thenReturn(null)
+ val icon2 = mock<Drawable>()
+ whenever(icon2.constantState).thenReturn(mock<Drawable.ConstantState>())
val item1: DockAppItem = TestUtils.createAppItem(icon = icon1)
val item2: DockAppItem = TestUtils.createAppItem(icon = icon2)
diff --git a/docklib/tests/src/com/android/car/docklib/view/DockAdapterTest.kt b/docklib/tests/src/com/android/car/docklib/view/DockAdapterTest.kt
index 564c30e2..d39ed0f5 100644
--- a/docklib/tests/src/com/android/car/docklib/view/DockAdapterTest.kt
+++ b/docklib/tests/src/com/android/car/docklib/view/DockAdapterTest.kt
@@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.car.docklib.TestUtils
+import com.android.car.docklib.data.DockAppItem
import com.google.common.truth.Truth.assertThat
import java.util.function.Consumer
import org.junit.Test
@@ -11,12 +12,15 @@ import org.junit.runner.RunWith
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
@RunWith(AndroidJUnit4::class)
class DockAdapterTest {
private val contextMock = mock<Context> {}
private val intentConsumerMock = mock<Consumer<Intent>> {}
+ private val dockItemViewHolderMock = mock<DockItemViewHolder> {}
@Test
fun setItems_dockSizeEqualToListSize_adapterHasDockSize() {
@@ -63,4 +67,56 @@ class DockAdapterTest {
verify(adapter).notifyItemChanged(1)
verify(adapter, times(0)).notifyItemChanged(2)
}
+
+ @Test
+ fun onBindViewHolder_emptyPayload_onBindViewHolderWithoutPayloadCalled() {
+ val adapter = spy(DockAdapter(3, intentConsumerMock, contextMock))
+
+ adapter.onBindViewHolder(dockItemViewHolderMock, 1, MutableList(0) {})
+
+ verify(adapter).onBindViewHolder(eq(dockItemViewHolderMock), eq(1))
+ }
+
+ @Test
+ fun onBindViewHolder_nullPayload_onBindViewHolderWithoutPayloadCalled() {
+ val adapter = spy(DockAdapter(3, intentConsumerMock, contextMock))
+
+ adapter.onBindViewHolder(dockItemViewHolderMock, 1, MutableList(1) {})
+
+ verify(adapter).onBindViewHolder(eq(dockItemViewHolderMock), eq(1))
+ }
+
+ @Test
+ fun onBindViewHolder_payloadOfIncorrectType_onBindViewHolderWithoutPayloadCalled() {
+ class DummyPayload
+ val adapter = spy(DockAdapter(3, intentConsumerMock, contextMock))
+
+ adapter.onBindViewHolder(dockItemViewHolderMock, 1, MutableList(1) {
+ DummyPayload()
+ })
+
+ verify(adapter).onBindViewHolder(eq(dockItemViewHolderMock), eq(1))
+ }
+
+ @Test
+ fun onBindViewHolder_payload_CHANGE_SAME_ITEM_TYPE_itemTypeChangedCalled() {
+ val dockAppItem0 = mock<DockAppItem> {}
+ val dockAppItem1 = mock<DockAppItem> {}
+ val dockAppItem2 = mock<DockAppItem> {}
+ val adapter = spy(
+ DockAdapter(
+ numItems = 3,
+ intentConsumerMock,
+ contextMock,
+ items = arrayOf(dockAppItem0, dockAppItem1, dockAppItem2)
+ )
+ )
+
+ adapter.onBindViewHolder(dockItemViewHolderMock, 1, MutableList(1) {
+ DockAdapter.PayloadType.CHANGE_SAME_ITEM_TYPE
+ })
+
+ verify(adapter, never()).onBindViewHolder(eq(dockItemViewHolderMock), eq(1))
+ verify(dockItemViewHolderMock).itemTypeChanged(eq(dockAppItem1))
+ }
}
diff --git a/docklib/tests/src/com/android/car/docklib/view/DockItemLongClickListenerTest.kt b/docklib/tests/src/com/android/car/docklib/view/DockItemLongClickListenerTest.kt
new file mode 100644
index 00000000..d8ba54b9
--- /dev/null
+++ b/docklib/tests/src/com/android/car/docklib/view/DockItemLongClickListenerTest.kt
@@ -0,0 +1,89 @@
+package com.android.car.docklib.view
+
+import android.content.Context
+import android.content.res.Resources
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.car.docklib.TestUtils
+import com.android.car.docklib.data.DockAppItem
+import com.android.car.ui.shortcutspopup.CarUiShortcutsPopup
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidJUnit4::class)
+class DockItemLongClickListenerTest {
+ private val dockAppItemMock = mock<DockAppItem>()
+ private val resourcesMock = mock<Resources>()
+ private val contextMock = mock<Context> { on { resources } doReturn resourcesMock }
+ private val viewMock = mock<View> { on { context } doReturn contextMock }
+ private val runnableMock1 = mock<Runnable>()
+ private val runnableMock2 = mock<Runnable>()
+ private val carUiShortcutsPopupMock = mock<CarUiShortcutsPopup>()
+ private val carUiShortcutsPopupBuilderMock = mock<CarUiShortcutsPopup.Builder>() {
+ on { addShortcut(any<CarUiShortcutsPopup.ShortcutItem>()) } doReturn it
+ on { build(any<Context>(), any<View>()) } doReturn carUiShortcutsPopupMock
+ }
+ private lateinit var dockItemLongClickListener: DockItemLongClickListener
+
+ @Before
+ fun setup() {
+ dockItemLongClickListener = createDockItemLongClickListener()
+ }
+
+ @Test
+ fun onLongClick_shortcutShown() {
+ dockItemLongClickListener.onLongClick(viewMock)
+
+ verify(carUiShortcutsPopupMock).show()
+ }
+
+ @Test
+ fun onLongClick_typeStatic_pinShortcutItem_parameterIsItemPinnedIsTrue() {
+ dockItemLongClickListener =
+ createDockItemLongClickListener(TestUtils.createAppItem(DockAppItem.Type.STATIC))
+
+ dockItemLongClickListener.onLongClick(viewMock)
+
+ verify(dockItemLongClickListener).createPinShortcutItem(
+ any<Resources>(),
+ eq(true),
+ any<Runnable>(),
+ any<Runnable>()
+ )
+ }
+
+ @Test
+ fun onLongClick_typeDynamic_pinShortcutItem_parameterIsItemPinnedIsFalse() {
+ dockItemLongClickListener =
+ createDockItemLongClickListener(TestUtils.createAppItem(DockAppItem.Type.DYNAMIC))
+
+ dockItemLongClickListener.onLongClick(viewMock)
+
+ verify(dockItemLongClickListener).createPinShortcutItem(
+ any<Resources>(),
+ eq(false),
+ any<Runnable>(),
+ any<Runnable>()
+ )
+ }
+
+ private fun createDockItemLongClickListener(
+ dockAppItem: DockAppItem = dockAppItemMock
+ ): DockItemLongClickListener {
+ return spy(object : DockItemLongClickListener(
+ dockAppItem,
+ runnableMock1,
+ runnableMock2
+ ) {
+ override fun createCarUiShortcutsPopupBuilder(): CarUiShortcutsPopup.Builder =
+ carUiShortcutsPopupBuilderMock
+ })
+ }
+}
diff --git a/libs/appgrid/lib/src/com/android/car/carlauncher/AppLauncherUtils.java b/libs/appgrid/lib/src/com/android/car/carlauncher/AppLauncherUtils.java
index 47ba16e1..dcf7c933 100644
--- a/libs/appgrid/lib/src/com/android/car/carlauncher/AppLauncherUtils.java
+++ b/libs/appgrid/lib/src/com/android/car/carlauncher/AppLauncherUtils.java
@@ -58,6 +58,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.car.dockutil.events.DockEventSenderHelper;
+import com.android.car.dockutil.shortcuts.PinShortcutItem;
import com.android.car.media.common.source.MediaSourceUtil;
import com.android.car.ui.shortcutspopup.CarUiShortcutsPopup;
@@ -496,27 +497,11 @@ public class AppLauncherUtils {
private static CarUiShortcutsPopup.ShortcutItem buildPinToDockShortcut(
ComponentName componentName, Context context) {
- // todo(b/314835197): fix pinning/opening media apps
- return new CarUiShortcutsPopup.ShortcutItem() {
- @Override
- public CarUiShortcutsPopup.ItemData data() {
- return new CarUiShortcutsPopup.ItemData(/* leftDrawable= */ R.drawable.ic_dock_pin,
- /* shortcutName= */
- context.getResources().getString(R.string.dock_pin_shortcut_label));
- }
-
- @Override
- public boolean onClick() {
- DockEventSenderHelper mHelper = new DockEventSenderHelper(context);
- mHelper.sendPinEvent(componentName);
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
- };
+ DockEventSenderHelper mHelper = new DockEventSenderHelper(context);
+ return new PinShortcutItem(context.getResources(), /* isItemPinned= */ false,
+ /* pinItemClickDelegate= */ () -> mHelper.sendPinEvent(componentName),
+ /* unpinItemClickDelegate= */ () -> mHelper.sendUnpinEvent(componentName)
+ );
}
/**