• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.content.pm.ShortcutManager.FLAG_MATCH_CACHED;
4 import static android.content.pm.ShortcutManager.FLAG_MATCH_DYNAMIC;
5 import static android.content.pm.ShortcutManager.FLAG_MATCH_MANIFEST;
6 import static android.content.pm.ShortcutManager.FLAG_MATCH_PINNED;
7 import static android.os.Build.VERSION_CODES.R;
8 import static java.util.stream.Collectors.toCollection;
9 
10 import android.content.Intent;
11 import android.content.IntentSender;
12 import android.content.IntentSender.SendIntentException;
13 import android.content.pm.ShortcutInfo;
14 import android.content.pm.ShortcutManager;
15 import android.os.Build;
16 import android.os.Build.VERSION_CODES;
17 import com.google.common.collect.ImmutableList;
18 import com.google.common.collect.Lists;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import org.robolectric.RuntimeEnvironment;
27 import org.robolectric.annotation.Implementation;
28 import org.robolectric.annotation.Implements;
29 
30 /** */
31 @Implements(value = ShortcutManager.class, minSdk = Build.VERSION_CODES.N_MR1)
32 public class ShadowShortcutManager {
33 
34   private static final int MAX_ICON_DIMENSION = 128;
35 
36   private final Map<String, ShortcutInfo> dynamicShortcuts = new HashMap<>();
37   private final Map<String, ShortcutInfo> activePinnedShortcuts = new HashMap<>();
38   private final Map<String, ShortcutInfo> disabledPinnedShortcuts = new HashMap<>();
39 
40   private List<ShortcutInfo> manifestShortcuts = ImmutableList.of();
41 
42   private boolean isRequestPinShortcutSupported = true;
43   private int maxShortcutCountPerActivity = 16;
44   private int maxIconHeight = MAX_ICON_DIMENSION;
45   private int maxIconWidth = MAX_ICON_DIMENSION;
46 
47   @Implementation
addDynamicShortcuts(List<ShortcutInfo> shortcutInfoList)48   protected boolean addDynamicShortcuts(List<ShortcutInfo> shortcutInfoList) {
49     for (ShortcutInfo shortcutInfo : shortcutInfoList) {
50       shortcutInfo.addFlags(ShortcutInfo.FLAG_DYNAMIC);
51       if (activePinnedShortcuts.containsKey(shortcutInfo.getId())) {
52         ShortcutInfo previousShortcut = activePinnedShortcuts.get(shortcutInfo.getId());
53         if (!previousShortcut.isImmutable()) {
54           activePinnedShortcuts.put(shortcutInfo.getId(), shortcutInfo);
55         }
56       } else if (disabledPinnedShortcuts.containsKey(shortcutInfo.getId())) {
57         ShortcutInfo previousShortcut = disabledPinnedShortcuts.get(shortcutInfo.getId());
58         if (!previousShortcut.isImmutable()) {
59           disabledPinnedShortcuts.put(shortcutInfo.getId(), shortcutInfo);
60         }
61       } else if (dynamicShortcuts.containsKey(shortcutInfo.getId())) {
62         ShortcutInfo previousShortcut = dynamicShortcuts.get(shortcutInfo.getId());
63         if (!previousShortcut.isImmutable()) {
64           dynamicShortcuts.put(shortcutInfo.getId(), shortcutInfo);
65         }
66       } else {
67         dynamicShortcuts.put(shortcutInfo.getId(), shortcutInfo);
68       }
69     }
70     return true;
71   }
72 
73   @Implementation(minSdk = Build.VERSION_CODES.O)
createShortcutResultIntent(ShortcutInfo shortcut)74   protected Intent createShortcutResultIntent(ShortcutInfo shortcut) {
75     if (disabledPinnedShortcuts.containsKey(shortcut.getId())) {
76       throw new IllegalArgumentException();
77     }
78     return new Intent();
79   }
80 
81   @Implementation
disableShortcuts(List<String> shortcutIds)82   protected void disableShortcuts(List<String> shortcutIds) {
83     disableShortcuts(shortcutIds, "Shortcut is disabled.");
84   }
85 
86   @Implementation
disableShortcuts(List<String> shortcutIds, CharSequence unused)87   protected void disableShortcuts(List<String> shortcutIds, CharSequence unused) {
88     for (String shortcutId : shortcutIds) {
89       ShortcutInfo shortcut = activePinnedShortcuts.remove(shortcutId);
90       if (shortcut != null) {
91         disabledPinnedShortcuts.put(shortcutId, shortcut);
92       }
93     }
94   }
95 
96   @Implementation
enableShortcuts(List<String> shortcutIds)97   protected void enableShortcuts(List<String> shortcutIds) {
98     for (String shortcutId : shortcutIds) {
99       ShortcutInfo shortcut = disabledPinnedShortcuts.remove(shortcutId);
100       if (shortcut != null) {
101         activePinnedShortcuts.put(shortcutId, shortcut);
102       }
103     }
104   }
105 
106   @Implementation
getDynamicShortcuts()107   protected List<ShortcutInfo> getDynamicShortcuts() {
108     return ImmutableList.copyOf(dynamicShortcuts.values());
109   }
110 
111   @Implementation
getIconMaxHeight()112   protected int getIconMaxHeight() {
113     return maxIconHeight;
114   }
115 
116   @Implementation
getIconMaxWidth()117   protected int getIconMaxWidth() {
118     return maxIconWidth;
119   }
120 
121   /** Sets the value returned by {@link #getIconMaxHeight()}. */
setIconMaxHeight(int height)122   public void setIconMaxHeight(int height) {
123     maxIconHeight = height;
124   }
125 
126   /** Sets the value returned by {@link #getIconMaxWidth()}. */
setIconMaxWidth(int width)127   public void setIconMaxWidth(int width) {
128     maxIconWidth = width;
129   }
130 
131   @Implementation
getManifestShortcuts()132   protected List<ShortcutInfo> getManifestShortcuts() {
133     return manifestShortcuts;
134   }
135 
136   /** Sets the value returned by {@link #getManifestShortcuts()}. */
setManifestShortcuts(List<ShortcutInfo> manifestShortcuts)137   public void setManifestShortcuts(List<ShortcutInfo> manifestShortcuts) {
138     for (ShortcutInfo shortcutInfo : manifestShortcuts) {
139       shortcutInfo.addFlags(ShortcutInfo.FLAG_MANIFEST);
140     }
141     this.manifestShortcuts = manifestShortcuts;
142   }
143 
144   @Implementation
getMaxShortcutCountPerActivity()145   protected int getMaxShortcutCountPerActivity() {
146     return maxShortcutCountPerActivity;
147   }
148 
149   /** Sets the value returned by {@link #getMaxShortcutCountPerActivity()} . */
setMaxShortcutCountPerActivity(int value)150   public void setMaxShortcutCountPerActivity(int value) {
151     maxShortcutCountPerActivity = value;
152   }
153 
154   @Implementation
getPinnedShortcuts()155   protected List<ShortcutInfo> getPinnedShortcuts() {
156     ImmutableList.Builder<ShortcutInfo> pinnedShortcuts = ImmutableList.builder();
157     pinnedShortcuts.addAll(activePinnedShortcuts.values());
158     pinnedShortcuts.addAll(disabledPinnedShortcuts.values());
159     return pinnedShortcuts.build();
160   }
161 
162   @Implementation
isRateLimitingActive()163   protected boolean isRateLimitingActive() {
164     return false;
165   }
166 
167   @Implementation(minSdk = Build.VERSION_CODES.O)
isRequestPinShortcutSupported()168   protected boolean isRequestPinShortcutSupported() {
169     return isRequestPinShortcutSupported;
170   }
171 
setIsRequestPinShortcutSupported(boolean isRequestPinShortcutSupported)172   public void setIsRequestPinShortcutSupported(boolean isRequestPinShortcutSupported) {
173     this.isRequestPinShortcutSupported = isRequestPinShortcutSupported;
174   }
175 
176   @Implementation
removeAllDynamicShortcuts()177   protected void removeAllDynamicShortcuts() {
178     dynamicShortcuts.clear();
179   }
180 
181   @Implementation
removeDynamicShortcuts(List<String> shortcutIds)182   protected void removeDynamicShortcuts(List<String> shortcutIds) {
183     for (String shortcutId : shortcutIds) {
184       dynamicShortcuts.remove(shortcutId);
185     }
186   }
187 
188   @Implementation
reportShortcutUsed(String shortcutId)189   protected void reportShortcutUsed(String shortcutId) {}
190 
191   @Implementation(minSdk = Build.VERSION_CODES.O)
requestPinShortcut(ShortcutInfo shortcut, IntentSender resultIntent)192   protected boolean requestPinShortcut(ShortcutInfo shortcut, IntentSender resultIntent) {
193     shortcut.addFlags(ShortcutInfo.FLAG_PINNED);
194     if (disabledPinnedShortcuts.containsKey(shortcut.getId())) {
195       throw new IllegalArgumentException(
196           "Shortcut with ID [" + shortcut.getId() + "] already exists and is disabled.");
197     }
198     if (dynamicShortcuts.containsKey(shortcut.getId())) {
199       activePinnedShortcuts.put(shortcut.getId(), dynamicShortcuts.remove(shortcut.getId()));
200     } else {
201       activePinnedShortcuts.put(shortcut.getId(), shortcut);
202     }
203     if (resultIntent != null) {
204       try {
205         resultIntent.sendIntent(RuntimeEnvironment.getApplication(), 0, null, null, null);
206       } catch (SendIntentException e) {
207         throw new IllegalStateException();
208       }
209     }
210     return true;
211   }
212 
213   @Implementation
setDynamicShortcuts(List<ShortcutInfo> shortcutInfoList)214   protected boolean setDynamicShortcuts(List<ShortcutInfo> shortcutInfoList) {
215     dynamicShortcuts.clear();
216     return addDynamicShortcuts(shortcutInfoList);
217   }
218 
219   @Implementation
updateShortcuts(List<ShortcutInfo> shortcutInfoList)220   protected boolean updateShortcuts(List<ShortcutInfo> shortcutInfoList) {
221     List<ShortcutInfo> existingShortcutsToUpdate = new ArrayList<>();
222     for (ShortcutInfo shortcutInfo : shortcutInfoList) {
223       if (dynamicShortcuts.containsKey(shortcutInfo.getId())
224           || activePinnedShortcuts.containsKey(shortcutInfo.getId())
225           || disabledPinnedShortcuts.containsKey(shortcutInfo.getId())) {
226         existingShortcutsToUpdate.add(shortcutInfo);
227       }
228     }
229     return addDynamicShortcuts(existingShortcutsToUpdate);
230   }
231 
232   /**
233    * No-op on Robolectric. The real implementation calls out to a service, which will NPE on
234    * Robolectric.
235    */
updateShortcutVisibility( final String packageName, final byte[] certificate, final boolean visible)236   protected void updateShortcutVisibility(
237       final String packageName, final byte[] certificate, final boolean visible) {}
238 
239   /**
240    * In Robolectric, ShadowShortcutManager doesn't perform any caching so long lived shortcuts are
241    * returned on place of shortcuts cached when shown in notifications.
242    */
243   @Implementation(minSdk = R)
getShortcuts(int matchFlags)244   protected List<ShortcutInfo> getShortcuts(int matchFlags) {
245     if (matchFlags == 0) {
246       return Lists.newArrayList();
247     }
248 
249     Set<ShortcutInfo> shortcutInfoSet = new HashSet<>();
250     shortcutInfoSet.addAll(getManifestShortcuts());
251     shortcutInfoSet.addAll(getDynamicShortcuts());
252     shortcutInfoSet.addAll(getPinnedShortcuts());
253 
254     return shortcutInfoSet.stream()
255         .filter(
256             shortcutInfo ->
257                 ((matchFlags & FLAG_MATCH_MANIFEST) != 0 && shortcutInfo.isDeclaredInManifest())
258                     || ((matchFlags & FLAG_MATCH_DYNAMIC) != 0 && shortcutInfo.isDynamic())
259                     || ((matchFlags & FLAG_MATCH_PINNED) != 0 && shortcutInfo.isPinned())
260                     || ((matchFlags & FLAG_MATCH_CACHED) != 0
261                         && (shortcutInfo.isCached() || shortcutInfo.isLongLived())))
262         .collect(toCollection(ArrayList::new));
263   }
264 
265   /**
266    * In Robolectric, ShadowShortcutManager doesn't handle rate limiting or shortcut count limits.
267    * So, pushDynamicShortcut is similar to {@link #addDynamicShortcuts(List)} but with only one
268    * {@link ShortcutInfo}.
269    */
270   @Implementation(minSdk = R)
pushDynamicShortcut(ShortcutInfo shortcut)271   protected void pushDynamicShortcut(ShortcutInfo shortcut) {
272     addDynamicShortcuts(Arrays.asList(shortcut));
273   }
274 
275   /**
276    * No-op on Robolectric. The real implementation calls out to a service, which will NPE on
277    * Robolectric.
278    */
279   @Implementation(minSdk = VERSION_CODES.R)
removeLongLivedShortcuts(List<String> shortcutIds)280   protected void removeLongLivedShortcuts(List<String> shortcutIds) {}
281 }
282