Files
lawnchair/src/com/android/launcher3/widget/WidgetHostViewLoader.java

177 lines
6.6 KiB
Java
Raw Normal View History

package com.android.launcher3.widget;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
Fix "The specified child already has a parent" IllegalStateException. The problem was due to a race condition between removing a prebound widget view from the drag layer and adding the same view to the workspace upon dropping it; if you let go of the widget immediately after picking it up, the latter happened before the former. Specifically, the flow was: long-click a widget --> drop --> remove the view from the drag layer if it's not null (it is, so nothing happens) --> the view is finally bound/inflated and added to the drag layer --> add the view to the workspace --> already has a parent. There are actually 2 problems here: one is that the bind/inflate is asynchronous, and can therefore happen after dropping the widget view being inflated, and the other is that the view is added to the workspace even though the transition has barely started (we usually ignore drops if the transition is less than half complete). It turns out that this second problem was also due to a race condition, this time between dropping a widget or app onto the workspace and calling LauncherStateTransitionAnimation.dispatchOnLauncherTransitionStart(). If the drop happened before the dispatch, as in the case of the crash, then the drop was accepted because the transition progress was still 1.0 from the previous transition. I fixed the first problem by removing the drag layer widget view in Launcher where it is potentially used instead of Workspace. And I fixed the second problem by setting mTransitionProgress to 0 in Workspace.onLauncherTransitionPrepare(). I also added some debugging logs. Bug: 23896857 Change-Id: I66944e6d3f23b70dea15f7fb01af0763a1bfcbda
2015-10-14 15:23:04 -07:00
import android.util.Log;
import android.view.View;
import com.android.launcher3.AppWidgetResizeFrame;
import com.android.launcher3.DropTarget;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.util.Thunk;
Fix "The specified child already has a parent" IllegalStateException. The problem was due to a race condition between removing a prebound widget view from the drag layer and adding the same view to the workspace upon dropping it; if you let go of the widget immediately after picking it up, the latter happened before the former. Specifically, the flow was: long-click a widget --> drop --> remove the view from the drag layer if it's not null (it is, so nothing happens) --> the view is finally bound/inflated and added to the drag layer --> add the view to the workspace --> already has a parent. There are actually 2 problems here: one is that the bind/inflate is asynchronous, and can therefore happen after dropping the widget view being inflated, and the other is that the view is added to the workspace even though the transition has barely started (we usually ignore drops if the transition is less than half complete). It turns out that this second problem was also due to a race condition, this time between dropping a widget or app onto the workspace and calling LauncherStateTransitionAnimation.dispatchOnLauncherTransitionStart(). If the drop happened before the dispatch, as in the case of the crash, then the drop was accepted because the transition progress was still 1.0 from the previous transition. I fixed the first problem by removing the drag layer widget view in Launcher where it is potentially used instead of Workspace. And I fixed the second problem by setting mTransitionProgress to 0 in Workspace.onLauncherTransitionPrepare(). I also added some debugging logs. Bug: 23896857 Change-Id: I66944e6d3f23b70dea15f7fb01af0763a1bfcbda
2015-10-14 15:23:04 -07:00
public class WidgetHostViewLoader implements DragController.DragListener {
private static final String TAG = "WidgetHostViewLoader";
private static final boolean LOGD = false;
/* Runnables to handle inflation and binding. */
@Thunk Runnable mInflateWidgetRunnable = null;
private Runnable mBindWidgetRunnable = null;
// TODO: technically, this class should not have to know the existence of the launcher.
@Thunk Launcher mLauncher;
@Thunk Handler mHandler;
@Thunk final View mView;
@Thunk final PendingAddWidgetInfo mInfo;
// Widget id generated for binding a widget host view or -1 for invalid id. The id is
// not is use as long as it is stored here and can be deleted safely. Once its used, this value
// to be set back to -1.
@Thunk int mWidgetLoadingId = -1;
public WidgetHostViewLoader(Launcher launcher, View view) {
mLauncher = launcher;
mHandler = new Handler();
mView = view;
mInfo = (PendingAddWidgetInfo) view.getTag();
}
@Override
public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
preloadWidget();
}
@Override
public void onDragEnd() {
Fix "The specified child already has a parent" IllegalStateException. The problem was due to a race condition between removing a prebound widget view from the drag layer and adding the same view to the workspace upon dropping it; if you let go of the widget immediately after picking it up, the latter happened before the former. Specifically, the flow was: long-click a widget --> drop --> remove the view from the drag layer if it's not null (it is, so nothing happens) --> the view is finally bound/inflated and added to the drag layer --> add the view to the workspace --> already has a parent. There are actually 2 problems here: one is that the bind/inflate is asynchronous, and can therefore happen after dropping the widget view being inflated, and the other is that the view is added to the workspace even though the transition has barely started (we usually ignore drops if the transition is less than half complete). It turns out that this second problem was also due to a race condition, this time between dropping a widget or app onto the workspace and calling LauncherStateTransitionAnimation.dispatchOnLauncherTransitionStart(). If the drop happened before the dispatch, as in the case of the crash, then the drop was accepted because the transition progress was still 1.0 from the previous transition. I fixed the first problem by removing the drag layer widget view in Launcher where it is potentially used instead of Workspace. And I fixed the second problem by setting mTransitionProgress to 0 in Workspace.onLauncherTransitionPrepare(). I also added some debugging logs. Bug: 23896857 Change-Id: I66944e6d3f23b70dea15f7fb01af0763a1bfcbda
2015-10-14 15:23:04 -07:00
if (LOGD) {
Log.d(TAG, "Cleaning up in onDragEnd()...");
}
// Cleanup up preloading state.
mLauncher.getDragController().removeDragListener(this);
mHandler.removeCallbacks(mBindWidgetRunnable);
mHandler.removeCallbacks(mInflateWidgetRunnable);
// Cleanup widget id
if (mWidgetLoadingId != -1) {
mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
mWidgetLoadingId = -1;
}
// The widget was inflated and added to the DragLayer -- remove it.
if (mInfo.boundWidget != null) {
Fix "The specified child already has a parent" IllegalStateException. The problem was due to a race condition between removing a prebound widget view from the drag layer and adding the same view to the workspace upon dropping it; if you let go of the widget immediately after picking it up, the latter happened before the former. Specifically, the flow was: long-click a widget --> drop --> remove the view from the drag layer if it's not null (it is, so nothing happens) --> the view is finally bound/inflated and added to the drag layer --> add the view to the workspace --> already has a parent. There are actually 2 problems here: one is that the bind/inflate is asynchronous, and can therefore happen after dropping the widget view being inflated, and the other is that the view is added to the workspace even though the transition has barely started (we usually ignore drops if the transition is less than half complete). It turns out that this second problem was also due to a race condition, this time between dropping a widget or app onto the workspace and calling LauncherStateTransitionAnimation.dispatchOnLauncherTransitionStart(). If the drop happened before the dispatch, as in the case of the crash, then the drop was accepted because the transition progress was still 1.0 from the previous transition. I fixed the first problem by removing the drag layer widget view in Launcher where it is potentially used instead of Workspace. And I fixed the second problem by setting mTransitionProgress to 0 in Workspace.onLauncherTransitionPrepare(). I also added some debugging logs. Bug: 23896857 Change-Id: I66944e6d3f23b70dea15f7fb01af0763a1bfcbda
2015-10-14 15:23:04 -07:00
if (LOGD) {
Log.d(TAG, "...removing widget from drag layer");
}
mLauncher.getDragLayer().removeView(mInfo.boundWidget);
mLauncher.getAppWidgetHost().deleteAppWidgetId(mInfo.boundWidget.getAppWidgetId());
mInfo.boundWidget = null;
}
}
/**
* Start preloading the widget.
*/
private boolean preloadWidget() {
final LauncherAppWidgetProviderInfo pInfo = mInfo.info;
if (pInfo.isCustomWidget) {
return false;
}
final Bundle options = getDefaultOptionsForWidget(mLauncher, mInfo);
// If there is a configuration activity, do not follow thru bound and inflate.
if (mInfo.getHandler().needsConfigure()) {
mInfo.bindOptions = options;
return false;
}
mBindWidgetRunnable = new Runnable() {
@Override
public void run() {
mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId();
Fix "The specified child already has a parent" IllegalStateException. The problem was due to a race condition between removing a prebound widget view from the drag layer and adding the same view to the workspace upon dropping it; if you let go of the widget immediately after picking it up, the latter happened before the former. Specifically, the flow was: long-click a widget --> drop --> remove the view from the drag layer if it's not null (it is, so nothing happens) --> the view is finally bound/inflated and added to the drag layer --> add the view to the workspace --> already has a parent. There are actually 2 problems here: one is that the bind/inflate is asynchronous, and can therefore happen after dropping the widget view being inflated, and the other is that the view is added to the workspace even though the transition has barely started (we usually ignore drops if the transition is less than half complete). It turns out that this second problem was also due to a race condition, this time between dropping a widget or app onto the workspace and calling LauncherStateTransitionAnimation.dispatchOnLauncherTransitionStart(). If the drop happened before the dispatch, as in the case of the crash, then the drop was accepted because the transition progress was still 1.0 from the previous transition. I fixed the first problem by removing the drag layer widget view in Launcher where it is potentially used instead of Workspace. And I fixed the second problem by setting mTransitionProgress to 0 in Workspace.onLauncherTransitionPrepare(). I also added some debugging logs. Bug: 23896857 Change-Id: I66944e6d3f23b70dea15f7fb01af0763a1bfcbda
2015-10-14 15:23:04 -07:00
if (LOGD) {
Log.d(TAG, "Binding widget, id: " + mWidgetLoadingId);
}
if(AppWidgetManagerCompat.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
mWidgetLoadingId, pInfo, options)) {
// Widget id bound. Inflate the widget.
mHandler.post(mInflateWidgetRunnable);
}
}
};
mInflateWidgetRunnable = new Runnable() {
@Override
public void run() {
Fix "The specified child already has a parent" IllegalStateException. The problem was due to a race condition between removing a prebound widget view from the drag layer and adding the same view to the workspace upon dropping it; if you let go of the widget immediately after picking it up, the latter happened before the former. Specifically, the flow was: long-click a widget --> drop --> remove the view from the drag layer if it's not null (it is, so nothing happens) --> the view is finally bound/inflated and added to the drag layer --> add the view to the workspace --> already has a parent. There are actually 2 problems here: one is that the bind/inflate is asynchronous, and can therefore happen after dropping the widget view being inflated, and the other is that the view is added to the workspace even though the transition has barely started (we usually ignore drops if the transition is less than half complete). It turns out that this second problem was also due to a race condition, this time between dropping a widget or app onto the workspace and calling LauncherStateTransitionAnimation.dispatchOnLauncherTransitionStart(). If the drop happened before the dispatch, as in the case of the crash, then the drop was accepted because the transition progress was still 1.0 from the previous transition. I fixed the first problem by removing the drag layer widget view in Launcher where it is potentially used instead of Workspace. And I fixed the second problem by setting mTransitionProgress to 0 in Workspace.onLauncherTransitionPrepare(). I also added some debugging logs. Bug: 23896857 Change-Id: I66944e6d3f23b70dea15f7fb01af0763a1bfcbda
2015-10-14 15:23:04 -07:00
if (LOGD) {
Log.d(TAG, "Inflating widget, id: " + mWidgetLoadingId);
}
if (mWidgetLoadingId == -1) {
return;
}
AppWidgetHostView hostView = mLauncher.getAppWidgetHost().createView(
(Context) mLauncher, mWidgetLoadingId, pInfo);
mInfo.boundWidget = hostView;
// We used up the widget Id in binding the above view.
mWidgetLoadingId = -1;
hostView.setVisibility(View.INVISIBLE);
int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(mInfo, false, true);
// We want the first widget layout to be the correct size. This will be important
// for width size reporting to the AppWidgetManager.
DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0],
unScaledSize[1]);
lp.x = lp.y = 0;
lp.customPosition = true;
hostView.setLayoutParams(lp);
Fix "The specified child already has a parent" IllegalStateException. The problem was due to a race condition between removing a prebound widget view from the drag layer and adding the same view to the workspace upon dropping it; if you let go of the widget immediately after picking it up, the latter happened before the former. Specifically, the flow was: long-click a widget --> drop --> remove the view from the drag layer if it's not null (it is, so nothing happens) --> the view is finally bound/inflated and added to the drag layer --> add the view to the workspace --> already has a parent. There are actually 2 problems here: one is that the bind/inflate is asynchronous, and can therefore happen after dropping the widget view being inflated, and the other is that the view is added to the workspace even though the transition has barely started (we usually ignore drops if the transition is less than half complete). It turns out that this second problem was also due to a race condition, this time between dropping a widget or app onto the workspace and calling LauncherStateTransitionAnimation.dispatchOnLauncherTransitionStart(). If the drop happened before the dispatch, as in the case of the crash, then the drop was accepted because the transition progress was still 1.0 from the previous transition. I fixed the first problem by removing the drag layer widget view in Launcher where it is potentially used instead of Workspace. And I fixed the second problem by setting mTransitionProgress to 0 in Workspace.onLauncherTransitionPrepare(). I also added some debugging logs. Bug: 23896857 Change-Id: I66944e6d3f23b70dea15f7fb01af0763a1bfcbda
2015-10-14 15:23:04 -07:00
if (LOGD) {
Log.d(TAG, "Adding host view to drag layer");
}
mLauncher.getDragLayer().addView(hostView);
mView.setTag(mInfo);
}
};
Fix "The specified child already has a parent" IllegalStateException. The problem was due to a race condition between removing a prebound widget view from the drag layer and adding the same view to the workspace upon dropping it; if you let go of the widget immediately after picking it up, the latter happened before the former. Specifically, the flow was: long-click a widget --> drop --> remove the view from the drag layer if it's not null (it is, so nothing happens) --> the view is finally bound/inflated and added to the drag layer --> add the view to the workspace --> already has a parent. There are actually 2 problems here: one is that the bind/inflate is asynchronous, and can therefore happen after dropping the widget view being inflated, and the other is that the view is added to the workspace even though the transition has barely started (we usually ignore drops if the transition is less than half complete). It turns out that this second problem was also due to a race condition, this time between dropping a widget or app onto the workspace and calling LauncherStateTransitionAnimation.dispatchOnLauncherTransitionStart(). If the drop happened before the dispatch, as in the case of the crash, then the drop was accepted because the transition progress was still 1.0 from the previous transition. I fixed the first problem by removing the drag layer widget view in Launcher where it is potentially used instead of Workspace. And I fixed the second problem by setting mTransitionProgress to 0 in Workspace.onLauncherTransitionPrepare(). I also added some debugging logs. Bug: 23896857 Change-Id: I66944e6d3f23b70dea15f7fb01af0763a1bfcbda
2015-10-14 15:23:04 -07:00
if (LOGD) {
Log.d(TAG, "About to bind/inflate widget");
}
mHandler.post(mBindWidgetRunnable);
return true;
}
public static Bundle getDefaultOptionsForWidget(Context context, PendingAddWidgetInfo info) {
Rect rect = new Rect();
AppWidgetResizeFrame.getWidgetSizeRanges(context, info.spanX, info.spanY, rect);
Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context,
info.componentName, null);
float density = context.getResources().getDisplayMetrics().density;
int xPaddingDips = (int) ((padding.left + padding.right) / density);
int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
Bundle options = new Bundle();
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
rect.left - xPaddingDips);
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
rect.top - yPaddingDips);
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
rect.right - xPaddingDips);
options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
rect.bottom - yPaddingDips);
return options;
}
}