summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/widget/WidgetHostViewLoader.java
blob: d65455053e013bf4493e91b34e075e912afbc163 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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;
    }
}