1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; 4 import static android.os.Build.VERSION_CODES.N; 5 6 import android.telephony.SubscriptionInfo; 7 import android.telephony.SubscriptionManager; 8 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; 9 import java.util.ArrayList; 10 import java.util.Arrays; 11 import java.util.Collections; 12 import java.util.HashSet; 13 import java.util.List; 14 import java.util.Set; 15 import org.robolectric.annotation.HiddenApi; 16 import org.robolectric.annotation.Implementation; 17 import org.robolectric.annotation.Implements; 18 import org.robolectric.annotation.Resetter; 19 import org.robolectric.util.ReflectionHelpers; 20 21 @Implements(value = SubscriptionManager.class, minSdk = LOLLIPOP_MR1) 22 public class ShadowSubscriptionManager { 23 24 private static int defaultSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 25 private static int defaultDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 26 private static int defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 27 private static int defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 28 29 /** Returns value set with {@link #setDefaultSubscriptionId(int)}. */ 30 @Implementation(minSdk = N) getDefaultSubscriptionId()31 protected static int getDefaultSubscriptionId() { 32 return defaultSubscriptionId; 33 } 34 35 /** Returns value set with {@link #setDefaultDataSubscriptionId(int)}. */ 36 @Implementation(minSdk = N) getDefaultDataSubscriptionId()37 protected static int getDefaultDataSubscriptionId() { 38 return defaultDataSubscriptionId; 39 } 40 41 /** Returns value set with {@link #setDefaultSmsSubscriptionId(int)}. */ 42 @Implementation(minSdk = N) getDefaultSmsSubscriptionId()43 protected static int getDefaultSmsSubscriptionId() { 44 return defaultSmsSubscriptionId; 45 } 46 47 /** Returns value set with {@link #setDefaultVoiceSubscriptionId(int)}. */ 48 @Implementation(minSdk = N) getDefaultVoiceSubscriptionId()49 protected static int getDefaultVoiceSubscriptionId() { 50 return defaultVoiceSubscriptionId; 51 } 52 53 /** Sets the value that will be returned by {@link #getDefaultSubscriptionId()}. */ setDefaultSubscriptionId(int defaultSubscriptionId)54 public static void setDefaultSubscriptionId(int defaultSubscriptionId) { 55 ShadowSubscriptionManager.defaultSubscriptionId = defaultSubscriptionId; 56 } 57 setDefaultDataSubscriptionId(int defaultDataSubscriptionId)58 public static void setDefaultDataSubscriptionId(int defaultDataSubscriptionId) { 59 ShadowSubscriptionManager.defaultDataSubscriptionId = defaultDataSubscriptionId; 60 } 61 setDefaultSmsSubscriptionId(int defaultSmsSubscriptionId)62 public static void setDefaultSmsSubscriptionId(int defaultSmsSubscriptionId) { 63 ShadowSubscriptionManager.defaultSmsSubscriptionId = defaultSmsSubscriptionId; 64 } 65 setDefaultVoiceSubscriptionId(int defaultVoiceSubscriptionId)66 public static void setDefaultVoiceSubscriptionId(int defaultVoiceSubscriptionId) { 67 ShadowSubscriptionManager.defaultVoiceSubscriptionId = defaultVoiceSubscriptionId; 68 } 69 70 /** 71 * Cache of {@link SubscriptionInfo} used by {@link #getActiveSubscriptionInfoList}. 72 * Managed by {@link #setActiveSubscriptionInfoList}. 73 */ 74 private List<SubscriptionInfo> subscriptionList = new ArrayList<>(); 75 /** 76 * List of listeners to be notified if the list of {@link SubscriptionInfo} changes. Managed by 77 * {@link #addOnSubscriptionsChangedListener} and {@link removeOnSubscriptionsChangedListener}. 78 */ 79 private List<OnSubscriptionsChangedListener> listeners = new ArrayList<>(); 80 /** 81 * Cache of subscription ids used by {@link #isNetworkRoaming}. Managed by {@link 82 * #setNetworkRoamingStatus} and {@link #clearNetworkRoamingStatus}. 83 */ 84 private Set<Integer> roamingSimSubscriptionIds = new HashSet<>(); 85 86 /** 87 * Returns the active list of {@link SubscriptionInfo} that were set via {@link 88 * #setActiveSubscriptionInfoList}. 89 */ 90 @Implementation(minSdk = LOLLIPOP_MR1) getActiveSubscriptionInfoList()91 protected List<SubscriptionInfo> getActiveSubscriptionInfoList() { 92 return subscriptionList; 93 } 94 95 /** 96 * Returns the size of the list of {@link SubscriptionInfo} that were set via {@link 97 * #setActiveSubscriptionInfoList}. If no list was set, returns 0. 98 */ 99 @Implementation(minSdk = LOLLIPOP_MR1) getActiveSubscriptionInfoCount()100 protected int getActiveSubscriptionInfoCount() { 101 return subscriptionList == null ? 0 : subscriptionList.size(); 102 } 103 104 /** 105 * Returns subscription that were set via {@link #setActiveSubscriptionInfoList} if it can find 106 * one with the specified id or null if none found. 107 */ 108 @Implementation(minSdk = LOLLIPOP_MR1) getActiveSubscriptionInfo(int subId)109 protected SubscriptionInfo getActiveSubscriptionInfo(int subId) { 110 if (subscriptionList == null) { 111 return null; 112 } 113 for (SubscriptionInfo info : subscriptionList) { 114 if (info.getSubscriptionId() == subId) { 115 return info; 116 } 117 } 118 return null; 119 } 120 121 /** 122 * Returns subscription that were set via {@link #setActiveSubscriptionInfoList} if it can find 123 * one with the specified slot index or null if none found. 124 */ 125 @Implementation(minSdk = N) getActiveSubscriptionInfoForSimSlotIndex(int slotIndex)126 protected SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex) { 127 if (subscriptionList == null) { 128 return null; 129 } 130 for (SubscriptionInfo info : subscriptionList) { 131 if (info.getSimSlotIndex() == slotIndex) { 132 return info; 133 } 134 } 135 return null; 136 } 137 138 /** 139 * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link 140 * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners. 141 * @param list - The subscription info list, can be null. 142 */ setActiveSubscriptionInfoList(List<SubscriptionInfo> list)143 public void setActiveSubscriptionInfoList(List<SubscriptionInfo> list) { 144 subscriptionList = list; 145 dispatchOnSubscriptionsChanged(); 146 } 147 148 /** 149 * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link 150 * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners. 151 */ setActiveSubscriptionInfos(SubscriptionInfo... infos)152 public void setActiveSubscriptionInfos(SubscriptionInfo... infos) { 153 if (infos == null) { 154 setActiveSubscriptionInfoList(Collections.emptyList()); 155 } else { 156 setActiveSubscriptionInfoList(Arrays.asList(infos)); 157 } 158 } 159 160 /** 161 * Adds a listener to a local list of listeners. Will be triggered by {@link 162 * #setActiveSubscriptionInfoList} when the local list of {@link SubscriptionInfo} is updated. 163 */ 164 @Implementation(minSdk = LOLLIPOP_MR1) addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener)165 protected void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { 166 listeners.add(listener); 167 } 168 169 /** 170 * Removes a listener from a local list of listeners. Will be triggered by {@link 171 * #setActiveSubscriptionInfoList} when the local list of {@link SubscriptionInfo} is updated. 172 */ 173 @Implementation(minSdk = LOLLIPOP_MR1) removeOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener)174 protected void removeOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { 175 listeners.remove(listener); 176 } 177 178 /** Returns subscription Ids that were set via {@link #setActiveSubscriptionInfoList}. */ 179 @Implementation(minSdk = LOLLIPOP_MR1) 180 @HiddenApi getActiveSubscriptionIdList()181 protected int[] getActiveSubscriptionIdList() { 182 final List<SubscriptionInfo> infos = getActiveSubscriptionInfoList(); 183 if (infos == null) { 184 return new int[0]; 185 } 186 int[] ids = new int[infos.size()]; 187 for (int i = 0; i < infos.size(); i++) { 188 ids[i] = infos.get(i).getSubscriptionId(); 189 } 190 return ids; 191 } 192 193 /** 194 * Notifies {@link OnSubscriptionsChangedListener} listeners that the list of {@link 195 * SubscriptionInfo} has been updated. 196 */ dispatchOnSubscriptionsChanged()197 private void dispatchOnSubscriptionsChanged() { 198 for (OnSubscriptionsChangedListener listener : listeners) { 199 listener.onSubscriptionsChanged(); 200 } 201 } 202 203 /** Clears the local cache of roaming subscription Ids used by {@link #isNetworkRoaming}. */ clearNetworkRoamingStatus()204 public void clearNetworkRoamingStatus(){ 205 roamingSimSubscriptionIds.clear(); 206 } 207 208 /** 209 * If isNetworkRoaming is set, it will mark the provided sim subscriptionId as roaming in a local 210 * cache. If isNetworkRoaming is unset it will remove the subscriptionId from the local cache. The 211 * local cache is used to provide roaming status returned by {@link #isNetworkRoaming}. 212 */ setNetworkRoamingStatus(int simSubscriptionId, boolean isNetworkRoaming)213 public void setNetworkRoamingStatus(int simSubscriptionId, boolean isNetworkRoaming) { 214 if (isNetworkRoaming) { 215 roamingSimSubscriptionIds.add(simSubscriptionId); 216 } else { 217 roamingSimSubscriptionIds.remove(simSubscriptionId); 218 } 219 } 220 221 /** 222 * Uses the local cache of roaming sim subscription Ids managed by {@link 223 * #setNetworkRoamingStatus} to return subscription Ids marked as roaming. Otherwise subscription 224 * Ids will be considered as non-roaming if they are not in the cache. 225 */ 226 @Implementation(minSdk = LOLLIPOP_MR1) isNetworkRoaming(int simSubscriptionId)227 protected boolean isNetworkRoaming(int simSubscriptionId) { 228 return roamingSimSubscriptionIds.contains(simSubscriptionId); 229 } 230 231 @Resetter reset()232 public static void reset() { 233 defaultDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 234 defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 235 defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 236 defaultSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 237 } 238 239 /** Builder class to create instance of {@link SubscriptionInfo}. */ 240 public static class SubscriptionInfoBuilder { 241 private final SubscriptionInfo subscriptionInfo = 242 ReflectionHelpers.callConstructor(SubscriptionInfo.class); 243 newBuilder()244 public static SubscriptionInfoBuilder newBuilder() { 245 return new SubscriptionInfoBuilder(); 246 } 247 buildSubscriptionInfo()248 public SubscriptionInfo buildSubscriptionInfo() { 249 return subscriptionInfo; 250 } 251 setId(int id)252 public SubscriptionInfoBuilder setId(int id) { 253 ReflectionHelpers.setField(subscriptionInfo, "mId", id); 254 return this; 255 } 256 setIccId(String iccId)257 public SubscriptionInfoBuilder setIccId(String iccId) { 258 ReflectionHelpers.setField(subscriptionInfo, "mIccId", iccId); 259 return this; 260 } 261 setSimSlotIndex(int index)262 public SubscriptionInfoBuilder setSimSlotIndex(int index) { 263 ReflectionHelpers.setField(subscriptionInfo, "mSimSlotIndex", index); 264 return this; 265 } 266 setDisplayName(String name)267 public SubscriptionInfoBuilder setDisplayName(String name) { 268 ReflectionHelpers.setField(subscriptionInfo, "mDisplayName", name); 269 return this; 270 } 271 setCarrierName(String carrierName)272 public SubscriptionInfoBuilder setCarrierName(String carrierName) { 273 ReflectionHelpers.setField(subscriptionInfo, "mCarrierName", carrierName); 274 return this; 275 } 276 setIconTint(int iconTint)277 public SubscriptionInfoBuilder setIconTint(int iconTint) { 278 ReflectionHelpers.setField(subscriptionInfo, "mIconTint", iconTint); 279 return this; 280 } 281 setNumber(String number)282 public SubscriptionInfoBuilder setNumber(String number) { 283 ReflectionHelpers.setField(subscriptionInfo, "mNumber", number); 284 return this; 285 } 286 setDataRoaming(int dataRoaming)287 public SubscriptionInfoBuilder setDataRoaming(int dataRoaming) { 288 ReflectionHelpers.setField(subscriptionInfo, "mDataRoaming", dataRoaming); 289 return this; 290 } 291 setCountryIso(String countryIso)292 public SubscriptionInfoBuilder setCountryIso(String countryIso) { 293 ReflectionHelpers.setField(subscriptionInfo, "mCountryIso", countryIso); 294 return this; 295 } 296 297 // Use {@link #newBuilder} to construct builders. SubscriptionInfoBuilder()298 private SubscriptionInfoBuilder() {} 299 } 300 } 301