Merge "Initial setup for widgets in Search" into ub-launcher3-master
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.android.launcher3.views.SearchResultWidget android:layout_height="wrap_content"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:gravity="center"
|
||||
android:layout_width="match_parent" />
|
||||
@@ -36,7 +36,7 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O
|
||||
private static final Rect sTmpRect = new Rect();
|
||||
|
||||
// Represents the cell size on the grid in the two orientations.
|
||||
private static final MainThreadInitializedObject<Point[]> CELL_SIZE =
|
||||
public static final MainThreadInitializedObject<Point[]> CELL_SIZE =
|
||||
new MainThreadInitializedObject<>(c -> {
|
||||
InvariantDeviceProfile inv = LauncherAppState.getIDP(c);
|
||||
return new Point[] {inv.landscapeProfile.getCellSize(),
|
||||
|
||||
@@ -111,6 +111,7 @@ import com.android.launcher3.allapps.AllAppsContainerView;
|
||||
import com.android.launcher3.allapps.AllAppsStore;
|
||||
import com.android.launcher3.allapps.AllAppsTransitionController;
|
||||
import com.android.launcher3.allapps.DiscoveryBounce;
|
||||
import com.android.launcher3.allapps.search.LiveSearchManager;
|
||||
import com.android.launcher3.anim.PropertyListBuilder;
|
||||
import com.android.launcher3.compat.AccessibilityManagerCompat;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
@@ -276,6 +277,8 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||
|
||||
private LifecycleRegistry mLifecycleRegistry;
|
||||
|
||||
private LiveSearchManager mLiveSearchManager;
|
||||
|
||||
@Thunk
|
||||
Workspace mWorkspace;
|
||||
@Thunk
|
||||
@@ -389,6 +392,8 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||
mIconCache = app.getIconCache();
|
||||
mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
|
||||
|
||||
mLiveSearchManager = new LiveSearchManager(this);
|
||||
|
||||
mDragController = new DragController(this);
|
||||
mAllAppsController = new AllAppsTransitionController(this);
|
||||
mStateManager = new StateManager<>(this, NORMAL);
|
||||
@@ -492,6 +497,10 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||
return mLifecycleRegistry;
|
||||
}
|
||||
|
||||
public LiveSearchManager getLiveSearchManager() {
|
||||
return mLiveSearchManager;
|
||||
}
|
||||
|
||||
protected LauncherOverlayManager getDefaultOverlay() {
|
||||
return new LauncherOverlayManager() { };
|
||||
}
|
||||
@@ -1583,6 +1592,7 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
|
||||
mAppTransitionManager.unregisterRemoteAnimations();
|
||||
mUserChangedCallbackCloseable.close();
|
||||
mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
|
||||
mLiveSearchManager.stop();
|
||||
}
|
||||
|
||||
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
|
||||
|
||||
@@ -46,6 +46,7 @@ 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;
|
||||
@@ -89,6 +90,8 @@ public class AllAppsGridAdapter extends
|
||||
|
||||
public static final int VIEW_TYPE_SEARCH_ICON = 1 << 14;
|
||||
|
||||
public static final int VIEW_TYPE_SEARCH_WIDGET_LIVE = 1 << 15;
|
||||
|
||||
// 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;
|
||||
@@ -181,7 +184,8 @@ public class AllAppsGridAdapter extends
|
||||
|| viewType == VIEW_TYPE_SEARCH_THUMBNAIL
|
||||
|| viewType == VIEW_TYPE_SEARCH_ICON_ROW
|
||||
|| viewType == VIEW_TYPE_SEARCH_ICON
|
||||
|| viewType == VIEW_TYPE_SEARCH_SUGGEST;
|
||||
|| viewType == VIEW_TYPE_SEARCH_SUGGEST
|
||||
|| viewType == VIEW_TYPE_SEARCH_WIDGET_LIVE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,6 +431,9 @@ public class AllAppsGridAdapter extends
|
||||
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));
|
||||
default:
|
||||
throw new RuntimeException("Unexpected view type");
|
||||
}
|
||||
@@ -469,6 +476,7 @@ public class AllAppsGridAdapter extends
|
||||
case VIEW_TYPE_SEARCH_PEOPLE:
|
||||
case VIEW_TYPE_SEARCH_THUMBNAIL:
|
||||
case VIEW_TYPE_SEARCH_SUGGEST:
|
||||
case VIEW_TYPE_SEARCH_WIDGET_LIVE:
|
||||
SearchAdapterItem item =
|
||||
(SearchAdapterItem) mApps.getAdapterItems().get(position);
|
||||
SearchTargetHandler payloadResultView = (SearchTargetHandler) holder.itemView;
|
||||
@@ -487,6 +495,9 @@ 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
|
||||
|
||||
@@ -264,11 +264,15 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
|
||||
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()) {
|
||||
mInsetController.onAnimationEnd(mProgress);
|
||||
if (Float.compare(mProgress, 0f) == 0) {
|
||||
mLauncher.getLiveSearchManager().start();
|
||||
EditText editText = mAppsView.getSearchUiManager().getEditText();
|
||||
if (editText != null) {
|
||||
editText.requestFocus();
|
||||
}
|
||||
}
|
||||
else {
|
||||
mLauncher.getLiveSearchManager().stop();
|
||||
}
|
||||
// TODO: should make the controller hide synchronously
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.allapps.search;
|
||||
|
||||
import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget;
|
||||
|
||||
import android.appwidget.AppWidgetHost;
|
||||
import android.appwidget.AppWidgetHostView;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.widget.PendingAddWidgetInfo;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Manages Lifecycle for Live search results
|
||||
*/
|
||||
public class LiveSearchManager {
|
||||
|
||||
public static final int SEARCH_APPWIDGET_HOST_ID = 2048;
|
||||
|
||||
private final Launcher mLauncher;
|
||||
private final AppWidgetManager mAppWidgetManger;
|
||||
private HashMap<ComponentKey, SearchWidgetInfoContainer> mWidgetPlaceholders = new HashMap<>();
|
||||
private SearchWidgetHost mSearchWidgetHost;
|
||||
|
||||
public LiveSearchManager(Launcher launcher) {
|
||||
mLauncher = launcher;
|
||||
mAppWidgetManger = AppWidgetManager.getInstance(launcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link AppWidgetHostView} from {@link AppWidgetProviderInfo}. Caches views for
|
||||
* quicker result within the same search session
|
||||
*/
|
||||
public SearchWidgetInfoContainer getPlaceHolderWidget(AppWidgetProviderInfo providerInfo) {
|
||||
if (mSearchWidgetHost == null) {
|
||||
throw new RuntimeException("AppWidgetHost has not been created yet");
|
||||
}
|
||||
|
||||
ComponentName provider = providerInfo.provider;
|
||||
UserHandle userHandle = providerInfo.getProfile();
|
||||
|
||||
ComponentKey key = new ComponentKey(provider, userHandle);
|
||||
SearchWidgetInfoContainer view = mWidgetPlaceholders.getOrDefault(key, null);
|
||||
if (mWidgetPlaceholders.containsKey(key)) {
|
||||
return mWidgetPlaceholders.get(key);
|
||||
}
|
||||
LauncherAppWidgetProviderInfo pinfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
|
||||
mLauncher, providerInfo);
|
||||
PendingAddWidgetInfo pendingAddWidgetInfo = new PendingAddWidgetInfo(pinfo);
|
||||
|
||||
Bundle options = getDefaultOptionsForWidget(mLauncher, pendingAddWidgetInfo);
|
||||
int appWidgetId = mSearchWidgetHost.allocateAppWidgetId();
|
||||
boolean success = mAppWidgetManger.bindAppWidgetIdIfAllowed(appWidgetId, userHandle,
|
||||
provider, options);
|
||||
if (!success) {
|
||||
mWidgetPlaceholders.put(key, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
view = (SearchWidgetInfoContainer) mSearchWidgetHost.createView(mLauncher, appWidgetId,
|
||||
providerInfo);
|
||||
view.setTag(pendingAddWidgetInfo);
|
||||
mWidgetPlaceholders.put(key, view);
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start search session
|
||||
*/
|
||||
public void start() {
|
||||
stop();
|
||||
mSearchWidgetHost = new SearchWidgetHost(mLauncher);
|
||||
mSearchWidgetHost.startListening();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop search session
|
||||
*/
|
||||
public void stop() {
|
||||
if (mSearchWidgetHost != null) {
|
||||
mSearchWidgetHost.stopListening();
|
||||
mSearchWidgetHost.deleteHost();
|
||||
for (SearchWidgetInfoContainer placeholder : mWidgetPlaceholders.values()) {
|
||||
placeholder.clearListeners();
|
||||
}
|
||||
mWidgetPlaceholders.clear();
|
||||
mSearchWidgetHost = null;
|
||||
}
|
||||
}
|
||||
|
||||
static class SearchWidgetHost extends AppWidgetHost {
|
||||
SearchWidgetHost(Context context) {
|
||||
super(context, SEARCH_APPWIDGET_HOST_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
|
||||
AppWidgetProviderInfo appWidget) {
|
||||
return new SearchWidgetInfoContainer(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.allapps.search;
|
||||
|
||||
import android.appwidget.AppWidgetHostView;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.Context;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A placeholder {@link AppWidgetHostView} used for managing widget search results
|
||||
*/
|
||||
public class SearchWidgetInfoContainer extends AppWidgetHostView {
|
||||
private int mAppWidgetId;
|
||||
private AppWidgetProviderInfo mProviderInfo;
|
||||
private RemoteViews mViews;
|
||||
private List<AppWidgetHostView> mListeners = new ArrayList<>();
|
||||
|
||||
public SearchWidgetInfoContainer(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
|
||||
mAppWidgetId = appWidgetId;
|
||||
mProviderInfo = info;
|
||||
for (AppWidgetHostView listener : mListeners) {
|
||||
listener.setAppWidget(mAppWidgetId, mProviderInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAppWidget(RemoteViews remoteViews) {
|
||||
mViews = remoteViews;
|
||||
for (AppWidgetHostView listener : mListeners) {
|
||||
listener.updateAppWidget(remoteViews);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a live {@link AppWidgetHostView} from placeholder
|
||||
*/
|
||||
public void attachWidget(AppWidgetHostView hv) {
|
||||
hv.setTag(getTag());
|
||||
hv.setAppWidget(mAppWidgetId, mProviderInfo);
|
||||
hv.updateAppWidget(mViews);
|
||||
mListeners.add(hv);
|
||||
}
|
||||
|
||||
/**
|
||||
* stops AppWidgetHostView from getting updates
|
||||
*/
|
||||
public void detachWidget(AppWidgetHostView hostView) {
|
||||
mListeners.remove(hostView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all AppWidgetHost update listeners
|
||||
*/
|
||||
public void clearListeners() {
|
||||
mListeners.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.AppWidgetResizeFrame;
|
||||
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.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 {
|
||||
|
||||
private static final String TAG = "SearchResultWidget";
|
||||
|
||||
public static final String TARGET_TYPE_WIDGET_LIVE = "widget";
|
||||
|
||||
private final Launcher mLauncher;
|
||||
private final AppWidgetHostView mHostView;
|
||||
|
||||
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);
|
||||
mHostView = new AppWidgetHostView(context);
|
||||
mLauncher = Launcher.getLauncher(context);
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
handleSelection(SearchTargetEvent.CHILD_SELECT);
|
||||
}
|
||||
return super.onInterceptTouchEvent(ev);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user