Merging from ub-launcher3-master @ build 6925377

Test: manual, presubmit on the source branch
x20/teams/android-launcher/merge/ub-launcher3-master_master_6925377.html

Change-Id: I928b100c8f41abff34047df69d988622123f9939
This commit is contained in:
Winson Chung
2020-10-23 09:32:16 -07:00
100 changed files with 3111 additions and 2461 deletions
+68 -5
View File
@@ -17,6 +17,7 @@
package com.android.launcher3;
import static com.android.launcher3.FastBitmapDrawable.newIcon;
import static com.android.launcher3.graphics.IconShape.getShape;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
@@ -27,15 +28,18 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Process;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.util.Property;
@@ -50,6 +54,8 @@ import androidx.core.graphics.ColorUtils;
import com.android.launcher3.Launcher.OnResumeCallback;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.AllAppsSectionDecorator;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.FolderIcon;
@@ -60,6 +66,7 @@ import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconCache.IconLoadRequest;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -79,7 +86,7 @@ import java.text.NumberFormat;
* too aggressive.
*/
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
IconLabelDotView, DraggableView, Reorderable {
IconLabelDotView, DraggableView, Reorderable, AllAppsSectionDecorator.SelfDecoratingView {
private static final int DISPLAY_WORKSPACE = 0;
private static final int DISPLAY_ALL_APPS = 1;
@@ -87,6 +94,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
private static final int DISPLAY_HERO_APP = 5;
private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
private static final float HIGHLIGHT_SCALE = 1.16f;
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
@@ -95,6 +104,11 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
private float mScaleForReorderBounce = 1f;
protected final Paint mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Path mHighlightPath = new Path();
protected int mHighlightColor = Color.TRANSPARENT;
private final BlurMaskFilter mHighlightShadowFilter;
private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
= new Property<BubbleTextView, Float>(Float.TYPE, "dotScale") {
@Override
@@ -208,6 +222,11 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
setEllipsize(TruncateAt.END);
setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
setTextAlpha(1f);
int shadowSize = context.getResources().getDimensionPixelSize(
R.dimen.blur_size_click_shadow);
mHighlightShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.INNER);
}
@Override
@@ -421,8 +440,38 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
@Override
public void onDraw(Canvas canvas) {
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && mHighlightColor != Color.TRANSPARENT) {
int count = canvas.save();
drawFocusHighlight(canvas);
canvas.restoreToCount(count);
}
super.onDraw(canvas);
drawDotIfNecessary(canvas);
}
protected void drawFocusHighlight(Canvas canvas) {
boolean isBadged = getTag() instanceof ItemInfo && !Process.myUserHandle().equals(
((ItemInfo) getTag()).user);
float insetScale = (HIGHLIGHT_SCALE - 1) / 2;
canvas.translate(-getIconSize() * insetScale, -insetScale * getIconSize());
float outlineSize = getIconSize() * HIGHLIGHT_SCALE;
mHighlightPath.reset();
mHighlightPaint.reset();
getIconBounds(mDotParams.iconBounds);
getShape().addToPath(mHighlightPath, mDotParams.iconBounds.left, mDotParams.iconBounds.top,
outlineSize / 2);
if (isBadged) {
float borderSize = outlineSize - getIconSize();
float badgeSize = LauncherIcons.getBadgeSizeForIconSize(getIconSize()) + borderSize;
float badgeInset = outlineSize - badgeSize;
getShape().addToPath(mHighlightPath, mDotParams.iconBounds.left + badgeInset,
mDotParams.iconBounds.top + badgeInset, badgeSize / 2);
}
mHighlightPaint.setMaskFilter(mHighlightShadowFilter);
mHighlightPaint.setColor(mDotParams.color);
canvas.drawPath(mHighlightPath, mHighlightPaint);
mHighlightPaint.setMaskFilter(null);
mHighlightPaint.setColor(mHighlightColor);
canvas.drawPath(mHighlightPath, mHighlightPaint);
}
/**
@@ -627,7 +676,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
/**
* Sets the icon for this view based on the layout direction.
*/
private void setIcon(Drawable icon) {
protected void setIcon(Drawable icon) {
if (mIsIconVisible) {
applyCompoundDrawables(icon);
}
@@ -787,10 +836,11 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
@Override
public SafeCloseable prepareDrawDragView() {
int highlightColor = mHighlightColor;
mHighlightColor = Color.TRANSPARENT;
resetIconScale();
setForceHideDot(true);
return () -> {
};
return () -> mHighlightColor = highlightColor;
}
private void resetIconScale() {
@@ -827,4 +877,17 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
});
iconUpdateAnimation.start();
}
@Override
public void decorate(int color) {
mHighlightColor = color;
invalidate();
}
@Override
public void removeDecoration() {
mHighlightColor = Color.TRANSPARENT;
invalidate();
}
}
+19
View File
@@ -25,6 +25,10 @@ import android.view.ViewDebug;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import java.util.function.Consumer;
/**
* View class that represents the bottom row of the home screen.
*/
@@ -34,6 +38,7 @@ public class Hotseat extends CellLayout implements Insettable {
private boolean mHasVerticalHotseat;
private Workspace mWorkspace;
private boolean mSendTouchToWorkspace;
@Nullable private Consumer<Boolean> mOnVisibilityAggregatedCallback;
public Hotseat(Context context) {
this(context, null);
@@ -129,4 +134,18 @@ public class Hotseat extends CellLayout implements Insettable {
}
return event.getY() > getCellHeight();
}
@Override
public void onVisibilityAggregated(boolean isVisible) {
super.onVisibilityAggregated(isVisible);
if (mOnVisibilityAggregatedCallback != null) {
mOnVisibilityAggregatedCallback.accept(isVisible);
}
}
/** Sets a callback to be called onVisibilityAggregated */
public void setOnVisibilityAggregatedCallback(@Nullable Consumer<Boolean> callback) {
mOnVisibilityAggregatedCallback = callback;
}
}
@@ -201,7 +201,7 @@ public class InvariantDeviceProfile {
DisplayController.getDefaultDisplay(context).getInfo(),
getPredefinedDeviceProfiles(context, gridName));
Info myInfo = new Info(context, display);
Info myInfo = new Info(display);
DisplayOption myDisplayOption = invDistWeightedInterpolate(
myInfo, getPredefinedDeviceProfiles(context, gridName));
+15
View File
@@ -34,6 +34,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
@@ -44,6 +45,7 @@ import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.DeadObjectException;
import android.os.Handler;
@@ -83,6 +85,7 @@ import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -661,6 +664,18 @@ public final class Utilities {
return slop * slop;
}
/**
* Helper method to create a content provider
*/
public static ContentObserver newContentObserver(Handler handler, Consumer<Uri> command) {
return new ContentObserver(handler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
command.accept(uri);
}
};
}
private static class FixedSizeEmptyDrawable extends ColorDrawable {
private final int mSize;
+30 -30
View File
@@ -2078,40 +2078,40 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
mLastReorderY = -1;
}
/*
*
* Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
* coordinate space. The argument xy is modified with the return result.
*/
private void mapPointFromSelfToChild(View v, float[] xy) {
xy[0] = xy[0] - v.getLeft();
xy[1] = xy[1] - v.getTop();
}
/*
*
* Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
* coordinate space. The argument xy is modified with the return result.
*/
private void mapPointFromSelfToChild(View v, float[] xy) {
xy[0] = xy[0] - v.getLeft();
xy[1] = xy[1] - v.getTop();
}
boolean isPointInSelfOverHotseat(int x, int y) {
mTempFXY[0] = x;
mTempFXY[1] = y;
mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempFXY, true);
View hotseat = mLauncher.getHotseat();
return mTempFXY[0] >= hotseat.getLeft() &&
mTempFXY[0] <= hotseat.getRight() &&
mTempFXY[1] >= hotseat.getTop() &&
mTempFXY[1] <= hotseat.getBottom();
}
boolean isPointInSelfOverHotseat(int x, int y) {
mTempFXY[0] = x;
mTempFXY[1] = y;
mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempFXY, true);
View hotseat = mLauncher.getHotseat();
return mTempFXY[0] >= hotseat.getLeft()
&& mTempFXY[0] <= hotseat.getRight()
&& mTempFXY[1] >= hotseat.getTop()
&& mTempFXY[1] <= hotseat.getBottom();
}
/**
* Updates the point in {@param xy} to point to the co-ordinate space of {@param layout}
* @param layout either hotseat of a page in workspace
* @param xy the point location in workspace co-ordinate space
*/
private void mapPointFromDropLayout(CellLayout layout, float[] xy) {
if (mLauncher.isHotseatLayout(layout)) {
mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, xy, true);
mLauncher.getDragLayer().mapCoordInSelfToDescendant(layout, xy);
} else {
mapPointFromSelfToChild(layout, xy);
}
}
private void mapPointFromDropLayout(CellLayout layout, float[] xy) {
if (mLauncher.isHotseatLayout(layout)) {
mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, xy, true);
mLauncher.getDragLayer().mapCoordInSelfToDescendant(layout, xy);
} else {
mapPointFromSelfToChild(layout, xy);
}
}
private boolean isDragWidget(DragObject d) {
return (d.dragInfo instanceof LauncherAppWidgetInfo ||
@@ -2393,9 +2393,9 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
spanY = mDragInfo.spanY;
}
final int container = mLauncher.isHotseatLayout(cellLayout) ?
LauncherSettings.Favorites.CONTAINER_HOTSEAT :
LauncherSettings.Favorites.CONTAINER_DESKTOP;
final int container = mLauncher.isHotseatLayout(cellLayout)
? LauncherSettings.Favorites.CONTAINER_HOTSEAT
: LauncherSettings.Favorites.CONTAINER_DESKTOP;
final int screenId = getIdForScreen(cellLayout);
if (!mLauncher.isHotseatLayout(cellLayout)
&& screenId != getScreenIdForPageIndex(mCurrentPage)
@@ -16,7 +16,7 @@
package com.android.launcher3.allapps;
import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import static com.android.launcher3.allapps.AllAppsGridAdapter.SearchAdapterItem;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@@ -57,6 +57,7 @@ import com.android.launcher3.Insettable;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusedItemDecorator;
import com.android.launcher3.model.data.AppInfo;
@@ -67,9 +68,7 @@ import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.SpringRelativeLayout;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.util.function.IntConsumer;
import com.android.systemui.plugins.shared.SearchTarget;
/**
* The all apps view container.
@@ -546,13 +545,9 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
return mLauncher.startActivitySafely(v, headerItem.getIntent(), headerItem);
}
AdapterItem focusedItem = getActiveRecyclerView().getApps().getFocusedChild();
if (focusedItem instanceof AdapterItemWithPayload) {
IntConsumer onSelection =
((AdapterItemWithPayload) focusedItem).getSelectionHandler();
if (onSelection != null) {
onSelection.accept(SearchTargetEvent.QUICK_SELECT);
return true;
}
if (focusedItem instanceof SearchAdapterItem) {
SearchTarget searchTarget = ((SearchAdapterItem) focusedItem).getSearchTarget();
SearchEventTracker.INSTANCE.get(getContext()).quickSelect(searchTarget);
}
if (focusedItem.appInfo != null) {
ItemInfo itemInfo = focusedItem.appInfo;
@@ -20,8 +20,6 @@ import static com.android.launcher3.touch.ItemLongClickListener.INSTANCE_ALL_APP
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -37,29 +35,22 @@ import androidx.annotation.Nullable;
import androidx.core.view.accessibility.AccessibilityEventCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityRecordCompat;
import androidx.lifecycle.LiveData;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.slice.Slice;
import androidx.slice.widget.SliceLiveData;
import androidx.slice.widget.SliceView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler;
import com.android.launcher3.allapps.search.AllAppsSearchBarController.SearchTargetHandler;
import com.android.launcher3.allapps.search.SearchSectionInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.views.HeroSearchResultView;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.launcher3.views.SearchSliceWrapper;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.util.List;
import java.util.function.IntConsumer;
/**
* The grid view adapter of all the apps.
@@ -100,9 +91,11 @@ public class AllAppsGridAdapter extends
public static final int VIEW_TYPE_SEARCH_SUGGEST = 1 << 13;
public static final int VIEW_TYPE_SEARCH_ICON = 1 << 14;
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON | VIEW_TYPE_SEARCH_ICON;
/**
* ViewHolder for each icon.
@@ -192,56 +185,25 @@ public class AllAppsGridAdapter extends
|| viewType == VIEW_TYPE_SEARCH_PEOPLE
|| viewType == VIEW_TYPE_SEARCH_THUMBNAIL
|| viewType == VIEW_TYPE_SEARCH_ICON_ROW
|| viewType == VIEW_TYPE_SEARCH_ICON
|| viewType == VIEW_TYPE_SEARCH_SUGGEST;
}
}
/**
* Extension of AdapterItem that contains an extra payload specific to item
*
* @param <T> Play load Type
*/
public static class AdapterItemWithPayload<T> extends AdapterItem {
private T mPayload;
private String mSearchSessionId;
private AllAppsSearchPlugin mPlugin;
private IntConsumer mSelectionHandler;
public static class SearchAdapterItem extends AdapterItem {
private SearchTarget mSearchTarget;
public AllAppsSearchPlugin getPlugin() {
return mPlugin;
}
public void setPlugin(AllAppsSearchPlugin plugin) {
mPlugin = plugin;
}
public AdapterItemWithPayload(T payload, int type, AllAppsSearchPlugin plugin) {
mPayload = payload;
public SearchAdapterItem(SearchTarget searchTarget, int type) {
mSearchTarget = searchTarget;
viewType = type;
mPlugin = plugin;
}
public void setSelectionHandler(IntConsumer runnable) {
mSelectionHandler = runnable;
public SearchTarget getSearchTarget() {
return mSearchTarget;
}
public void setSearchSessionId(String searchSessionId) {
mSearchSessionId = searchSessionId;
}
public String getSearchSessionId() {
return mSearchSessionId;
}
public IntConsumer getSelectionHandler() {
return mSelectionHandler;
}
public T getPayload() {
return mPayload;
}
}
/**
@@ -426,11 +388,8 @@ public class AllAppsGridAdapter extends
R.layout.all_apps_icon, parent, false);
icon.setLongPressTimeoutFactor(1f);
icon.setOnFocusChangeListener(mIconFocusListener);
if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
icon.setOnClickListener(mOnIconClickListener);
icon.setOnLongClickListener(mOnIconLongClickListener);
}
icon.setOnClickListener(mOnIconClickListener);
icon.setOnLongClickListener(mOnIconLongClickListener);
// Ensure the all apps icon height matches the workspace icons in portrait mode.
icon.getLayoutParams().height = mLauncher.getDeviceProfile().allAppsCellHeightPx;
return new ViewHolder(icon);
@@ -446,6 +405,9 @@ public class AllAppsGridAdapter extends
case VIEW_TYPE_ALL_APPS_DIVIDER:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.all_apps_divider, parent, false));
case VIEW_TYPE_SEARCH_ICON:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_icon, parent, false));
case VIEW_TYPE_SEARCH_CORPUS_TITLE:
return new ViewHolder(
mLayoutInflater.inflate(R.layout.search_section_title, parent, false));
@@ -480,6 +442,10 @@ public class AllAppsGridAdapter extends
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()
&& holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) {
((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration();
}
switch (holder.getItemViewType()) {
case VIEW_TYPE_ICON:
AdapterItem adapterItem = mApps.getAdapterItems().get(position);
@@ -487,34 +453,6 @@ public class AllAppsGridAdapter extends
BubbleTextView icon = (BubbleTextView) holder.itemView;
icon.reset();
icon.applyFromApplicationInfo(info);
if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
break;
}
//TODO: replace with custom TopHitBubbleTextView with support for both shortcut
// and apps
if (adapterItem instanceof AdapterItemWithPayload) {
AdapterItemWithPayload item = (AdapterItemWithPayload) adapterItem;
item.setSelectionHandler(type -> {
SearchTargetEvent e = new SearchTargetEvent(SearchTarget.ItemType.APP,
type, item.position, item.getSearchSessionId());
e.bundle = HeroSearchResultView.getAppBundle(info);
if (item.getPlugin() != null) {
item.getPlugin().notifySearchTargetEvent(e);
}
});
icon.setOnClickListener(view -> {
item.getSelectionHandler().accept(SearchTargetEvent.SELECT);
mOnIconClickListener.onClick(view);
});
icon.setOnLongClickListener(view -> {
item.getSelectionHandler().accept(SearchTargetEvent.SELECT);
return mOnIconLongClickListener.onLongClick(view);
});
}
else {
icon.setOnClickListener(mOnIconClickListener);
icon.setOnLongClickListener(mOnIconLongClickListener);
}
break;
case VIEW_TYPE_EMPTY_SEARCH:
TextView emptyViewText = (TextView) holder.itemView;
@@ -532,39 +470,25 @@ public class AllAppsGridAdapter extends
break;
case VIEW_TYPE_SEARCH_SLICE:
SliceView sliceView = (SliceView) holder.itemView;
AdapterItemWithPayload<Uri> slicePayload =
(AdapterItemWithPayload<Uri>) mApps.getAdapterItems().get(position);
sliceView.setOnSliceActionListener((info1, s) -> {
if (slicePayload.getPlugin() != null) {
SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
SearchTarget.ItemType.SETTINGS_SLICE,
SearchTargetEvent.CHILD_SELECT, slicePayload.position,
slicePayload.getSearchSessionId());
searchTargetEvent.bundle = new Bundle();
searchTargetEvent.bundle.putParcelable("uri", slicePayload.getPayload());
slicePayload.getPlugin().notifySearchTargetEvent(searchTargetEvent);
}
});
try {
LiveData<Slice> liveData = SliceLiveData.fromUri(mLauncher,
slicePayload.getPayload());
liveData.observe((Launcher) mLauncher, sliceView);
sliceView.setTag(liveData);
} catch (Exception ignored) {
}
SearchAdapterItem slicePayload = (SearchAdapterItem) mApps.getAdapterItems().get(
position);
SearchTarget searchTarget = slicePayload.getSearchTarget();
sliceView.setTag(new SearchSliceWrapper(mLauncher, sliceView, searchTarget));
break;
case VIEW_TYPE_SEARCH_CORPUS_TITLE:
case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON:
case VIEW_TYPE_SEARCH_HERO_APP:
case VIEW_TYPE_SEARCH_ROW:
case VIEW_TYPE_SEARCH_ICON:
case VIEW_TYPE_SEARCH_ICON_ROW:
case VIEW_TYPE_SEARCH_PEOPLE:
case VIEW_TYPE_SEARCH_THUMBNAIL:
case VIEW_TYPE_SEARCH_SUGGEST:
AdapterItemWithPayload item =
(AdapterItemWithPayload) mApps.getAdapterItems().get(position);
PayloadResultHandler payloadResultView = (PayloadResultHandler) holder.itemView;
payloadResultView.setup(item);
SearchAdapterItem item =
(SearchAdapterItem) mApps.getAdapterItems().get(position);
SearchTargetHandler payloadResultView = (SearchTargetHandler) holder.itemView;
payloadResultView.applySearchTarget(item.getSearchTarget());
break;
case VIEW_TYPE_ALL_APPS_DIVIDER:
// nothing to do
@@ -576,17 +500,15 @@ public class AllAppsGridAdapter extends
public void onViewRecycled(@NonNull ViewHolder holder) {
super.onViewRecycled(holder);
if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
if (holder.itemView instanceof BubbleTextView) {
BubbleTextView icon = (BubbleTextView) holder.itemView;
icon.setOnClickListener(null);
icon.setOnLongClickListener(null);
} else if (holder.itemView instanceof SliceView) {
if (holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) {
((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration();
}
if (holder.itemView instanceof SliceView) {
SliceView sliceView = (SliceView) holder.itemView;
sliceView.setOnSliceActionListener(null);
if (sliceView.getTag() instanceof LiveData) {
LiveData sliceLiveData = (LiveData) sliceView.getTag();
sliceLiveData.removeObservers((Launcher) mLauncher);
if (sliceView.getTag() instanceof SearchSliceWrapper) {
((SearchSliceWrapper) sliceView.getTag()).destroy();
}
sliceView.setTag(null);
}
}
@@ -20,14 +20,16 @@ import android.util.AttributeSet;
import android.view.MotionEvent;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
public class AllAppsPagedView extends PagedView<PersonalWorkSlidingTabStrip> {
final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
static final float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
static final float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
static final float TOUCH_SLOP_DAMPING_FACTOR = 4;
public AllAppsPagedView(Context context) {
public AllAppsPagedView(Context context) {
this(context, null);
}
@@ -37,6 +39,10 @@ public class AllAppsPagedView extends PagedView<PersonalWorkSlidingTabStrip> {
public AllAppsPagedView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
int topPadding = FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 0
: context.getResources().getDimensionPixelOffset(
R.dimen.all_apps_header_top_padding);
setPadding(0, topPadding, 0, 0);
}
@Override
@@ -24,6 +24,7 @@ import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.View;
@@ -45,6 +46,8 @@ import java.util.List;
* A RecyclerView with custom fast scroll support for the all apps view.
*/
public class AllAppsRecyclerView extends BaseRecyclerView {
private static final String TAG = "AllAppsContainerView";
private static final boolean DEBUG = true;
private AlphabeticalAppsList mApps;
private final int mNumAppsPerRow;
@@ -131,7 +134,9 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
if (mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
mEmptySearchBackground.draw(c);
}
if (DEBUG) {
Log.d(TAG, "onDraw at = " + System.currentTimeMillis());
}
super.onDraw(c);
}
@@ -26,6 +26,7 @@ import androidx.core.graphics.ColorUtils;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
import com.android.launcher3.allapps.search.SearchSectionInfo;
import com.android.launcher3.util.Themes;
@@ -53,6 +54,9 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
int i = 0;
while (i < itemCount) {
View view = parent.getChildAt(i);
if (view instanceof SelfDecoratingView) {
((SelfDecoratingView) view).removeDecoration();
}
int position = parent.getChildAdapterPosition(view);
AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position);
if (adapterItem.searchSectionInfo != null) {
@@ -90,7 +94,10 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
if (mAppsView.getFloatingHeaderView().getFocusedChild() == null
&& mAppsView.getApps().getFocusedChild() != null) {
int index = mAppsView.getApps().getFocusedChildIndex();
if (index >= 0 && index < parent.getChildCount()) {
AppsGridLayoutManager layoutManager = (AppsGridLayoutManager)
mAppsView.getActiveRecyclerView().getLayoutManager();
if (layoutManager.findFirstVisibleItemPosition() <= index
&& index < parent.getChildCount()) {
decorationHandler.onFocusDraw(c, parent.getChildAt(index));
}
}
@@ -101,8 +108,8 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
* Handles grouping and drawing of items in the same all apps sections.
*/
public static class SectionDecorationHandler {
private static final int FILL_ALPHA = (int) (.3f * 255);
private static final int FOCUS_ALPHA = (int) (.8f * 255);
private static final int FILL_ALPHA = 0;
private static final int FOCUS_ALPHA = (int) (.9f * 255);
protected RectF mBounds = new RectF();
private final boolean mIsFullWidth;
@@ -152,6 +159,10 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
if (view == null) {
return;
}
if (view instanceof SelfDecoratingView) {
((SelfDecoratingView) view).decorate(mFocusColor);
return;
}
mPaint.setColor(mFocusColor);
canvas.drawRoundRect(view.getLeft(), view.getTop(),
view.getRight(), view.getBottom(), mRadius, mRadius, mPaint);
@@ -165,4 +176,18 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
}
}
/**
* An interface for a view to draw highlight indicator
*/
public interface SelfDecoratingView {
/**
* Removes decorations drawing if focus is acquired by another view
*/
void removeDecoration();
/**
* Draws highlight indicator on view.
*/
void decorate(int focusColor);
}
}
@@ -68,16 +68,16 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
@Override
public Float get(AllAppsTransitionController controller) {
return controller.mProgress;
}
@Override
public Float get(AllAppsTransitionController controller) {
return controller.mProgress;
}
@Override
public void setValue(AllAppsTransitionController controller, float progress) {
controller.setProgress(progress);
}
};
@Override
public void setValue(AllAppsTransitionController controller, float progress) {
controller.setProgress(progress);
}
};
private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 0;
@@ -133,7 +133,6 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
* in xml-based animations which also handle updating the appropriate UI.
*
* @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace
*
* @see #setState(LauncherState)
* @see #setStateWithAnimation(LauncherState, StateAnimationConfig, PendingAnimation)
*/
@@ -238,7 +237,7 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
mInsetController = new AllAppsInsetTransitionController(mShiftRange, mAppsView);
mLauncher.getSystemUiController().updateUiState(UI_STATE_ALLAPPS,
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
@@ -178,16 +178,46 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
/**
* Sets results list for search
*/
public boolean setSearchResults(ArrayList<AdapterItem> f) {
if (f == null || mSearchResults != f) {
boolean same = mSearchResults != null && mSearchResults.equals(f);
mSearchResults = f;
public boolean setSearchResults(ArrayList<AdapterItem> results) {
if (results == null || mSearchResults != results) {
boolean same = mSearchResults != null && mSearchResults.equals(results);
mSearchResults = results;
onAppsUpdated();
return !same;
}
return false;
}
public boolean appendSearchResults(ArrayList<AdapterItem> results) {
if (mSearchResults != null && results != null && results.size() > 0) {
updateSearchAdapterItems(results, mSearchResults.size());
refreshRecyclerView();
return true;
}
return false;
}
void updateSearchAdapterItems(ArrayList<AdapterItem> list, int offset) {
SearchSectionInfo lastSection = null;
for (int i = 0; i < list.size(); i++) {
AdapterItem adapterItem = list.get(i);
adapterItem.position = offset + i;
mAdapterItems.add(adapterItem);
if (adapterItem.searchSectionInfo != lastSection) {
if (adapterItem.searchSectionInfo != null) {
adapterItem.searchSectionInfo.setPosStart(adapterItem.position);
}
if (lastSection != null) {
lastSection.setPosEnd(adapterItem.position - 1);
}
lastSection = adapterItem.searchSectionInfo;
}
if (adapterItem.isCountedForAccessibility()) {
mAccessibilityResultsCount++;
}
}
}
/**
* Updates internals when the set of apps are updated.
*/
@@ -294,28 +324,7 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
}
appSection.setPosEnd(mApps.isEmpty() ? appSection.getPosStart() : position - 1);
} else {
List<AppInfo> appInfos = new ArrayList<>();
SearchSectionInfo lastSection = null;
for (int i = 0; i < mSearchResults.size(); i++) {
AdapterItem adapterItem = mSearchResults.get(i);
adapterItem.position = i;
mAdapterItems.add(adapterItem);
if (adapterItem.searchSectionInfo != lastSection) {
if (adapterItem.searchSectionInfo != null) {
adapterItem.searchSectionInfo.setPosStart(i);
}
if (lastSection != null) {
lastSection.setPosEnd(i - 1);
}
lastSection = adapterItem.searchSectionInfo;
}
if (AllAppsGridAdapter.isIconViewType(adapterItem.viewType)) {
appInfos.add(adapterItem.appInfo);
}
if (adapterItem.isCountedForAccessibility()) {
mAccessibilityResultsCount++;
}
}
updateSearchAdapterItems(mSearchResults, 0);
if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
// Append the search market item
if (hasNoFilteredResults()) {
@@ -111,8 +111,8 @@ public class FloatingHeaderView extends LinearLayout implements
public FloatingHeaderView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mHeaderTopPadding = context.getResources()
.getDimensionPixelSize(R.dimen.all_apps_header_top_padding);
mHeaderTopPadding = FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 0 :
context.getResources().getDimensionPixelSize(R.dimen.all_apps_header_top_padding);
}
@Override
@@ -130,6 +130,7 @@ public class FloatingHeaderView extends LinearLayout implements
}
}
mFixedRows = rows.toArray(new FloatingHeaderRow[rows.size()]);
setPadding(0, mHeaderTopPadding, 0, 0);
mAllRows = mFixedRows;
}
@@ -247,7 +248,9 @@ public class FloatingHeaderView extends LinearLayout implements
public int getMaxTranslation() {
if (mMaxTranslation == 0 && mTabsHidden) {
return getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_bottom_padding);
int paddingOffset = getResources().getDimensionPixelSize(
R.dimen.all_apps_search_bar_bottom_padding);
return FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 0 : paddingOffset;
} else if (mMaxTranslation > 0 && mTabsHidden) {
return mMaxTranslation + getPaddingTop();
} else {
@@ -30,13 +30,11 @@ import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.util.ArrayList;
import java.util.List;
@@ -114,7 +112,7 @@ public class AllAppsSearchBarController
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_GO) {
// selectFocusedView should return SearchTargetEvent that is passed onto onClick
if (Launcher.getLauncher(mLauncher).getAppsView().selectFocusedView(v)) {
return true;
@@ -196,9 +194,16 @@ public class AllAppsSearchBarController
/**
* Called when the search from primary source is complete.
*
* @param items sorted list of search result adapter items.
* @param items sorted list of search result adapter items
*/
void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items);
void onSearchResult(String query, ArrayList<AdapterItem> items);
/**
* Called when the search from secondary source is complete.
*
* @param items sorted list of search result adapter items
*/
void onAppendSearchResult(String query, ArrayList<AdapterItem> items);
/**
* Called when the search results should be cleared.
@@ -208,50 +213,20 @@ public class AllAppsSearchBarController
/**
* An interface for supporting dynamic search results
*
* @param <T> Type of payload
*/
public interface PayloadResultHandler<T> {
/**
* Updates View using Adapter's payload
*/
public interface SearchTargetHandler {
default void setup(AdapterItemWithPayload<T> adapterItemWithPayload) {
Object[] targetInfo = getTargetInfo();
if (targetInfo != null) {
targetInfo[0] = adapterItemWithPayload.getSearchSessionId();
targetInfo[1] = adapterItemWithPayload.position;
}
applyAdapterInfo(adapterItemWithPayload);
/**
* Update view using values from {@link SearchTarget}
*/
void applySearchTarget(SearchTarget searchTarget);
/**
* Handles selection of SearchTarget
*/
default void handleSelection(int eventType) {
}
void applyAdapterInfo(AdapterItemWithPayload<T> adapterItemWithPayload);
/**
* Gets object created by {@link PayloadResultHandler#createTargetInfo()}
*/
Object[] getTargetInfo();
/**
* Creates a wrapper object to hold searchSessionId and item position
*/
default Object[] createTargetInfo() {
return new Object[2];
}
/**
* Generates a SearchTargetEvent object for a PayloadHandlerView
*/
default SearchTargetEvent getSearchTargetEvent(SearchTarget.ItemType itemType,
int eventType) {
Object[] targetInfo = getTargetInfo();
if (targetInfo == null) return null;
String searchSessionId = (String) targetInfo[0];
int position = (int) targetInfo[1];
return new SearchTargetEvent(itemType, eventType,
position, searchSessionId);
}
}
@@ -42,7 +42,7 @@ import com.android.launcher3.Insettable;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.SearchUiManager;
@@ -173,7 +173,7 @@ public class AppsSearchContainerLayout extends ExtendedEditText
}
@Override
public void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items) {
public void onSearchResult(String query, ArrayList<AdapterItem> items) {
if (items != null) {
mApps.setSearchResults(items);
notifyResultChanged();
@@ -181,6 +181,14 @@ public class AppsSearchContainerLayout extends ExtendedEditText
}
}
@Override
public void onAppendSearchResult(String query, ArrayList<AdapterItem> items) {
if (items != null) {
mApps.appendSearchResults(items);
notifyResultChanged();
}
}
@Override
public void clearSearchResult() {
if (mApps.setSearchResults(null)) {
@@ -16,6 +16,7 @@
package com.android.launcher3.allapps.search;
import android.content.Context;
import android.os.CancellationSignal;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
@@ -47,11 +48,12 @@ public class AppsSearchPipeline implements SearchPipeline {
}
@Override
public void performSearch(String query, Consumer<ArrayList<AdapterItem>> callback) {
public void query(String input, Consumer<ArrayList<AdapterItem>> callback,
CancellationSignal cancellationSignal) {
mLauncherAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
List<AppInfo> matchingResults = getTitleMatchResult(apps.data, query);
List<AppInfo> matchingResults = getTitleMatchResult(apps.data, input);
callback.accept(getAdapterItems(matchingResults));
}
});
@@ -46,8 +46,10 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
@Override
public void doSearch(final String query,
final AllAppsSearchBarController.Callbacks callback) {
mAppsSearchPipeline.performSearch(query,
results -> mResultHandler.post(() -> callback.onSearchResult(query, results)));
mAppsSearchPipeline.query(query,
results -> mResultHandler.post(
() -> callback.onSearchResult(query, results)),
null);
}
public static boolean matches(AppInfo info, String query, StringMatcher matcher) {
@@ -0,0 +1,93 @@
/*
* Copyright (C) 2017 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.launcher3.allapps.search;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.content.Context;
import androidx.annotation.Nullable;
import com.android.launcher3.allapps.search.AllAppsSearchBarController.SearchTargetHandler;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.util.WeakHashMap;
/**
* A singleton class to track and report search events to search provider
*/
public class SearchEventTracker {
@Nullable
private AllAppsSearchPlugin mPlugin;
private final WeakHashMap<SearchTarget, SearchTargetHandler>
mCallbacks = new WeakHashMap<>();
public static final MainThreadInitializedObject<SearchEventTracker> INSTANCE =
new MainThreadInitializedObject<>(SearchEventTracker::new);
private SearchEventTracker(Context context) {
}
/**
* Returns instance of SearchEventTracker
*/
public static SearchEventTracker getInstance(Context context) {
return SearchEventTracker.INSTANCE.get(context);
}
/**
* Sets current connected plugin for event reporting
*/
public void setPlugin(@Nullable AllAppsSearchPlugin plugin) {
mPlugin = plugin;
}
/**
* Sends SearchTargetEvent to search provider
*/
public void notifySearchTargetEvent(SearchTargetEvent searchTargetEvent) {
if (mPlugin != null) {
UI_HELPER_EXECUTOR.post(() -> mPlugin.notifySearchTargetEvent(searchTargetEvent));
}
}
/**
* Registers a {@link SearchTargetHandler} to handle quick launch for specified SearchTarget.
*/
public void registerWeakHandler(SearchTarget searchTarget, SearchTargetHandler targetHandler) {
mCallbacks.put(searchTarget, targetHandler);
}
/**
* Handles quick select for SearchTarget
*/
public void quickSelect(SearchTarget searchTarget) {
SearchTargetHandler searchTargetHandler = mCallbacks.get(searchTarget);
if (searchTargetHandler != null) {
searchTargetHandler.handleSelection(SearchTargetEvent.QUICK_SELECT);
}
}
/**
* flushes all registered quick select handlers
*/
public void clearHandlers() {
mCallbacks.clear();
}
}
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.allapps.search;
import android.os.CancellationSignal;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import java.util.ArrayList;
@@ -23,10 +25,13 @@ import java.util.function.Consumer;
/**
* An interface for handling search within pipeline
*/
// Remove when System Service API is added.
public interface SearchPipeline {
/**
* Perform query
*/
void performSearch(String query, Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> cb);
void query(String input,
Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> callback,
CancellationSignal cancellationSignal);
}
@@ -22,7 +22,7 @@ import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHa
*/
public class SearchSectionInfo {
private String mTitle;
private String mSectionId;
private SectionDecorationHandler mDecorationHandler;
public int getPosStart() {
@@ -48,8 +48,8 @@ public class SearchSectionInfo {
this(null);
}
public SearchSectionInfo(String title) {
mTitle = title;
public SearchSectionInfo(String sectionId) {
mSectionId = sectionId;
}
public void setDecorationHandler(SectionDecorationHandler sectionDecorationHandler) {
@@ -62,9 +62,9 @@ public class SearchSectionInfo {
}
/**
* Returns the section's title
* Returns the section's ID
*/
public String getTitle() {
return mTitle == null ? "" : mTitle;
public String getSectionId() {
return mSectionId == null ? "" : mSectionId;
}
}
@@ -156,10 +156,14 @@ public final class FeatureFlags {
"ENABLE_DATABASE_RESTORE", true,
"Enable database restore when new restore session is created");
public static final BooleanFlag ENABLE_UNIVERSAL_SMARTSPACE = getDebugFlag(
"ENABLE_UNIVERSAL_SMARTSPACE", false,
public static final BooleanFlag ENABLE_SMARTSPACE_UNIVERSAL = getDebugFlag(
"ENABLE_SMARTSPACE_UNIVERSAL", false,
"Replace Smartspace with a version rendered by System UI.");
public static final BooleanFlag ENABLE_SMARTSPACE_BLUECHIP = getDebugFlag(
"ENABLE_SMARTSPACE_BLUECHIP", false,
"Replace Smartspace with the Bluechip version. Ignored if ENABLE_SMARTSPACE_UNIVERSAL is enabled.");
public static final BooleanFlag ENABLE_SYSTEM_VELOCITY_PROVIDER = getDebugFlag(
"ENABLE_SYSTEM_VELOCITY_PROVIDER", true,
"Use system VelocityTracker's algorithm for motion pause detection.");
@@ -178,7 +182,7 @@ public final class FeatureFlags {
"Uses a separate recents activity instead of using the integrated recents+Launcher UI");
public static final BooleanFlag ENABLE_MINIMAL_DEVICE = getDebugFlag(
"ENABLE_MINIMAL_DEVICE", true,
"ENABLE_MINIMAL_DEVICE", false,
"Allow user to toggle minimal device mode in launcher.");
public static void initialize(Context context) {
@@ -53,8 +53,8 @@ import com.android.launcher3.R;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.views.BaseDragLayer;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -87,7 +87,6 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener
private Bundle mWidgetOptions;
private boolean mFinishOnPause = false;
private InstantAppResolver mInstantAppResolver;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -101,7 +100,6 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener
mApp = LauncherAppState.getInstance(this);
mIdp = mApp.getInvariantDeviceProfile();
mInstantAppResolver = InstantAppResolver.newInstance(this);
// Use the application context to get the device profile, as in multiwindow-mode, the
// confirmation activity might be rotated.
@@ -322,6 +320,8 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener
}
private void logCommand(StatsLogManager.EventEnum command) {
getStatsLogManager().logger().log(command);
getStatsLogManager().logger()
.withItemInfo((ItemInfo) mWidgetCell.getWidgetView().getTag())
.log(command);
}
}
@@ -50,6 +50,7 @@ import android.util.LongSparseArray;
import android.util.TimingLogger;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
@@ -315,6 +316,7 @@ public class LoaderTask implements Runnable {
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
final boolean isSafeMode = pmHelper.isSafeMode();
final boolean isSdCardReady = Utilities.isBootCompleted();
final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
boolean clearDb = false;
try {
@@ -402,6 +404,7 @@ public class LoaderTask implements Runnable {
WorkspaceItemInfo info;
LauncherAppWidgetInfo appWidgetInfo;
LauncherAppWidgetProviderInfo widgetProviderInfo;
Intent intent;
String targetPkg;
@@ -731,6 +734,19 @@ public class LoaderTask implements Runnable {
+ appWidgetInfo.spanX + "x" + appWidgetInfo.spanY);
continue;
}
widgetProviderInfo =
widgetHelper.getLauncherAppWidgetInfo(appWidgetId);
if (widgetProviderInfo != null
&& (appWidgetInfo.spanX < widgetProviderInfo.minSpanX
|| appWidgetInfo.spanY < widgetProviderInfo.minSpanY)) {
// This can happen when display size changes.
c.markDeleted("Widget removed, min sizes not met: "
+ "span=" + appWidgetInfo.spanX + "x"
+ appWidgetInfo.spanY + " minSpan="
+ widgetProviderInfo.minSpanX + "x"
+ widgetProviderInfo.minSpanY);
continue;
}
if (!c.isOnWorkspaceOrHotseat()) {
c.markDeleted("Widget found where container != " +
"CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
@@ -32,6 +32,7 @@ import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SH
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_TASK;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.CONTAINER_NOT_SET;
import static com.android.launcher3.shortcuts.ShortcutKey.EXTRA_SHORTCUT_ID;
import android.content.ComponentName;
import android.content.ContentValues;
@@ -50,10 +51,10 @@ import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import com.android.launcher3.logger.LauncherAtom.PredictionContainer;
import com.android.launcher3.logger.LauncherAtom.SearchResultContainer;
import com.android.launcher3.logger.LauncherAtom.SettingsContainer;
import com.android.launcher3.logger.LauncherAtom.Shortcut;
import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer;
import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ContentWriter;
import java.util.Optional;
@@ -282,9 +283,14 @@ public class ItemInfo {
case ITEM_TYPE_DEEP_SHORTCUT:
itemBuilder
.setShortcut(nullableComponent
.map(component -> LauncherAtom.Shortcut.newBuilder()
.setShortcutName(component.flattenToShortString())
.setShortcutId(ShortcutKey.fromItemInfo(this).getId()))
.map(component -> {
Shortcut.Builder lsb = Shortcut.newBuilder()
.setShortcutName(component.flattenToShortString());
Optional.ofNullable(getIntent())
.map(i -> i.getStringExtra(EXTRA_SHORTCUT_ID))
.ifPresent(lsb::setShortcutId);
return lsb;
})
.orElse(LauncherAtom.Shortcut.newBuilder()));
break;
case ITEM_TYPE_SHORTCUT:
@@ -61,4 +61,8 @@ public class RemoteActionItemInfo extends ItemInfoWithIcon {
public boolean shouldStartInLauncher() {
return mShouldStart;
}
public boolean isEscapeHatch() {
return mToken.contains("item_type:[ESCAPE_HATCH]");
}
}
@@ -275,7 +275,8 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchBackTutorialPreference.setSummary("Learn how to use the Back gesture");
launchBackTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra(
"tutorial_type", "RIGHT_EDGE_BACK_NAVIGATION"));
"tutorial_steps",
new String[] {"RIGHT_EDGE_BACK_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchBackTutorialPreference);
@@ -284,7 +285,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchHomeTutorialPreference.setTitle("Launch Home Tutorial");
launchHomeTutorialPreference.setSummary("Learn how to use the Home gesture");
launchHomeTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra("tutorial_type", "HOME_NAVIGATION"));
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {"HOME_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchHomeTutorialPreference);
@@ -293,7 +296,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchOverviewTutorialPreference.setTitle("Launch Overview Tutorial");
launchOverviewTutorialPreference.setSummary("Learn how to use the Overview gesture");
launchOverviewTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra("tutorial_type", "OVERVIEW_NAVIGATION"));
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {"OVERVIEW_NAVIGATION"}));
return true;
});
sandboxCategory.addPreference(launchOverviewTutorialPreference);
@@ -302,7 +307,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchAssistantTutorialPreference.setTitle("Launch Assistant Tutorial");
launchAssistantTutorialPreference.setSummary("Learn how to use the Assistant gesture");
launchAssistantTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra("tutorial_type", "ASSISTANT"));
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {"ASSISTANT"}));
return true;
});
sandboxCategory.addPreference(launchAssistantTutorialPreference);
@@ -311,7 +318,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
launchSandboxModeTutorialPreference.setTitle("Launch Sandbox Mode");
launchSandboxModeTutorialPreference.setSummary("Practice navigation gestures");
launchSandboxModeTutorialPreference.setOnPreferenceClickListener(preference -> {
startActivity(launchSandboxIntent.putExtra("tutorial_type", "SANDBOX_MODE"));
startActivity(launchSandboxIntent.putExtra(
"tutorial_steps",
new String[] {"SANDBOX_MODE"}));
return true;
});
sandboxCategory.addPreference(launchSandboxModeTutorialPreference);
@@ -21,14 +21,9 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.Handler;
import android.provider.Settings;
import android.util.Log;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -43,16 +38,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener {
public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
private final ContentResolver mContentResolver;
private boolean mSystemAutoRotateEnabled;
private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
updateAutoRotateSetting();
}
};
public static boolean getAllowRotationDefaultValue() {
// If the device's pixel density was scaled (usually via settings for A11y), use the
// original dimensions to determine if rotation is allowed of not.
@@ -106,20 +91,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener {
} else {
mSharedPrefs = null;
}
mContentResolver = activity.getContentResolver();
}
private void updateAutoRotateSetting() {
int autoRotateEnabled = 0;
try {
autoRotateEnabled = Settings.System.getInt(mContentResolver,
Settings.System.ACCELEROMETER_ROTATION);
} catch (Settings.SettingNotFoundException e) {
Log.e(TAG, "autorotate setting not found", e);
}
mSystemAutoRotateEnabled = autoRotateEnabled == 1;
}
@Override
@@ -129,7 +100,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener {
getAllowRotationDefaultValue());
if (mHomeRotationEnabled != wasRotationEnabled) {
notifyChange();
updateAutoRotateSetting();
}
}
@@ -165,11 +135,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener {
if (!mInitialized) {
mInitialized = true;
notifyChange();
mContentResolver.registerContentObserver(
Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
false, mSystemAutoRotateObserver);
updateAutoRotateSetting();
}
}
@@ -179,7 +144,6 @@ public class RotationHelper implements OnSharedPreferenceChangeListener {
if (mSharedPrefs != null) {
mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver);
}
}
@@ -225,9 +189,8 @@ public class RotationHelper implements OnSharedPreferenceChangeListener {
@Override
public String toString() {
return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d,"
+ " mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, mHomeRotationEnabled=%b,"
+ " mSystemAutoRotateEnabled=%b]",
+ " mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, mHomeRotationEnabled=%b]",
mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags,
mIgnoreAutoRotateSettings, mHomeRotationEnabled, mSystemAutoRotateEnabled);
mIgnoreAutoRotateSettings, mHomeRotationEnabled);
}
}
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2020 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.launcher3.util;
import android.os.Looper;
import androidx.annotation.WorkerThread;
/**
* Utility class to define an object which does most of it's processing on a
* dedicated background thread.
*/
public abstract class BgObjectWithLooper {
/**
* Start initialization of the object
*/
public final void initializeInBackground(String threadName) {
new Thread(this::runOnThread, threadName).start();
}
private void runOnThread() {
Looper.prepare();
onInitialized(Looper.myLooper());
Looper.loop();
}
/**
* Called on the background thread to handle initialization
*/
@WorkerThread
protected abstract void onInitialized(Looper looper);
}
@@ -31,6 +31,8 @@ import android.view.Display;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Utilities;
import java.util.ArrayList;
/**
@@ -157,13 +159,13 @@ public class DisplayController implements DisplayListener {
private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
private DisplayController.Info mInfo;
private DisplayHolder(Context displayContext) {
private DisplayHolder(Context displayContext, Display display) {
mDisplayContext = displayContext;
// Note that the Display object must be obtained from DisplayManager which is
// associated to the display context, so the Display is isolated from Activity and
// Application to provide the actual state of device that excludes the additional
// adjustment and override.
mInfo = new DisplayController.Info(mDisplayContext);
mInfo = new DisplayController.Info(display);
mId = mInfo.id;
}
@@ -180,22 +182,31 @@ public class DisplayController implements DisplayListener {
}
protected void handleOnChange() {
Display display = Utilities.ATLEAST_R
? mDisplayContext.getDisplay()
: mDisplayContext
.getSystemService(DisplayManager.class)
.getDisplay(mId);
if (display == null) {
return;
}
Info oldInfo = mInfo;
Info info = new Info(mDisplayContext);
Info newInfo = new Info(display);
int change = 0;
if (info.hasDifferentSize(oldInfo)) {
if (newInfo.hasDifferentSize(oldInfo)) {
change |= CHANGE_SIZE;
}
if (oldInfo.rotation != info.rotation) {
if (newInfo.rotation != oldInfo.rotation) {
change |= CHANGE_ROTATION;
}
if (info.singleFrameMs != oldInfo.singleFrameMs) {
if (newInfo.singleFrameMs != oldInfo.singleFrameMs) {
change |= CHANGE_FRAME_DELAY;
}
if (change != 0) {
mInfo = info;
mInfo = newInfo;
final int flags = change;
MAIN_EXECUTOR.execute(() -> notifyChange(flags));
}
@@ -216,7 +227,7 @@ public class DisplayController implements DisplayListener {
// Use application context to create display context so that it can have its own
// Resources.
Context displayContext = context.getApplicationContext().createDisplayContext(display);
return new DisplayHolder(displayContext);
return new DisplayHolder(displayContext, display);
}
}
@@ -244,12 +255,7 @@ public class DisplayController implements DisplayListener {
this.metrics = metrics;
}
private Info(Context context) {
this(context, context.getSystemService(DisplayManager.class)
.getDisplay(DEFAULT_DISPLAY));
}
public Info(Context context, Display display) {
public Info(Display display) {
id = display.getDisplayId();
rotation = display.getRotation();
@@ -262,7 +268,8 @@ public class DisplayController implements DisplayListener {
display.getRealSize(realSize);
display.getCurrentSizeRange(smallestSize, largestSize);
metrics = context.getResources().getDisplayMetrics();
metrics = new DisplayMetrics();
display.getMetrics(metrics);
}
private boolean hasDifferentSize(Info info) {
@@ -20,10 +20,8 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.graphics.Point;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.View;
@@ -37,8 +35,9 @@ import com.android.launcher3.DropTarget;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.search.AllAppsSearchBarController.SearchTargetHandler;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.graphics.DragPreviewProvider;
@@ -48,24 +47,28 @@ import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.launcher3.util.ComponentKey;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.util.ArrayList;
import java.util.List;
/**
* A view representing a high confidence app search result that includes shortcuts
* TODO (sfufa@) consolidate this with SearchResultIconRow
*/
public class HeroSearchResultView extends LinearLayout implements DragSource,
PayloadResultHandler<List<Pair<ShortcutInfo, ItemInfoWithIcon>>> {
public class HeroSearchResultView extends LinearLayout implements DragSource, SearchTargetHandler {
public static final String TARGET_TYPE_HERO_APP = "hero_app";
public static final int MAX_SHORTCUTS_COUNT = 2;
private final Object[] mTargetInfo = createTargetInfo();
BubbleTextView mBubbleTextView;
View mIconView;
BubbleTextView[] mDeepShortcutTextViews = new BubbleTextView[2];
AllAppsSearchPlugin mPlugin;
private SearchTarget mSearchTarget;
private BubbleTextView mBubbleTextView;
private View mIconView;
private BubbleTextView[] mDeepShortcutTextViews = new BubbleTextView[2];
public HeroSearchResultView(Context context) {
super(context);
@@ -96,8 +99,6 @@ public class HeroSearchResultView extends LinearLayout implements DragSource,
launcher.getItemOnClickListener().onClick(view);
});
mBubbleTextView.setOnLongClickListener(new HeroItemDragHandler(getContext(), this));
setLayoutParams(
new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, grid.allAppsCellHeightPx));
mDeepShortcutTextViews[0] = findViewById(R.id.shortcut_0);
@@ -108,35 +109,39 @@ public class HeroSearchResultView extends LinearLayout implements DragSource,
grid.allAppsIconSizePx));
bubbleTextView.setOnClickListener(view -> {
WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) bubbleTextView.getTag();
SearchTargetEvent event = getSearchTargetEvent(
SearchTarget.ItemType.APP_HERO,
SearchTargetEvent.CHILD_SELECT);
event.bundle = getAppBundle(itemInfo);
event.bundle.putString("shortcut_id", itemInfo.getDeepShortcutId());
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(event);
}
SearchTargetEvent event = new SearchTargetEvent.Builder(mSearchTarget,
SearchTargetEvent.CHILD_SELECT).setShortcutPosition(itemInfo.rank).build();
SearchEventTracker.getInstance(getContext()).notifySearchTargetEvent(event);
launcher.getItemOnClickListener().onClick(view);
});
}
}
/**
* Apply {@link ItemInfo} for appIcon and shortcut Icons
*/
@Override
public void applyAdapterInfo(
AdapterItemWithPayload<List<Pair<ShortcutInfo, ItemInfoWithIcon>>> adapterItem) {
mBubbleTextView.applyFromApplicationInfo(adapterItem.appInfo);
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
AllAppsStore apps = Launcher.getLauncher(getContext()).getAppsView().getAppsStore();
AppInfo appInfo = apps.getApp(new ComponentKey(searchTarget.getComponentName(),
searchTarget.getUserHandle()));
List<ShortcutInfo> infos = mSearchTarget.getShortcutInfos();
ArrayList<Pair<ShortcutInfo, ItemInfoWithIcon>> shortcuts = new ArrayList<>();
for (int i = 0; infos != null && i < infos.size() && i < MAX_SHORTCUTS_COUNT; i++) {
ShortcutInfo shortcutInfo = infos.get(i);
ItemInfoWithIcon si = new WorkspaceItemInfo(shortcutInfo, getContext());
si.rank = i;
shortcuts.add(new Pair<>(shortcutInfo, si));
}
mBubbleTextView.applyFromApplicationInfo(appInfo);
mIconView.setBackground(mBubbleTextView.getIcon());
mIconView.setTag(adapterItem.appInfo);
List<Pair<ShortcutInfo, ItemInfoWithIcon>> shortcutDetails = adapterItem.getPayload();
mIconView.setTag(appInfo);
LauncherAppState appState = LauncherAppState.getInstance(getContext());
for (int i = 0; i < mDeepShortcutTextViews.length; i++) {
BubbleTextView shortcutView = mDeepShortcutTextViews[i];
mDeepShortcutTextViews[i].setVisibility(shortcutDetails.size() > i ? VISIBLE : GONE);
if (i < shortcutDetails.size()) {
Pair<ShortcutInfo, ItemInfoWithIcon> p = shortcutDetails.get(i);
mDeepShortcutTextViews[i].setVisibility(shortcuts.size() > i ? VISIBLE : GONE);
if (i < shortcuts.size()) {
Pair<ShortcutInfo, ItemInfoWithIcon> p = shortcuts.get(i);
//apply ItemInfo and prepare view
shortcutView.applyFromWorkspaceItem((WorkspaceItemInfo) p.second);
MODEL_EXECUTOR.execute(() -> {
@@ -146,13 +151,7 @@ public class HeroSearchResultView extends LinearLayout implements DragSource,
});
}
}
mPlugin = adapterItem.getPlugin();
adapterItem.setSelectionHandler(this::handleSelection);
}
@Override
public Object[] getTargetInfo() {
return mTargetInfo;
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
}
@Override
@@ -190,38 +189,21 @@ public class HeroSearchResultView extends LinearLayout implements DragSource,
mLauncher.getWorkspace().beginDragShared(mContainer.mBubbleTextView,
draggableView, mContainer, itemInfo, previewProvider, new DragOptions());
SearchTargetEvent event = mContainer.getSearchTargetEvent(
SearchTarget.ItemType.APP_HERO, SearchTargetEvent.LONG_PRESS);
event.bundle = getAppBundle(itemInfo);
if (mContainer.mPlugin != null) {
mContainer.mPlugin.notifySearchTargetEvent(event);
}
SearchTargetEvent event = new SearchTargetEvent.Builder(mContainer.mSearchTarget,
SearchTargetEvent.LONG_PRESS).build();
SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(event);
return false;
}
}
private void handleSelection(int eventType) {
@Override
public void handleSelection(int eventType) {
ItemInfo itemInfo = (ItemInfo) mBubbleTextView.getTag();
if (itemInfo == null) return;
Launcher launcher = Launcher.getLauncher(getContext());
launcher.startActivitySafely(this, itemInfo.getIntent(), itemInfo);
SearchTargetEvent event = getSearchTargetEvent(
SearchTarget.ItemType.APP_HERO, eventType);
event.bundle = getAppBundle(itemInfo);
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(event);
}
}
/**
* Helper method to generate {@link SearchTargetEvent} bundle from {@link ItemInfo}
*/
public static Bundle getAppBundle(ItemInfo itemInfo) {
Bundle b = new Bundle();
b.putParcelable(Intent.EXTRA_COMPONENT_NAME, itemInfo.getTargetComponent());
b.putParcelable(Intent.EXTRA_USER, itemInfo.user);
return b;
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
}
@@ -0,0 +1,100 @@
/*
* Copyright (C) 2020 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.launcher3.views;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.ComponentKey;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
/**
* A {@link BubbleTextView} representing a single cell result in AllApps
*/
public class SearchResultIcon extends BubbleTextView implements
AllAppsSearchBarController.SearchTargetHandler, View.OnClickListener,
View.OnLongClickListener {
public static final String TARGET_TYPE_APP = "app";
private final Launcher mLauncher;
private SearchTarget mSearchTarget;
public SearchResultIcon(Context context) {
this(context, null, 0);
}
public SearchResultIcon(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchResultIcon(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mLauncher = Launcher.getLauncher(getContext());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
setLongPressTimeoutFactor(1f);
setOnFocusChangeListener(mLauncher.getFocusHandler());
setOnClickListener(this);
setOnLongClickListener(this);
getLayoutParams().height = mLauncher.getDeviceProfile().allAppsCellHeightPx;
}
@Override
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
AllAppsStore appsStore = mLauncher.getAppsView().getAppsStore();
SearchEventTracker.getInstance(getContext()).registerWeakHandler(mSearchTarget, this);
if (searchTarget.getItemType().equals(TARGET_TYPE_APP)) {
AppInfo appInfo = appsStore.getApp(new ComponentKey(searchTarget.getComponentName(),
searchTarget.getUserHandle()));
applyFromApplicationInfo(appInfo);
}
}
@Override
public void handleSelection(int eventType) {
mLauncher.getItemOnClickListener().onClick(this);
SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
@Override
public void onClick(View view) {
handleSelection(SearchTargetEvent.SELECT);
}
@Override
public boolean onLongClick(View view) {
SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, SearchTargetEvent.LONG_PRESS).build());
return ItemLongClickListener.INSTANCE_ALL_APPS.onLongClick(view);
}
}
@@ -22,100 +22,159 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.app.RemoteAction;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.util.AttributeSet;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.RemoteActionItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.launcher3.util.Themes;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTarget.ItemType;
import com.android.systemui.plugins.shared.SearchTargetEvent;
/**
* A view representing a stand alone shortcut search result
*/
public class SearchResultIconRow extends DoubleShadowBubbleTextView implements
AllAppsSearchBarController.PayloadResultHandler<SearchTarget> {
AllAppsSearchBarController.SearchTargetHandler {
private final Object[] mTargetInfo = createTargetInfo();
private ShortcutInfo mShortcutInfo;
private AllAppsSearchPlugin mPlugin;
private AdapterItemWithPayload<SearchTarget> mAdapterItem;
public static final String TARGET_TYPE_REMOTE_ACTION = "remote_action";
public static final String TARGET_TYPE_SUGGEST = "suggest";
public static final String TARGET_TYPE_SHORTCUT = "shortcut";
public static final String REMOTE_ACTION_SHOULD_START = "should_start_for_result";
public static final String REMOTE_ACTION_TOKEN = "action_token";
private final int mCustomIconResId;
private final boolean mMatchesInset;
private SearchTarget mSearchTarget;
public SearchResultIconRow(@NonNull Context context) {
super(context);
this(context, null, 0);
}
public SearchResultIconRow(@NonNull Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
this(context, attrs, 0);
}
public SearchResultIconRow(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.SearchResultIconRow, defStyleAttr, 0);
mCustomIconResId = a.getResourceId(R.styleable.SearchResultIconRow_customIcon, 0);
mMatchesInset = a.getBoolean(R.styleable.SearchResultIconRow_matchTextInsetWithQuery,
false);
a.recycle();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
Launcher launcher = Launcher.getLauncher(getContext());
if (mMatchesInset && launcher.getAppsView() != null && getParent() != null) {
EditText editText = launcher.getAppsView().getSearchUiManager().getEditText();
if (editText != null) {
int counterOffset = getIconSize() + getCompoundDrawablePadding() / 2;
setPadding(editText.getLeft() - counterOffset, getPaddingTop(),
getPaddingRight(), getPaddingBottom());
}
}
}
@Override
public void applyAdapterInfo(AdapterItemWithPayload<SearchTarget> adapterItemWithPayload) {
if (mAdapterItem != null) {
mAdapterItem.setSelectionHandler(null);
}
mAdapterItem = adapterItemWithPayload;
SearchTarget payload = adapterItemWithPayload.getPayload();
mPlugin = adapterItemWithPayload.getPlugin();
protected void drawFocusHighlight(Canvas canvas) {
mHighlightPaint.setColor(mHighlightColor);
float r = Themes.getDialogCornerRadius(getContext());
canvas.drawRoundRect(0, 0, getWidth(), getHeight(), r, r, mHighlightPaint);
}
if (payload.mRemoteAction != null) {
prepareUsingRemoteAction(payload.mRemoteAction,
payload.bundle.getString(SearchTarget.REMOTE_ACTION_TOKEN),
payload.bundle.getBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START));
} else {
prepareUsingShortcutInfo(payload.shortcuts.get(0));
@Override
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
String type = searchTarget.getItemType();
if (type.equals(TARGET_TYPE_REMOTE_ACTION) || type.equals(TARGET_TYPE_SUGGEST)) {
prepareUsingRemoteAction(searchTarget.getRemoteAction(),
searchTarget.getExtras().getString(REMOTE_ACTION_TOKEN),
searchTarget.getExtras().getBoolean(REMOTE_ACTION_SHOULD_START),
type.equals(TARGET_TYPE_REMOTE_ACTION));
} else if (type.equals(TARGET_TYPE_SHORTCUT)) {
prepareUsingShortcutInfo(searchTarget.getShortcutInfos().get(0));
}
setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
adapterItemWithPayload.setSelectionHandler(this::handleSelection);
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
}
private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) {
mShortcutInfo = shortcutInfo;
WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(mShortcutInfo, getContext());
WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext());
applyFromWorkspaceItem(workspaceItemInfo);
LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext());
MODEL_EXECUTOR.execute(() -> {
launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, mShortcutInfo);
reapplyItemInfoAsync(workspaceItemInfo);
});
if (!loadIconFromResource()) {
MODEL_EXECUTOR.execute(() -> {
launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, shortcutInfo);
reapplyItemInfoAsync(workspaceItemInfo);
});
}
}
private void prepareUsingRemoteAction(RemoteAction remoteAction, String token, boolean start) {
private void prepareUsingRemoteAction(RemoteAction remoteAction, String token, boolean start,
boolean useIconToBadge) {
RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(remoteAction, token, start);
applyFromRemoteActionInfo(itemInfo);
UI_HELPER_EXECUTOR.post(() -> {
// If the Drawable from the remote action is not AdaptiveBitmap, styling will not work.
try (LauncherIcons li = LauncherIcons.obtain(getContext())) {
Drawable d = itemInfo.getRemoteAction().getIcon().loadDrawable(getContext());
itemInfo.bitmap = li.createBadgedIconBitmap(d, itemInfo.user,
Build.VERSION.SDK_INT);
reapplyItemInfoAsync(itemInfo);
}
});
if (itemInfo.isEscapeHatch() || !loadIconFromResource()) {
UI_HELPER_EXECUTOR.post(() -> {
// If the Drawable from the remote action is not AdaptiveBitmap, styling will not
// work.
try (LauncherIcons li = LauncherIcons.obtain(getContext())) {
Drawable d = itemInfo.getRemoteAction().getIcon().loadDrawable(getContext());
BitmapInfo bitmap = li.createBadgedIconBitmap(d, itemInfo.user,
Build.VERSION.SDK_INT);
if (useIconToBadge) {
BitmapInfo placeholder = li.createIconBitmap(
itemInfo.getRemoteAction().getTitle().toString().substring(0, 1),
bitmap.color);
itemInfo.bitmap = li.badgeBitmap(placeholder.icon, bitmap);
} else {
itemInfo.bitmap = bitmap;
}
reapplyItemInfoAsync(itemInfo);
}
});
}
}
private boolean loadIconFromResource() {
if (mCustomIconResId == 0) return false;
setIcon(Launcher.getLauncher(getContext()).getDrawable(mCustomIconResId));
return true;
}
void reapplyItemInfoAsync(ItemInfoWithIcon itemInfoWithIcon) {
@@ -123,33 +182,15 @@ public class SearchResultIconRow extends DoubleShadowBubbleTextView implements
}
@Override
public Object[] getTargetInfo() {
return mTargetInfo;
}
private void handleSelection(int eventType) {
public void handleSelection(int eventType) {
ItemInfo itemInfo = (ItemInfo) getTag();
Launcher launcher = Launcher.getLauncher(getContext());
final SearchTargetEvent searchTargetEvent;
if (itemInfo instanceof WorkspaceItemInfo) {
ItemClickHandler.onClickAppShortcut(this, (WorkspaceItemInfo) itemInfo, launcher);
searchTargetEvent = getSearchTargetEvent(SearchTarget.ItemType.SHORTCUT,
eventType);
searchTargetEvent.shortcut = mShortcutInfo;
} else {
RemoteActionItemInfo remoteItemInfo = (RemoteActionItemInfo) itemInfo;
ItemClickHandler.onClickRemoteAction(launcher, remoteItemInfo);
searchTargetEvent = getSearchTargetEvent(ItemType.ACTION,
eventType);
searchTargetEvent.bundle = new Bundle();
searchTargetEvent.remoteAction = remoteItemInfo.getRemoteAction();
searchTargetEvent.bundle.putBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START,
remoteItemInfo.shouldStartInLauncher());
searchTargetEvent.bundle.putString(SearchTarget.REMOTE_ACTION_TOKEN,
remoteItemInfo.getToken());
}
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(searchTargetEvent);
ItemClickHandler.onClickRemoteAction(launcher, (RemoteActionItemInfo) itemInfo);
}
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
}
@@ -23,9 +23,12 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Process;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageButton;
@@ -39,10 +42,10 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.util.Themes;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.LauncherIcons;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
@@ -52,7 +55,9 @@ import java.util.ArrayList;
* A view representing a single people search result in all apps
*/
public class SearchResultPeopleView extends LinearLayout implements
AllAppsSearchBarController.PayloadResultHandler<Bundle> {
AllAppsSearchBarController.SearchTargetHandler {
public static final String TARGET_TYPE_PEOPLE = "people";
private final int mIconSize;
private final int mButtonSize;
@@ -60,9 +65,10 @@ public class SearchResultPeopleView extends LinearLayout implements
private View mIconView;
private TextView mTitleView;
private ImageButton[] mProviderButtons = new ImageButton[3];
private AllAppsSearchPlugin mPlugin;
private Intent mIntent;
private final Object[] mTargetInfo = createTargetInfo();
private SearchTarget mSearchTarget;
public SearchResultPeopleView(Context context) {
this(context, null, 0);
@@ -99,21 +105,19 @@ public class SearchResultPeopleView extends LinearLayout implements
}
@Override
public void applyAdapterInfo(
AllAppsGridAdapter.AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
Bundle payload = adapterItemWithPayload.getPayload();
mPlugin = adapterItemWithPayload.getPlugin();
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
Bundle payload = searchTarget.getExtras();
mTitleView.setText(payload.getString("title"));
mIntent = payload.getParcelable("intent");
Bitmap icon = payload.getParcelable("icon");
if (icon != null) {
RoundedBitmapDrawable d = RoundedBitmapDrawableFactory.create(getResources(), icon);
float radius = Themes.getDialogCornerRadius(getContext());
d.setCornerRadius(radius);
d.setBounds(0, 0, mIconSize, mIconSize);
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(),
Bitmap.createScaledBitmap(icon, mIconSize, mIconSize, false));
mIconView.setBackground(d);
Bitmap contactIcon = payload.getParcelable("icon");
try (LauncherIcons li = LauncherIcons.obtain(getContext())) {
BitmapInfo badgeInfo = li.createBadgedIconBitmap(
getAppIcon(mIntent.getPackage()), Process.myUserHandle(),
Build.VERSION.SDK_INT);
setIcon(li.badgeBitmap(roundBitmap(contactIcon), badgeInfo).icon, false);
} catch (Exception e) {
setIcon(contactIcon, true);
}
ArrayList<Bundle> providers = payload.getParcelableArrayList("providers");
@@ -122,59 +126,80 @@ public class SearchResultPeopleView extends LinearLayout implements
if (providers != null && i < providers.size()) {
Bundle provider = providers.get(i);
Intent intent = provider.getParcelable("intent");
setupProviderButton(button, provider, intent, adapterItemWithPayload);
String pkg = provider.getString("package_name");
setupProviderButton(button, provider, intent);
UI_HELPER_EXECUTOR.post(() -> {
try {
ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(
pkg, 0);
Drawable appIcon = applicationInfo.loadIcon(mPackageManager);
String pkg = provider.getString("package_name");
Drawable appIcon = getAppIcon(pkg);
if (appIcon != null) {
MAIN_EXECUTOR.post(() -> button.setImageDrawable(appIcon));
} catch (PackageManager.NameNotFoundException ignored) {
}
});
button.setVisibility(VISIBLE);
} else {
button.setVisibility(GONE);
}
}
adapterItemWithPayload.setSelectionHandler(this::handleSelection);
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
}
@Override
public Object[] getTargetInfo() {
return mTargetInfo;
/**
* Normalizes the bitmap to look like rounded App Icon
* TODO(b/170234747) to support styling, generate adaptive icon drawable and generate
* bitmap from it.
*/
private Bitmap roundBitmap(Bitmap icon) {
final RoundedBitmapDrawable d = RoundedBitmapDrawableFactory.create(getResources(), icon);
d.setCornerRadius(R.attr.folderIconRadius);
d.setBounds(0, 0, mIconSize, mIconSize);
final Bitmap bitmap = Bitmap.createBitmap(d.getBounds().width(), d.getBounds().height(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
d.draw(canvas);
return bitmap;
}
private void setupProviderButton(ImageButton button, Bundle provider, Intent intent,
AllAppsGridAdapter.AdapterItem adapterItem) {
private void setIcon(Bitmap icon, Boolean round) {
if (round) {
RoundedBitmapDrawable d = RoundedBitmapDrawableFactory.create(getResources(), icon);
d.setCornerRadius(R.attr.folderIconRadius);
d.setBounds(0, 0, mIconSize, mIconSize);
mIconView.setBackground(d);
} else {
mIconView.setBackground(new BitmapDrawable(getResources(), icon));
}
}
private Drawable getAppIcon(String pkg) {
try {
ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(
pkg, 0);
return applicationInfo.loadIcon(mPackageManager);
} catch (PackageManager.NameNotFoundException ignored) {
return null;
}
}
private void setupProviderButton(ImageButton button, Bundle provider, Intent intent) {
Launcher launcher = Launcher.getLauncher(getContext());
button.setOnClickListener(b -> {
launcher.startActivitySafely(b, intent, null);
SearchTargetEvent searchTargetEvent = getSearchTargetEvent(
SearchTarget.ItemType.PEOPLE,
SearchTargetEvent.CHILD_SELECT);
searchTargetEvent.bundle = new Bundle();
searchTargetEvent.bundle.putParcelable("intent", mIntent);
searchTargetEvent.bundle.putBundle("provider", provider);
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(searchTargetEvent);
}
Bundle bundle = new Bundle();
bundle.putBundle("provider", provider);
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget,
SearchTargetEvent.CHILD_SELECT).setExtras(bundle).build());
});
}
private void handleSelection(int eventType) {
@Override
public void handleSelection(int eventType) {
if (mIntent != null) {
Launcher launcher = Launcher.getLauncher(getContext());
launcher.startActivitySafely(this, mIntent, null);
SearchTargetEvent searchTargetEvent = getSearchTargetEvent(SearchTarget.ItemType.PEOPLE,
eventType);
searchTargetEvent.bundle = new Bundle();
searchTargetEvent.bundle.putParcelable("intent", mIntent);
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(searchTargetEvent);
}
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
}
}
@@ -21,6 +21,11 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
@@ -36,20 +41,28 @@ import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.util.Themes;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
/**
* A View representing a PlayStore item.
*/
public class SearchResultPlayItem extends LinearLayout implements
AllAppsSearchBarController.PayloadResultHandler<Bundle> {
AllAppsSearchBarController.SearchTargetHandler {
public static final String TARGET_TYPE_PLAY = "play";
private static final int BITMAP_CROP_MASK_COLOR = 0xff424242;
final Paint mIconPaint = new Paint();
final Rect mTempRect = new Rect();
private final DeviceProfile mDeviceProfile;
private View mIconView;
private TextView mTitleView;
@@ -57,8 +70,8 @@ public class SearchResultPlayItem extends LinearLayout implements
private Button mPreviewButton;
private String mPackageName;
private boolean mIsInstantGame;
private AllAppsSearchPlugin mPlugin;
private final Object[] mTargetInfo = createTargetInfo();
private SearchTarget mSearchTarget;
public SearchResultPlayItem(Context context) {
@@ -91,14 +104,35 @@ public class SearchResultPlayItem extends LinearLayout implements
iconParams.height = mDeviceProfile.allAppsIconSizePx;
iconParams.width = mDeviceProfile.allAppsIconSizePx;
setOnClickListener(view -> handleSelection(SearchTargetEvent.SELECT));
}
private Bitmap getRoundedBitmap(Bitmap bitmap) {
final int iconSize = bitmap.getWidth();
final float radius = Themes.getDialogCornerRadius(getContext());
Bitmap output = BitmapRenderer.createHardwareBitmap(iconSize, iconSize, (canvas) -> {
mTempRect.set(0, 0, iconSize, iconSize);
final RectF rectF = new RectF(mTempRect);
mIconPaint.setAntiAlias(true);
mIconPaint.reset();
canvas.drawARGB(0, 0, 0, 0);
mIconPaint.setColor(BITMAP_CROP_MASK_COLOR);
canvas.drawRoundRect(rectF, radius, radius, mIconPaint);
mIconPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, mTempRect, mTempRect, mIconPaint);
});
return output;
}
@Override
public void applyAdapterInfo(AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
Bundle bundle = adapterItemWithPayload.getPayload();
mPlugin = adapterItemWithPayload.getPlugin();
adapterItemWithPayload.setSelectionHandler(this::handleSelection);
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
Bundle bundle = searchTarget.getExtras();
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
if (bundle.getString("package", "").equals(mPackageName)) {
return;
}
@@ -109,17 +143,19 @@ public class SearchResultPlayItem extends LinearLayout implements
// TODO: Should use a generic type to get values b/165320033
showIfNecessary(mDetailViews[0], bundle.getString("price"));
showIfNecessary(mDetailViews[1], bundle.getString("rating"));
showIfNecessary(mDetailViews[2], bundle.getString("category"));
mIconView.setBackgroundResource(R.drawable.ic_deepshortcut_placeholder);
UI_HELPER_EXECUTOR.execute(() -> {
try {
// TODO: Handle caching
URL url = new URL(bundle.getString("icon_url"));
Bitmap bitmap = BitmapFactory.decodeStream(url.openStream());
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(),
URLConnection con = url.openConnection();
// TODO: monitor memory and investigate if it's better to use glide
con.addRequestProperty("Cache-Control", "max-age: 0");
con.setUseCaches(true);
Bitmap bitmap = BitmapFactory.decodeStream(con.getInputStream());
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), getRoundedBitmap(
Bitmap.createScaledBitmap(bitmap, mDeviceProfile.allAppsIconSizePx,
mDeviceProfile.allAppsIconSizePx, false));
mDeviceProfile.allAppsIconSizePx, false)));
mIconView.post(() -> mIconView.setBackground(bitmapDrawable));
} catch (IOException e) {
e.printStackTrace();
@@ -127,11 +163,6 @@ public class SearchResultPlayItem extends LinearLayout implements
});
}
@Override
public Object[] getTargetInfo() {
return mTargetInfo;
}
private void showIfNecessary(TextView textView, @Nullable String string) {
if (string == null || string.isEmpty()) {
textView.setVisibility(GONE);
@@ -141,7 +172,8 @@ public class SearchResultPlayItem extends LinearLayout implements
}
}
private void handleSelection(int eventType) {
@Override
public void handleSelection(int eventType) {
if (mPackageName == null) return;
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(
"https://play.google.com/store/apps/details?id="
@@ -167,12 +199,7 @@ public class SearchResultPlayItem extends LinearLayout implements
}
private void logSearchEvent(int eventType) {
SearchTargetEvent searchTargetEvent = getSearchTargetEvent(
SearchTarget.ItemType.PLAY_RESULTS, eventType);
searchTargetEvent.bundle = new Bundle();
searchTargetEvent.bundle.putString("package_name", mPackageName);
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(searchTargetEvent);
}
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
}
@@ -1,131 +0,0 @@
/*
* Copyright (C) 2020 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.launcher3.views;
import static com.android.systemui.plugins.shared.SearchTarget.ItemType.SUGGEST;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.RemoteActionItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
/**
* A view representing a fallback search suggestion row.
*/
public class SearchResultSuggestRow extends LinearLayout implements
View.OnClickListener, AllAppsSearchBarController.PayloadResultHandler<SearchTarget> {
private final Object[] mTargetInfo = createTargetInfo();
private AllAppsSearchPlugin mPlugin;
private AdapterItemWithPayload<SearchTarget> mAdapterItem;
private TextView mTitle;
public SearchResultSuggestRow(@NonNull Context context) {
super(context);
}
public SearchResultSuggestRow(@NonNull Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
}
public SearchResultSuggestRow(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mTitle = findViewById(R.id.title);
setOnClickListener(this);
}
@Override
public void applyAdapterInfo(AdapterItemWithPayload<SearchTarget> adapterItemWithPayload) {
mAdapterItem = adapterItemWithPayload;
SearchTarget payload = adapterItemWithPayload.getPayload();
mPlugin = adapterItemWithPayload.getPlugin();
if (payload.mRemoteAction != null) {
RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(payload.mRemoteAction,
payload.bundle.getString(SearchTarget.REMOTE_ACTION_TOKEN),
payload.bundle.getBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START));
setTag(itemInfo);
}
showIfAvailable(mTitle, payload.mRemoteAction.getTitle().toString());
setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
adapterItemWithPayload.setSelectionHandler(this::handleSelection);
}
@Override
public Object[] getTargetInfo() {
return mTargetInfo;
}
private void handleSelection(int eventType) {
ItemInfo itemInfo = (ItemInfo) getTag();
Launcher launcher = Launcher.getLauncher(getContext());
if (itemInfo instanceof RemoteActionItemInfo) return;
RemoteActionItemInfo remoteItemInfo = (RemoteActionItemInfo) itemInfo;
ItemClickHandler.onClickRemoteAction(launcher, remoteItemInfo);
SearchTargetEvent searchTargetEvent = getSearchTargetEvent(SUGGEST, eventType);
searchTargetEvent.bundle = new Bundle();
searchTargetEvent.remoteAction = remoteItemInfo.getRemoteAction();
searchTargetEvent.bundle.putBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START,
remoteItemInfo.shouldStartInLauncher());
searchTargetEvent.bundle.putString(SearchTarget.REMOTE_ACTION_TOKEN,
remoteItemInfo.getToken());
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(searchTargetEvent);
}
}
@Override
public void onClick(View view) {
handleSelection(SearchTargetEvent.SELECT);
}
private void showIfAvailable(TextView view, @Nullable String string) {
System.out.println("Plugin suggest string:" + string);
if (TextUtils.isEmpty(string)) {
view.setVisibility(GONE);
} else {
System.out.println("Plugin suggest string:" + string);
view.setVisibility(VISIBLE);
view.setText(string);
}
}
}
@@ -21,14 +21,16 @@ import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.systemui.plugins.shared.SearchTarget;
/**
* Header text view that shows a title for a given section in All apps search
*/
public class SearchSectionHeaderView extends TextView implements
AllAppsSearchBarController.PayloadResultHandler<String> {
AllAppsSearchBarController.SearchTargetHandler {
public static final String TARGET_TYPE_SECTION_HEADER = "section_header";
public SearchSectionHeaderView(Context context) {
super(context);
}
@@ -43,8 +45,8 @@ public class SearchSectionHeaderView extends TextView implements
}
@Override
public void applyAdapterInfo(AllAppsGridAdapter.AdapterItemWithPayload<String> adapterItem) {
String title = adapterItem.getPayload();
public void applySearchTarget(SearchTarget searchTarget) {
String title = searchTarget.getExtras().getString("title");
if (title == null || !title.isEmpty()) {
setText(title);
setVisibility(VISIBLE);
@@ -52,9 +54,4 @@ public class SearchSectionHeaderView extends TextView implements
setVisibility(INVISIBLE);
}
}
@Override
public Object[] getTargetInfo() {
return null;
}
}
@@ -29,9 +29,8 @@ import androidx.annotation.Nullable;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
@@ -41,14 +40,16 @@ import java.util.ArrayList;
* A row of tappable TextViews with a breadcrumb for settings search.
*/
public class SearchSettingsRowView extends LinearLayout implements
View.OnClickListener, AllAppsSearchBarController.PayloadResultHandler<Bundle> {
View.OnClickListener, AllAppsSearchBarController.SearchTargetHandler {
public static final String TARGET_TYPE_SETTINGS_ROW = "settings_row";
private TextView mTitleView;
private TextView mDescriptionView;
private TextView mBreadcrumbsView;
private Intent mIntent;
private AllAppsSearchPlugin mPlugin;
private final Object[] mTargetInfo = createTargetInfo();
private SearchTarget mSearchTarget;
public SearchSettingsRowView(@NonNull Context context) {
@@ -75,10 +76,9 @@ public class SearchSettingsRowView extends LinearLayout implements
}
@Override
public void applyAdapterInfo(
AllAppsGridAdapter.AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
Bundle bundle = adapterItemWithPayload.getPayload();
mPlugin = adapterItemWithPayload.getPlugin();
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
Bundle bundle = searchTarget.getExtras();
mIntent = bundle.getParcelable("intent");
showIfAvailable(mTitleView, bundle.getString("title"));
showIfAvailable(mDescriptionView, bundle.getString("description"));
@@ -86,12 +86,7 @@ public class SearchSettingsRowView extends LinearLayout implements
//TODO: implement RTL friendly breadcrumbs view
showIfAvailable(mBreadcrumbsView, breadcrumbs != null
? String.join(" > ", breadcrumbs) : null);
adapterItemWithPayload.setSelectionHandler(this::handleSelection);
}
@Override
public Object[] getTargetInfo() {
return mTargetInfo;
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
}
private void showIfAvailable(TextView view, @Nullable String string) {
@@ -108,19 +103,15 @@ public class SearchSettingsRowView extends LinearLayout implements
handleSelection(SearchTargetEvent.SELECT);
}
private void handleSelection(int eventType) {
@Override
public void handleSelection(int eventType) {
if (mIntent == null) return;
// TODO: create ItemInfo object and then use it to call startActivityForResult for proper
// WW logging
Launcher launcher = Launcher.getLauncher(getContext());
launcher.startActivityForResult(mIntent, 0);
SearchTargetEvent searchTargetEvent = getSearchTargetEvent(
SearchTarget.ItemType.SETTINGS_ROW, eventType);
searchTargetEvent.bundle = new Bundle();
searchTargetEvent.bundle.putParcelable("intent", mIntent);
if (mPlugin != null) {
mPlugin.notifySearchTargetEvent(searchTargetEvent);
}
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
}
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2020 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.launcher3.views;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.slice.Slice;
import androidx.slice.SliceItem;
import androidx.slice.widget.EventInfo;
import androidx.slice.widget.SliceLiveData;
import androidx.slice.widget.SliceView;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
/**
* A Wrapper class for {@link SliceView} search results
*/
public class SearchSliceWrapper implements SliceView.OnSliceActionListener {
public static final String TARGET_TYPE_SLICE = "settings_slice";
private static final String TAG = "SearchSliceController";
private static final String URI_EXTRA_KEY = "slice_uri";
private final Launcher mLauncher;
private final SearchTarget mSearchTarget;
private final SliceView mSliceView;
private LiveData<Slice> mSliceLiveData;
public SearchSliceWrapper(Context context, SliceView sliceView, SearchTarget searchTarget) {
mLauncher = Launcher.getLauncher(context);
mSearchTarget = searchTarget;
mSliceView = sliceView;
sliceView.setOnSliceActionListener(this);
try {
mSliceLiveData = SliceLiveData.fromUri(mLauncher, getSliceUri());
mSliceLiveData.observe((Launcher) mLauncher, sliceView);
} catch (Exception ex) {
Log.e(TAG, "unable to bind slice", ex);
}
}
/**
* Unregisters event handlers and removes lifecycle observer
*/
public void destroy() {
mSliceView.setOnSliceActionListener(null);
mSliceLiveData.removeObservers(mLauncher);
}
@Override
public void onSliceAction(@NonNull EventInfo info, @NonNull SliceItem item) {
SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget,
SearchTargetEvent.CHILD_SELECT).build());
}
private Uri getSliceUri() {
return mSearchTarget.getExtras().getParcelable(URI_EXTRA_KEY);
}
}
@@ -15,6 +15,9 @@
*/
package com.android.launcher3.views;
import static com.android.launcher3.views.SearchResultIconRow.REMOTE_ACTION_SHOULD_START;
import static com.android.launcher3.views.SearchResultIconRow.REMOTE_ACTION_TOKEN;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -26,14 +29,13 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.RemoteActionItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Themes;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
@@ -41,11 +43,12 @@ import com.android.systemui.plugins.shared.SearchTargetEvent;
* A view representing a high confidence app search result that includes shortcuts
*/
public class ThumbnailSearchResultView extends androidx.appcompat.widget.AppCompatImageView
implements AllAppsSearchBarController.PayloadResultHandler<SearchTarget> {
implements AllAppsSearchBarController.SearchTargetHandler {
private final Object[] mTargetInfo = createTargetInfo();
AllAppsSearchPlugin mPlugin;
int mPosition;
public static final String TARGET_TYPE_SCREENSHOT = "screenshot";
public static final String TARGET_TYPE_SCREENSHOT_LEGACY = "screenshot_legacy";
private SearchTarget mSearchTarget;
public ThumbnailSearchResultView(Context context) {
super(context);
@@ -59,7 +62,8 @@ public class ThumbnailSearchResultView extends androidx.appcompat.widget.AppComp
super(context, attrs, defStyleAttr);
}
private void handleSelection(int eventType) {
@Override
public void handleSelection(int eventType) {
Launcher launcher = Launcher.getLauncher(getContext());
ItemInfo itemInfo = (ItemInfo) getTag();
if (itemInfo instanceof RemoteActionItemInfo) {
@@ -68,33 +72,29 @@ public class ThumbnailSearchResultView extends androidx.appcompat.widget.AppComp
} else {
ItemClickHandler.onClickAppShortcut(this, (WorkspaceItemInfo) itemInfo, launcher);
}
if (mPlugin != null) {
SearchTargetEvent event = getSearchTargetEvent(
SearchTarget.ItemType.SCREENSHOT, eventType);
mPlugin.notifySearchTargetEvent(event);
}
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
@Override
public void applyAdapterInfo(AdapterItemWithPayload<SearchTarget> adapterItem) {
Launcher launcher = Launcher.getLauncher(getContext());
mPosition = adapterItem.position;
SearchTarget target = adapterItem.getPayload();
public void applySearchTarget(SearchTarget target) {
mSearchTarget = target;
Bitmap bitmap;
if (target.mRemoteAction != null) {
RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(target.mRemoteAction,
target.bundle.getString(SearchTarget.REMOTE_ACTION_TOKEN),
target.bundle.getBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START));
ItemClickHandler.onClickRemoteAction(launcher, itemInfo);
bitmap = ((BitmapDrawable) target.mRemoteAction.getIcon()
if (target.getRemoteAction() != null) {
RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(target.getRemoteAction(),
target.getExtras().getString(REMOTE_ACTION_TOKEN),
target.getExtras().getBoolean(REMOTE_ACTION_SHOULD_START));
bitmap = ((BitmapDrawable) target.getRemoteAction().getIcon()
.loadDrawable(getContext())).getBitmap();
setTag(itemInfo);
// crop
bitmap = Bitmap.createBitmap(bitmap, 0,
bitmap.getHeight() / 2 - bitmap.getWidth() / 2,
bitmap.getWidth(), bitmap.getWidth());
} else {
bitmap = (Bitmap) target.bundle.getParcelable("bitmap");
bitmap = (Bitmap) target.getExtras().getParcelable("bitmap");
WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
itemInfo.intent = new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse(target.bundle.getString("uri")))
.setData(Uri.parse(target.getExtras().getString("uri")))
.setType("image/*")
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
setTag(itemInfo);
@@ -103,12 +103,6 @@ public class ThumbnailSearchResultView extends androidx.appcompat.widget.AppComp
drawable.setCornerRadius(Themes.getDialogCornerRadius(getContext()));
setImageDrawable(drawable);
setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
mPlugin = adapterItem.getPlugin();
adapterItem.setSelectionHandler(this::handleSelection);
}
@Override
public Object[] getTargetInfo() {
return mTargetInfo;
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(target, this);
}
}