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:
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.search;
|
||||
|
||||
import static com.android.launcher3.allapps.AllAppsGridAdapter.VIEW_TYPE_ICON;
|
||||
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.allapps.AllAppsGridAdapter;
|
||||
import com.android.launcher3.allapps.search.SearchAdapterProvider;
|
||||
import com.android.systemui.plugins.shared.SearchTarget;
|
||||
|
||||
/**
|
||||
* Provides views for on-device search results
|
||||
*/
|
||||
public class DeviceSearchAdapterProvider extends SearchAdapterProvider {
|
||||
|
||||
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) | VIEW_TYPE_ICON;
|
||||
public static final int VIEW_TYPE_SEARCH_WIDGET_LIVE = 1 << 15;
|
||||
public static final int VIEW_TYPE_SEARCH_WIDGET_PREVIEW = 1 << 16;
|
||||
|
||||
|
||||
private final SparseIntArray mViewTypeToLayoutMap = new SparseIntArray();
|
||||
|
||||
public DeviceSearchAdapterProvider(Launcher launcher) {
|
||||
super(launcher);
|
||||
|
||||
mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_ICON, R.layout.search_result_icon);
|
||||
mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_CORPUS_TITLE, R.layout.search_section_title);
|
||||
mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_ROW_WITH_BUTTON,
|
||||
R.layout.search_result_play_item);
|
||||
mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_ROW, R.layout.search_result_settings_row);
|
||||
mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_SLICE, R.layout.search_result_slice);
|
||||
mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_ICON_ROW, R.layout.search_result_icon_row);
|
||||
mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_PEOPLE, R.layout.search_result_people_item);
|
||||
mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_THUMBNAIL, R.layout.search_result_thumbnail);
|
||||
mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_SUGGEST, R.layout.search_result_suggest);
|
||||
mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_WIDGET_LIVE, R.layout.search_result_widget_live);
|
||||
mViewTypeToLayoutMap.put(VIEW_TYPE_SEARCH_WIDGET_PREVIEW,
|
||||
R.layout.search_result_widget_preview);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindView(AllAppsGridAdapter.ViewHolder holder, int position) {
|
||||
SearchAdapterItem item = (SearchAdapterItem) Launcher.getLauncher(mLauncher)
|
||||
.getAppsView().getApps().getAdapterItems().get(position);
|
||||
SearchTargetHandler
|
||||
payloadResultView =
|
||||
(SearchTargetHandler) holder.itemView;
|
||||
payloadResultView.applySearchTarget(item.getSearchTarget());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSearchView(int viewType) {
|
||||
return mViewTypeToLayoutMap.get(viewType, -1) != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AllAppsGridAdapter.ViewHolder onCreateViewHolder(LayoutInflater inflater,
|
||||
ViewGroup parent, int viewType) {
|
||||
return new AllAppsGridAdapter.ViewHolder(inflater.inflate(
|
||||
mViewTypeToLayoutMap.get(viewType), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGridSpanSize(int viewType, int appsPerRow) {
|
||||
if (viewType == VIEW_TYPE_SEARCH_THUMBNAIL
|
||||
|| viewType == VIEW_TYPE_SEARCH_WIDGET_PREVIEW) {
|
||||
return appsPerRow;
|
||||
}
|
||||
return super.getGridSpanSize(viewType, appsPerRow);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onAdapterItemSelected(AllAppsGridAdapter.AdapterItem focusedItem) {
|
||||
if (focusedItem instanceof SearchTargetHandler) {
|
||||
SearchTarget searchTarget = ((SearchAdapterItem) focusedItem).getSearchTarget();
|
||||
SearchEventTracker.INSTANCE.get(mLauncher).quickSelect(searchTarget);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.search;
|
||||
|
||||
import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_ICON;
|
||||
import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_ICON_ROW;
|
||||
import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_PEOPLE;
|
||||
import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_ROW;
|
||||
import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_ROW_WITH_BUTTON;
|
||||
import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_SLICE;
|
||||
import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_SUGGEST;
|
||||
import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_THUMBNAIL;
|
||||
import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_WIDGET_LIVE;
|
||||
import static com.android.launcher3.search.DeviceSearchAdapterProvider.VIEW_TYPE_SEARCH_WIDGET_PREVIEW;
|
||||
|
||||
import com.android.launcher3.allapps.AllAppsGridAdapter;
|
||||
import com.android.systemui.plugins.shared.SearchTarget;
|
||||
|
||||
/**
|
||||
* Extension of AdapterItem that contains an extra payload specific to item
|
||||
*/
|
||||
public class SearchAdapterItem extends AllAppsGridAdapter.AdapterItem {
|
||||
private SearchTarget mSearchTarget;
|
||||
|
||||
|
||||
private static final int AVAILABLE_FOR_ACCESSIBILITY = VIEW_TYPE_SEARCH_ROW_WITH_BUTTON
|
||||
| VIEW_TYPE_SEARCH_SLICE | VIEW_TYPE_SEARCH_ROW | VIEW_TYPE_SEARCH_PEOPLE
|
||||
| VIEW_TYPE_SEARCH_THUMBNAIL | VIEW_TYPE_SEARCH_ICON_ROW | VIEW_TYPE_SEARCH_ICON
|
||||
| VIEW_TYPE_SEARCH_WIDGET_PREVIEW | VIEW_TYPE_SEARCH_WIDGET_LIVE
|
||||
| VIEW_TYPE_SEARCH_SUGGEST;
|
||||
|
||||
public SearchAdapterItem(SearchTarget searchTarget, int type) {
|
||||
mSearchTarget = searchTarget;
|
||||
viewType = type;
|
||||
}
|
||||
|
||||
public SearchTarget getSearchTarget() {
|
||||
return mSearchTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCountedForAccessibility() {
|
||||
return (AVAILABLE_FOR_ACCESSIBILITY & viewType) == viewType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.search;
|
||||
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* 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.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.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
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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.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.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
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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.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.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
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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.search;
|
||||
|
||||
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.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
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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 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.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
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 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;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
|
||||
/**
|
||||
* {@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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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 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.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
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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.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.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
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
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
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.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.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, 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.search;
|
||||
|
||||
import com.android.systemui.plugins.shared.SearchTarget;
|
||||
|
||||
/**
|
||||
* 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,114 @@
|
||||
/*
|
||||
* 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.search.SearchResultIcon.REMOTE_ACTION_SHOULD_START;
|
||||
import static com.android.launcher3.search.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.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 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);
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.allapps.search.SearchAdapterProvider;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.appprediction.PredictionRowView;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
@@ -51,6 +52,7 @@ import com.android.launcher3.logging.StatsLogManager.StatsLogger;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.search.DeviceSearchAdapterProvider;
|
||||
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
|
||||
import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
|
||||
@@ -263,6 +265,11 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchAdapterProvider createSearchAdapterProvider() {
|
||||
return new DeviceSearchAdapterProvider(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TouchController[] createTouchControllers() {
|
||||
Mode mode = SysUINavigationMode.getMode(this);
|
||||
|
||||
@@ -45,6 +45,7 @@ import com.android.launcher3.LauncherAnimationRunner.AnimationResult;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.WrappedAnimationRunnerImpl;
|
||||
import com.android.launcher3.WrappedLauncherAnimationRunner;
|
||||
import com.android.launcher3.allapps.search.SearchAdapterProvider;
|
||||
import com.android.launcher3.anim.Interpolators;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.compat.AccessibilityManagerCompat;
|
||||
@@ -91,6 +92,7 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
|
||||
|
||||
// Strong refs to runners which are cleared when the activity is destroyed
|
||||
private WrappedAnimationRunnerImpl mActivityLaunchAnimationRunner;
|
||||
private SearchAdapterProvider mSearchAdapterProvider;
|
||||
|
||||
/**
|
||||
* Init drag layer and overview panel views.
|
||||
|
||||
Reference in New Issue
Block a user