diff options
Diffstat (limited to 'shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java')
-rw-r--r-- | shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java | 212 |
1 files changed, 139 insertions, 73 deletions
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java index 7b2a756b2..871e9f3a8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java @@ -1,8 +1,6 @@ package org.robolectric.shadows; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; @@ -54,19 +52,17 @@ import android.telephony.TelephonyManager.CellInfoCallback; import android.telephony.VisualVoicemailSmsFilterSettings; import android.telephony.emergency.EmergencyNumber; import android.text.TextUtils; -import android.util.SparseArray; -import android.util.SparseBooleanArray; -import android.util.SparseIntArray; import com.google.common.base.Ascii; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -88,17 +84,24 @@ public class ShadowTelephonyManager { @RealObject protected TelephonyManager realTelephonyManager; - private final Map<PhoneStateListener, Integer> phoneStateRegistrations = new HashMap<>(); + private final Map<PhoneStateListener, Integer> phoneStateRegistrations = + Collections.synchronizedMap(new LinkedHashMap<>()); private final /*List<TelephonyCallback>*/ List<Object> telephonyCallbackRegistrations = new ArrayList<>(); - private final Map<Integer, String> slotIndexToDeviceId = new HashMap<>(); - private final Map<Integer, String> slotIndexToImei = new HashMap<>(); - private final Map<Integer, String> slotIndexToMeid = new HashMap<>(); - private final Map<PhoneAccountHandle, Boolean> voicemailVibrationEnabledMap = new HashMap<>(); - private final Map<PhoneAccountHandle, Uri> voicemailRingtoneUriMap = new HashMap<>(); - private final Map<PhoneAccountHandle, TelephonyManager> phoneAccountToTelephonyManagers = - new HashMap<>(); - private final Map<PhoneAccountHandle, Integer> phoneAccountHandleSubscriptionId = new HashMap<>(); + private static final Map<Integer, String> slotIndexToDeviceId = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<Integer, String> slotIndexToImei = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<Integer, String> slotIndexToMeid = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<PhoneAccountHandle, Boolean> voicemailVibrationEnabledMap = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<PhoneAccountHandle, Uri> voicemailRingtoneUriMap = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<PhoneAccountHandle, TelephonyManager> phoneAccountToTelephonyManagers = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<PhoneAccountHandle, Integer> phoneAccountHandleSubscriptionId = + Collections.synchronizedMap(new LinkedHashMap<>()); private PhoneStateListener lastListener; private /*TelephonyCallback*/ Object lastTelephonyCallback; @@ -117,47 +120,53 @@ public class ShadowTelephonyManager { private String simOperator = ""; private String simOperatorName; private String simSerialNumber; - private boolean readPhoneStatePermission = true; + private static volatile boolean readPhoneStatePermission = true; private int phoneType = TelephonyManager.PHONE_TYPE_GSM; private String line1Number; private int networkType; private int dataNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN; private int voiceNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN; - private List<CellInfo> allCellInfo = Collections.emptyList(); - private List<CellInfo> callbackCellInfos = null; - private CellLocation cellLocation = null; + private static volatile List<CellInfo> allCellInfo = Collections.emptyList(); + private static volatile List<CellInfo> callbackCellInfos = null; + private static volatile CellLocation cellLocation = null; private int callState = CALL_STATE_IDLE; private int dataState = TelephonyManager.DATA_DISCONNECTED; private int dataActivity = TelephonyManager.DATA_ACTIVITY_NONE; private String incomingPhoneNumber = null; - private boolean isSmsCapable = true; - private boolean voiceCapable = true; + private static volatile boolean isSmsCapable = true; + private static volatile boolean voiceCapable = true; private String voiceMailNumber; private String voiceMailAlphaTag; - private int phoneCount = 1; - private int activeModemCount = 1; - private Map<Integer, TelephonyManager> subscriptionIdsToTelephonyManagers = new HashMap<>(); + private static volatile int phoneCount = 1; + private static volatile int activeModemCount = 1; + private static volatile Map<Integer, TelephonyManager> subscriptionIdsToTelephonyManagers = + Collections.synchronizedMap(new LinkedHashMap<>()); private PersistableBundle carrierConfig; private ServiceState serviceState; private boolean isNetworkRoaming; - private final SparseIntArray simStates = new SparseIntArray(); - private final SparseIntArray currentPhoneTypes = new SparseIntArray(); - private final SparseArray<List<String>> carrierPackageNames = new SparseArray<>(); - private final Map<Integer, String> simCountryIsoMap = new HashMap<>(); + private static final Map<Integer, Integer> simStates = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<Integer, Integer> currentPhoneTypes = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<Integer, List<String>> carrierPackageNames = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final Map<Integer, String> simCountryIsoMap = + Collections.synchronizedMap(new LinkedHashMap<>()); private int simCarrierId; private int carrierIdFromSimMccMnc; private String subscriberId; - private /*UiccSlotInfo[]*/ Object uiccSlotInfos; - private /*UiccCardInfo[]*/ Object uiccCardsInfo = new ArrayList<>(); + private static volatile /*UiccSlotInfo[]*/ Object uiccSlotInfos; + private static volatile /*List<UiccCardInfo>*/ Object uiccCardsInfo = new ArrayList<>(); private String visualVoicemailPackageName = null; private SignalStrength signalStrength; private boolean dataEnabled = false; private final Set<Integer> dataDisabledReasons = new HashSet<>(); private boolean isRttSupported; - private boolean isTtyModeSupported; - private final SparseBooleanArray subIdToHasCarrierPrivileges = new SparseBooleanArray(); - private final List<String> sentDialerSpecialCodes = new ArrayList<>(); - private boolean hearingAidCompatibilitySupported = false; + private static volatile boolean isTtyModeSupported; + private static final Map<Integer, Boolean> subIdToHasCarrierPrivileges = + Collections.synchronizedMap(new LinkedHashMap<>()); + private static final List<String> sentDialerSpecialCodes = new ArrayList<>(); + private static volatile boolean hearingAidCompatibilitySupported = false; private int requestCellInfoUpdateErrorCode = 0; private Throwable requestCellInfoUpdateDetail = null; private Object telephonyDisplayInfo; @@ -165,7 +174,7 @@ public class ShadowTelephonyManager { private static int callComposerStatus = 0; private VisualVoicemailSmsParams lastVisualVoicemailSmsParams; private VisualVoicemailSmsFilterSettings visualVoicemailSmsFilterSettings; - private boolean emergencyCallbackMode; + private static volatile boolean emergencyCallbackMode; private static Map<Integer, List<EmergencyNumber>> emergencyNumbersList; /** @@ -176,17 +185,45 @@ public class ShadowTelephonyManager { */ private Object callback; - private /*PhoneCapability*/ Object phoneCapability; + private static volatile /*PhoneCapability*/ Object phoneCapability; - { - resetSimStates(); - resetSimCountryIsos(); + static { + resetAllSimStates(); + resetAllSimCountryIsos(); } @Resetter public static void reset() { + subscriptionIdsToTelephonyManagers.clear(); + resetAllSimStates(); + currentPhoneTypes.clear(); + carrierPackageNames.clear(); + resetAllSimCountryIsos(); + slotIndexToDeviceId.clear(); + slotIndexToImei.clear(); + slotIndexToMeid.clear(); + voicemailVibrationEnabledMap.clear(); + voicemailRingtoneUriMap.clear(); + phoneAccountToTelephonyManagers.clear(); + phoneAccountHandleSubscriptionId.clear(); + subIdToHasCarrierPrivileges.clear(); + allCellInfo = Collections.emptyList(); + cellLocation = null; + callbackCellInfos = null; + uiccSlotInfos = null; + uiccCardsInfo = new ArrayList<>(); callComposerStatus = 0; + emergencyCallbackMode = false; emergencyNumbersList = null; + phoneCapability = null; + isTtyModeSupported = false; + readPhoneStatePermission = true; + isSmsCapable = true; + voiceCapable = true; + phoneCount = 1; + activeModemCount = 1; + sentDialerSpecialCodes.clear(); + hearingAidCompatibilitySupported = false; } @Implementation(minSdk = S) @@ -217,7 +254,7 @@ public class ShadowTelephonyManager { } public void setPhoneCapability(/*PhoneCapability*/ Object phoneCapability) { - this.phoneCapability = phoneCapability; + ShadowTelephonyManager.phoneCapability = phoneCapability; } @Implementation(minSdk = S) @@ -548,11 +585,21 @@ public class ShadowTelephonyManager { } /** Clears {@code subId} to simCountryIso mapping and resets to default state. */ - public void resetSimCountryIsos() { + public static void resetAllSimCountryIsos() { simCountryIsoMap.clear(); simCountryIsoMap.put(0, ""); } + /** + * Clears {@code subId} to simCountryIso mapping and resets to default state. + * + * @deprecated for resetAllSimCountryIsos + */ + @Deprecated + public void resetSimCountryIsos() { + resetAllSimCountryIsos(); + } + @Implementation protected int getSimState() { return getSimState(/* slotIndex= */ 0); @@ -570,12 +617,12 @@ public class ShadowTelephonyManager { @Implementation(minSdk = O) protected int getSimState(int slotIndex) { - return simStates.get(slotIndex, TelephonyManager.SIM_STATE_UNKNOWN); + return simStates.getOrDefault(slotIndex, TelephonyManager.SIM_STATE_UNKNOWN); } /** Sets the UICC slots information returned by {@link #getUiccSlotsInfo()}. */ public void setUiccSlotsInfo(/*UiccSlotInfo[]*/ Object uiccSlotsInfos) { - this.uiccSlotInfos = uiccSlotsInfos; + ShadowTelephonyManager.uiccSlotInfos = uiccSlotsInfos; } /** Returns the UICC slots information set by {@link #setUiccSlotsInfo}. */ @@ -586,25 +633,35 @@ public class ShadowTelephonyManager { } /** Sets the UICC cards information returned by {@link #getUiccCardsInfo()}. */ - public void setUiccCardsInfo(/*UiccCardsInfo[]*/ Object uiccCardsInfo) { - this.uiccCardsInfo = uiccCardsInfo; + public void setUiccCardsInfo(/*List<UiccCardInfo>*/ Object uiccCardsInfo) { + ShadowTelephonyManager.uiccCardsInfo = uiccCardsInfo; } /** Returns the UICC cards information set by {@link #setUiccCardsInfo}. */ @Implementation(minSdk = Q) @HiddenApi - protected /*UiccSlotInfo[]*/ Object getUiccCardsInfo() { + protected /*List<UiccCardInfo>*/ Object getUiccCardsInfo() { return uiccCardsInfo; } /** Clears {@code slotIndex} to state mapping and resets to default state. */ - public void resetSimStates() { + public static void resetAllSimStates() { simStates.clear(); simStates.put(0, TelephonyManager.SIM_STATE_READY); } + /** + * Clears {@code slotIndex} to state mapping and resets to default state. + * + * @deprecated use resetAllSimStates() + */ + @Deprecated + public void resetSimStates() { + resetAllSimStates(); + } + public void setReadPhoneStatePermission(boolean readPhoneStatePermission) { - this.readPhoneStatePermission = readPhoneStatePermission; + ShadowTelephonyManager.readPhoneStatePermission = readPhoneStatePermission; } private void checkReadPhoneStatePermission() { @@ -712,18 +769,16 @@ public class ShadowTelephonyManager { this.voiceNetworkType = voiceNetworkType; } - @Implementation(minSdk = JELLY_BEAN_MR1) + @Implementation protected List<CellInfo> getAllCellInfo() { return allCellInfo; } public void setAllCellInfo(List<CellInfo> allCellInfo) { - this.allCellInfo = allCellInfo; + ShadowTelephonyManager.allCellInfo = allCellInfo; - if (VERSION.SDK_INT >= JELLY_BEAN_MR1) { - for (PhoneStateListener listener : getListenersForFlags(LISTEN_CELL_INFO)) { - listener.onCellInfoChanged(allCellInfo); - } + for (PhoneStateListener listener : getListenersForFlags(LISTEN_CELL_INFO)) { + listener.onCellInfoChanged(allCellInfo); } if (VERSION.SDK_INT >= S) { for (CellInfoListener listener : getCallbackForListener(CellInfoListener.class)) { @@ -739,6 +794,7 @@ public class ShadowTelephonyManager { @Implementation(minSdk = Q) protected void requestCellInfoUpdate(Object cellInfoExecutor, Object cellInfoCallback) { Executor executor = (Executor) cellInfoExecutor; + List<CellInfo> callbackCellInfos = ShadowTelephonyManager.callbackCellInfos; if (callbackCellInfos == null) { // ignore } else if (requestCellInfoUpdateErrorCode != 0 || requestCellInfoUpdateDetail != null) { @@ -768,7 +824,7 @@ public class ShadowTelephonyManager { * setAllCellInfo}. */ public void setCallbackCellInfos(List<CellInfo> callbackCellInfos) { - this.callbackCellInfos = callbackCellInfos; + ShadowTelephonyManager.callbackCellInfos = callbackCellInfos; } /** @@ -782,12 +838,11 @@ public class ShadowTelephonyManager { @Implementation protected CellLocation getCellLocation() { - return this.cellLocation; + return ShadowTelephonyManager.cellLocation; } public void setCellLocation(CellLocation cellLocation) { - this.cellLocation = cellLocation; - + ShadowTelephonyManager.cellLocation = cellLocation; for (PhoneStateListener listener : getListenersForFlags(LISTEN_CELL_LOCATION)) { listener.onCellLocationChanged(cellLocation); } @@ -798,7 +853,7 @@ public class ShadowTelephonyManager { } } - @Implementation(minSdk = JELLY_BEAN_MR2) + @Implementation protected String getGroupIdLevel1() { checkReadPhoneStatePermission(); return this.groupIdLevel1; @@ -810,13 +865,18 @@ public class ShadowTelephonyManager { @CallSuper protected void initListener(PhoneStateListener listener, int flags) { + // grab the state "atomically" before doing callbacks, in case they modify the state + String incomingPhoneNumber = this.incomingPhoneNumber; + List<CellInfo> allCellInfo = ShadowTelephonyManager.allCellInfo; + CellLocation cellLocation = ShadowTelephonyManager.cellLocation; + Object telephonyDisplayInfo = this.telephonyDisplayInfo; + ServiceState serviceState = this.serviceState; + if ((flags & LISTEN_CALL_STATE) != 0) { listener.onCallStateChanged(callState, incomingPhoneNumber); } if ((flags & LISTEN_CELL_INFO) != 0) { - if (VERSION.SDK_INT >= JELLY_BEAN_MR1) { - listener.onCellInfoChanged(allCellInfo); - } + listener.onCellInfoChanged(allCellInfo); } if ((flags & LISTEN_CELL_LOCATION) != 0) { listener.onCellLocationChanged(cellLocation); @@ -837,6 +897,12 @@ public class ShadowTelephonyManager { if (VERSION.SDK_INT < S) { return; } + // grab the state "atomically" before doing callbacks, in case they modify the state + int callState = this.callState; + List<CellInfo> allCellInfo = ShadowTelephonyManager.allCellInfo; + CellLocation cellLocation = ShadowTelephonyManager.cellLocation; + Object telephonyDisplayInfo = this.telephonyDisplayInfo; + ServiceState serviceState = this.serviceState; if (callback instanceof CallStateListener) { ((CallStateListener) callback).onCallStateChanged(callState); @@ -858,7 +924,7 @@ public class ShadowTelephonyManager { protected Iterable<PhoneStateListener> getListenersForFlags(int flags) { return Iterables.filter( - phoneStateRegistrations.keySet(), + ImmutableSet.copyOf(phoneStateRegistrations.keySet()), new Predicate<PhoneStateListener>() { @Override public boolean apply(PhoneStateListener input) { @@ -874,7 +940,7 @@ public class ShadowTelephonyManager { */ protected <T> Iterable<T> getCallbackForListener(Class<T> clazz) { // Only selects TelephonyCallback with matching class. - return Iterables.filter(telephonyCallbackRegistrations, clazz); + return Iterables.filter(ImmutableList.copyOf(telephonyCallbackRegistrations), clazz); } /** @@ -887,7 +953,7 @@ public class ShadowTelephonyManager { /** Sets the value returned by {@link TelephonyManager#isSmsCapable()}. */ public void setIsSmsCapable(boolean isSmsCapable) { - this.isSmsCapable = isSmsCapable; + ShadowTelephonyManager.isSmsCapable = isSmsCapable; } /** @@ -947,7 +1013,7 @@ public class ShadowTelephonyManager { /** Sets the value returned by {@link TelephonyManager#getPhoneCount()}. */ public void setPhoneCount(int phoneCount) { - this.phoneCount = phoneCount; + ShadowTelephonyManager.phoneCount = phoneCount; } /** Returns 1 by default or the value specified via {@link #setActiveModemCount(int)}. */ @@ -958,7 +1024,7 @@ public class ShadowTelephonyManager { /** Sets the value returned by {@link TelephonyManager#getActiveModemCount()}. */ public void setActiveModemCount(int activeModemCount) { - this.activeModemCount = activeModemCount; + ShadowTelephonyManager.activeModemCount = activeModemCount; } /** @@ -985,7 +1051,7 @@ public class ShadowTelephonyManager { /** Sets the value returned by {@link #isVoiceCapable()}. */ public void setVoiceCapable(boolean voiceCapable) { - this.voiceCapable = voiceCapable; + ShadowTelephonyManager.voiceCapable = voiceCapable; } /** @@ -1109,7 +1175,7 @@ public class ShadowTelephonyManager { @Implementation(minSdk = M) @HiddenApi protected int getCurrentPhoneType(int subId) { - return currentPhoneTypes.get(subId, TelephonyManager.PHONE_TYPE_NONE); + return currentPhoneTypes.getOrDefault(subId, TelephonyManager.PHONE_TYPE_NONE); } /** Sets the phone type for the given {@code subId}. */ @@ -1118,7 +1184,7 @@ public class ShadowTelephonyManager { } /** Removes all {@code subId} to {@code phoneType} mappings. */ - public void clearPhoneTypes() { + public static void clearPhoneTypes() { currentPhoneTypes.clear(); } @@ -1288,7 +1354,7 @@ public class ShadowTelephonyManager { * @param emergencyCallbackMode whether the device is in ECBM or not. */ public void setEmergencyCallbackMode(boolean emergencyCallbackMode) { - this.emergencyCallbackMode = emergencyCallbackMode; + ShadowTelephonyManager.emergencyCallbackMode = emergencyCallbackMode; } @Implementation(minSdk = Build.VERSION_CODES.O) @@ -1377,7 +1443,7 @@ public class ShadowTelephonyManager { /** Sets the value to be returned by {@link #isTtyModeSupported()} */ public void setTtyModeSupported(boolean isTtyModeSupported) { - this.isTtyModeSupported = isTtyModeSupported; + ShadowTelephonyManager.isTtyModeSupported = isTtyModeSupported; } /** @@ -1386,7 +1452,7 @@ public class ShadowTelephonyManager { @Implementation(minSdk = Build.VERSION_CODES.N) @HiddenApi protected boolean hasCarrierPrivileges(int subId) { - return subIdToHasCarrierPrivileges.get(subId); + return subIdToHasCarrierPrivileges.getOrDefault(subId, false); } public void setHasCarrierPrivileges(boolean hasCarrierPrivileges) { |