| package com.android.launcher3.widget; |
| |
| import android.appwidget.AppWidgetHostView; |
| import android.appwidget.AppWidgetManager; |
| import android.content.Context; |
| import android.graphics.Rect; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.util.Log; |
| import android.view.View; |
| |
| import com.android.launcher3.AppWidgetResizeFrame; |
| import com.android.launcher3.DragLayer; |
| import com.android.launcher3.Launcher; |
| import com.android.launcher3.LauncherAppWidgetProviderInfo; |
| import com.android.launcher3.compat.AppWidgetManagerCompat; |
| |
| public class WidgetHostViewLoader { |
| |
| private static final boolean DEBUG = false; |
| private static final String TAG = "WidgetHostViewLoader"; |
| |
| /* constants used for widget loading state. */ |
| private static final int WIDGET_NO_CLEANUP_REQUIRED = -1; |
| private static final int WIDGET_PRELOAD_PENDING = 0; |
| private static final int WIDGET_BOUND = 1; |
| private static final int WIDGET_INFLATED = 2; |
| |
| int mState = WIDGET_NO_CLEANUP_REQUIRED; |
| |
| /* Runnables to handle inflation and binding. */ |
| private Runnable mInflateWidgetRunnable = null; |
| private Runnable mBindWidgetRunnable = null; |
| |
| /* Id of the widget being handled. */ |
| int mWidgetLoadingId = -1; |
| PendingAddWidgetInfo mCreateWidgetInfo = null; |
| |
| // TODO: technically, this class should not have to know the existence of the launcher. |
| private Launcher mLauncher; |
| private Handler mHandler; |
| |
| public WidgetHostViewLoader(Launcher launcher) { |
| mLauncher = launcher; |
| mHandler = new Handler(); |
| } |
| |
| /** |
| * Start loading the widget. |
| */ |
| public void load(View v) { |
| if (mCreateWidgetInfo != null) { |
| // Just in case the cleanup process wasn't properly executed. |
| finish(false); |
| } |
| boolean status = false; |
| if (v.getTag() instanceof PendingAddWidgetInfo) { |
| mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag()); |
| status = preloadWidget(v, mCreateWidgetInfo); |
| } |
| if (DEBUG) { |
| Log.d(TAG, String.format("load started on [state=%d, status=%s]", mState, status)); |
| } |
| } |
| |
| |
| /** |
| * Clean up according to what the last known state was. |
| * @param widgetIdUsed {@code true} if the widgetId was consumed which can happen only |
| * when view is fully inflated |
| */ |
| public void finish(boolean widgetIdUsed) { |
| if (DEBUG) { |
| Log.d(TAG, String.format("cancel on state [%d] widgetId=[%d]", |
| mState, mWidgetLoadingId)); |
| } |
| |
| // If the widget was not added, we may need to do further cleanup. |
| PendingAddWidgetInfo info = mCreateWidgetInfo; |
| mCreateWidgetInfo = null; |
| |
| if (mState == WIDGET_PRELOAD_PENDING) { |
| // We never did any preloading, so just remove pending callbacks to do so |
| mHandler.removeCallbacks(mBindWidgetRunnable); |
| mHandler.removeCallbacks(mInflateWidgetRunnable); |
| } else if (mState == WIDGET_BOUND) { |
| // Delete the widget id which was allocated |
| if (mWidgetLoadingId != -1 && !info.isCustomWidget()) { |
| mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId); |
| } |
| |
| // We never got around to inflating the widget, so remove the callback to do so. |
| mHandler.removeCallbacks(mInflateWidgetRunnable); |
| } else if (mState == WIDGET_INFLATED && !widgetIdUsed) { |
| // Delete the widget id which was allocated |
| if (mWidgetLoadingId != -1 && !info.isCustomWidget()) { |
| mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId); |
| } |
| |
| // The widget was inflated and added to the DragLayer -- remove it. |
| AppWidgetHostView widget = info.boundWidget; |
| mLauncher.getDragLayer().removeView(widget); |
| } |
| setState(WIDGET_NO_CLEANUP_REQUIRED); |
| mWidgetLoadingId = -1; |
| } |
| |
| private boolean preloadWidget(final View v, final PendingAddWidgetInfo info) { |
| final LauncherAppWidgetProviderInfo pInfo = info.info; |
| |
| final Bundle options = pInfo.isCustomWidget ? null : |
| getDefaultOptionsForWidget(mLauncher, info); |
| |
| // If there is a configuration activity, do not follow thru bound and inflate. |
| if (pInfo.configure != null) { |
| info.bindOptions = options; |
| return false; |
| } |
| setState(WIDGET_PRELOAD_PENDING); |
| mBindWidgetRunnable = new Runnable() { |
| @Override |
| public void run() { |
| if (pInfo.isCustomWidget) { |
| setState(WIDGET_BOUND); |
| return; |
| } |
| |
| mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId(); |
| if(AppWidgetManagerCompat.getInstance(mLauncher).bindAppWidgetIdIfAllowed( |
| mWidgetLoadingId, pInfo, options)) { |
| setState(WIDGET_BOUND); |
| } |
| } |
| }; |
| mHandler.post(mBindWidgetRunnable); |
| |
| mInflateWidgetRunnable = new Runnable() { |
| @Override |
| public void run() { |
| if (mState != WIDGET_BOUND) { |
| return; |
| } |
| AppWidgetHostView hostView = mLauncher.getAppWidgetHost().createView( |
| (Context) mLauncher, mWidgetLoadingId, pInfo); |
| info.boundWidget = hostView; |
| setState(WIDGET_INFLATED); |
| hostView.setVisibility(View.INVISIBLE); |
| int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info, false); |
| |
| // 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); |
| mLauncher.getDragLayer().addView(hostView); |
| v.setTag(info); |
| } |
| }; |
| mHandler.post(mInflateWidgetRunnable); |
| return true; |
| } |
| |
| public static Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) { |
| Bundle options = null; |
| Rect rect = new Rect(); |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { |
| AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, rect); |
| Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher, |
| info.componentName, null); |
| |
| float density = launcher.getResources().getDisplayMetrics().density; |
| int xPaddingDips = (int) ((padding.left + padding.right) / density); |
| int yPaddingDips = (int) ((padding.top + padding.bottom) / density); |
| |
| 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; |
| } |
| |
| private void setState(int state) { |
| if (DEBUG) { |
| Log.d(TAG, String.format(" state [%d -> %d]", mState, state)); |
| } |
| mState = state; |
| } |
| } |