summaryrefslogtreecommitdiff
path: root/java/src
diff options
context:
space:
mode:
author Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> 2024-10-10 22:51:43 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-10-10 22:51:43 +0000
commit33867d44db91247f2d5742f011574c651fe42e88 (patch)
treec70cd14761b84fe1dfa7f87b8138f2cf7e9cc7a0 /java/src
parent102bf02d3a21cdf402e6b1de6f3cb0794ca9c34f (diff)
parentb134f387d15bf2b3184c38b4c0397265056bbd1c (diff)
Merge "Add Launcher hover effect" into main
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/intentresolver/ChooserListAdapter.java32
-rw-r--r--java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java7
-rw-r--r--java/src/com/android/intentresolver/chooser/TargetInfo.java4
-rw-r--r--java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt29
-rw-r--r--java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt18
-rw-r--r--java/src/com/android/intentresolver/icons/HoverBitmapDrawable.kt41
-rw-r--r--java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt73
7 files changed, 185 insertions, 19 deletions
diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java
index 016eb714..c2bb1f23 100644
--- a/java/src/com/android/intentresolver/ChooserListAdapter.java
+++ b/java/src/com/android/intentresolver/ChooserListAdapter.java
@@ -18,6 +18,7 @@ package com.android.intentresolver;
import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
+import static com.android.intentresolver.Flags.targetHoverAndKeyboardFocusStates;
import android.app.ActivityManager;
import android.app.prediction.AppTarget;
@@ -59,6 +60,8 @@ import com.android.intentresolver.widget.BadgeTextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.google.common.collect.ImmutableList;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -365,7 +368,10 @@ public class ChooserListAdapter extends ResolverListAdapter {
@Override
View onCreateView(ViewGroup parent) {
- return mInflater.inflate(R.layout.chooser_grid_item, parent, false);
+ int layout = targetHoverAndKeyboardFocusStates()
+ ? R.layout.chooser_grid_item_hover
+ : R.layout.chooser_grid_item;
+ return mInflater.inflate(layout, parent, false);
}
@Override
@@ -522,8 +528,10 @@ public class ChooserListAdapter extends ResolverListAdapter {
public void updateAlphabeticalList(Runnable onCompleted) {
final DisplayResolveInfoAzInfoComparator
comparator = new DisplayResolveInfoAzInfoComparator(mContext);
- final List<DisplayResolveInfo> allTargets = new ArrayList<>();
- allTargets.addAll(getTargetsInCurrentDisplayList());
+ ImmutableList<DisplayResolveInfo> displayList = getTargetsInCurrentDisplayList();
+ final List<DisplayResolveInfo> allTargets =
+ new ArrayList<>(displayList.size() + mCallerTargets.size());
+ allTargets.addAll(displayList);
allTargets.addAll(mCallerTargets);
new AsyncTask<Void, Void, List<DisplayResolveInfo>>() {
@@ -543,6 +551,24 @@ public class ChooserListAdapter extends ResolverListAdapter {
// Consolidate multiple targets from same app.
return allTargets
.stream()
+ .map(appTarget -> {
+ if (targetHoverAndKeyboardFocusStates()) {
+ // Icon drawables are effectively cached per target info.
+ // Without cloning target infos, the same target info could be used
+ // for two different positions in the grid: once in the ranked
+ // targets row (from ResolverListAdapter#mDisplayList or
+ // #mCallerTargets, see #getItem()) and again in the all-app-target
+ // grid (copied from #mDisplayList and #mCallerTargets to
+ // #mSortedList).
+ // Using the same drawable for two list items would result in visual
+ // effects being applied to both simultaneously.
+ DisplayResolveInfo copy = appTarget.copy();
+ copy.getDisplayIconHolder().setDisplayIcon(null);
+ return copy;
+ } else {
+ return appTarget;
+ }
+ })
.collect(Collectors.groupingBy(target ->
target.getResolvedComponentName().getPackageName()
+ "#" + target.getDisplayLabel()
diff --git a/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java b/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
index 5e44c53e..f0674a27 100644
--- a/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
+++ b/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
@@ -239,4 +239,11 @@ public class DisplayResolveInfo implements TargetInfo {
public void setPinned(boolean pinned) {
mPinned = pinned;
}
+
+ /**
+ * Creates a copy of the object.
+ */
+ public DisplayResolveInfo copy() {
+ return new DisplayResolveInfo(this);
+ }
}
diff --git a/java/src/com/android/intentresolver/chooser/TargetInfo.java b/java/src/com/android/intentresolver/chooser/TargetInfo.java
index ba6c3c05..e5f40001 100644
--- a/java/src/com/android/intentresolver/chooser/TargetInfo.java
+++ b/java/src/com/android/intentresolver/chooser/TargetInfo.java
@@ -65,7 +65,7 @@ public interface TargetInfo {
* @param icon the icon to return on subsequent calls to {@link #getDisplayIcon()}.
* Implementations may discard this request as a no-op if they don't support setting.
*/
- void setDisplayIcon(Drawable icon);
+ void setDisplayIcon(@Nullable Drawable icon);
}
/** A simple mutable-container implementation of {@link IconHolder}. */
@@ -78,7 +78,7 @@ public interface TargetInfo {
return mDisplayIcon;
}
- public void setDisplayIcon(Drawable icon) {
+ public void setDisplayIcon(@Nullable Drawable icon) {
mDisplayIcon = icon;
}
}
diff --git a/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt b/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt
index b0c26777..793b7621 100644
--- a/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt
+++ b/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt
@@ -23,6 +23,7 @@ import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.UserHandle
import androidx.collection.LruCache
+import com.android.intentresolver.Flags.targetHoverAndKeyboardFocusStates
import com.android.intentresolver.chooser.DisplayResolveInfo
import com.android.intentresolver.chooser.SelectableTargetInfo
import java.util.function.Consumer
@@ -46,11 +47,9 @@ class CachingTargetDataLoader(
callback: Consumer<Drawable>,
): Drawable? {
val cacheKey = info.toCacheKey()
- return getCachedAppIcon(cacheKey, userHandle)?.let { BitmapDrawable(context.resources, it) }
+ return getCachedAppIcon(cacheKey, userHandle)?.toDrawable()
?: targetDataLoader.getOrLoadAppTargetIcon(info, userHandle) { drawable ->
- (drawable as? BitmapDrawable)?.bitmap?.let {
- getProfileIconCache(userHandle).put(cacheKey, it)
- }
+ drawable.extractBitmap()?.let { getProfileIconCache(userHandle).put(cacheKey, it) }
callback.accept(drawable)
}
}
@@ -61,12 +60,10 @@ class CachingTargetDataLoader(
callback: Consumer<Drawable>,
): Drawable? {
val cacheKey = info.toCacheKey()
- return cacheKey
- ?.let { getCachedAppIcon(it, userHandle) }
- ?.let { BitmapDrawable(context.resources, it) }
+ return cacheKey?.let { getCachedAppIcon(it, userHandle) }?.toDrawable()
?: targetDataLoader.getOrLoadDirectShareIcon(info, userHandle) { drawable ->
if (cacheKey != null) {
- (drawable as? BitmapDrawable)?.bitmap?.let {
+ drawable.extractBitmap()?.let {
getProfileIconCache(userHandle).put(cacheKey, it)
}
}
@@ -102,4 +99,20 @@ class CachingTargetDataLoader(
append(directShareShortcutInfo?.id ?: "")
}
}
+
+ private fun Bitmap.toDrawable(): Drawable {
+ return if (targetHoverAndKeyboardFocusStates()) {
+ HoverBitmapDrawable(this)
+ } else {
+ BitmapDrawable(context.resources, this)
+ }
+ }
+
+ private fun Drawable.extractBitmap(): Bitmap? {
+ return when (this) {
+ is BitmapDrawable -> bitmap
+ is HoverBitmapDrawable -> bitmap
+ else -> null
+ }
+ }
}
diff --git a/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt b/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt
index 117c769d..e181f4f3 100644
--- a/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt
+++ b/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt
@@ -18,6 +18,7 @@ package com.android.intentresolver.icons
import android.app.ActivityManager
import android.content.Context
+import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.AsyncTask
@@ -27,6 +28,7 @@ import androidx.annotation.GuardedBy
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
+import com.android.intentresolver.Flags.targetHoverAndKeyboardFocusStates
import com.android.intentresolver.R
import com.android.intentresolver.TargetPresentationGetter
import com.android.intentresolver.chooser.DisplayResolveInfo
@@ -72,9 +74,7 @@ class DefaultTargetDataLoader(
val taskId = nextTaskId.getAndIncrement()
LoadIconTask(context, info, presentationFactory) { bitmap ->
removeTask(taskId)
- callback.accept(
- bitmap?.let { BitmapDrawable(context.resources, it) } ?: loadIconPlaceholder()
- )
+ callback.accept(bitmap?.toDrawable() ?: loadIconPlaceholder())
}
.also { addTask(taskId, it) }
.executeOnExecutor(executor)
@@ -93,9 +93,7 @@ class DefaultTargetDataLoader(
presentationFactory,
) { bitmap ->
removeTask(taskId)
- callback.accept(
- bitmap?.let { BitmapDrawable(context.resources, it) } ?: loadIconPlaceholder()
- )
+ callback.accept(bitmap?.toDrawable() ?: loadIconPlaceholder())
}
.also { addTask(taskId, it) }
.executeOnExecutor(executor)
@@ -140,4 +138,12 @@ class DefaultTargetDataLoader(
activeTasks.clear()
}
}
+
+ private fun Bitmap.toDrawable(): Drawable {
+ return if (targetHoverAndKeyboardFocusStates()) {
+ HoverBitmapDrawable(this)
+ } else {
+ BitmapDrawable(context.resources, this)
+ }
+ }
}
diff --git a/java/src/com/android/intentresolver/icons/HoverBitmapDrawable.kt b/java/src/com/android/intentresolver/icons/HoverBitmapDrawable.kt
new file mode 100644
index 00000000..4a21df92
--- /dev/null
+++ b/java/src/com/android/intentresolver/icons/HoverBitmapDrawable.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 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
+ *
+ * https://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.intentresolver.icons
+
+import android.graphics.Bitmap
+import com.android.launcher3.icons.FastBitmapDrawable
+
+/** A [FastBitmapDrawable] extension that provides access to the bitmap. */
+class HoverBitmapDrawable(val bitmap: Bitmap) : FastBitmapDrawable(bitmap) {
+
+ override fun newConstantState(): FastBitmapConstantState {
+ return HoverBitmapDrawableState(bitmap, iconColor)
+ }
+
+ private class HoverBitmapDrawableState(private val bitmap: Bitmap, color: Int) :
+ FastBitmapConstantState(bitmap, color) {
+ override fun createDrawable(): FastBitmapDrawable {
+ return HoverBitmapDrawable(bitmap)
+ }
+ }
+
+ companion object {
+ init {
+ setFlagHoverEnabled(true)
+ }
+ }
+}
diff --git a/java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt b/java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt
new file mode 100644
index 00000000..28934495
--- /dev/null
+++ b/java/src/com/android/intentresolver/widget/ChooserTargetItemView.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2024 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
+ *
+ * https://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.intentresolver.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import android.widget.ImageView
+import android.widget.LinearLayout
+
+class ChooserTargetItemView(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ defStyleRes: Int,
+) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
+ constructor(context: Context) : this(context, null)
+
+ constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyleAttr: Int,
+ ) : this(context, attrs, defStyleAttr, 0)
+
+ private var iconView: ImageView? = null
+
+ override fun onViewAdded(child: View) {
+ super.onViewAdded(child)
+ if (child is ImageView) {
+ iconView = child
+ }
+ }
+
+ override fun onViewRemoved(child: View?) {
+ super.onViewRemoved(child)
+ if (child === iconView) {
+ iconView = null
+ }
+ }
+
+ override fun onHoverEvent(event: MotionEvent): Boolean {
+ val iconView = iconView ?: return false
+ if (!isEnabled) return true
+ when (event.action) {
+ MotionEvent.ACTION_HOVER_ENTER -> {
+ iconView.isHovered = true
+ }
+ MotionEvent.ACTION_HOVER_EXIT -> {
+ iconView.isHovered = false
+ }
+ }
+ return true
+ }
+
+ override fun onInterceptHoverEvent(event: MotionEvent?) = true
+}