1 package org.robolectric.shadows; 2 3 import android.app.Application; 4 import android.appwidget.AppWidgetManager; 5 import android.appwidget.AppWidgetProvider; 6 import android.appwidget.AppWidgetProviderInfo; 7 import android.content.ComponentName; 8 import android.content.Context; 9 import android.view.View; 10 import android.widget.RemoteViews; 11 import java.util.ArrayList; 12 import java.util.HashMap; 13 import java.util.List; 14 import java.util.Map; 15 import org.robolectric.RuntimeEnvironment; 16 import org.robolectric.Shadows; 17 import org.robolectric.annotation.HiddenApi; 18 import org.robolectric.annotation.Implementation; 19 import org.robolectric.annotation.Implements; 20 import org.robolectric.annotation.RealObject; 21 import org.robolectric.shadows.util.AppSingletonizer; 22 import org.robolectric.util.ReflectionHelpers; 23 24 @SuppressWarnings({"UnusedDeclaration"}) 25 @Implements(AppWidgetManager.class) 26 public class ShadowAppWidgetManager { 27 private static final 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 Shadows.shadowOf(appWidgetManager).context = applicationContext; 42 return appWidgetManager; 43 } 44 }; 45 46 @RealObject 47 private AppWidgetManager realAppWidgetManager; 48 49 private Context context; 50 private final Map<Integer, WidgetInfo> widgetInfos = new HashMap<>(); 51 private int nextWidgetId = 1; 52 private boolean alwaysRecreateViewsDuringUpdate = false; 53 private boolean allowedToBindWidgets; 54 private boolean validWidgetProviderComponentName = true; 55 private final ArrayList<AppWidgetProviderInfo> installedProviders = new ArrayList<>(); 56 57 private static void bind(AppWidgetManager appWidgetManager, Context context) { 58 // todo: implement 59 } 60 61 62 /** 63 * Finds or creates an {@code AppWidgetManager} for the given {@code context} 64 * 65 * @param context the {@code context} for which to produce an assoicated {@code AppWidgetManager} 66 * @return the {@code AppWidgetManager} associated with the given {@code context} 67 */ 68 @Implementation 69 public static AppWidgetManager getInstance(Context context) { 70 return instances.getInstance(context); 71 } 72 73 @Implementation 74 public void updateAppWidget(int[] appWidgetIds, RemoteViews views) { 75 for (int appWidgetId : appWidgetIds) { 76 updateAppWidget(appWidgetId, views); 77 } 78 } 79 80 /** 81 * Simulates updating an {@code AppWidget} with a new set of views 82 * 83 * @param appWidgetId id of widget 84 * @param views views to update 85 */ 86 @Implementation 87 public void updateAppWidget(int appWidgetId, RemoteViews views) { 88 WidgetInfo widgetInfo = widgetInfos.get(appWidgetId); 89 int layoutId = views.getLayoutId(); 90 if (widgetInfo.layoutId != layoutId || alwaysRecreateViewsDuringUpdate) { 91 widgetInfo.view = createWidgetView(layoutId); 92 widgetInfo.layoutId = layoutId; 93 } 94 widgetInfo.lastRemoteViews = views; 95 views.reapply(context, widgetInfo.view); 96 } 97 98 @Implementation 99 public int[] getAppWidgetIds(ComponentName provider) { 100 List<Integer> idList = new ArrayList<>(); 101 for (int id : widgetInfos.keySet()) { 102 WidgetInfo widgetInfo = widgetInfos.get(id); 103 if (provider.equals(widgetInfo.providerComponent)) { 104 idList.add(id); 105 } 106 } 107 int ids[] = new int[idList.size()]; 108 for (int i = 0; i < idList.size(); i++) { 109 ids[i] = idList.get(i); 110 } 111 return ids; 112 } 113 114 @Implementation 115 public List<AppWidgetProviderInfo> getInstalledProviders() { 116 return new ArrayList<>(installedProviders); 117 } 118 119 public void addInstalledProvider(AppWidgetProviderInfo appWidgetProviderInfo) { 120 installedProviders.add(appWidgetProviderInfo); 121 } 122 123 public void addBoundWidget(int appWidgetId, AppWidgetProviderInfo providerInfo) { 124 addInstalledProvider(providerInfo); 125 bindAppWidgetId(appWidgetId, providerInfo.provider); 126 widgetInfos.get(appWidgetId).info = providerInfo; 127 } 128 129 @Deprecated 130 public void putWidgetInfo(int appWidgetId, AppWidgetProviderInfo expectedWidgetInfo) { 131 addBoundWidget(appWidgetId, expectedWidgetInfo); 132 } 133 134 @Implementation 135 public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { 136 WidgetInfo widgetInfo = widgetInfos.get(appWidgetId); 137 if (widgetInfo == null) return null; 138 return widgetInfo.info; 139 } 140 141 @HiddenApi @Implementation 142 public void bindAppWidgetId(int appWidgetId, ComponentName provider) { 143 WidgetInfo widgetInfo = new WidgetInfo(provider); 144 widgetInfos.put(appWidgetId, widgetInfo); 145 for (AppWidgetProviderInfo appWidgetProviderInfo : installedProviders) { 146 if (provider != null && provider.equals(appWidgetProviderInfo.provider)) { 147 widgetInfo.info = appWidgetProviderInfo; 148 } 149 } 150 } 151 152 @Implementation 153 public boolean bindAppWidgetIdIfAllowed(int appWidgetId, ComponentName provider) { 154 if (validWidgetProviderComponentName) { 155 bindAppWidgetId(appWidgetId, provider); 156 return allowedToBindWidgets; 157 } else { 158 throw new IllegalArgumentException("not an appwidget provider"); 159 } 160 } 161 162 /** 163 * Triggers a reapplication of the most recent set of actions against the widget, which is what happens when the 164 * phone is rotated. Does not attempt to simulate a change in screen geometry. 165 * 166 * @param appWidgetId the ID of the widget to be affected 167 */ 168 public void reconstructWidgetViewAsIfPhoneWasRotated(int appWidgetId) { 169 WidgetInfo widgetInfo = widgetInfos.get(appWidgetId); 170 widgetInfo.view = createWidgetView(widgetInfo.layoutId); 171 widgetInfo.lastRemoteViews.reapply(context, widgetInfo.view); 172 } 173 174 /** 175 * Creates a widget by inflating its layout. 176 * 177 * @param appWidgetProviderClass the app widget provider class 178 * @param widgetLayoutId id of the layout to inflate 179 * @return the ID of the new widget 180 */ 181 public int createWidget(Class<? extends AppWidgetProvider> appWidgetProviderClass, int widgetLayoutId) { 182 return createWidgets(appWidgetProviderClass, widgetLayoutId, 1)[0]; 183 } 184 185 /** 186 * Creates a bunch of widgets by inflating the same layout multiple times. 187 * 188 * @param appWidgetProviderClass the app widget provider class 189 * @param widgetLayoutId id of the layout to inflate 190 * @param howManyToCreate number of new widgets to create 191 * @return the IDs of the new widgets 192 */ 193 public int[] createWidgets(Class<? extends AppWidgetProvider> appWidgetProviderClass, int widgetLayoutId, int howManyToCreate) { 194 AppWidgetProvider appWidgetProvider = ReflectionHelpers.callConstructor(appWidgetProviderClass); 195 196 int[] newWidgetIds = new int[howManyToCreate]; 197 for (int i = 0; i < howManyToCreate; i++) { 198 View widgetView = createWidgetView(widgetLayoutId); 199 200 int myWidgetId = nextWidgetId++; 201 widgetInfos.put(myWidgetId, new WidgetInfo(widgetView, widgetLayoutId, appWidgetProvider)); 202 newWidgetIds[i] = myWidgetId; 203 } 204 205 appWidgetProvider.onUpdate(context, realAppWidgetManager, newWidgetIds); 206 return newWidgetIds; 207 } 208 209 private void createWidgetProvider(Class<? extends AppWidgetProvider> appWidgetProviderClass, int... newWidgetIds) { 210 AppWidgetProvider appWidgetProvider = ReflectionHelpers.callConstructor(appWidgetProviderClass); 211 appWidgetProvider.onUpdate(context, realAppWidgetManager, newWidgetIds); 212 } 213 214 private View createWidgetView(int widgetLayoutId) { 215 return new RoboLayoutInflater(RuntimeEnvironment.application).inflate(widgetLayoutId, null); 216 } 217 218 /** 219 * @param widgetId id of the desired widget 220 * @return the widget associated with {@code widgetId} 221 */ 222 public View getViewFor(int widgetId) { 223 return widgetInfos.get(widgetId).view; 224 } 225 226 /** 227 * @param widgetId id of the widget whose provider is to be returned 228 * @return the {@code AppWidgetProvider} associated with {@code widgetId} 229 */ 230 public AppWidgetProvider getAppWidgetProviderFor(int widgetId) { 231 return widgetInfos.get(widgetId).appWidgetProvider; 232 } 233 234 /** 235 * Enables testing of widget behavior when all of the views are recreated on every 236 * update. This is useful for ensuring that your widget will behave correctly even 237 * if it is restarted by the OS between events. 238 * 239 * @param alwaysRecreate whether or not to always recreate the views 240 */ 241 public void setAlwaysRecreateViewsDuringUpdate(boolean alwaysRecreate) { 242 alwaysRecreateViewsDuringUpdate = alwaysRecreate; 243 } 244 245 /** 246 * @return the state of the{@code alwaysRecreateViewsDuringUpdate} flag 247 */ 248 public boolean getAlwaysRecreateViewsDuringUpdate() { 249 return alwaysRecreateViewsDuringUpdate; 250 } 251 252 public void setAllowedToBindAppWidgets(boolean allowed) { 253 allowedToBindWidgets = allowed; 254 } 255 256 public void setValidWidgetProviderComponentName(boolean validWidgetProviderComponentName) { 257 this.validWidgetProviderComponentName = validWidgetProviderComponentName; 258 } 259 260 private static class WidgetInfo { 261 View view; 262 int layoutId; 263 final AppWidgetProvider appWidgetProvider; 264 RemoteViews lastRemoteViews; 265 final ComponentName providerComponent; 266 AppWidgetProviderInfo info; 267 268 public WidgetInfo(View view, int layoutId, AppWidgetProvider appWidgetProvider) { 269 this.view = view; 270 this.layoutId = layoutId; 271 this.appWidgetProvider = appWidgetProvider; 272 String packageName = appWidgetProvider.getClass().getPackage().getName(); 273 String className = appWidgetProvider.getClass().getName(); 274 providerComponent = new ComponentName(packageName, className); 275 } 276 277 public WidgetInfo(ComponentName providerComponent) { 278 this.providerComponent = providerComponent; 279 this.appWidgetProvider = null; 280 } 281 } 282 } 283