Files
lawnchair/quickstep/src/com/android/launcher3/search/SearchResultIcon.java
thiruram 43b1d40918 [AA+] Add QueryLength to DeviceSearchResultContainer.
Bug: 178562918
Change-Id: I4891433bcd8848edd92f103448f5c00c5f9619e4
2021-02-08 10:08:11 -08:00

333 lines
14 KiB
Java

/*
* 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.search;
import static com.android.launcher3.model.data.SearchActionItemInfo.FLAG_BADGE_WITH_PACKAGE;
import static com.android.launcher3.model.data.SearchActionItemInfo.FLAG_PRIMARY_ICON_FROM_TITLE;
import static com.android.launcher3.search.SearchTargetUtil.BUNDLE_EXTRA_PRIMARY_ICON_FROM_TITLE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.app.search.SearchAction;
import android.app.search.SearchTarget;
import android.app.search.SearchTargetEvent;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ShortcutInfo;
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.Icon;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.android.app.search.ResultType;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import com.android.launcher3.logger.LauncherAtomExtensions.DeviceSearchResultContainer;
import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.model.data.SearchActionItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.Themes;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.function.Consumer;
/**
* A {@link BubbleTextView} representing a single cell result in AllApps
*/
public class SearchResultIcon extends BubbleTextView implements
SearchTargetHandler, View.OnClickListener,
View.OnLongClickListener {
//Play store thumbnail process workaround
private final Rect mTempRect = new Rect();
private final Paint mIconPaint = new Paint();
private static final int BITMAP_CROP_MASK_COLOR = 0xff424242;
private final Launcher mLauncher;
private final SearchSessionTracker mSearchSessionTracker;
private String mTargetId;
private Consumer<ItemInfoWithIcon> mOnItemInfoChanged;
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());
mSearchSessionTracker = SearchSessionTracker.getInstance(getContext());
}
private boolean mLongPressSupported;
@Override
protected void onFinishInflate() {
super.onFinishInflate();
setLongPressTimeoutFactor(1f);
setOnFocusChangeListener(mLauncher.getFocusHandler());
setOnClickListener(this);
setOnLongClickListener(this);
setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
mLauncher.getDeviceProfile().allAppsCellHeightPx));
}
/**
* Applies {@link SearchTarget} to view. registers a consumer after a corresponding {@link
* ItemInfoWithIcon} is created
*/
public void apply(SearchTarget searchTarget, List<SearchTarget> inlineItems,
Consumer<ItemInfoWithIcon> cb) {
mOnItemInfoChanged = cb;
apply(searchTarget, inlineItems);
}
@Override
public void apply(SearchTarget parentTarget, List<SearchTarget> children) {
mTargetId = parentTarget.getId();
if (parentTarget.getShortcutInfo() != null) {
prepareUsingShortcutInfo(parentTarget.getShortcutInfo());
mLongPressSupported = true;
} else if (parentTarget.getSearchAction() != null) {
prepareUsingSearchAction(parentTarget);
mLongPressSupported = false;
} else {
String className = parentTarget.getExtras().getString(SearchTargetUtil.EXTRA_CLASS);
prepareUsingApp(new ComponentName(parentTarget.getPackageName(), className),
parentTarget.getUserHandle());
mLongPressSupported = true;
}
}
private void prepareUsingSearchAction(SearchTarget searchTarget) {
SearchAction searchAction = searchTarget.getSearchAction();
Bundle extras = searchAction.getExtras();
SearchActionItemInfo itemInfo = new SearchActionItemInfo(searchAction.getIcon(),
searchTarget.getPackageName(), searchTarget.getUserHandle(),
searchAction.getTitle()) {
// Workaround to log ItemInfo with DeviceSearchResultContainer without
// updating ItemInfo.container field.
@Override
public ContainerInfo getContainerInfo() {
return buildDeviceSearchResultContainer();
}
};
itemInfo.setIntent(searchAction.getIntent());
itemInfo.setPendingIntent(searchAction.getPendingIntent());
//TODO: remove this after flags are introduced in SearchAction. Settings results require
// startActivityForResult
boolean isSettingsResult = searchTarget.getResultType() == ResultType.SETTING;
if ((extras != null && extras.getBoolean(
SearchTargetUtil.BUNDLE_EXTRA_SHOULD_START_FOR_RESULT))
|| isSettingsResult) {
itemInfo.setFlags(SearchActionItemInfo.FLAG_SHOULD_START_FOR_RESULT);
} else if (extras != null && extras.getBoolean(
SearchTargetUtil.BUNDLE_EXTRA_SHOULD_START)) {
itemInfo.setFlags(SearchActionItemInfo.FLAG_SHOULD_START);
}
if (extras != null && extras.getBoolean(
SearchTargetUtil.BUNDLE_EXTRA_BADGE_WITH_PACKAGE)) {
itemInfo.setFlags(FLAG_BADGE_WITH_PACKAGE);
}
if (extras != null && extras.getBoolean(BUNDLE_EXTRA_PRIMARY_ICON_FROM_TITLE)) {
itemInfo.setFlags(FLAG_PRIMARY_ICON_FROM_TITLE);
}
notifyItemInfoChanged(itemInfo);
LauncherAppState appState = LauncherAppState.getInstance(mLauncher);
MODEL_EXECUTOR.post(() -> {
try (LauncherIcons li = LauncherIcons.obtain(getContext())) {
Icon icon = searchTarget.getSearchAction().getIcon();
BitmapInfo pkgBitmap = getPackageBitmap(appState, searchTarget);
if (itemInfo.hasFlags(FLAG_PRIMARY_ICON_FROM_TITLE)) {
// create a bitmap with first char if FLAG_PRIMARY_ICON_FROM_TITLE is set
itemInfo.bitmap = li.createIconBitmap(String.valueOf(itemInfo.title.charAt(0)),
pkgBitmap.color);
} else if (icon == null) {
// Use default icon from package name
itemInfo.bitmap = pkgBitmap;
} else {
boolean isPlayResult = searchTarget.getResultType() == ResultType.PLAY;
if (isPlayResult) {
Bitmap b = getPlayResultBitmap(searchAction.getIcon());
itemInfo.bitmap = b == null
? BitmapInfo.LOW_RES_INFO : BitmapInfo.fromBitmap(b);
} else {
itemInfo.bitmap = li.createBadgedIconBitmap(icon.loadDrawable(getContext()),
itemInfo.user, false);
}
}
// badge with package name
if (itemInfo.hasFlags(FLAG_BADGE_WITH_PACKAGE) && itemInfo.bitmap != pkgBitmap) {
itemInfo.bitmap = li.badgeBitmap(itemInfo.bitmap.icon, pkgBitmap);
}
}
MAIN_EXECUTOR.post(() -> applyFromSearchActionItemInfo(itemInfo));
});
}
private static BitmapInfo getPackageBitmap(LauncherAppState appState, SearchTarget target) {
PackageItemInfo pkgInfo = new PackageItemInfo(target.getPackageName());
pkgInfo.user = target.getUserHandle();
appState.getIconCache().getTitleAndIconForApp(pkgInfo, false);
return pkgInfo.bitmap;
}
private Bitmap getPlayResultBitmap(Icon icon) {
try {
int iconSize = getIconSize();
URL url = new URL(icon.getUri().toString());
URLConnection con = url.openConnection();
con.addRequestProperty("Cache-Control", "max-age: 0");
con.setUseCaches(true);
Bitmap bitmap = BitmapFactory.decodeStream(con.getInputStream());
return getRoundedBitmap(Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private Bitmap getRoundedBitmap(Bitmap bitmap) {
final int iconSize = bitmap.getWidth();
final float radius = Themes.getDialogCornerRadius(getContext());
return 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);
});
}
private void prepareUsingApp(ComponentName componentName, UserHandle userHandle) {
AllAppsStore appsStore = mLauncher.getAppsView().getAppsStore();
AppInfo appInfo = new AppInfo(
appsStore.getApp(new ComponentKey(componentName, userHandle))) {
// Workaround to log ItemInfo with DeviceSearchResultContainer without
// updating ItemInfo.container field.
@Override
public ContainerInfo getContainerInfo() {
return buildDeviceSearchResultContainer();
}
};
if (appInfo == null) {
setVisibility(GONE);
return;
}
applyFromApplicationInfo(appInfo);
notifyItemInfoChanged(appInfo);
}
private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) {
WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext()) {
// Workaround to log ItemInfo with DeviceSearchResultContainer without
// updating ItemInfo.container field.
@Override
public ContainerInfo getContainerInfo() {
return buildDeviceSearchResultContainer();
}
};
notifyItemInfoChanged(workspaceItemInfo);
LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext());
MODEL_EXECUTOR.execute(() -> {
launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, shortcutInfo);
MAIN_EXECUTOR.post(() -> applyFromWorkspaceItem(workspaceItemInfo));
});
}
@Override
public boolean quickSelect() {
this.performClick();
notifyEvent(mLauncher, mTargetId, SearchTargetEvent.ACTION_LAUNCH_KEYBOARD_FOCUS);
return true;
}
@Override
public void onClick(View view) {
ItemClickHandler.INSTANCE.onClick(view);
notifyEvent(mLauncher, mTargetId, SearchTargetEvent.ACTION_LAUNCH_TOUCH);
}
@Override
public boolean onLongClick(View view) {
if (!mLongPressSupported) {
return false;
}
notifyEvent(mLauncher, mTargetId, SearchTargetEvent.ACTION_LONGPRESS);
return ItemLongClickListener.INSTANCE_ALL_APPS.onLongClick(this);
}
private void notifyItemInfoChanged(ItemInfoWithIcon itemInfoWithIcon) {
if (mOnItemInfoChanged != null) {
mOnItemInfoChanged.accept(itemInfoWithIcon);
mOnItemInfoChanged = null;
}
}
private ContainerInfo buildDeviceSearchResultContainer() {
return ContainerInfo.newBuilder().setExtendedContainers(
ExtendedContainers
.newBuilder()
.setDeviceSearchResultContainer(
mSearchSessionTracker.getQueryLength()
.map(queryLength -> DeviceSearchResultContainer.newBuilder()
.setQueryLength(queryLength))
.orElse(DeviceSearchResultContainer.newBuilder())))
.build();
}
}