Merge " Migrate from Plugin SearchTarget to API search Target [3/3]"

This commit is contained in:
Samuel Fufa
2021-01-19 22:05:49 +00:00
committed by Android (Google) Code Review
13 changed files with 309 additions and 100 deletions

View File

@@ -23,6 +23,8 @@ import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.android.app.search.LayoutType;
import com.android.app.search.ResultType;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsContainerView;
@@ -78,10 +80,10 @@ public class DeviceSearchAdapterProvider extends SearchAdapterProvider {
SearchTargetHandler
payloadResultView =
(SearchTargetHandler) holder.itemView;
if (FeatureFlags.SEARCH_TARGET_LEGACY.get()) {
if (!FeatureFlags.USE_SEARCH_API.get()) {
payloadResultView.applySearchTarget(item.getSearchTargetLegacy());
} else {
payloadResultView.applySearchTarget(item.getSearchTarget());
payloadResultView.applySearchTarget(item.getSearchTarget(), item.getInlineItems());
}
}
@@ -123,9 +125,24 @@ public class DeviceSearchAdapterProvider extends SearchAdapterProvider {
* Returns -1 if viewType is not found
*/
public int getViewTypeForSearchTarget(SearchTarget t) {
//TODO: Replace with values from :SearchUi
if (t.getResultType() == 1 && t.getLayoutType().equals("icon")) {
return VIEW_TYPE_SEARCH_ICON;
if (t.getLayoutType().equals(LayoutType.TEXT_HEADER)) {
return VIEW_TYPE_SEARCH_CORPUS_TITLE;
}
switch (t.getResultType()) {
case ResultType.APPLICATION:
if (t.getLayoutType().equals(LayoutType.ICON_SINGLE_VERTICAL_TEXT)) {
return VIEW_TYPE_SEARCH_ICON;
}
break;
case ResultType.SETTING:
if (t.getLayoutType().equals(LayoutType.ICON_SLICE)) {
return VIEW_TYPE_SEARCH_SLICE;
}
return VIEW_TYPE_SEARCH_ROW;
case ResultType.SHORTCUT:
return VIEW_TYPE_SEARCH_ICON_ROW;
case ResultType.PLAY:
return VIEW_TYPE_SEARCH_ROW_WITH_BUTTON;
}
return -1;
}

View File

@@ -32,12 +32,16 @@ import android.app.search.SearchTarget;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.systemui.plugins.shared.SearchTargetLegacy;
import java.util.ArrayList;
import java.util.List;
/**
* Extension of AdapterItem that contains an extra payload specific to item
*/
public class SearchAdapterItem extends AllAppsGridAdapter.AdapterItem {
private SearchTargetLegacy mSearchTargetLegacy;
private SearchTarget mSearchTarget;
private List<SearchTarget> mInlineItems = new ArrayList<>();
private static final int AVAILABLE_FOR_ACCESSIBILITY = VIEW_TYPE_SEARCH_ROW_WITH_BUTTON
@@ -65,6 +69,9 @@ public class SearchAdapterItem extends AllAppsGridAdapter.AdapterItem {
return mSearchTarget;
}
public List<SearchTarget> getInlineItems() {
return mInlineItems;
}
@Override
protected boolean isCountedForAccessibility() {
return (AVAILABLE_FOR_ACCESSIBILITY & viewType) == viewType;

View File

@@ -31,10 +31,12 @@ 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.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.AppInfo;
@@ -46,6 +48,7 @@ import com.android.launcher3.util.ComponentKey;
import com.android.systemui.plugins.shared.SearchTargetEventLegacy;
import com.android.systemui.plugins.shared.SearchTargetLegacy;
import java.util.List;
import java.util.function.Consumer;
/**
@@ -128,10 +131,27 @@ public class SearchResultIcon extends BubbleTextView implements
}
}
/**
* Applies {@link SearchTarget} to view. registers a consumer after a corresponding
* {@link ItemInfoWithIcon} is created
*/
public void applySearchTarget(SearchTarget searchTarget, List<SearchTarget> inlineItems,
Consumer<ItemInfoWithIcon> cb) {
mOnItemInfoChanged = cb;
applySearchTarget(searchTarget, inlineItems);
}
@Override
public void applySearchTarget(SearchTarget searchTarget) {
prepareUsingApp(new ComponentName(searchTarget.getPackageName(),
searchTarget.getExtras().getString("class")), searchTarget.getUserHandle());
public void applySearchTarget(SearchTarget parentTarget, List<SearchTarget> children) {
switch (parentTarget.getResultType()) {
case ResultType.APPLICATION:
prepareUsingApp(new ComponentName(parentTarget.getPackageName(),
parentTarget.getExtras().getString("class")), parentTarget.getUserHandle());
break;
case ResultType.SHORTCUT:
prepareUsingShortcutInfo(parentTarget.getShortcutInfo());
break;
}
}
private void prepareUsingApp(ComponentName componentName, UserHandle userHandle) {
@@ -185,7 +205,9 @@ public class SearchResultIcon extends BubbleTextView implements
@Override
public void handleSelection(int eventType) {
mLauncher.getItemOnClickListener().onClick(this);
reportEvent(eventType);
if (!FeatureFlags.USE_SEARCH_API.get()) {
reportEvent(eventType);
}
}
private void reportEvent(int eventType) {

View File

@@ -18,6 +18,7 @@ package com.android.launcher3.search;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.app.search.SearchTarget;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ShortcutInfo;
@@ -32,6 +33,7 @@ import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.app.search.ResultType;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
@@ -111,6 +113,16 @@ public class SearchResultIconRow extends LinearLayout implements
setOnLongClickListener(this);
}
@Override
public void applySearchTarget(SearchTarget parentTarget, List<SearchTarget> children) {
mResultIcon.applySearchTarget(parentTarget, children, this);
if (parentTarget.getResultType() == ResultType.SHORTCUT) {
ShortcutInfo shortcutInfo = parentTarget.getShortcutInfo();
setProviderDetails(new ComponentName(shortcutInfo.getPackage(), ""),
shortcutInfo.getUserHandle());
}
}
@Override
public void applySearchTarget(SearchTargetLegacy searchTarget) {
mSearchTarget = searchTarget;

View File

@@ -17,6 +17,8 @@ package com.android.launcher3.search;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.app.search.SearchAction;
import android.app.search.SearchTarget;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -27,8 +29,6 @@ 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;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -43,12 +43,11 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.util.Themes;
import com.android.systemui.plugins.shared.SearchTargetEventLegacy;
import com.android.systemui.plugins.shared.SearchTargetLegacy;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
/**
* A View representing a PlayStore item.
@@ -67,9 +66,8 @@ public class SearchResultPlayItem extends LinearLayout implements
private TextView[] mDetailViews = new TextView[3];
private Button mPreviewButton;
private String mPackageName;
private boolean mIsInstantGame;
private SearchTargetLegacy mSearchTarget;
private Intent mIntent;
private Intent mSecondaryIntent;
public SearchResultPlayItem(Context context) {
@@ -93,7 +91,7 @@ public class SearchResultPlayItem extends LinearLayout implements
mIconView = findViewById(R.id.icon);
mTitleView = findViewById(R.id.title_view);
mPreviewButton = findViewById(R.id.try_button);
mPreviewButton.setOnClickListener(view -> launchInstantGame());
mPreviewButton.setOnClickListener(view -> launchIntent(mSecondaryIntent));
mDetailViews[0] = findViewById(R.id.detail_0);
mDetailViews[1] = findViewById(R.id.detail_1);
mDetailViews[2] = findViewById(R.id.detail_2);
@@ -101,9 +99,59 @@ public class SearchResultPlayItem extends LinearLayout implements
ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams();
iconParams.height = mDeviceProfile.allAppsIconSizePx;
iconParams.width = mDeviceProfile.allAppsIconSizePx;
setOnClickListener(view -> handleSelection(SearchTargetEventLegacy.SELECT));
setOnClickListener(view -> launchIntent(mIntent));
}
private void showIfNecessary(TextView textView, @Nullable String string) {
if (string == null || string.isEmpty()) {
textView.setVisibility(GONE);
} else {
textView.setText(string);
textView.setVisibility(VISIBLE);
}
}
private void launchIntent(Intent intent) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(intent);
}
@Override
public void applySearchTarget(SearchTarget parentTarget, List<SearchTarget> children) {
if (parentTarget.getPackageName().equals(mPackageName)) {
return;
}
mPackageName = parentTarget.getPackageName();
SearchAction action = parentTarget.getSearchAction();
mTitleView.setText(action.getTitle());
showIfNecessary(mDetailViews[0], action.getSubtitle().toString());
mIntent = action.getIntent();
mIconView.setBackgroundResource(R.drawable.ic_deepshortcut_placeholder);
loadIcon(action.getIcon().getUri().toString());
mSecondaryIntent = children.size() == 1 ? children.get(0).getSearchAction().getIntent()
: null;
mPreviewButton.setVisibility(mSecondaryIntent == null ? GONE : VISIBLE);
}
private void loadIcon(String iconUrl) {
UI_HELPER_EXECUTOR.execute(() -> {
try {
URL url = new URL(iconUrl);
URLConnection con = url.openConnection();
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)));
mIconView.post(() -> mIconView.setBackground(bitmapDrawable));
} catch (IOException e) {
e.printStackTrace();
}
});
}
private Bitmap getRoundedBitmap(Bitmap bitmap) {
final int iconSize = bitmap.getWidth();
@@ -124,80 +172,4 @@ public class SearchResultPlayItem extends LinearLayout implements
});
return output;
}
@Override
public void applySearchTarget(SearchTargetLegacy searchTarget) {
mSearchTarget = searchTarget;
Bundle bundle = searchTarget.getExtras();
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
if (bundle.getString("package", "").equals(mPackageName)) {
return;
}
mIsInstantGame = bundle.getBoolean("instant_game", false);
mPackageName = bundle.getString("package");
mPreviewButton.setVisibility(mIsInstantGame ? VISIBLE : GONE);
mTitleView.setText(bundle.getString("title"));
// TODO: Should use a generic type to get values b/165320033
showIfNecessary(mDetailViews[0], bundle.getString("price"));
showIfNecessary(mDetailViews[1], bundle.getString("rating"));
mIconView.setBackgroundResource(R.drawable.ic_deepshortcut_placeholder);
UI_HELPER_EXECUTOR.execute(() -> {
try {
URL url = new URL(bundle.getString("icon_url"));
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)));
mIconView.post(() -> mIconView.setBackground(bitmapDrawable));
} catch (IOException e) {
e.printStackTrace();
}
});
}
private void showIfNecessary(TextView textView, @Nullable String string) {
if (string == null || string.isEmpty()) {
textView.setVisibility(GONE);
} else {
textView.setText(string);
textView.setVisibility(VISIBLE);
}
}
@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="
+ mPackageName));
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(i);
logSearchEvent(eventType);
}
private void launchInstantGame() {
if (!mIsInstantGame) return;
Intent intent = new Intent(Intent.ACTION_VIEW);
String referrer = "Pixel_Launcher";
String id = mPackageName;
String deepLinkUrl = "market://details?id=" + id + "&launch=true&referrer=" + referrer;
intent.setPackage("com.android.vending");
intent.setData(Uri.parse(deepLinkUrl));
intent.putExtra("overlay", true);
intent.putExtra("callerId", getContext().getPackageName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(intent);
logSearchEvent(SearchTargetEventLegacy.CHILD_SELECT);
}
private void logSearchEvent(int eventType) {
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEventLegacy.Builder(mSearchTarget, eventType).build());
}
}

View File

@@ -15,6 +15,7 @@
*/
package com.android.launcher3.search;
import android.app.search.SearchTarget;
import android.content.Context;
import android.net.Uri;
import android.util.AttributeSet;
@@ -35,6 +36,8 @@ import com.android.launcher3.R;
import com.android.systemui.plugins.shared.SearchTargetEventLegacy;
import com.android.systemui.plugins.shared.SearchTargetLegacy;
import java.util.List;
/**
* A slice view wrapper with settings app icon at start
*/
@@ -88,6 +91,18 @@ public class SearchResultSettingsSlice extends LinearLayout implements
}
}
@Override
public void applySearchTarget(SearchTarget parentTarget, List<SearchTarget> children) {
reset();
try {
mSliceLiveData = mLauncher.getLiveSearchManager().getSliceForUri(
parentTarget.getSliceUri());
mSliceLiveData.observe(mLauncher, mSliceView);
} catch (Exception ex) {
Log.e(TAG, "unable to bind slice", ex);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();

View File

@@ -15,6 +15,7 @@
*/
package com.android.launcher3.search;
import android.app.search.SearchTarget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;
@@ -23,6 +24,8 @@ import androidx.annotation.Nullable;
import com.android.systemui.plugins.shared.SearchTargetLegacy;
import java.util.List;
/**
* Header text view that shows a title for a given section in All apps search
*/
@@ -53,4 +56,10 @@ public class SearchSectionHeaderView extends TextView implements
setVisibility(INVISIBLE);
}
}
@Override
public void applySearchTarget(SearchTarget parentTarget, List<SearchTarget> children) {
setText(parentTarget.getSearchAction().getTitle());
setVisibility(VISIBLE);
}
}

View File

@@ -0,0 +1,137 @@
/*
* 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.util.Executors.UI_HELPER_EXECUTOR;
import android.app.search.Query;
import android.app.search.SearchContext;
import android.app.search.SearchSession;
import android.app.search.SearchTarget;
import android.app.search.SearchUiManager;
import android.content.Context;
import android.os.CancellationSignal;
import android.text.TextUtils;
import android.util.Log;
import com.android.app.search.ResultType;
import com.android.launcher3.allapps.AllAppsGridAdapter;
import com.android.launcher3.allapps.AllAppsSectionDecorator;
import com.android.launcher3.allapps.search.SearchPipeline;
import com.android.launcher3.allapps.search.SearchSectionInfo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.function.Consumer;
/**
* Search pipeline utilizing {@link android.app.search.SearchUiManager}
*/
public class SearchServicePipeline implements SearchPipeline {
private static final int SUPPORTED_RESULT_TYPES =
ResultType.APPLICATION | ResultType.SHORTCUT | ResultType.PLAY | ResultType.PEOPLE
| ResultType.SETTING;
private static final int REQUEST_TIMEOUT = 200;
private static final String TAG = "SearchServicePipeline";
private final Context mContext;
private final SearchSession mSession;
private final DeviceSearchAdapterProvider mAdapterProvider;
private boolean mCanceled = false;
public SearchServicePipeline(Context context, DeviceSearchAdapterProvider adapterProvider) {
mContext = context;
mAdapterProvider = adapterProvider;
SearchUiManager manager = context.getSystemService(SearchUiManager.class);
mSession = manager.createSearchSession(
new SearchContext(SUPPORTED_RESULT_TYPES, REQUEST_TIMEOUT, null));
}
@Override
public void query(String input, Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> callback,
CancellationSignal cancellationSignal) {
mCanceled = false;
Query query = new Query(input, System.currentTimeMillis(), null);
mSession.query(query, UI_HELPER_EXECUTOR, items -> {
if (!mCanceled) {
callback.accept(this.onResult(items));
}
Log.w(TAG, "Ignoring results due to cancel signal");
});
}
/**
* Given A list of search Targets, pairs a group of search targets to a AdapterItem that can
* be inflated in AllAppsRecyclerView
*/
private ArrayList<AllAppsGridAdapter.AdapterItem> onResult(List<SearchTarget> searchTargets) {
HashMap<String, SearchAdapterItem> adapterMap = new LinkedHashMap<>();
List<SearchTarget> unmappedChildren = new ArrayList<>();
SearchSectionInfo section = new SearchSectionInfo();
section.setDecorationHandler(
new AllAppsSectionDecorator.SectionDecorationHandler(mContext, true));
for (SearchTarget target : searchTargets) {
if (!TextUtils.isEmpty(target.getParentId())) {
if (!addChildToParent(target, adapterMap)) {
unmappedChildren.add(target);
}
continue;
}
int viewType = mAdapterProvider.getViewTypeForSearchTarget(target);
if (viewType != -1) {
SearchAdapterItem adapterItem = new SearchAdapterItem(target, viewType);
adapterItem.searchSectionInfo = section;
adapterMap.put(target.getId(), adapterItem);
}
}
for (SearchTarget s : unmappedChildren) {
if (!addChildToParent(s, adapterMap)) {
Log.w(TAG,
"Unable to pair child " + s.getId() + " to parent " + s.getParentId());
}
}
return new ArrayList<>(adapterMap.values());
}
/**
* Adds a child SearchTarget to a collection of searchTarget children with a shared parentId.
* Returns false if no parent searchTarget with id=$parentId does not exists.
*/
private boolean addChildToParent(SearchTarget target, HashMap<String, SearchAdapterItem> map) {
if (!map.containsKey(target.getParentId())) return false;
map.get(target.getParentId()).getInlineItems().add(target);
return true;
}
/**
* Unregister callbacks and destroy search session
*/
public void destroy() {
mSession.destroy();
}
/**
* Cancels current ongoing search request.
*/
public void cancel() {
mCanceled = true;
}
}

View File

@@ -19,6 +19,8 @@ import static com.android.launcher3.FastBitmapDrawable.newIcon;
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.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -97,6 +99,14 @@ public class SearchSettingsRowView extends LinearLayout implements
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
}
@Override
public void applySearchTarget(SearchTarget parentTarget, List<SearchTarget> children) {
SearchAction action = parentTarget.getSearchAction();
mIconView.setContentDescription(action.getTitle());
showIfAvailable(mTitleView, action.getTitle().toString());
showIfAvailable(mBreadcrumbsView, action.getSubtitle().toString());
}
private void showIfAvailable(TextView view, @Nullable String string) {
if (TextUtils.isEmpty(string)) {
view.setVisibility(GONE);

View File

@@ -20,6 +20,8 @@ import android.app.search.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetLegacy;
import java.util.List;
/**
* An interface for supporting dynamic search results
*/
@@ -28,13 +30,14 @@ public interface SearchTargetHandler {
/**
* Update view using values from {@link SearchTargetLegacy}
*/
void applySearchTarget(SearchTargetLegacy searchTarget);
default void applySearchTarget(SearchTargetLegacy searchTarget) {
}
/**
* Update view using values from {@link SearchTargetLegacy}
*/
default void applySearchTarget(SearchTarget searchTarget){
default void applySearchTarget(SearchTarget parentTarget, List<SearchTarget> children) {
}
/**

View File

@@ -220,6 +220,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
@Override
public void onDestroy() {
super.onDestroy();
getAppsView().getSearchUiManager().destroy();
if (mHotseatPredictionController != null) {
mHotseatPredictionController.destroy();
mHotseatPredictionController = null;

View File

@@ -58,6 +58,11 @@ public interface SearchUiManager {
void setContentVisibility(int visibleElements, PropertySetter setter,
Interpolator interpolator);
/**
* Called when activity is destroyed. Used to close search system services
*/
default void destroy(){}
/**
* Returns true if the QSB should be visible for the given set of visible elements
*/

View File

@@ -98,9 +98,8 @@ public final class FeatureFlags {
public static final BooleanFlag ENABLE_DEVICE_SEARCH = getDebugFlag(
"ENABLE_DEVICE_SEARCH", false, "Allows on device search in all apps");
public static final BooleanFlag SEARCH_TARGET_LEGACY = getDebugFlag(
"SEARCH_TARGET_LEGACY", true,
"Use SearchTarget provided by plugin lib (only during migration)");
public static final BooleanFlag USE_SEARCH_API = getDebugFlag(
"USE_SEARCH_API", true, "Use SearchUIManager api for device search");
public static final BooleanFlag DISABLE_INITIAL_IME_IN_ALLAPPS = getDebugFlag(
"DISABLE_INITIAL_IME_IN_ALLAPPS", false, "Disable default IME state in all apps");