Merge "Add QuickstepWidgetHolder for widget handling" into tm-qpr-dev am: 04c89fe38e

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/20418248

Change-Id: Ibe09a5d1ab53070bc6a4b797f95e9d853f86fbc0
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Sihua Ma
2022-12-19 19:15:29 +00:00
committed by Automerger Merge Worker
14 changed files with 384 additions and 72 deletions

View File

@@ -25,6 +25,7 @@
<string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
<string name="test_information_handler_class" translatable="false">com.android.quickstep.QuickstepTestInformationHandler</string>
<string name="window_manager_proxy_class" translatable="false">com.android.quickstep.util.SystemWindowManagerProxy</string>
<string name="widget_holder_factory_class" translatable="false">com.android.launcher3.uioverrides.QuickstepWidgetHolder$QuickstepHolderFactory</string>
<!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
determines how many thumbnails will be fetched in the background. -->

View File

@@ -17,14 +17,9 @@
package com.android.launcher3.uioverrides;
import android.app.Person;
import android.appwidget.AppWidgetHost;
import android.content.pm.ShortcutInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.Utilities;
import com.android.launcher3.widget.LauncherWidgetHolder;
/**
* A wrapper for the hidden API calls
@@ -37,14 +32,4 @@ public class ApiWrapper {
Person[] persons = si.getPersons();
return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
}
/**
* Set the interaction handler for the host
* @param host AppWidgetHost that needs the interaction handler
* @param handler InteractionHandler for the views in the host
*/
public static void setHostInteractionHandler(@NonNull AppWidgetHost host,
@Nullable LauncherWidgetHolder.LauncherWidgetInteractionHandler handler) {
host.setInteractionHandler(handler::onInteraction);
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright (C) 2022 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.uioverrides;
import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.os.Looper;
import androidx.annotation.NonNull;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.LauncherWidgetHolder;
import java.util.function.IntConsumer;
/**
* {@link AppWidgetHost} that is used to receive the changes to the widgets without
* storing any {@code Activity} info like that of the launcher.
*/
final class QuickstepAppWidgetHost extends AppWidgetHost {
private final @NonNull Context mContext;
private final @NonNull IntConsumer mAppWidgetRemovedCallback;
private final @NonNull LauncherWidgetHolder.ProviderChangedListener mProvidersChangedListener;
QuickstepAppWidgetHost(@NonNull Context context, @NonNull IntConsumer appWidgetRemovedCallback,
@NonNull LauncherWidgetHolder.ProviderChangedListener listener,
@NonNull Looper looper) {
super(context, APPWIDGET_HOST_ID, null, looper);
mContext = context;
mAppWidgetRemovedCallback = appWidgetRemovedCallback;
mProvidersChangedListener = listener;
}
@Override
protected void onProvidersChanged() {
mProvidersChangedListener.notifyWidgetProvidersChanged();
}
@Override
public void onAppWidgetRemoved(int appWidgetId) {
mAppWidgetRemovedCallback.accept(appWidgetId);
}
@Override
protected void onProviderChanged(int appWidgetId, @NonNull AppWidgetProviderInfo appWidget) {
LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo.fromProviderInfo(
mContext, appWidget);
super.onProviderChanged(appWidgetId, info);
// The super method updates the dimensions of the providerInfo. Update the
// launcher spans accordingly.
info.initSpans(mContext, LauncherAppState.getIDP(mContext));
}
}

View File

@@ -34,11 +34,9 @@ import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.LauncherWidgetHolder;
/** Provides a Quickstep specific animation when launching an activity from an app widget. */
class QuickstepInteractionHandler
implements LauncherWidgetHolder.LauncherWidgetInteractionHandler {
class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
private static final String TAG = "QuickstepInteractionHandler";

View File

@@ -96,7 +96,6 @@ import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.proxy.ProxyActivityStarter;
import com.android.launcher3.proxy.StartActivityParams;
@@ -106,6 +105,7 @@ import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
import com.android.launcher3.taskbar.TaskbarManager;
import com.android.launcher3.uioverrides.QuickstepWidgetHolder.QuickstepHolderFactory;
import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
@@ -517,9 +517,11 @@ public class QuickstepLauncher extends Launcher {
@Override
protected LauncherWidgetHolder createAppWidgetHolder() {
LauncherWidgetHolder appWidgetHolder = super.createAppWidgetHolder();
appWidgetHolder.setInteractionHandler(new QuickstepInteractionHandler(this));
return appWidgetHolder;
final QuickstepHolderFactory factory =
(QuickstepHolderFactory) LauncherWidgetHolder.HolderFactory.newFactory(this);
return factory.newInstance(this,
appWidgetId -> getWorkspace().removeWidget(appWidgetId),
new QuickstepInteractionHandler(this));
}
@Override

View File

@@ -0,0 +1,270 @@
/**
* Copyright (C) 2022 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.uioverrides;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
import android.util.SparseArray;
import android.widget.RemoteViews;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.LauncherWidgetHolder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
/**
* {@link LauncherWidgetHolder} that puts the app widget host in the background
*/
public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
private static final List<QuickstepWidgetHolder> sHolders = new ArrayList<>();
private static final SparseArray<QuickstepWidgetHolderListener> sListeners =
new SparseArray<>();
private static AppWidgetHost sWidgetHost = null;
private final @Nullable RemoteViews.InteractionHandler mInteractionHandler;
private final @NonNull IntConsumer mAppWidgetRemovedCallback;
private final ArrayList<ProviderChangedListener> mProviderChangedListeners = new ArrayList<>();
@Thunk
QuickstepWidgetHolder(@NonNull Context context,
@Nullable IntConsumer appWidgetRemovedCallback,
@Nullable RemoteViews.InteractionHandler interactionHandler) {
super(context, appWidgetRemovedCallback);
mAppWidgetRemovedCallback = appWidgetRemovedCallback != null ? appWidgetRemovedCallback
: i -> {};
mInteractionHandler = interactionHandler;
sHolders.add(this);
}
@Override
@NonNull
protected AppWidgetHost createHost(@NonNull Context context,
@Nullable IntConsumer appWidgetRemovedCallback) {
if (sWidgetHost == null) {
sWidgetHost = new QuickstepAppWidgetHost(context.getApplicationContext(),
i -> MAIN_EXECUTOR.execute(() ->
sHolders.forEach(h -> h.mAppWidgetRemovedCallback.accept(i))),
() -> MAIN_EXECUTOR.execute(() ->
sHolders.forEach(h -> h.mProviderChangedListeners.forEach(
ProviderChangedListener::notifyWidgetProvidersChanged))),
UI_HELPER_EXECUTOR.getLooper());
if (!WidgetsModel.GO_DISABLE_WIDGETS) {
sWidgetHost.startListening();
}
}
return sWidgetHost;
}
/**
* Delete the specified app widget from the host
* @param appWidgetId The ID of the app widget to be deleted
*/
@Override
public void deleteAppWidgetId(int appWidgetId) {
super.deleteAppWidgetId(appWidgetId);
sListeners.remove(appWidgetId);
}
/**
* Called when the launcher is destroyed
*/
@Override
public void destroy() {
sHolders.remove(this);
}
/**
* Add a listener that is triggered when the providers of the widgets are changed
* @param listener The listener that notifies when the providers changed
*/
@Override
public void addProviderChangeListener(
@NonNull LauncherWidgetHolder.ProviderChangedListener listener) {
mProviderChangedListeners.add(listener);
}
/**
* Remove the specified listener from the host
* @param listener The listener that is to be removed from the host
*/
@Override
public void removeProviderChangeListener(
LauncherWidgetHolder.ProviderChangedListener listener) {
mProviderChangedListeners.remove(listener);
}
/**
* Stop the host from updating the widget views
*/
@Override
public void stopListening() {
if (WidgetsModel.GO_DISABLE_WIDGETS) {
return;
}
sWidgetHost.setAppWidgetHidden();
setListeningFlag(false);
}
/**
* Create a view for the specified app widget
* @param context The activity context for which the view is created
* @param appWidgetId The ID of the widget
* @param appWidget The {@link LauncherAppWidgetProviderInfo} of the widget
* @return A view for the widget
*/
@NonNull
@Override
public LauncherAppWidgetHostView createView(@NonNull Context context, int appWidgetId,
@NonNull LauncherAppWidgetProviderInfo appWidget) {
LauncherAppWidgetHostView widgetView = getPendingView(appWidgetId);
if (widgetView != null) {
removePendingView(appWidgetId);
} else {
widgetView = new LauncherAppWidgetHostView(context);
}
widgetView.setInteractionHandler(mInteractionHandler);
widgetView.setAppWidget(appWidgetId, appWidget);
QuickstepWidgetHolderListener listener = sListeners.get(appWidgetId);
if (listener == null) {
listener = new QuickstepWidgetHolderListener(this, widgetView);
sWidgetHost.setListener(appWidgetId, listener);
sListeners.put(appWidgetId, listener);
} else {
listener.resetView(this, widgetView);
}
return widgetView;
}
/**
* Clears all the views from the host
*/
@Override
public void clearViews() {
for (int i = sListeners.size() - 1; i >= 0; i--) {
sListeners.valueAt(i).mView.remove(this);
}
}
private static class QuickstepWidgetHolderListener
implements AppWidgetHost.AppWidgetHostListener {
@NonNull
private final Map<QuickstepWidgetHolder, AppWidgetHostView> mView = new WeakHashMap<>();
@Nullable
private RemoteViews mRemoteViews = null;
QuickstepWidgetHolderListener(@NonNull QuickstepWidgetHolder holder,
@NonNull LauncherAppWidgetHostView view) {
mView.put(holder, view);
}
@UiThread
public void resetView(@NonNull QuickstepWidgetHolder holder,
@NonNull AppWidgetHostView view) {
mView.put(holder, view);
view.updateAppWidget(mRemoteViews);
}
@Override
@WorkerThread
public void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo info) {
mRemoteViews = null;
executeOnMainExecutor(v -> v.onUpdateProviderInfo(info));
}
@Override
@WorkerThread
public void updateAppWidget(@Nullable RemoteViews views) {
mRemoteViews = views;
executeOnMainExecutor(v -> v.updateAppWidget(mRemoteViews));
}
@Override
@WorkerThread
public void onViewDataChanged(int viewId) {
executeOnMainExecutor(v -> v.onViewDataChanged(viewId));
}
private void executeOnMainExecutor(Consumer<AppWidgetHostView> consumer) {
MAIN_EXECUTOR.execute(() -> mView.values().forEach(consumer));
}
}
/**
* {@code HolderFactory} subclass that takes an interaction handler as one of the parameters
* when creating a new instance.
*/
public static class QuickstepHolderFactory extends HolderFactory {
@SuppressWarnings("unused")
public QuickstepHolderFactory(Context context) { }
@Override
public LauncherWidgetHolder newInstance(@NonNull Context context,
@Nullable IntConsumer appWidgetRemovedCallback) {
return newInstance(context, appWidgetRemovedCallback, null);
}
/**
* @param context The context of the caller
* @param appWidgetRemovedCallback The callback that is called when widgets are removed
* @param interactionHandler The interaction handler when the widgets are clicked
* @return A new {@link LauncherWidgetHolder} instance
*/
public LauncherWidgetHolder newInstance(@NonNull Context context,
@Nullable IntConsumer appWidgetRemovedCallback,
@Nullable RemoteViews.InteractionHandler interactionHandler) {
if (!FeatureFlags.ENABLE_WIDGET_HOST_IN_BACKGROUND.get()) {
return new LauncherWidgetHolder(context, appWidgetRemovedCallback) {
@Override
protected AppWidgetHost createHost(Context context,
@Nullable IntConsumer appWidgetRemovedCallback) {
AppWidgetHost host = super.createHost(context, appWidgetRemovedCallback);
host.setInteractionHandler(interactionHandler);
return host;
}
};
}
return new QuickstepWidgetHolder(context, appWidgetRemovedCallback, interactionHandler);
}
}
}

View File

@@ -83,6 +83,7 @@
<string name="model_delegate_class" translatable="false"></string>
<string name="window_manager_proxy_class" translatable="false"></string>
<string name="secondary_display_predictions_class" translatable="false"></string>
<string name="widget_holder_factory_class" translatable="false"></string>
<!-- View ID to use for QSB widget -->
<item type="id" name="qsb_widget" />

View File

@@ -1548,8 +1548,8 @@ public class Launcher extends StatefulActivity<LauncherState>
}
protected LauncherWidgetHolder createAppWidgetHolder() {
return new LauncherWidgetHolder(this,
appWidgetId -> getWorkspace().removeWidget(appWidgetId));
return LauncherWidgetHolder.HolderFactory.newFactory(this).newInstance(
this, appWidgetId -> getWorkspace().removeWidget(appWidgetId));
}
public LauncherModel getModel() {

View File

@@ -1093,7 +1093,7 @@ public class LauncherProvider extends ContentProvider {
*/
@NonNull
public LauncherWidgetHolder newLauncherWidgetHolder() {
return new LauncherWidgetHolder(mContext);
return LauncherWidgetHolder.newInstance(mContext);
}
@Override

View File

@@ -295,7 +295,7 @@ public class AddItemActivity extends BaseActivity
mWidgetCell.setRemoteViewsPreview(PinItemDragListener.getPreview(mRequest));
mAppWidgetManager = new WidgetManagerHelper(this);
mAppWidgetHolder = new LauncherWidgetHolder(this);
mAppWidgetHolder = LauncherWidgetHolder.newInstance(this);
PendingAddWidgetInfo pendingInfo =
new PendingAddWidgetInfo(widgetInfo, CONTAINER_PIN_WIDGETS);

View File

@@ -355,7 +355,7 @@ public class RestoreDbTask {
private void restoreAppWidgetIdsIfExists(Context context) {
SharedPreferences prefs = LauncherPrefs.getPrefs(context);
if (prefs.contains(APPWIDGET_OLD_IDS) && prefs.contains(APPWIDGET_IDS)) {
LauncherWidgetHolder holder = new LauncherWidgetHolder(context);
LauncherWidgetHolder holder = LauncherWidgetHolder.newInstance(context);
AppWidgetsRestoredReceiver.restoreAppWidgetIds(context,
IntArray.fromConcatString(prefs.getString(APPWIDGET_OLD_IDS, "")).toArray(),
IntArray.fromConcatString(prefs.getString(APPWIDGET_IDS, "")).toArray(),

View File

@@ -19,7 +19,6 @@ import static android.app.Activity.RESULT_CANCELED;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.app.PendingIntent;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
@@ -29,7 +28,6 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.SparseArray;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.Toast;
@@ -45,7 +43,7 @@ import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.ResourceBasedOverride;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import java.util.function.IntConsumer;
@@ -86,11 +84,7 @@ public class LauncherWidgetHolder {
// TODO(b/191735836): Replace with SplashScreen.SPLASH_SCREEN_STYLE_EMPTY when un-hidden
private static final int SPLASH_SCREEN_STYLE_EMPTY = 0;
public LauncherWidgetHolder(@NonNull Context context) {
this(context, null);
}
public LauncherWidgetHolder(@NonNull Context context,
protected LauncherWidgetHolder(@NonNull Context context,
@Nullable IntConsumer appWidgetRemovedCallback) {
mContext = context;
mWidgetHost = createHost(context, appWidgetRemovedCallback);
@@ -320,15 +314,6 @@ public class LauncherWidgetHolder {
mFlags &= ~FLAG_LISTENING;
}
/**
* Set the interaction handler for the widget host
* @param handler The interaction handler
*/
public void setInteractionHandler(
@Nullable LauncherWidgetInteractionHandler handler) {
ApiWrapper.setHostInteractionHandler(mWidgetHost, handler);
}
/**
* Delete the host
*/
@@ -489,20 +474,35 @@ public class LauncherWidgetHolder {
}
/**
* Set as a substitution for the hidden interaction handler in RemoteViews
* Returns the new LauncherWidgetHolder instance
*/
public interface LauncherWidgetInteractionHandler {
public static LauncherWidgetHolder newInstance(Context context) {
return HolderFactory.newFactory(context).newInstance(context, null);
}
/**
* A factory class that generates new instances of {@code LauncherWidgetHolder}
*/
public static class HolderFactory implements ResourceBasedOverride {
/**
* Invoked when the user performs an interaction on the View.
*
* @param view the View with which the user interacted
* @param pendingIntent the base PendingIntent associated with the view
* @param response the response to the interaction, which knows how to fill in the
* attached PendingIntent
* @param context The context of the caller
* @param appWidgetRemovedCallback The callback that is called when widgets are removed
* @return A new instance of {@code LauncherWidgetHolder}
*/
boolean onInteraction(
View view,
PendingIntent pendingIntent,
RemoteViews.RemoteResponse response);
public LauncherWidgetHolder newInstance(@NonNull Context context,
@Nullable IntConsumer appWidgetRemovedCallback) {
return new LauncherWidgetHolder(context, appWidgetRemovedCallback);
}
/**
* @param context The context of the caller
* @return A new instance of factory class for widget holders. If not specified, returning
* {@code HolderFactory} by default.
*/
public static HolderFactory newFactory(Context context) {
return Overrides.getObject(
HolderFactory.class, context, R.string.widget_holder_factory_class);
}
}
}

View File

@@ -17,14 +17,9 @@
package com.android.launcher3.uioverrides;
import android.app.Person;
import android.appwidget.AppWidgetHost;
import android.content.pm.ShortcutInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.Utilities;
import com.android.launcher3.widget.LauncherWidgetHolder;
/**
* A wrapper for the hidden API calls
@@ -36,14 +31,4 @@ public class ApiWrapper {
public static Person[] getPersons(ShortcutInfo si) {
return Utilities.EMPTY_PERSON_ARRAY;
}
/**
* Set the interaction handler for the host
* @param host AppWidgetHost that needs the interaction handler
* @param handler InteractionHandler for the views in the host
*/
public static void setHostInteractionHandler(@NonNull AppWidgetHost host,
@Nullable LauncherWidgetHolder.LauncherWidgetInteractionHandler handler) {
// No-op
}
}

View File

@@ -70,7 +70,7 @@ public class WidgetUtils {
pendingInfo.minSpanY = item.minSpanY;
Bundle options = pendingInfo.getDefaultSizeOptions(targetContext);
LauncherWidgetHolder holder = new LauncherWidgetHolder(targetContext);
LauncherWidgetHolder holder = LauncherWidgetHolder.newInstance(targetContext);
try {
int widgetId = holder.allocateAppWidgetId();
if (!new WidgetManagerHelper(targetContext)