1package com.xtremelabs.robolectric.shadows;
2
3import android.app.Activity;
4import android.app.Application;
5import android.appwidget.AppWidgetManager;
6import android.appwidget.AppWidgetProvider;
7import android.content.ComponentName;
8import android.content.Context;
9import android.view.View;
10import android.widget.RemoteViews;
11import com.xtremelabs.robolectric.internal.AppSingletonizer;
12import com.xtremelabs.robolectric.internal.Implementation;
13import com.xtremelabs.robolectric.internal.Implements;
14import com.xtremelabs.robolectric.internal.RealObject;
15
16import java.util.ArrayList;
17import java.util.HashMap;
18import java.util.List;
19import java.util.Map;
20
21import static com.xtremelabs.robolectric.Robolectric.newInstanceOf;
22import static com.xtremelabs.robolectric.Robolectric.shadowOf;
23
24@SuppressWarnings({"UnusedDeclaration"})
25@Implements(AppWidgetManager.class)
26public class ShadowAppWidgetManager {
27    private static AppSingletonizer<AppWidgetManager> instances = new AppSingletonizer<AppWidgetManager>(AppWidgetManager.class) {
28        @Override
29        protected AppWidgetManager get(ShadowApplication shadowApplication) {
30            return shadowApplication.appWidgetManager;
31        }
32
33        @Override
34        protected void set(ShadowApplication shadowApplication, AppWidgetManager instance) {
35            shadowApplication.appWidgetManager = instance;
36        }
37
38        @Override
39        protected AppWidgetManager createInstance(Application applicationContext) {
40            AppWidgetManager appWidgetManager = super.createInstance(applicationContext);
41            shadowOf(appWidgetManager).context = applicationContext;
42            return appWidgetManager;
43        }
44    };
45
46    @RealObject
47    private AppWidgetManager realAppWidgetManager;
48
49    private Context context;
50    private Map<Integer, WidgetInfo> widgetInfos = new HashMap<Integer, WidgetInfo>();
51    private int nextWidgetId = 1;
52    private boolean alwaysRecreateViewsDuringUpdate = false;
53
54    private static void bind(AppWidgetManager appWidgetManager, Context context) {
55        // todo: implement
56    }
57
58
59    /**
60     * Finds or creates an {@code AppWidgetManager} for the given {@code context}
61     *
62     * @param context the {@code context} for which to produce an assoicated {@code AppWidgetManager}
63     * @return the {@code AppWidgetManager} associated with the given {@code context}
64     */
65    @Implementation
66    public static AppWidgetManager getInstance(Context context) {
67        return instances.getInstance(context);
68    }
69
70    @Implementation
71    public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
72        for (int appWidgetId : appWidgetIds) {
73            updateAppWidget(appWidgetId, views);
74        }
75    }
76
77    /**
78     * Simulates updating an {@code AppWidget} with a new set of views
79     *
80     * @param appWidgetId id of widget
81     * @param views       views to update
82     */
83    @Implementation
84    public void updateAppWidget(int appWidgetId, RemoteViews views) {
85        WidgetInfo widgetInfo = getWidgetInfo(appWidgetId);
86        int layoutId = views.getLayoutId();
87        if (widgetInfo.layoutId != layoutId || alwaysRecreateViewsDuringUpdate) {
88            widgetInfo.view = createWidgetView(layoutId);
89            widgetInfo.layoutId = layoutId;
90        }
91        widgetInfo.lastRemoteViews = views;
92        views.reapply(context, widgetInfo.view);
93    }
94
95    @Implementation
96    public int[] getAppWidgetIds(ComponentName provider) {
97        List<Integer> idList = new ArrayList<Integer>();
98        for (int id : widgetInfos.keySet()) {
99            WidgetInfo widgetInfo = widgetInfos.get(id);
100            String widgetClass = widgetInfo.appWidgetProvider.getClass().getName();
101            String widgetPackage = widgetInfo.appWidgetProvider.getClass().getPackage().getName();
102            if (provider.getClassName().equals(widgetClass) && provider.getPackageName().equals(widgetPackage)) {
103                idList.add(id);
104            }
105        }
106        int ids[] = new int[idList.size()];
107        for (int i = 0; i < idList.size(); i++) {
108            ids[i] = idList.get(i);
109        }
110        return ids;
111    }
112
113    /**
114     * Triggers a reapplication of the most recent set of actions against the widget, which is what happens when the
115     * phone is rotated. Does not attempt to simulate a change in screen geometry.
116     *
117     * @param appWidgetId the ID of the widget to be affected
118     */
119    public void reconstructWidgetViewAsIfPhoneWasRotated(int appWidgetId) {
120        WidgetInfo widgetInfo = getWidgetInfo(appWidgetId);
121        widgetInfo.view = createWidgetView(widgetInfo.layoutId);
122        widgetInfo.lastRemoteViews.reapply(context, widgetInfo.view);
123    }
124
125    /**
126     * Creates a widget by inflating its layout.
127     *
128     * @param appWidgetProviderClass the app widget provider class
129     * @param widgetLayoutId         id of the layout to inflate
130     * @return the ID of the new widget
131     */
132    public int createWidget(Class<? extends AppWidgetProvider> appWidgetProviderClass, int widgetLayoutId) {
133        return createWidgets(appWidgetProviderClass, widgetLayoutId, 1)[0];
134    }
135
136    /**
137     * Creates a bunch of widgets by inflating the same layout multiple times.
138     *
139     * @param appWidgetProviderClass the app widget provider class
140     * @param widgetLayoutId         id of the layout to inflate
141     * @param howManyToCreate        number of new widgets to create
142     * @return the IDs of the new widgets
143     */
144    public int[] createWidgets(Class<? extends AppWidgetProvider> appWidgetProviderClass, int widgetLayoutId, int howManyToCreate) {
145        AppWidgetProvider appWidgetProvider = newInstanceOf(appWidgetProviderClass);
146
147        int[] newWidgetIds = new int[howManyToCreate];
148        for (int i = 0; i < howManyToCreate; i++) {
149            View widgetView = createWidgetView(widgetLayoutId);
150
151            int myWidgetId = nextWidgetId++;
152            widgetInfos.put(myWidgetId, new WidgetInfo(widgetView, widgetLayoutId, appWidgetProvider));
153            newWidgetIds[i] = myWidgetId;
154        }
155
156        appWidgetProvider.onUpdate(context, realAppWidgetManager, newWidgetIds);
157        return newWidgetIds;
158    }
159
160    private void createWidgetProvider(Class<? extends AppWidgetProvider> appWidgetProviderClass, int... newWidgetIds) {
161        AppWidgetProvider appWidgetProvider = newInstanceOf(appWidgetProviderClass);
162        appWidgetProvider.onUpdate(context, realAppWidgetManager, newWidgetIds);
163    }
164
165    private View createWidgetView(int widgetLayoutId) {
166        return new Activity().getLayoutInflater().inflate(widgetLayoutId, null);
167    }
168
169    /**
170     * Non-Android accessor.
171     *
172     * @param widgetId id of the desired widget
173     * @return the widget associated with {@code widgetId}
174     */
175    public View getViewFor(int widgetId) {
176        return getWidgetInfo(widgetId).view;
177    }
178
179    /**
180     * Non-Android accessor.
181     *
182     * @param widgetId id of the widget whose provider is to be returned
183     * @return the {@code AppWidgetProvider} associated with {@code widgetId}
184     */
185    public AppWidgetProvider getAppWidgetProviderFor(int widgetId) {
186        return getWidgetInfo(widgetId).appWidgetProvider;
187    }
188
189    /**
190     * Non-Android mechanism that enables testing of widget behavior when all of the views are recreated on every
191     * update. This is useful for ensuring that your widget will behave correctly even if it is restarted by the OS
192     * between events.
193     *
194     * @param alwaysRecreate whether or not to always recreate the views
195     */
196    public void setAlwaysRecreateViewsDuringUpdate(boolean alwaysRecreate) {
197        alwaysRecreateViewsDuringUpdate = alwaysRecreate;
198    }
199
200    /**
201     * Non-Android accessor.
202     *
203     * @return the state of the{@code alwaysRecreateViewsDuringUpdate} flag
204     */
205    public boolean getAlwaysRecreateViewsDuringUpdate() {
206        return alwaysRecreateViewsDuringUpdate;
207    }
208
209    private WidgetInfo getWidgetInfo(int widgetId) {
210        return widgetInfos.get(widgetId);
211    }
212
213    private class WidgetInfo {
214        private View view;
215        private int layoutId;
216        private AppWidgetProvider appWidgetProvider;
217        private RemoteViews lastRemoteViews;
218
219        public WidgetInfo(View view, int layoutId, AppWidgetProvider appWidgetProvider) {
220            this.view = view;
221            this.layoutId = layoutId;
222            this.appWidgetProvider = appWidgetProvider;
223        }
224    }
225}
226