1 package com.android.launcher3.widget;
2 
3 import android.appwidget.AppWidgetHostView;
4 import android.appwidget.AppWidgetManager;
5 import android.content.Context;
6 import android.graphics.Rect;
7 import android.os.Bundle;
8 import android.os.Handler;
9 import android.util.Log;
10 import android.view.View;
11 
12 import com.android.launcher3.AppWidgetResizeFrame;
13 import com.android.launcher3.DropTarget;
14 import com.android.launcher3.Launcher;
15 import com.android.launcher3.LauncherAppWidgetProviderInfo;
16 import com.android.launcher3.compat.AppWidgetManagerCompat;
17 import com.android.launcher3.dragndrop.DragController;
18 import com.android.launcher3.dragndrop.DragLayer;
19 import com.android.launcher3.dragndrop.DragOptions;
20 import com.android.launcher3.util.Thunk;
21 
22 public class WidgetHostViewLoader implements DragController.DragListener {
23     private static final String TAG = "WidgetHostViewLoader";
24     private static final boolean LOGD = false;
25 
26     /* Runnables to handle inflation and binding. */
27     @Thunk Runnable mInflateWidgetRunnable = null;
28     private Runnable mBindWidgetRunnable = null;
29 
30     // TODO: technically, this class should not have to know the existence of the launcher.
31     @Thunk Launcher mLauncher;
32     @Thunk Handler mHandler;
33     @Thunk final View mView;
34     @Thunk final PendingAddWidgetInfo mInfo;
35 
36     // Widget id generated for binding a widget host view or -1 for invalid id. The id is
37     // not is use as long as it is stored here and can be deleted safely. Once its used, this value
38     // to be set back to -1.
39     @Thunk int mWidgetLoadingId = -1;
40 
WidgetHostViewLoader(Launcher launcher, View view)41     public WidgetHostViewLoader(Launcher launcher, View view) {
42         mLauncher = launcher;
43         mHandler = new Handler();
44         mView = view;
45         mInfo = (PendingAddWidgetInfo) view.getTag();
46     }
47 
48     @Override
onDragStart(DropTarget.DragObject dragObject, DragOptions options)49     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
50         preloadWidget();
51     }
52 
53     @Override
onDragEnd()54     public void onDragEnd() {
55         if (LOGD) {
56             Log.d(TAG, "Cleaning up in onDragEnd()...");
57         }
58 
59         // Cleanup up preloading state.
60         mLauncher.getDragController().removeDragListener(this);
61 
62         mHandler.removeCallbacks(mBindWidgetRunnable);
63         mHandler.removeCallbacks(mInflateWidgetRunnable);
64 
65         // Cleanup widget id
66         if (mWidgetLoadingId != -1) {
67             mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
68             mWidgetLoadingId = -1;
69         }
70 
71         // The widget was inflated and added to the DragLayer -- remove it.
72         if (mInfo.boundWidget != null) {
73             if (LOGD) {
74                 Log.d(TAG, "...removing widget from drag layer");
75             }
76             mLauncher.getDragLayer().removeView(mInfo.boundWidget);
77             mLauncher.getAppWidgetHost().deleteAppWidgetId(mInfo.boundWidget.getAppWidgetId());
78             mInfo.boundWidget = null;
79         }
80     }
81 
82     /**
83      * Start preloading the widget.
84      */
preloadWidget()85     private boolean preloadWidget() {
86         final LauncherAppWidgetProviderInfo pInfo = mInfo.info;
87 
88         if (pInfo.isCustomWidget()) {
89             return false;
90         }
91         final Bundle options = getDefaultOptionsForWidget(mLauncher, mInfo);
92 
93         // If there is a configuration activity, do not follow thru bound and inflate.
94         if (mInfo.getHandler().needsConfigure()) {
95             mInfo.bindOptions = options;
96             return false;
97         }
98 
99         mBindWidgetRunnable = new Runnable() {
100             @Override
101             public void run() {
102                 mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId();
103                 if (LOGD) {
104                     Log.d(TAG, "Binding widget, id: " + mWidgetLoadingId);
105                 }
106                 if(AppWidgetManagerCompat.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
107                         mWidgetLoadingId, pInfo, options)) {
108 
109                     // Widget id bound. Inflate the widget.
110                     mHandler.post(mInflateWidgetRunnable);
111                 }
112             }
113         };
114 
115         mInflateWidgetRunnable = new Runnable() {
116             @Override
117             public void run() {
118                 if (LOGD) {
119                     Log.d(TAG, "Inflating widget, id: " + mWidgetLoadingId);
120                 }
121                 if (mWidgetLoadingId == -1) {
122                     return;
123                 }
124                 AppWidgetHostView hostView = mLauncher.getAppWidgetHost().createView(
125                         (Context) mLauncher, mWidgetLoadingId, pInfo);
126                 mInfo.boundWidget = hostView;
127 
128                 // We used up the widget Id in binding the above view.
129                 mWidgetLoadingId = -1;
130 
131                 hostView.setVisibility(View.INVISIBLE);
132                 int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(mInfo);
133                 // We want the first widget layout to be the correct size. This will be important
134                 // for width size reporting to the AppWidgetManager.
135                 DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0],
136                         unScaledSize[1]);
137                 lp.x = lp.y = 0;
138                 lp.customPosition = true;
139                 hostView.setLayoutParams(lp);
140                 if (LOGD) {
141                     Log.d(TAG, "Adding host view to drag layer");
142                 }
143                 mLauncher.getDragLayer().addView(hostView);
144                 mView.setTag(mInfo);
145             }
146         };
147 
148         if (LOGD) {
149             Log.d(TAG, "About to bind/inflate widget");
150         }
151         mHandler.post(mBindWidgetRunnable);
152         return true;
153     }
154 
getDefaultOptionsForWidget(Context context, PendingAddWidgetInfo info)155     public static Bundle getDefaultOptionsForWidget(Context context, PendingAddWidgetInfo info) {
156         Rect rect = new Rect();
157         AppWidgetResizeFrame.getWidgetSizeRanges(context, info.spanX, info.spanY, rect);
158         Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context,
159                 info.componentName, null);
160 
161         float density = context.getResources().getDisplayMetrics().density;
162         int xPaddingDips = (int) ((padding.left + padding.right) / density);
163         int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
164 
165         Bundle options = new Bundle();
166         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
167                 rect.left - xPaddingDips);
168         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
169                 rect.top - yPaddingDips);
170         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
171                 rect.right - xPaddingDips);
172         options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
173                 rect.bottom - yPaddingDips);
174         return options;
175     }
176 }
177