Merge "Initial setup for widgets in Search" into ub-launcher3-master

This commit is contained in:
Samuel Fufa
2020-11-09 18:33:51 +00:00
committed by Android (Google) Code Review
8 changed files with 371 additions and 2 deletions
+5
View File
@@ -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(),
+10
View File
@@ -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);
}
}