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

Setup architecture for separation between aosp and quickstep search as setup for switch to android.app.SearchTarget

Bug: 177223401
Test: manual
Change-Id: Iefd069a34d5e5551bf731e9171958e93377774aa
This commit is contained in:
Samuel Fufa
2021-01-12 12:59:39 -06:00
parent fa39cdcbac
commit fd58d23cdf
35 changed files with 413 additions and 199 deletions
@@ -43,6 +43,8 @@ import android.widget.Toast;
import androidx.annotation.Nullable;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.allapps.search.DefaultSearchAdapterProvider;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.model.data.ItemInfo;
@@ -187,7 +189,7 @@ public abstract class BaseDraggingActivity extends BaseActivity
logAppLaunch(item, instanceId);
}
return true;
} catch (NullPointerException|ActivityNotFoundException|SecurityException e) {
} catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e);
}
@@ -290,4 +292,12 @@ public abstract class BaseDraggingActivity extends BaseActivity
display.getSize(mwSize);
return new WindowBounds(new Rect(0, 0, mwSize.x, mwSize.y), new Rect());
}
/**
* Creates and returns {@link SearchAdapterProvider} for build variant specific search result
* views
*/
public SearchAdapterProvider createSearchAdapterProvider() {
return new DefaultSearchAdapterProvider(this);
}
}
@@ -16,7 +16,6 @@
package com.android.launcher3.allapps;
import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
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,7 +56,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.allapps.search.SearchAdapterProvider;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusedItemDecorator;
import com.android.launcher3.model.data.AppInfo;
@@ -68,7 +67,6 @@ 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.SearchTarget;
/**
* The all apps view container.
@@ -110,6 +108,8 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
private Rect mInsets = new Rect();
SearchAdapterProvider mSearchAdapterProvider;
public AllAppsContainerView(Context context) {
this(context, null);
}
@@ -124,6 +124,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
mLauncher = BaseDraggingActivity.fromContext(context);
mLauncher.addOnDeviceProfileChangeListener(this);
mSearchAdapterProvider = mLauncher.createSearchAdapterProvider();
mSearchQueryBuilder = new SpannableStringBuilder();
Selection.setSelection(mSearchQueryBuilder, 0);
@@ -141,6 +142,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
addSpringView(R.id.all_apps_tabs_view_pager);
mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT);
}
/**
@@ -545,9 +547,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
return mLauncher.startActivitySafely(v, headerItem.getIntent(), headerItem);
}
AdapterItem focusedItem = getActiveRecyclerView().getApps().getFocusedChild();
if (focusedItem instanceof SearchAdapterItem) {
SearchTarget searchTarget = ((SearchAdapterItem) focusedItem).getSearchTarget();
SearchEventTracker.INSTANCE.get(getContext()).quickSelect(searchTarget);
if (mSearchAdapterProvider.onAdapterItemSelected(focusedItem)) {
return true;
}
if (focusedItem.appInfo != null) {
@@ -677,7 +677,8 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
AdapterHolder(boolean isWork) {
mIsWork = isWork;
appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork);
adapter = new AllAppsGridAdapter(mLauncher, getLayoutInflater(), appsList);
adapter = new AllAppsGridAdapter(mLauncher, getLayoutInflater(), appsList,
mSearchAdapterProvider);
appsList.setAdapter(adapter);
layoutManager = adapter.getLayoutManager();
}
@@ -41,13 +41,11 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.AllAppsSearchBarController.SearchTargetHandler;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
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.SearchResultWidget;
import com.android.systemui.plugins.shared.SearchTarget;
import java.util.List;
@@ -72,31 +70,11 @@ public class AllAppsGridAdapter extends
// A divider that separates the apps list and the search market button
public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 4;
public static final int VIEW_TYPE_SEARCH_CORPUS_TITLE = 1 << 5;
public static final int VIEW_TYPE_SEARCH_ROW_WITH_BUTTON = 1 << 7;
public static final int VIEW_TYPE_SEARCH_ROW = 1 << 8;
public static final int VIEW_TYPE_SEARCH_SLICE = 1 << 9;
public static final int VIEW_TYPE_SEARCH_ICON_ROW = 1 << 10;
public static final int VIEW_TYPE_SEARCH_PEOPLE = 1 << 11;
public static final int VIEW_TYPE_SEARCH_THUMBNAIL = 1 << 12;
public static final int VIEW_TYPE_SEARCH_SUGGEST = 1 << 13;
public static final int VIEW_TYPE_SEARCH_ICON = 1 << 14;
public static final int VIEW_TYPE_SEARCH_WIDGET_LIVE = 1 << 15;
public static final int VIEW_TYPE_SEARCH_WIDGET_PREVIEW = 1 << 16;
// 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 | VIEW_TYPE_SEARCH_ICON;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
private final SearchAdapterProvider mSearchAdapterProvider;
/**
* ViewHolder for each icon.
@@ -177,32 +155,8 @@ public class AllAppsGridAdapter extends
return item;
}
boolean isCountedForAccessibility() {
return viewType == VIEW_TYPE_ICON
|| viewType == VIEW_TYPE_SEARCH_ROW_WITH_BUTTON
|| viewType == VIEW_TYPE_SEARCH_SLICE
|| viewType == VIEW_TYPE_SEARCH_ROW
|| 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
*/
public static class SearchAdapterItem extends AdapterItem {
private SearchTarget mSearchTarget;
public SearchAdapterItem(SearchTarget searchTarget, int type) {
mSearchTarget = searchTarget;
viewType = type;
}
public SearchTarget getSearchTarget() {
return mSearchTarget;
protected boolean isCountedForAccessibility() {
return viewType == VIEW_TYPE_ICON || viewType == VIEW_TYPE_SEARCH_MARKET;
}
}
@@ -289,9 +243,8 @@ public class AllAppsGridAdapter extends
int viewType = mApps.getAdapterItems().get(position).viewType;
if (isIconViewType(viewType)) {
return 1 * SPAN_MULTIPLIER;
} else if (viewType == VIEW_TYPE_SEARCH_THUMBNAIL
|| viewType == VIEW_TYPE_SEARCH_WIDGET_PREVIEW) {
return mAppsPerRow;
} else if (mSearchAdapterProvider.isSearchView(viewType)) {
return mSearchAdapterProvider.getGridSpanSize(viewType, mAppsPerRow);
} else {
// Section breaks span the full width
return mAppsPerRow * SPAN_MULTIPLIER;
@@ -321,7 +274,7 @@ public class AllAppsGridAdapter extends
private Intent mMarketSearchIntent;
public AllAppsGridAdapter(BaseDraggingActivity launcher, LayoutInflater inflater,
AlphabeticalAppsList apps) {
AlphabeticalAppsList apps, SearchAdapterProvider searchAdapterProvider) {
Resources res = launcher.getResources();
mLauncher = launcher;
mApps = apps;
@@ -334,6 +287,8 @@ public class AllAppsGridAdapter extends
mOnIconClickListener = launcher.getItemOnClickListener();
setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns);
mSearchAdapterProvider = searchAdapterProvider;
}
public void setAppsPerRow(int appsPerRow) {
@@ -406,40 +361,11 @@ 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));
case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_play_item, parent, false));
case VIEW_TYPE_SEARCH_ROW:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_settings_row, parent, false));
case VIEW_TYPE_SEARCH_SLICE:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_slice, parent, false));
case VIEW_TYPE_SEARCH_ICON_ROW:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_icon_row, parent, false));
case VIEW_TYPE_SEARCH_PEOPLE:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_people_item, parent, false));
case VIEW_TYPE_SEARCH_THUMBNAIL:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_thumbnail, parent, false));
case VIEW_TYPE_SEARCH_SUGGEST:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_suggest, parent, false));
case VIEW_TYPE_SEARCH_WIDGET_LIVE:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_widget_live, parent, false));
case VIEW_TYPE_SEARCH_WIDGET_PREVIEW:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.search_result_widget_preview, parent, false));
default:
if (mSearchAdapterProvider.isSearchView(viewType)) {
return mSearchAdapterProvider.onCreateViewHolder(mLayoutInflater, parent,
viewType);
}
throw new RuntimeException("Unexpected view type");
}
}
@@ -472,25 +398,11 @@ public class AllAppsGridAdapter extends
searchView.setVisibility(View.GONE);
}
break;
case VIEW_TYPE_SEARCH_CORPUS_TITLE:
case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON:
case VIEW_TYPE_SEARCH_SLICE:
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:
case VIEW_TYPE_SEARCH_WIDGET_LIVE:
case VIEW_TYPE_SEARCH_WIDGET_PREVIEW:
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
break;
default:
mSearchAdapterProvider.onBindView(holder, position);
}
}
@@ -501,9 +413,6 @@ public class AllAppsGridAdapter extends
if (holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) {
((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration();
}
if (holder.itemView instanceof SearchResultWidget) {
((SearchResultWidget) holder.itemView).removeListener();
}
}
@Override
@@ -104,7 +104,6 @@ public class AllAppsRecyclerView extends BaseRecyclerView {
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET, 1);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows * mNumAppsPerRow);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_CORPUS_TITLE, 1);
mViewHeights.clear();
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, grid.allAppsCellHeightPx);
@@ -34,7 +34,6 @@ 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 java.util.ArrayList;
import java.util.List;
@@ -211,23 +210,5 @@ public class AllAppsSearchBarController
void clearSearchResult();
}
/**
* An interface for supporting dynamic search results
*/
public interface SearchTargetHandler {
/**
* Update view using values from {@link SearchTarget}
*/
void applySearchTarget(SearchTarget searchTarget);
/**
* Handles selection of SearchTarget
*/
default void handleSelection(int eventType) {
}
}
}
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2021 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 android.view.LayoutInflater;
import android.view.ViewGroup;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.allapps.AllAppsGridAdapter;
/**
* Provides views for local search results
*/
public class DefaultSearchAdapterProvider extends SearchAdapterProvider {
public DefaultSearchAdapterProvider(BaseDraggingActivity launcher) {
super(launcher);
}
@Override
public void onBindView(AllAppsGridAdapter.ViewHolder holder, int position) {
}
@Override
public boolean isSearchView(int viewType) {
return false;
}
@Override
public AllAppsGridAdapter.ViewHolder onCreateViewHolder(LayoutInflater layoutInflater,
ViewGroup parent, int viewType) {
return null;
}
@Override
public boolean onAdapterItemSelected(AllAppsGridAdapter.AdapterItem focusedItem) {
return false;
}
}
@@ -0,0 +1,65 @@
/*
* Copyright (C) 2021 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 android.view.LayoutInflater;
import android.view.ViewGroup;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.allapps.AllAppsGridAdapter;
/**
* A UI expansion wrapper providing for search results
*/
public abstract class SearchAdapterProvider {
protected final BaseDraggingActivity mLauncher;
public SearchAdapterProvider(BaseDraggingActivity launcher) {
mLauncher = launcher;
}
/**
* Called from RecyclerView.Adapter#onBindViewHolder
*/
public abstract void onBindView(AllAppsGridAdapter.ViewHolder holder, int position);
/**
* Returns whether or not viewType can be handled by searchProvider
*/
public abstract boolean isSearchView(int viewType);
/**
* Called from RecyclerView.Adapter#onCreateViewHolder
*/
public abstract AllAppsGridAdapter.ViewHolder onCreateViewHolder(LayoutInflater layoutInflater,
ViewGroup parent, int viewType);
/**
* Returns how many cells a view should span
*/
public int getGridSpanSize(int viewType, int appsPerRow) {
return appsPerRow * AllAppsGridAdapter.SPAN_MULTIPLIER;
}
/**
* handles selection event on search adapter item. Returns false if provider can not handle
* event
*/
public abstract boolean onAdapterItemSelected(AllAppsGridAdapter.AdapterItem focusedItem);
}
@@ -1,93 +0,0 @@
/*
* 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();
}
}
@@ -1,222 +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.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
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.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.AppInfo;
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.ItemLongClickListener;
import com.android.launcher3.util.ComponentKey;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.util.function.Consumer;
/**
* 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";
public static final String TARGET_TYPE_HERO_APP = "hero_app";
public static final String TARGET_TYPE_SHORTCUT = "shortcut";
public static final String TARGET_TYPE_REMOTE_ACTION = "remote_action";
public static final String REMOTE_ACTION_SHOULD_START = "should_start_for_result";
public static final String REMOTE_ACTION_TOKEN = "action_token";
private static final String[] LONG_PRESS_SUPPORTED_TYPES =
new String[]{TARGET_TYPE_APP, TARGET_TYPE_SHORTCUT, TARGET_TYPE_HERO_APP};
private final Launcher mLauncher;
private SearchTarget mSearchTarget;
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());
}
@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 search target with a ItemInfoWithIcon consumer to be called after itemInfo is
* constructed
*/
public void applySearchTarget(SearchTarget searchTarget, Consumer<ItemInfoWithIcon> cb) {
mOnItemInfoChanged = cb;
applySearchTarget(searchTarget);
}
@Override
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
SearchEventTracker.getInstance(getContext()).registerWeakHandler(mSearchTarget, this);
setVisibility(VISIBLE);
switch (searchTarget.getItemType()) {
case TARGET_TYPE_APP:
case TARGET_TYPE_HERO_APP:
prepareUsingApp(searchTarget.getComponentName(), searchTarget.getUserHandle());
break;
case TARGET_TYPE_SHORTCUT:
prepareUsingShortcutInfo(searchTarget.getShortcutInfos().get(0));
break;
case TARGET_TYPE_REMOTE_ACTION:
prepareUsingRemoteAction(searchTarget.getRemoteAction(),
searchTarget.getExtras().getString(REMOTE_ACTION_TOKEN),
searchTarget.getExtras().getBoolean(REMOTE_ACTION_SHOULD_START),
searchTarget.getItemType().equals(TARGET_TYPE_REMOTE_ACTION));
break;
}
}
private void prepareUsingApp(ComponentName componentName, UserHandle userHandle) {
AllAppsStore appsStore = mLauncher.getAppsView().getAppsStore();
AppInfo appInfo = appsStore.getApp(new ComponentKey(componentName, userHandle));
if (appInfo == null) {
setVisibility(GONE);
return;
}
applyFromApplicationInfo(appInfo);
notifyItemInfoChanged(appInfo);
}
private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) {
WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext());
notifyItemInfoChanged(workspaceItemInfo);
LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext());
MODEL_EXECUTOR.execute(() -> {
launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, shortcutInfo);
MAIN_EXECUTOR.post(() -> applyFromWorkspaceItem(workspaceItemInfo));
});
}
private void prepareUsingRemoteAction(RemoteAction remoteAction, String token, boolean start,
boolean useIconToBadge) {
RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(remoteAction, token, start);
notifyItemInfoChanged(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());
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;
}
}
MAIN_EXECUTOR.post(() -> applyFromRemoteActionInfo(itemInfo));
});
}
@Override
public void handleSelection(int eventType) {
mLauncher.getItemOnClickListener().onClick(this);
reportEvent(eventType);
}
private void reportEvent(int eventType) {
SearchTargetEvent.Builder b = new SearchTargetEvent.Builder(mSearchTarget, eventType);
if (mSearchTarget.getItemType().equals(TARGET_TYPE_SHORTCUT)) {
b.setShortcutPosition(0);
}
SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(b.build());
}
@Override
public void onClick(View view) {
handleSelection(SearchTargetEvent.SELECT);
}
@Override
public boolean onLongClick(View view) {
if (!supportsLongPress(mSearchTarget.getItemType())) {
return false;
}
reportEvent(SearchTargetEvent.LONG_PRESS);
return ItemLongClickListener.INSTANCE_ALL_APPS.onLongClick(view);
}
private boolean supportsLongPress(String type) {
for (String t : LONG_PRESS_SUPPORTED_TYPES) {
if (t.equals(type)) return true;
}
return false;
}
private void notifyItemInfoChanged(ItemInfoWithIcon itemInfoWithIcon) {
if (mOnItemInfoChanged != null) {
mOnItemInfoChanged.accept(itemInfoWithIcon);
mOnItemInfoChanged = null;
}
}
}
@@ -1,201 +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.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* A full width representation of {@link SearchResultIcon} with a secondary label and inline
* shortcuts
*/
public class SearchResultIconRow extends LinearLayout implements
AllAppsSearchBarController.SearchTargetHandler, View.OnClickListener,
View.OnLongClickListener, Consumer<ItemInfoWithIcon> {
public static final int MAX_SHORTCUTS_COUNT = 2;
private final Launcher mLauncher;
private final LauncherAppState mLauncherAppState;
private SearchResultIcon mResultIcon;
private TextView mTitleView;
private TextView mDescriptionView;
private BubbleTextView[] mShortcutViews = new BubbleTextView[2];
private SearchTarget mSearchTarget;
private PackageItemInfo mProviderInfo;
public SearchResultIconRow(Context context) {
this(context, null, 0);
}
public SearchResultIconRow(Context context,
@Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchResultIconRow(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(getContext());
mLauncherAppState = LauncherAppState.getInstance(getContext());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
int iconSize = mLauncher.getDeviceProfile().allAppsIconSizePx;
mResultIcon = findViewById(R.id.icon);
mTitleView = findViewById(R.id.title);
mDescriptionView = findViewById(R.id.desc);
mShortcutViews[0] = findViewById(R.id.shortcut_0);
mShortcutViews[1] = findViewById(R.id.shortcut_1);
mResultIcon.getLayoutParams().height = iconSize;
mResultIcon.getLayoutParams().width = iconSize;
mResultIcon.setTextVisibility(false);
for (BubbleTextView bubbleTextView : mShortcutViews) {
ViewGroup.LayoutParams lp = bubbleTextView.getLayoutParams();
lp.width = iconSize;
bubbleTextView.setOnClickListener(view -> {
WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) bubbleTextView.getTag();
SearchTargetEvent event = new SearchTargetEvent.Builder(mSearchTarget,
SearchTargetEvent.CHILD_SELECT).setShortcutPosition(itemInfo.rank).build();
SearchEventTracker.getInstance(getContext()).notifySearchTargetEvent(event);
mLauncher.getItemOnClickListener().onClick(view);
});
}
setOnClickListener(this);
setOnLongClickListener(this);
}
@Override
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
mResultIcon.applySearchTarget(searchTarget, this);
String itemType = searchTarget.getItemType();
boolean showDesc = itemType.equals(SearchResultIcon.TARGET_TYPE_SHORTCUT);
mDescriptionView.setVisibility(showDesc ? VISIBLE : GONE);
if (itemType.equals(SearchResultIcon.TARGET_TYPE_SHORTCUT)) {
ShortcutInfo shortcutInfo = searchTarget.getShortcutInfos().get(0);
setProviderDetails(new ComponentName(shortcutInfo.getPackage(), ""),
shortcutInfo.getUserHandle());
} else if (itemType.equals(SearchResultIcon.TARGET_TYPE_HERO_APP)) {
showInlineShortcuts(mSearchTarget.getShortcutInfos());
} else if (itemType.equals(SearchResultIcon.TARGET_TYPE_REMOTE_ACTION)) {
CharSequence desc = mSearchTarget.getRemoteAction().getContentDescription();
if (!TextUtils.isEmpty(desc)) {
mDescriptionView.setVisibility(VISIBLE);
mDescriptionView.setText(desc);
}
}
if (!itemType.equals(SearchResultIcon.TARGET_TYPE_HERO_APP)) {
showInlineShortcuts(new ArrayList<>());
}
}
@Override
public void accept(ItemInfoWithIcon itemInfoWithIcon) {
mTitleView.setText(itemInfoWithIcon.title);
}
private void showInlineShortcuts(List<ShortcutInfo> infos) {
if (infos == null) return;
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));
}
for (int i = 0; i < mShortcutViews.length; i++) {
BubbleTextView shortcutView = mShortcutViews[i];
mShortcutViews[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(() -> {
// load unbadged shortcut in background and update view when icon ready
mLauncherAppState.getIconCache().getUnbadgedShortcutIcon(p.second, p.first);
MAIN_EXECUTOR.post(() -> shortcutView.reapplyItemInfo(p.second));
});
}
}
}
private void setProviderDetails(ComponentName componentName, UserHandle userHandle) {
PackageItemInfo packageItemInfo = new PackageItemInfo(componentName.getPackageName());
if (mProviderInfo == packageItemInfo) return;
MODEL_EXECUTOR.post(() -> {
packageItemInfo.user = userHandle;
mLauncherAppState.getIconCache().getTitleAndIconForApp(packageItemInfo, true);
MAIN_EXECUTOR.post(() -> {
mDescriptionView.setText(packageItemInfo.title);
mProviderInfo = packageItemInfo;
});
});
}
@Override
public void handleSelection(int eventType) {
mResultIcon.handleSelection(eventType);
}
@Override
public void onClick(View view) {
mResultIcon.performClick();
}
@Override
public boolean onLongClick(View view) {
mResultIcon.performLongClick();
return false;
}
}
@@ -1,205 +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.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.content.Context;
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;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
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.search.AllAppsSearchBarController;
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;
import java.util.ArrayList;
/**
* A view representing a single people search result in all apps
*/
public class SearchResultPeopleView extends LinearLayout implements
AllAppsSearchBarController.SearchTargetHandler {
public static final String TARGET_TYPE_PEOPLE = "people";
private final int mIconSize;
private final int mButtonSize;
private final PackageManager mPackageManager;
private View mIconView;
private TextView mTitleView;
private ImageButton[] mProviderButtons = new ImageButton[3];
private Intent mIntent;
private SearchTarget mSearchTarget;
public SearchResultPeopleView(Context context) {
this(context, null, 0);
}
public SearchResultPeopleView(Context context,
@Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchResultPeopleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
DeviceProfile deviceProfile = Launcher.getLauncher(getContext()).getDeviceProfile();
mPackageManager = getContext().getPackageManager();
mIconSize = deviceProfile.iconSizePx;
mButtonSize = (int) (deviceProfile.iconSizePx / 1.5f);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mIconView = findViewById(R.id.icon);
mIconView.getLayoutParams().height = mIconSize;
mIconView.getLayoutParams().width = mIconSize;
mTitleView = findViewById(R.id.title);
mProviderButtons[0] = findViewById(R.id.provider_0);
mProviderButtons[1] = findViewById(R.id.provider_1);
mProviderButtons[2] = findViewById(R.id.provider_2);
for (ImageButton button : mProviderButtons) {
button.getLayoutParams().width = mButtonSize;
button.getLayoutParams().height = mButtonSize;
}
setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
}
@Override
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
Bundle payload = searchTarget.getExtras();
mTitleView.setText(payload.getString("title"));
mIntent = payload.getParcelable("intent");
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");
for (int i = 0; i < mProviderButtons.length; i++) {
ImageButton button = mProviderButtons[i];
if (providers != null && i < providers.size()) {
Bundle provider = providers.get(i);
Intent intent = provider.getParcelable("intent");
setupProviderButton(button, provider, intent);
UI_HELPER_EXECUTOR.post(() -> {
String pkg = provider.getString("package_name");
Drawable appIcon = getAppIcon(pkg);
if (appIcon != null) {
MAIN_EXECUTOR.post(() -> button.setImageDrawable(appIcon));
}
});
button.setVisibility(VISIBLE);
} else {
button.setVisibility(GONE);
}
}
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
}
/**
* 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 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);
Bundle bundle = new Bundle();
bundle.putBundle("provider", provider);
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget,
SearchTargetEvent.CHILD_SELECT).setExtras(bundle).build());
});
}
@Override
public void handleSelection(int eventType) {
if (mIntent != null) {
Launcher launcher = Launcher.getLauncher(getContext());
launcher.startActivitySafely(this, mIntent, null);
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
}
}
@@ -1,205 +0,0 @@
/*
* Copyright (C) 2015 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.launcher3.util.Executors.UI_HELPER_EXECUTOR;
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;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
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.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;
private TextView[] mDetailViews = new TextView[3];
private Button mPreviewButton;
private String mPackageName;
private boolean mIsInstantGame;
private SearchTarget mSearchTarget;
public SearchResultPlayItem(Context context) {
this(context, null, 0);
}
public SearchResultPlayItem(Context context,
@Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchResultPlayItem(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDeviceProfile = Launcher.getLauncher(getContext()).getDeviceProfile();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mIconView = findViewById(R.id.icon);
mTitleView = findViewById(R.id.title_view);
mPreviewButton = findViewById(R.id.try_button);
mPreviewButton.setOnClickListener(view -> launchInstantGame());
mDetailViews[0] = findViewById(R.id.detail_0);
mDetailViews[1] = findViewById(R.id.detail_1);
mDetailViews[2] = findViewById(R.id.detail_2);
ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams();
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 applySearchTarget(SearchTarget 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(SearchTargetEvent.CHILD_SELECT);
}
private void logSearchEvent(int eventType) {
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
}
@@ -1,128 +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 android.content.Context;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.slice.Slice;
import androidx.slice.SliceItem;
import androidx.slice.widget.EventInfo;
import androidx.slice.widget.SliceView;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
/**
* A slice view wrapper with settings app icon at start
*/
public class SearchResultSettingsSlice extends LinearLayout implements
AllAppsSearchBarController.SearchTargetHandler, 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 SliceView mSliceView;
private View mIcon;
private LiveData<Slice> mSliceLiveData;
private SearchTarget mSearchTarget;
private final Launcher mLauncher;
public SearchResultSettingsSlice(Context context) {
this(context, null, 0);
}
public SearchResultSettingsSlice(Context context,
@Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchResultSettingsSlice(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(getContext());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mSliceView = findViewById(R.id.slice);
mIcon = findViewById(R.id.icon);
SearchSettingsRowView.applySettingsIcon(mLauncher, mIcon);
}
@Override
public void applySearchTarget(SearchTarget searchTarget) {
reset();
mSearchTarget = searchTarget;
try {
mSliceLiveData = mLauncher.getLiveSearchManager().getSliceForUri(getSliceUri());
mSliceLiveData.observe(mLauncher, mSliceView);
} catch (Exception ex) {
Log.e(TAG, "unable to bind slice", ex);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mSliceView.setOnSliceActionListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
reset();
}
@Override
public void handleSelection(int eventType) {
SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget,
SearchTargetEvent.CHILD_SELECT).build());
}
private void reset() {
mSliceView.setOnSliceActionListener(null);
if (mSliceLiveData != null) {
mSliceLiveData.removeObservers(mLauncher);
}
}
@Override
public void onSliceAction(@NonNull EventInfo eventInfo, @NonNull SliceItem sliceItem) {
handleSelection(SearchTargetEvent.CHILD_SELECT);
}
private Uri getSliceUri() {
return mSearchTarget.getExtras().getParcelable(URI_EXTRA_KEY);
}
}
@@ -1,62 +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 android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ViewGroup;
import com.android.launcher3.R;
/**
* {@link SearchResultIconRow} with custom drawable resource
*/
public class SearchResultSuggestion extends SearchResultIcon {
public static final String TARGET_TYPE_SUGGEST = "suggest";
private final Drawable mCustomIcon;
public SearchResultSuggestion(Context context) {
this(context, null, 0);
}
public SearchResultSuggestion(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchResultSuggestion(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.SearchResultSuggestion, defStyle, 0);
mCustomIcon = a.getDrawable(R.styleable.SearchResultSuggestion_customIcon);
a.recycle();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
ViewGroup.LayoutParams lp = getLayoutParams();
lp.height = BaseDragLayer.LayoutParams.WRAP_CONTENT;
}
@Override
protected void setIcon(Drawable icon) {
super.setIcon(mCustomIcon);
}
}
@@ -1,204 +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 android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.AppWidgetResizeFrame;
import com.android.launcher3.CheckLongPressHelper;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.allapps.search.SearchWidgetInfoContainer;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
/**
* displays live version of a widget upon receiving {@link AppWidgetProviderInfo} from Search
* provider
*/
public class SearchResultWidget extends RelativeLayout implements
AllAppsSearchBarController.SearchTargetHandler, DraggableView, View.OnLongClickListener {
private static final String TAG = "SearchResultWidget";
public static final String TARGET_TYPE_WIDGET_LIVE = "widget";
private final Rect mWidgetOffset = new Rect();
private final Launcher mLauncher;
private final CheckLongPressHelper mLongPressHelper;
private final GestureDetector mClickDetector;
private final AppWidgetHostView mHostView;
private final float mScaleToFit;
private SearchTarget mSearchTarget;
private AppWidgetProviderInfo mProviderInfo;
private SearchWidgetInfoContainer mInfoContainer;
public SearchResultWidget(@NonNull Context context) {
this(context, null, 0);
}
public SearchResultWidget(@NonNull Context context,
@Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchResultWidget(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
mHostView = new AppWidgetHostView(context);
DeviceProfile grid = mLauncher.getDeviceProfile();
mScaleToFit = Math.min(grid.appWidgetScale.x, grid.appWidgetScale.y);
// detect tap event on widget container for search target event reporting
mClickDetector = new GestureDetector(context,
new ClickListener(() -> handleSelection(SearchTargetEvent.CHILD_SELECT)));
mLongPressHelper = new CheckLongPressHelper(this);
mLongPressHelper.setLongPressTimeoutFactor(1);
setOnLongClickListener(this);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
addView(mHostView);
}
@Override
public void applySearchTarget(SearchTarget searchTarget) {
if (searchTarget.getExtras() == null
|| searchTarget.getExtras().getParcelable("provider") == null) {
setVisibility(GONE);
return;
}
AppWidgetProviderInfo providerInfo = searchTarget.getExtras().getParcelable("provider");
if (mProviderInfo != null && providerInfo.provider.equals(mProviderInfo.provider)
&& providerInfo.getProfile().equals(mProviderInfo.getProfile())) {
return;
}
removeListener();
mSearchTarget = searchTarget;
mProviderInfo = providerInfo;
mInfoContainer = mLauncher.getLiveSearchManager().getPlaceHolderWidget(providerInfo);
if (mInfoContainer == null) {
setVisibility(GONE);
return;
}
setVisibility(VISIBLE);
mInfoContainer.attachWidget(mHostView);
PendingAddWidgetInfo info = (PendingAddWidgetInfo) mHostView.getTag();
int[] size = mLauncher.getWorkspace().estimateItemSize(info);
mHostView.getLayoutParams().width = size[0];
mHostView.getLayoutParams().height = size[1];
AppWidgetResizeFrame.updateWidgetSizeRanges(mHostView, mLauncher, info.spanX,
info.spanY);
mHostView.requestLayout();
setTag(info);
}
/**
* Stops hostView from getting updates on a widget provider
*/
public void removeListener() {
if (mInfoContainer != null) {
mInfoContainer.detachWidget(mHostView);
}
}
@Override
public void handleSelection(int eventType) {
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
mLongPressHelper.onTouchEvent(ev);
mClickDetector.onTouchEvent(ev);
return mLongPressHelper.hasPerformedLongPress();
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
mLongPressHelper.onTouchEvent(ev);
return true;
}
@Override
public void cancelLongPress() {
super.cancelLongPress();
mLongPressHelper.cancelLongPress();
}
@Override
public int getViewType() {
return DraggableView.DRAGGABLE_WIDGET;
}
@Override
public void getSourceVisualDragBounds(Rect bounds) {
mHostView.getHitRect(mWidgetOffset);
int width = (int) (mHostView.getMeasuredWidth() * mScaleToFit);
int height = (int) (mHostView.getMeasuredHeight() * mScaleToFit);
bounds.set(mWidgetOffset.left,
mWidgetOffset.top,
width + mWidgetOffset.left,
height + mWidgetOffset.top);
}
@Override
public boolean onLongClick(View view) {
ItemLongClickListener.INSTANCE_ALL_APPS.onLongClick(view);
handleSelection(SearchTargetEvent.LONG_PRESS);
return false;
}
static class ClickListener extends GestureDetector.SimpleOnGestureListener {
private final Runnable mCb;
ClickListener(Runnable cb) {
mCb = cb;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
mCb.run();
return super.onSingleTapConfirmed(e);
}
}
}
@@ -1,141 +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.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.widget.BaseWidgetSheet;
import com.android.launcher3.widget.PendingItemDragHelper;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.WidgetImageView;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
/**
* displays preview of a widget upon receiving {@link AppWidgetProviderInfo} from Search provider
*/
public class SearchResultWidgetPreview extends LinearLayout implements
AllAppsSearchBarController.SearchTargetHandler, View.OnLongClickListener,
View.OnClickListener {
public static final String TARGET_TYPE_WIDGET_PREVIEW = "widget_preview";
private final Launcher mLauncher;
private final LauncherAppState mAppState;
private WidgetCell mWidgetCell;
private Toast mWidgetToast;
private SearchTarget mSearchTarget;
public SearchResultWidgetPreview(Context context) {
this(context, null, 0);
}
public SearchResultWidgetPreview(Context context,
@Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchResultWidgetPreview(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
mAppState = LauncherAppState.getInstance(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mWidgetCell = findViewById(R.id.widget_cell);
mWidgetCell.setOnLongClickListener(this);
mWidgetCell.setOnClickListener(this);
}
@Override
public void applySearchTarget(SearchTarget searchTarget) {
if (searchTarget.getExtras() == null
|| searchTarget.getExtras().getParcelable("provider") == null) {
setVisibility(GONE);
return;
}
mSearchTarget = searchTarget;
AppWidgetProviderInfo providerInfo = searchTarget.getExtras().getParcelable("provider");
LauncherAppWidgetProviderInfo pInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
getContext(), providerInfo);
MODEL_EXECUTOR.post(() -> {
WidgetItem widgetItem = new WidgetItem(pInfo, mLauncher.getDeviceProfile().inv,
mAppState.getIconCache());
MAIN_EXECUTOR.post(() -> {
mWidgetCell.applyFromCellItem(widgetItem, mAppState.getWidgetCache());
mWidgetCell.ensurePreview();
});
});
}
@Override
public boolean onLongClick(View view) {
view.cancelLongPress();
if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
if (mWidgetCell.getTag() == null) return false;
WidgetImageView imageView = mWidgetCell.getWidgetView();
if (imageView.getBitmap() == null) {
return false;
}
int[] loc = new int[2];
mLauncher.getDragLayer().getLocationInDragLayer(imageView, loc);
new PendingItemDragHelper(mWidgetCell).startDrag(
imageView.getBitmapBounds(), imageView.getBitmap().getWidth(), imageView.getWidth(),
new Point(loc[0], loc[1]), mLauncher.getAppsView(), new DragOptions());
handleSelection(SearchTargetEvent.LONG_PRESS);
return true;
}
@Override
public void onClick(View view) {
mWidgetToast = BaseWidgetSheet.showWidgetToast(getContext(), mWidgetToast);
handleSelection(SearchTargetEvent.SELECT);
}
@Override
public void handleSelection(int eventType) {
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
}
@@ -1,57 +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 android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.annotation.Nullable;
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.SearchTargetHandler {
public static final String TARGET_TYPE_SECTION_HEADER = "section_header";
public SearchSectionHeaderView(Context context) {
super(context);
}
public SearchSectionHeaderView(Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
}
public SearchSectionHeaderView(Context context, @Nullable AttributeSet attrs, int styleAttr) {
super(context, attrs, styleAttr);
}
@Override
public void applySearchTarget(SearchTarget searchTarget) {
String title = searchTarget.getExtras().getString("title");
if (title == null || !title.isEmpty()) {
setText(title);
setVisibility(VISIBLE);
} else {
setVisibility(INVISIBLE);
}
}
}
@@ -1,153 +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.launcher3.FastBitmapDrawable.newIcon;
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.PackageManager;
import android.content.pm.ResolveInfo;
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.FastBitmapDrawable;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.AllAppsSearchBarController;
import com.android.launcher3.allapps.search.SearchEventTracker;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.systemui.plugins.shared.SearchTarget;
import com.android.systemui.plugins.shared.SearchTargetEvent;
import java.util.ArrayList;
import java.util.List;
/**
* A row of clickable TextViews with a breadcrumb for settings search.
*/
public class SearchSettingsRowView extends LinearLayout implements
View.OnClickListener, AllAppsSearchBarController.SearchTargetHandler {
public static final String TARGET_TYPE_SETTINGS_ROW = "settings_row";
private View mIconView;
private TextView mTitleView;
private TextView mBreadcrumbsView;
private Intent mIntent;
private SearchTarget mSearchTarget;
public SearchSettingsRowView(@NonNull Context context) {
this(context, null, 0);
}
public SearchSettingsRowView(@NonNull Context context,
@Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SearchSettingsRowView(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mIconView = findViewById(R.id.icon);
mTitleView = findViewById(R.id.title);
mBreadcrumbsView = findViewById(R.id.breadcrumbs);
setOnClickListener(this);
applySettingsIcon(Launcher.getLauncher(getContext()), mIconView);
}
@Override
public void applySearchTarget(SearchTarget searchTarget) {
mSearchTarget = searchTarget;
Bundle bundle = searchTarget.getExtras();
mIntent = bundle.getParcelable("intent");
showIfAvailable(mTitleView, bundle.getString("title"));
mIconView.setContentDescription(bundle.getString("title"));
ArrayList<String> breadcrumbs = bundle.getStringArrayList("breadcrumbs");
//TODO: implement RTL friendly breadcrumbs view
showIfAvailable(mBreadcrumbsView, breadcrumbs != null
? String.join(" > ", breadcrumbs) : null);
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
}
private void showIfAvailable(TextView view, @Nullable String string) {
if (TextUtils.isEmpty(string)) {
view.setVisibility(GONE);
} else {
view.setVisibility(VISIBLE);
view.setText(string);
}
}
@Override
public void onClick(View view) {
handleSelection(SearchTargetEvent.SELECT);
}
@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);
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
/**
* Requests settings app icon from {@link com.android.launcher3.icons.IconCache} and applies
* to to view
*/
public static void applySettingsIcon(Launcher launcher, View view) {
LauncherAppState appState = LauncherAppState.getInstance(launcher);
MODEL_EXECUTOR.post(() -> {
PackageItemInfo packageItemInfo = new PackageItemInfo(getSettingsPackageName(launcher));
appState.getIconCache().getTitleAndIconForApp(packageItemInfo, false);
MAIN_EXECUTOR.post(() -> {
FastBitmapDrawable iconDrawable = newIcon(appState.getContext(), packageItemInfo);
view.setBackground(iconDrawable);
});
});
}
private static String getSettingsPackageName(Launcher launcher) {
Intent intent = new Intent(android.provider.Settings.ACTION_SETTINGS);
List<ResolveInfo> resolveInfos = launcher.getPackageManager().queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
if (resolveInfos.size() == 0) {
return "";
}
return resolveInfos.get(0).activityInfo.packageName;
}
}
@@ -1,116 +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.launcher3.views.SearchResultIcon.REMOTE_ACTION_SHOULD_START;
import static com.android.launcher3.views.SearchResultIcon.REMOTE_ACTION_TOKEN;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.util.AttributeSet;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import com.android.launcher3.Launcher;
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.shared.SearchTarget;
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.SearchTargetHandler {
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);
}
public ThumbnailSearchResultView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ThumbnailSearchResultView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void handleSelection(int eventType) {
Launcher launcher = Launcher.getLauncher(getContext());
ItemInfo itemInfo = (ItemInfo) getTag();
if (itemInfo instanceof RemoteActionItemInfo) {
RemoteActionItemInfo remoteItemInfo = (RemoteActionItemInfo) itemInfo;
ItemClickHandler.onClickRemoteAction(launcher, remoteItemInfo);
} else {
ItemClickHandler.onClickAppShortcut(this, (WorkspaceItemInfo) itemInfo, launcher);
}
SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
}
@Override
public void applySearchTarget(SearchTarget target) {
mSearchTarget = target;
Bitmap bitmap;
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();
// crop
if (bitmap.getWidth() < bitmap.getHeight()) {
bitmap = Bitmap.createBitmap(bitmap, 0,
bitmap.getHeight() / 2 - bitmap.getWidth() / 2,
bitmap.getWidth(), bitmap.getWidth());
} else {
bitmap = Bitmap.createBitmap(bitmap, bitmap.getWidth() / 2 - bitmap.getHeight() / 2,
0,
bitmap.getHeight(), bitmap.getHeight());
}
setTag(itemInfo);
} else {
bitmap = (Bitmap) target.getExtras().getParcelable("bitmap");
WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
itemInfo.intent = new Intent(Intent.ACTION_VIEW)
.setData(Uri.parse(target.getExtras().getString("uri")))
.setType("image/*")
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
setTag(itemInfo);
}
RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(null, bitmap);
drawable.setCornerRadius(Themes.getDialogCornerRadius(getContext()));
setImageDrawable(drawable);
setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(target, this);
}
}