blob: d65455053e013bf4493e91b34e075e912afbc163 [file] [log] [blame]
Hyunyoung Songb99ff3e2015-04-23 15:17:50 -07001package com.android.launcher3.widget;
2
3import android.appwidget.AppWidgetHostView;
4import android.appwidget.AppWidgetManager;
5import android.content.Context;
6import android.graphics.Rect;
7import android.os.Build;
8import android.os.Bundle;
9import android.os.Handler;
10import android.util.Log;
11import android.view.View;
12
13import com.android.launcher3.AppWidgetResizeFrame;
14import com.android.launcher3.DragLayer;
15import com.android.launcher3.Launcher;
16import com.android.launcher3.LauncherAppWidgetProviderInfo;
17import com.android.launcher3.compat.AppWidgetManagerCompat;
18
19public class WidgetHostViewLoader {
20
21 private static final boolean DEBUG = false;
22 private static final String TAG = "WidgetHostViewLoader";
23
24 /* constants used for widget loading state. */
25 private static final int WIDGET_NO_CLEANUP_REQUIRED = -1;
26 private static final int WIDGET_PRELOAD_PENDING = 0;
27 private static final int WIDGET_BOUND = 1;
28 private static final int WIDGET_INFLATED = 2;
29
30 int mState = WIDGET_NO_CLEANUP_REQUIRED;
31
32 /* Runnables to handle inflation and binding. */
33 private Runnable mInflateWidgetRunnable = null;
34 private Runnable mBindWidgetRunnable = null;
35
36 /* Id of the widget being handled. */
37 int mWidgetLoadingId = -1;
38 PendingAddWidgetInfo mCreateWidgetInfo = null;
39
40 // TODO: technically, this class should not have to know the existence of the launcher.
41 private Launcher mLauncher;
42 private Handler mHandler;
43
44 public WidgetHostViewLoader(Launcher launcher) {
45 mLauncher = launcher;
46 mHandler = new Handler();
47 }
48
49 /**
50 * Start loading the widget.
51 */
52 public void load(View v) {
53 if (mCreateWidgetInfo != null) {
54 // Just in case the cleanup process wasn't properly executed.
55 finish(false);
56 }
57 boolean status = false;
58 if (v.getTag() instanceof PendingAddWidgetInfo) {
59 mCreateWidgetInfo = new PendingAddWidgetInfo((PendingAddWidgetInfo) v.getTag());
60 status = preloadWidget(v, mCreateWidgetInfo);
61 }
62 if (DEBUG) {
63 Log.d(TAG, String.format("load started on [state=%d, status=%s]", mState, status));
64 }
65 }
66
67
68 /**
69 * Clean up according to what the last known state was.
70 * @param widgetIdUsed {@code true} if the widgetId was consumed which can happen only
71 * when view is fully inflated
72 */
73 public void finish(boolean widgetIdUsed) {
74 if (DEBUG) {
75 Log.d(TAG, String.format("cancel on state [%d] widgetId=[%d]",
76 mState, mWidgetLoadingId));
77 }
78
79 // If the widget was not added, we may need to do further cleanup.
80 PendingAddWidgetInfo info = mCreateWidgetInfo;
81 mCreateWidgetInfo = null;
82
83 if (mState == WIDGET_PRELOAD_PENDING) {
84 // We never did any preloading, so just remove pending callbacks to do so
85 mHandler.removeCallbacks(mBindWidgetRunnable);
86 mHandler.removeCallbacks(mInflateWidgetRunnable);
87 } else if (mState == WIDGET_BOUND) {
88 // Delete the widget id which was allocated
89 if (mWidgetLoadingId != -1 && !info.isCustomWidget()) {
90 mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
91 }
92
93 // We never got around to inflating the widget, so remove the callback to do so.
94 mHandler.removeCallbacks(mInflateWidgetRunnable);
95 } else if (mState == WIDGET_INFLATED && !widgetIdUsed) {
96 // Delete the widget id which was allocated
97 if (mWidgetLoadingId != -1 && !info.isCustomWidget()) {
98 mLauncher.getAppWidgetHost().deleteAppWidgetId(mWidgetLoadingId);
99 }
100
101 // The widget was inflated and added to the DragLayer -- remove it.
102 AppWidgetHostView widget = info.boundWidget;
103 mLauncher.getDragLayer().removeView(widget);
104 }
105 setState(WIDGET_NO_CLEANUP_REQUIRED);
106 mWidgetLoadingId = -1;
107 }
108
109 private boolean preloadWidget(final View v, final PendingAddWidgetInfo info) {
110 final LauncherAppWidgetProviderInfo pInfo = info.info;
111
112 final Bundle options = pInfo.isCustomWidget ? null :
113 getDefaultOptionsForWidget(mLauncher, info);
114
115 // If there is a configuration activity, do not follow thru bound and inflate.
116 if (pInfo.configure != null) {
117 info.bindOptions = options;
118 return false;
119 }
120 setState(WIDGET_PRELOAD_PENDING);
121 mBindWidgetRunnable = new Runnable() {
122 @Override
123 public void run() {
124 if (pInfo.isCustomWidget) {
125 setState(WIDGET_BOUND);
126 return;
127 }
128
129 mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId();
130 if(AppWidgetManagerCompat.getInstance(mLauncher).bindAppWidgetIdIfAllowed(
131 mWidgetLoadingId, pInfo, options)) {
132 setState(WIDGET_BOUND);
133 }
134 }
135 };
136 mHandler.post(mBindWidgetRunnable);
137
138 mInflateWidgetRunnable = new Runnable() {
139 @Override
140 public void run() {
141 if (mState != WIDGET_BOUND) {
142 return;
143 }
144 AppWidgetHostView hostView = mLauncher.getAppWidgetHost().createView(
145 (Context) mLauncher, mWidgetLoadingId, pInfo);
146 info.boundWidget = hostView;
147 setState(WIDGET_INFLATED);
148 hostView.setVisibility(View.INVISIBLE);
149 int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(info, false);
150
151 // We want the first widget layout to be the correct size. This will be important
152 // for width size reporting to the AppWidgetManager.
153 DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0],
154 unScaledSize[1]);
155 lp.x = lp.y = 0;
156 lp.customPosition = true;
157 hostView.setLayoutParams(lp);
158 mLauncher.getDragLayer().addView(hostView);
159 v.setTag(info);
160 }
161 };
162 mHandler.post(mInflateWidgetRunnable);
163 return true;
164 }
165
166 public static Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) {
167 Bundle options = null;
168 Rect rect = new Rect();
169 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
170 AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, rect);
171 Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher,
172 info.componentName, null);
173
174 float density = launcher.getResources().getDisplayMetrics().density;
175 int xPaddingDips = (int) ((padding.left + padding.right) / density);
176 int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
177
178 options = new Bundle();
179 options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
180 rect.left - xPaddingDips);
181 options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
182 rect.top - yPaddingDips);
183 options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
184 rect.right - xPaddingDips);
185 options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
186 rect.bottom - yPaddingDips);
187 }
188 return options;
189 }
190
191 private void setState(int state) {
192 if (DEBUG) {
193 Log.d(TAG, String.format(" state [%d -> %d]", mState, state));
194 }
195 mState = state;
196 }
197}