Home | History | Annotate | Download | only in shadows
      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