blob: 0864354d9e41a3bd179275b0dce92d1f321f7b2b [file] [log] [blame]
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;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.telephony.PhoneStateListener.LISTEN_CALL_STATE;
import static android.telephony.PhoneStateListener.LISTEN_CELL_INFO;
import static android.telephony.PhoneStateListener.LISTEN_CELL_LOCATION;
import static android.telephony.PhoneStateListener.LISTEN_NONE;
import static android.telephony.PhoneStateListener.LISTEN_SERVICE_STATE;
import static android.telephony.TelephonyManager.CALL_STATE_IDLE;
import static android.telephony.TelephonyManager.CALL_STATE_RINGING;
import android.Manifest.permission;
import android.annotation.CallSuper;
import android.app.ActivityThread;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.PersistableBundle;
import android.os.SystemProperties;
import android.telecom.PhoneAccountHandle;
import android.telephony.Annotation.NetworkType;
import android.telephony.Annotation.OverrideNetworkType;
import android.telephony.CellInfo;
import android.telephony.CellLocation;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyCallback.CallStateListener;
import android.telephony.TelephonyCallback.CellInfoListener;
import android.telephony.TelephonyCallback.CellLocationListener;
import android.telephony.TelephonyCallback.DisplayInfoListener;
import android.telephony.TelephonyCallback.ServiceStateListener;
import android.telephony.TelephonyCallback.SignalStrengthsListener;
import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
import android.telephony.TelephonyManager.CellInfoCallback;
import android.telephony.VisualVoicemailSmsFilterSettings;
import android.text.TextUtils;
import android.util.SparseArray;
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.Iterables;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Executor;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
@Implements(value = TelephonyManager.class, looseSignatures = true)
public class ShadowTelephonyManager {
@RealObject protected TelephonyManager realTelephonyManager;
private final Map<PhoneStateListener, Integer> phoneStateRegistrations = new HashMap<>();
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 PhoneStateListener lastListener;
private /*TelephonyCallback*/ Object lastTelephonyCallback;
private int lastEventFlags;
private String deviceId;
private String imei;
private String meid;
private String groupIdLevel1;
private String networkOperatorName = "";
private String networkCountryIso;
private String networkOperator = "";
private Locale simLocale;
private String simOperator;
private String simOperatorName;
private String simSerialNumber;
private 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 int callState = CALL_STATE_IDLE;
private int dataState = TelephonyManager.DATA_DISCONNECTED;
private String incomingPhoneNumber = null;
private boolean isSmsCapable = true;
private 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 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 int simCarrierId;
private int carrierIdFromSimMccMnc;
private String subscriberId;
private /*UiccSlotInfo[]*/ Object uiccSlotInfos;
private /*UiccCardInfo[]*/ Object uiccCardsInfo;
private String visualVoicemailPackageName = null;
private SignalStrength signalStrength;
private boolean dataEnabled = false;
private boolean isRttSupported;
private final List<String> sentDialerSpecialCodes = new ArrayList<>();
private boolean hearingAidCompatibilitySupported = false;
private int requestCellInfoUpdateErrorCode = 0;
private Throwable requestCellInfoUpdateDetail = null;
private Object telephonyDisplayInfo;
private boolean isDataConnectionAllowed;
private static int callComposerStatus = 0;
private VisualVoicemailSmsParams lastVisualVoicemailSmsParams;
private VisualVoicemailSmsFilterSettings visualVoicemailSmsFilterSettings;
private boolean emergencyCallbackMode;
/**
* Should be {@link TelephonyManager.BootstrapAuthenticationCallback} but this object was
* introduced in Android S, so we are using Object to avoid breaking other SDKs
*
* <p>XXX Look into using the real types if we're now compiling against S
*/
private Object callback;
{
resetSimStates();
resetSimCountryIsos();
}
@Resetter
public static void reset() {
callComposerStatus = 0;
}
@Implementation(minSdk = S)
protected void setCallComposerStatus(int callComposerStatus) {
ShadowTelephonyManager.callComposerStatus = callComposerStatus;
}
@Implementation(minSdk = S)
@HiddenApi
protected int getCallComposerStatus() {
return callComposerStatus;
}
public Object getBootstrapAuthenticationCallback() {
return callback;
}
@Implementation(minSdk = S)
@HiddenApi
public void bootstrapAuthenticationRequest(
Object appType,
Object nafId,
Object securityProtocol,
Object forceBootStrapping,
Object e,
Object callback) {
this.callback = callback;
}
@Implementation
protected void listen(PhoneStateListener listener, int flags) {
lastListener = listener;
lastEventFlags = flags;
if (flags == LISTEN_NONE) {
phoneStateRegistrations.remove(listener);
} else {
initListener(listener, flags);
phoneStateRegistrations.put(listener, flags);
}
}
/**
* Returns the most recent listener passed to #listen().
*
* @return Phone state listener.
* @deprecated Avoid using.
*/
@Deprecated
public PhoneStateListener getListener() {
return lastListener;
}
/**
* Returns the most recent flags passed to #listen().
*
* @return Event flags.
* @deprecated Avoid using.
*/
@Deprecated
public int getEventFlags() {
return lastEventFlags;
}
@Implementation(minSdk = S)
public void registerTelephonyCallback(
/*Executor*/ Object executor, /*TelephonyCallback*/ Object callback) {
Preconditions.checkArgument(executor instanceof Executor);
Preconditions.checkArgument(callback instanceof TelephonyCallback);
lastTelephonyCallback = callback;
initTelephonyCallback(callback);
telephonyCallbackRegistrations.add(callback);
}
@Implementation(minSdk = TIRAMISU)
protected void registerTelephonyCallback(
/*int*/ Object includeLocationData, /*Executor*/
Object executor, /*TelephonyCallback*/
Object callback) {
Preconditions.checkArgument(includeLocationData instanceof Integer);
registerTelephonyCallback(executor, callback);
}
@Implementation(minSdk = S)
public void unregisterTelephonyCallback(/*TelephonyCallback*/ Object callback) {
telephonyCallbackRegistrations.remove(callback);
}
/** Returns the most recent callback passed to #registerTelephonyCallback(). */
public /*TelephonyCallback*/ Object getLastTelephonyCallback() {
return lastTelephonyCallback;
}
/** Call state may be specified via {@link #setCallState(int)}. */
@Implementation
protected int getCallState() {
checkReadPhoneStatePermission();
return callState;
}
/** Sets the current call state to the desired state and updates any listeners. */
public void setCallState(int callState) {
setCallState(callState, null);
}
/**
* Sets the current call state with the option to specify an incoming phone number for the
* CALL_STATE_RINGING state. The incoming phone number will be ignored for all other cases.
*/
public void setCallState(int callState, String incomingPhoneNumber) {
if (callState != CALL_STATE_RINGING) {
incomingPhoneNumber = null;
}
this.callState = callState;
this.incomingPhoneNumber = incomingPhoneNumber;
for (PhoneStateListener listener : getListenersForFlags(LISTEN_CALL_STATE)) {
listener.onCallStateChanged(callState, incomingPhoneNumber);
}
if (VERSION.SDK_INT >= S) {
for (CallStateListener listener : getCallbackForListener(CallStateListener.class)) {
listener.onCallStateChanged(callState);
}
}
}
/**
* Data state may be specified via {@link #setDataState(int)}. If no override is set, this
* defaults to {@link TelephonyManager#DATA_DISCONNECTED}.
*/
@Implementation
protected int getDataState() {
return dataState;
}
/** Sets the data state returned by {@link #getDataState()}. */
public void setDataState(int dataState) {
this.dataState = dataState;
}
@Implementation
protected String getDeviceId() {
checkReadPhoneStatePermission();
return deviceId;
}
public void setDeviceId(String newDeviceId) {
deviceId = newDeviceId;
}
@Implementation(minSdk = LOLLIPOP_MR1)
public void setNetworkOperatorName(String networkOperatorName) {
this.networkOperatorName = networkOperatorName;
}
@Implementation(minSdk = LOLLIPOP)
protected String getImei() {
checkReadPhoneStatePermission();
return imei;
}
@Implementation(minSdk = O)
protected String getImei(int slotIndex) {
checkReadPhoneStatePermission();
return slotIndexToImei.get(slotIndex);
}
/** Set the IMEI returned by getImei(). */
public void setImei(String imei) {
this.imei = imei;
}
/** Set the IMEI returned by {@link #getImei(int)}. */
public void setImei(int slotIndex, String imei) {
slotIndexToImei.put(slotIndex, imei);
}
@Implementation(minSdk = O)
protected String getMeid() {
checkReadPhoneStatePermission();
return meid;
}
@Implementation(minSdk = O)
protected String getMeid(int slotIndex) {
checkReadPhoneStatePermission();
return slotIndexToMeid.get(slotIndex);
}
/** Set the MEID returned by getMeid(). */
public void setMeid(String meid) {
this.meid = meid;
}
/** Set the MEID returned by {@link #getMeid(int)}. */
public void setMeid(int slotIndex, String meid) {
slotIndexToMeid.put(slotIndex, meid);
}
@Implementation
protected String getNetworkOperatorName() {
return networkOperatorName;
}
@Implementation(minSdk = LOLLIPOP_MR1, maxSdk = P)
public void setNetworkCountryIso(String networkCountryIso) {
this.networkCountryIso = networkCountryIso;
}
/**
* Returns the SIM country lowercase. This matches the API this shadows:
* https://developer.android.com/reference/android/telephony/TelephonyManager#getNetworkCountryIso().
*/
@Implementation
protected String getNetworkCountryIso() {
return networkCountryIso == null ? null : Ascii.toLowerCase(networkCountryIso);
}
/** Sets the sim locale returned by {@link #getSimLocale()}. */
public void setSimLocale(Locale simLocale) {
this.simLocale = simLocale;
}
/** Returns sim locale set by {@link #setSimLocale}. */
@Implementation(minSdk = Q)
protected Locale getSimLocale() {
return simLocale;
}
public void setNetworkOperator(String networkOperator) {
this.networkOperator = networkOperator;
}
@Implementation
protected String getNetworkOperator() {
return networkOperator;
}
@Implementation
protected String getSimOperator() {
return simOperator;
}
public void setSimOperator(String simOperator) {
this.simOperator = simOperator;
}
@Implementation
protected String getSimOperatorName() {
return simOperatorName;
}
@Implementation(minSdk = LOLLIPOP_MR1)
public void setSimOperatorName(String simOperatorName) {
this.simOperatorName = simOperatorName;
}
@Implementation
protected String getSimSerialNumber() {
checkReadPhoneStatePermission();
return this.simSerialNumber;
}
/** sets the serial number that will be returned by {@link #getSimSerialNumber}. */
public void setSimSerialNumber(String simSerialNumber) {
this.simSerialNumber = simSerialNumber;
}
/**
* Returns the SIM country lowercase. This matches the API it shadows:
* https://developer.android.com/reference/android/telephony/TelephonyManager#getSimCountryIso().
*/
@Implementation
protected String getSimCountryIso() {
String simCountryIso = simCountryIsoMap.get(/* subId= */ 0);
return simCountryIso == null ? simCountryIso : Ascii.toLowerCase(simCountryIso);
}
@Implementation(minSdk = N, maxSdk = Q)
@HiddenApi
protected String getSimCountryIso(int subId) {
return simCountryIsoMap.get(subId);
}
@Implementation(minSdk = LOLLIPOP_MR1)
public void setSimCountryIso(String simCountryIso) {
setSimCountryIso(/* subId= */ 0, simCountryIso);
}
/** Sets the {@code simCountryIso} for the given {@code subId}. */
public void setSimCountryIso(int subId, String simCountryIso) {
simCountryIsoMap.put(subId, simCountryIso);
}
/** Clears {@code subId} to simCountryIso mapping and resets to default state. */
public void resetSimCountryIsos() {
simCountryIsoMap.clear();
simCountryIsoMap.put(0, "");
}
@Implementation
protected int getSimState() {
return getSimState(/* slotIndex= */ 0);
}
/** Sets the sim state of slot 0. */
public void setSimState(int simState) {
setSimState(/* slotIndex= */ 0, simState);
}
/** Set the sim state for the given {@code slotIndex}. */
public void setSimState(int slotIndex, int state) {
simStates.put(slotIndex, state);
}
@Implementation(minSdk = O)
protected int getSimState(int slotIndex) {
return simStates.get(slotIndex, TelephonyManager.SIM_STATE_UNKNOWN);
}
/** Sets the UICC slots information returned by {@link #getUiccSlotsInfo()}. */
public void setUiccSlotsInfo(/*UiccSlotInfo[]*/ Object uiccSlotsInfos) {
this.uiccSlotInfos = uiccSlotsInfos;
}
/** Returns the UICC slots information set by {@link #setUiccSlotsInfo}. */
@Implementation(minSdk = P)
@HiddenApi
protected /*UiccSlotInfo[]*/ Object getUiccSlotsInfo() {
return uiccSlotInfos;
}
/** Sets the UICC cards information returned by {@link #getUiccCardsInfo()}. */
public void setUiccCardsInfo(/*UiccCardsInfo[]*/ Object uiccCardsInfo) {
this.uiccCardsInfo = uiccCardsInfo;
}
/** Returns the UICC cards information set by {@link #setUiccCardsInfo}. */
@Implementation(minSdk = Q)
@HiddenApi
protected /*UiccSlotInfo[]*/ Object getUiccCardsInfo() {
return uiccCardsInfo;
}
/** Clears {@code slotIndex} to state mapping and resets to default state. */
public void resetSimStates() {
simStates.clear();
simStates.put(0, TelephonyManager.SIM_STATE_READY);
}
public void setReadPhoneStatePermission(boolean readPhoneStatePermission) {
this.readPhoneStatePermission = readPhoneStatePermission;
}
private void checkReadPhoneStatePermission() {
if (!readPhoneStatePermission) {
throw new SecurityException();
}
}
private void checkReadPrivilegedPhoneStatePermission() {
if (!checkPermission(permission.READ_PRIVILEGED_PHONE_STATE)) {
throw new SecurityException();
}
}
static ShadowInstrumentation getShadowInstrumentation() {
ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
return Shadow.extract(activityThread.getInstrumentation());
}
static boolean checkPermission(String permission) {
return getShadowInstrumentation()
.checkPermission(permission, android.os.Process.myPid(), android.os.Process.myUid())
== PERMISSION_GRANTED;
}
@Implementation
protected int getPhoneType() {
return phoneType;
}
@Implementation(minSdk = LOLLIPOP_MR1)
public void setPhoneType(int phoneType) {
this.phoneType = phoneType;
}
@Implementation
protected String getLine1Number() {
checkReadPhoneStatePermission();
return line1Number;
}
public void setLine1Number(String line1Number) {
this.line1Number = line1Number;
}
@Implementation
protected int getNetworkType() {
checkReadPhoneStatePermission();
return networkType;
}
/**
* @deprecated {@link TelephonyManager#getNetworkType()} was replaced with {@link
* TelephonyManager#getDataNetworkType()} in Android N, and has been deprecated in Android R.
* Use {@link #setDataNetworkType instead}.
*/
@Deprecated
public void setNetworkType(int networkType) {
this.networkType = networkType;
}
/**
* Returns whatever value was set by the last call to {@link #setDataNetworkType}, defaulting to
* {@link TelephonyManager#NETWORK_TYPE_UNKNOWN} if it was never called.
*/
@Implementation(minSdk = N)
protected int getDataNetworkType() {
checkReadPhoneStatePermission();
return dataNetworkType;
}
/**
* Sets the value to be returned by calls to {@link #getDataNetworkType}. This <b>should</b>
* correspond to one of the {@code NETWORK_TYPE_*} constants defined on {@link TelephonyManager},
* but this is not enforced.
*/
@Implementation(minSdk = LOLLIPOP_MR1)
public void setDataNetworkType(int dataNetworkType) {
this.dataNetworkType = dataNetworkType;
}
/**
* Returns whatever value was set by the last call to {@link #setVoiceNetworkType}, defaulting to
* {@link TelephonyManager#NETWORK_TYPE_UNKNOWN} if it was never called.
*
* <p>An exception will be thrown if the READ_PHONE_STATE permission has not been granted.
*/
@Implementation(minSdk = N)
protected int getVoiceNetworkType() {
checkReadPhoneStatePermission();
return voiceNetworkType;
}
/**
* Sets the value to be returned by calls to {@link getVoiceNetworkType}. This <b>should</b>
* correspond to one of the {@code NETWORK_TYPE_*} constants defined on {@link TelephonyManager},
* but this is not enforced.
*/
public void setVoiceNetworkType(int voiceNetworkType) {
this.voiceNetworkType = voiceNetworkType;
}
@Implementation(minSdk = JELLY_BEAN_MR1)
protected List<CellInfo> getAllCellInfo() {
return allCellInfo;
}
public void setAllCellInfo(List<CellInfo> allCellInfo) {
this.allCellInfo = allCellInfo;
if (VERSION.SDK_INT >= JELLY_BEAN_MR1) {
for (PhoneStateListener listener : getListenersForFlags(LISTEN_CELL_INFO)) {
listener.onCellInfoChanged(allCellInfo);
}
}
if (VERSION.SDK_INT >= S) {
for (CellInfoListener listener : getCallbackForListener(CellInfoListener.class)) {
listener.onCellInfoChanged(allCellInfo);
}
}
}
/**
* Returns the value set by {@link #setCallbackCellInfos}, defaulting to calling the real {@link
* TelephonyManager#NETWORK_TYPE_UNKNOWN} if it was never called.
*/
@Implementation(minSdk = Q)
protected void requestCellInfoUpdate(Object cellInfoExecutor, Object cellInfoCallback) {
Executor executor = (Executor) cellInfoExecutor;
if (callbackCellInfos == null) {
// ignore
} else if (requestCellInfoUpdateErrorCode != 0 || requestCellInfoUpdateDetail != null) {
// perform the "failure" callback operation via the specified executor
executor.execute(
() -> {
// Must cast 'callback' inside the anonymous class to avoid NoClassDefFoundError when
// referring to 'CellInfoCallback'.
CellInfoCallback callback = (CellInfoCallback) cellInfoCallback;
callback.onError(requestCellInfoUpdateErrorCode, requestCellInfoUpdateDetail);
});
} else {
// perform the "success" callback operation via the specified executor
executor.execute(
() -> {
// Must cast 'callback' inside the anonymous class to avoid NoClassDefFoundError when
// referring to 'CellInfoCallback'.
CellInfoCallback callback = (CellInfoCallback) cellInfoCallback;
callback.onCellInfo(callbackCellInfos);
});
}
}
/**
* Sets the value to be returned by calls to {@link requestCellInfoUpdate}. Note that it does not
* set the value to be returned by calls to {@link getAllCellInfo}; for that, see {@link
* setAllCellInfo}.
*/
public void setCallbackCellInfos(List<CellInfo> callbackCellInfos) {
this.callbackCellInfos = callbackCellInfos;
}
/**
* Sets the values to be returned by a presumed error condition in {@link requestCellInfoUpdate}.
* These values will persist until cleared: to clear, set (0, null) using this method.
*/
public void setRequestCellInfoUpdateErrorValues(int errorCode, Throwable detail) {
requestCellInfoUpdateErrorCode = errorCode;
requestCellInfoUpdateDetail = detail;
}
@Implementation
protected CellLocation getCellLocation() {
return this.cellLocation;
}
public void setCellLocation(CellLocation cellLocation) {
this.cellLocation = cellLocation;
for (PhoneStateListener listener : getListenersForFlags(LISTEN_CELL_LOCATION)) {
listener.onCellLocationChanged(cellLocation);
}
if (VERSION.SDK_INT >= S) {
for (CellLocationListener listener : getCallbackForListener(CellLocationListener.class)) {
listener.onCellLocationChanged(cellLocation);
}
}
}
@Implementation(minSdk = JELLY_BEAN_MR2)
protected String getGroupIdLevel1() {
checkReadPhoneStatePermission();
return this.groupIdLevel1;
}
public void setGroupIdLevel1(String groupIdLevel1) {
this.groupIdLevel1 = groupIdLevel1;
}
@CallSuper
protected void initListener(PhoneStateListener listener, int flags) {
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);
}
}
if ((flags & LISTEN_CELL_LOCATION) != 0) {
listener.onCellLocationChanged(cellLocation);
}
if (telephonyDisplayInfo != null
&& ((flags & PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED) != 0)) {
listener.onDisplayInfoChanged((TelephonyDisplayInfo) telephonyDisplayInfo);
}
if (serviceState != null && ((flags & PhoneStateListener.LISTEN_SERVICE_STATE) != 0)) {
listener.onServiceStateChanged(serviceState);
}
}
@CallSuper
protected void initTelephonyCallback(Object callback) {
if (VERSION.SDK_INT < S) {
return;
}
if (callback instanceof CallStateListener) {
((CallStateListener) callback).onCallStateChanged(callState);
}
if (callback instanceof CellInfoListener) {
((CellInfoListener) callback).onCellInfoChanged(allCellInfo);
}
if (callback instanceof CellLocationListener) {
((CellLocationListener) callback).onCellLocationChanged(cellLocation);
}
if (telephonyDisplayInfo != null && callback instanceof DisplayInfoListener) {
((DisplayInfoListener) callback)
.onDisplayInfoChanged((TelephonyDisplayInfo) telephonyDisplayInfo);
}
if (serviceState != null && callback instanceof ServiceStateListener) {
((ServiceStateListener) callback).onServiceStateChanged(serviceState);
}
}
protected Iterable<PhoneStateListener> getListenersForFlags(int flags) {
return Iterables.filter(
phoneStateRegistrations.keySet(),
new Predicate<PhoneStateListener>() {
@Override
public boolean apply(PhoneStateListener input) {
// only select PhoneStateListeners with matching flags
return (phoneStateRegistrations.get(input) & flags) != 0;
}
});
}
/**
* Returns a view of {@code telephonyCallbackRegistrations} containing all elements that are of
* the type {@code clazz}.
*/
protected <T> Iterable<T> getCallbackForListener(Class<T> clazz) {
// Only selects TelephonyCallback with matching class.
return Iterables.filter(telephonyCallbackRegistrations, clazz);
}
/**
* @return true by default, or the value specified via {@link #setIsSmsCapable(boolean)}
*/
@Implementation
protected boolean isSmsCapable() {
return isSmsCapable;
}
/** Sets the value returned by {@link TelephonyManager#isSmsCapable()}. */
public void setIsSmsCapable(boolean isSmsCapable) {
this.isSmsCapable = isSmsCapable;
}
/**
* Returns a new empty {@link PersistableBundle} by default, or the value specified via {@link
* #setCarrierConfig(PersistableBundle)}.
*/
@Implementation(minSdk = O)
protected PersistableBundle getCarrierConfig() {
checkReadPhoneStatePermission();
return carrierConfig != null ? carrierConfig : new PersistableBundle();
}
/**
* Sets the value returned by {@link TelephonyManager#getCarrierConfig()}.
*
* @param carrierConfig
*/
public void setCarrierConfig(PersistableBundle carrierConfig) {
this.carrierConfig = carrierConfig;
}
/**
* Returns {@code null} by default, or the value specified via {@link
* #setVoiceMailNumber(String)}.
*/
@Implementation
protected String getVoiceMailNumber() {
checkReadPhoneStatePermission();
return voiceMailNumber;
}
/** Sets the value returned by {@link TelephonyManager#getVoiceMailNumber()}. */
public void setVoiceMailNumber(String voiceMailNumber) {
this.voiceMailNumber = voiceMailNumber;
}
/**
* Returns {@code null} by default or the value specified via {@link
* #setVoiceMailAlphaTag(String)}.
*/
@Implementation
protected String getVoiceMailAlphaTag() {
checkReadPhoneStatePermission();
return voiceMailAlphaTag;
}
/** Sets the value returned by {@link TelephonyManager#getVoiceMailAlphaTag()}. */
public void setVoiceMailAlphaTag(String voiceMailAlphaTag) {
this.voiceMailAlphaTag = voiceMailAlphaTag;
}
/** Returns 1 by default or the value specified via {@link #setPhoneCount(int)}. */
@Implementation(minSdk = M)
protected int getPhoneCount() {
return phoneCount;
}
/** Sets the value returned by {@link TelephonyManager#getPhoneCount()}. */
public void setPhoneCount(int phoneCount) {
this.phoneCount = phoneCount;
}
/** Returns 1 by default or the value specified via {@link #setActiveModemCount(int)}. */
@Implementation(minSdk = R)
protected int getActiveModemCount() {
return activeModemCount;
}
/** Sets the value returned by {@link TelephonyManager#getActiveModemCount()}. */
public void setActiveModemCount(int activeModemCount) {
this.activeModemCount = activeModemCount;
}
/**
* Returns {@code null} by default or the value specified via {@link #setDeviceId(int, String)}.
*/
@Implementation(minSdk = M)
protected String getDeviceId(int slot) {
return slotIndexToDeviceId.get(slot);
}
/** Sets the value returned by {@link TelephonyManager#getDeviceId(int)}. */
public void setDeviceId(int slot, String deviceId) {
slotIndexToDeviceId.put(slot, deviceId);
}
/**
* Returns {@code true} by default or the value specified via {@link #setVoiceCapable(boolean)}.
*/
@Implementation(minSdk = LOLLIPOP_MR1)
protected boolean isVoiceCapable() {
return voiceCapable;
}
/** Sets the value returned by {@link #isVoiceCapable()}. */
public void setVoiceCapable(boolean voiceCapable) {
this.voiceCapable = voiceCapable;
}
/**
* Returns {@code null} by default or the value specified via {@link
* #setVoicemailVibrationEnabled(PhoneAccountHandle, boolean)}.
*/
@Implementation(minSdk = N)
protected boolean isVoicemailVibrationEnabled(PhoneAccountHandle handle) {
Boolean result = voicemailVibrationEnabledMap.get(handle);
return result != null ? result : false;
}
/**
* Sets the value returned by {@link
* TelephonyManager#isVoicemailVibrationEnabled(PhoneAccountHandle)}.
*/
@Implementation(minSdk = O)
protected void setVoicemailVibrationEnabled(PhoneAccountHandle handle, boolean isEnabled) {
voicemailVibrationEnabledMap.put(handle, isEnabled);
}
/**
* Returns {@code null} by default or the value specified via {@link
* #setVoicemailRingtoneUri(PhoneAccountHandle, Uri)}.
*/
@Implementation(minSdk = N)
protected Uri getVoicemailRingtoneUri(PhoneAccountHandle handle) {
return voicemailRingtoneUriMap.get(handle);
}
/**
* Sets the value returned by {@link
* TelephonyManager#getVoicemailRingtoneUri(PhoneAccountHandle)}.
*/
@Implementation(minSdk = O)
protected void setVoicemailRingtoneUri(PhoneAccountHandle handle, Uri uri) {
voicemailRingtoneUriMap.put(handle, uri);
}
/**
* Returns {@code null} by default or the value specified via {@link
* #setTelephonyManagerForHandle(PhoneAccountHandle, TelephonyManager)}.
*/
@Implementation(minSdk = O)
protected TelephonyManager createForPhoneAccountHandle(PhoneAccountHandle handle) {
checkReadPhoneStatePermission();
return phoneAccountToTelephonyManagers.get(handle);
}
/**
* Sets the value returned by {@link
* TelephonyManager#createForPhoneAccountHandle(PhoneAccountHandle)}.
*/
public void setTelephonyManagerForHandle(
PhoneAccountHandle handle, TelephonyManager telephonyManager) {
phoneAccountToTelephonyManagers.put(handle, telephonyManager);
}
/**
* Returns {@code null} by default or the value specified via {@link
* #setTelephonyManagerForSubscriptionId(int, TelephonyManager)}
*/
@Implementation(minSdk = N)
protected TelephonyManager createForSubscriptionId(int subId) {
return subscriptionIdsToTelephonyManagers.get(subId);
}
/** Sets the value returned by {@link TelephonyManager#createForSubscriptionId(int)}. */
public void setTelephonyManagerForSubscriptionId(
int subscriptionId, TelephonyManager telephonyManager) {
subscriptionIdsToTelephonyManagers.put(subscriptionId, telephonyManager);
}
/**
* Returns {@code null} by default or the value specified via {@link
* #setServiceState(ServiceState)}
*/
@Implementation(minSdk = O)
protected ServiceState getServiceState() {
checkReadPhoneStatePermission();
return serviceState;
}
/**
* Returns {@code null} by default or the value specified via {@link
* #setServiceState(ServiceState)}
*/
@Implementation(minSdk = TIRAMISU)
protected ServiceState getServiceState(int includeLocationData) {
return getServiceState();
}
/** Sets the value returned by {@link TelephonyManager#getServiceState()}. */
public void setServiceState(ServiceState serviceState) {
this.serviceState = serviceState;
for (PhoneStateListener listener : getListenersForFlags(LISTEN_SERVICE_STATE)) {
listener.onServiceStateChanged(serviceState);
}
if (VERSION.SDK_INT >= S) {
for (ServiceStateListener listener : getCallbackForListener(ServiceStateListener.class)) {
listener.onServiceStateChanged(serviceState);
}
}
}
/**
* Returns {@code false} by default or the value specified via {@link
* #setIsNetworkRoaming(boolean)}
*/
@Implementation
protected boolean isNetworkRoaming() {
return isNetworkRoaming;
}
/** Sets the value returned by {@link TelephonyManager#isNetworkRoaming()}. */
public void setIsNetworkRoaming(boolean isNetworkRoaming) {
this.isNetworkRoaming = isNetworkRoaming;
}
@Implementation(minSdk = M)
@HiddenApi
protected int getCurrentPhoneType(int subId) {
return currentPhoneTypes.get(subId, TelephonyManager.PHONE_TYPE_NONE);
}
/** Sets the phone type for the given {@code subId}. */
public void setCurrentPhoneType(int subId, int phoneType) {
currentPhoneTypes.put(subId, phoneType);
}
/** Removes all {@code subId} to {@code phoneType} mappings. */
public void clearPhoneTypes() {
currentPhoneTypes.clear();
}
@Implementation(minSdk = M)
@HiddenApi
protected List<String> getCarrierPackageNamesForIntentAndPhone(Intent intent, int phoneId) {
return carrierPackageNames.get(phoneId);
}
@Implementation(minSdk = LOLLIPOP)
@HiddenApi
protected List<String> getCarrierPackageNamesForIntent(Intent intent) {
return carrierPackageNames.get(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
}
/** Sets the {@code packages} for the given {@code phoneId}. */
public void setCarrierPackageNamesForPhone(int phoneId, List<String> packages) {
carrierPackageNames.put(phoneId, packages);
}
@Implementation(minSdk = Q)
protected int getCarrierIdFromSimMccMnc() {
return carrierIdFromSimMccMnc;
}
/** Sets the value to be returned by {@link #getCarrierIdFromSimMccMnc()}. */
public void setCarrierIdFromSimMccMnc(int carrierIdFromSimMccMnc) {
this.carrierIdFromSimMccMnc = carrierIdFromSimMccMnc;
}
@Implementation(minSdk = P)
protected int getSimCarrierId() {
return simCarrierId;
}
/** Sets the value to be returned by {@link #getSimCarrierId()}. */
public void setSimCarrierId(int simCarrierId) {
this.simCarrierId = simCarrierId;
}
@Implementation
protected String getSubscriberId() {
checkReadPhoneStatePermission();
return subscriberId;
}
/** Sets the value to be returned by {@link #getSubscriberId()}. */
public void setSubscriberId(String subscriberId) {
this.subscriberId = subscriberId;
}
/** Returns the value set by {@link #setVisualVoicemailPackageName(String)}. */
@Implementation(minSdk = O)
protected String getVisualVoicemailPackageName() {
checkReadPhoneStatePermission();
return visualVoicemailPackageName;
}
/** Sets the value to be returned by {@link #getVisualVoicemailPackageName()}. */
public void setVisualVoicemailPackageName(String visualVoicemailPackageName) {
this.visualVoicemailPackageName = visualVoicemailPackageName;
}
@Implementation(minSdk = P)
protected SignalStrength getSignalStrength() {
return signalStrength;
}
/** Sets the value to be returned by {@link #getSignalStrength()} */
public void setSignalStrength(SignalStrength signalStrength) {
this.signalStrength = signalStrength;
for (PhoneStateListener listener :
getListenersForFlags(PhoneStateListener.LISTEN_SIGNAL_STRENGTHS)) {
listener.onSignalStrengthsChanged(signalStrength);
}
if (VERSION.SDK_INT >= S) {
for (SignalStrengthsListener listener :
getCallbackForListener(SignalStrengthsListener.class)) {
listener.onSignalStrengthsChanged(signalStrength);
}
}
}
/**
* Cribbed from {@link android.telephony.PhoneNumberUtils#isEmergencyNumberInternal}.
*
* <p>TODO: need better implementation
*/
@Implementation(minSdk = Build.VERSION_CODES.Q)
protected boolean isEmergencyNumber(String number) {
if (ShadowServiceManager.getService(Context.TELEPHONY_SERVICE) == null) {
throw new IllegalStateException("telephony service is null.");
}
if (number == null) {
return false;
}
Context context = ReflectionHelpers.getField(realTelephonyManager, "mContext");
Locale locale = context == null ? null : context.getResources().getConfiguration().locale;
String defaultCountryIso = locale == null ? null : locale.getCountry();
int slotId = -1;
boolean useExactMatch = true;
// retrieve the list of emergency numbers
// check read-write ecclist property first
String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId);
String emergencyNumbers = SystemProperties.get(ecclist, "");
if (TextUtils.isEmpty(emergencyNumbers)) {
// then read-only ecclist property since old RIL only uses this
emergencyNumbers = SystemProperties.get("ro.ril.ecclist");
}
if (!TextUtils.isEmpty(emergencyNumbers)) {
// searches through the comma-separated list for a match,
// return true if one is found.
for (String emergencyNum : emergencyNumbers.split(",")) {
// It is not possible to append additional digits to an emergency number to dial
// the number in Brazil - it won't connect.
if (useExactMatch || "BR".equalsIgnoreCase(defaultCountryIso)) {
if (number.equals(emergencyNum)) {
return true;
}
} else {
if (number.startsWith(emergencyNum)) {
return true;
}
}
}
// no matches found against the list!
return false;
}
emergencyNumbers = ((slotId < 0) ? "112,911,000,08,110,118,119,999" : "112,911");
for (String emergencyNum : emergencyNumbers.split(",")) {
if (useExactMatch) {
if (number.equals(emergencyNum)) {
return true;
}
} else {
if (number.startsWith(emergencyNum)) {
return true;
}
}
}
return false;
}
/**
* Emergency Callback Mode (ECBM) is typically set by the carrier, for a time window of 5 minutes
* after the last outgoing emergency call. The user can exit ECBM via a system notification.
*
* @param emergencyCallbackMode whether the device is in ECBM or not.
*/
public void setEmergencyCallbackMode(boolean emergencyCallbackMode) {
this.emergencyCallbackMode = emergencyCallbackMode;
}
@Implementation(minSdk = Build.VERSION_CODES.O)
protected boolean getEmergencyCallbackMode() {
checkReadPrivilegedPhoneStatePermission();
return emergencyCallbackMode;
}
@Implementation(minSdk = Build.VERSION_CODES.Q)
protected boolean isPotentialEmergencyNumber(String number) {
return isEmergencyNumber(number);
}
/**
* Implementation for {@link TelephonyManager#isDataEnabled}.
*
* @return False by default, unless set with {@link TelephonyManager#setDataEnabled}.
*/
@Implementation(minSdk = Build.VERSION_CODES.O)
protected boolean isDataEnabled() {
checkReadPhoneStatePermission();
return dataEnabled;
}
/**
* Implementation for {@link TelephonyManager#setDataEnabled}. Marked as public in order to allow
* it to be used as a test API.
*/
@Implementation(minSdk = Build.VERSION_CODES.O)
public void setDataEnabled(boolean enabled) {
dataEnabled = enabled;
}
/**
* Implementation for {@link TelephonyManager#isRttSupported}.
*
* @return False by default, unless set with {@link #setRttSupported(boolean)}.
*/
@Implementation(minSdk = Build.VERSION_CODES.Q)
protected boolean isRttSupported() {
return isRttSupported;
}
/** Sets the value to be returned by {@link #isRttSupported()} */
public void setRttSupported(boolean isRttSupported) {
this.isRttSupported = isRttSupported;
}
/**
* Implementation for {@link TelephonyManager#sendDialerSpecialCode(String)}.
*
* @param inputCode special code to be sent.
*/
@Implementation(minSdk = O)
public void sendDialerSpecialCode(String inputCode) {
sentDialerSpecialCodes.add(inputCode);
}
/**
* Returns immutable list of special codes sent using {@link
* TelephonyManager#sendDialerSpecialCode(String)}. Special codes contained in the list are in the
* order they were sent.
*/
public List<String> getSentDialerSpecialCodes() {
return ImmutableList.copyOf(sentDialerSpecialCodes);
}
/** Sets the value to be returned by {@link #isHearingAidCompatibilitySupported()}. */
public void setHearingAidCompatibilitySupported(boolean isSupported) {
hearingAidCompatibilitySupported = isSupported;
}
/**
* Implementation for {@link TelephonyManager#isHearingAidCompatibilitySupported()}.
*
* @return False by default, unless set with {@link
* #setHearingAidCompatibilitySupported(boolean)}.
*/
@Implementation(minSdk = M)
protected boolean isHearingAidCompatibilitySupported() {
return hearingAidCompatibilitySupported;
}
/**
* Creates a {@link TelephonyDisplayInfo}.
*
* @param networkType The packet-switching cellular network type (see {@link NetworkType})
* @param overrideNetworkType The override network type (see {@link OverrideNetworkType})
*/
public static Object createTelephonyDisplayInfo(
@NetworkType int networkType, @OverrideNetworkType int overrideNetworkType) {
return new TelephonyDisplayInfo(networkType, overrideNetworkType);
}
/**
* Sets the current {@link TelephonyDisplayInfo}, and notifies all the {@link PhoneStateListener}s
* that were registered with the {@link PhoneStateListener#LISTEN_DISPLAY_INFO_CHANGED} flag.
*
* @param telephonyDisplayInfo The {@link TelephonyDisplayInfo} to set. May not be null.
* @throws NullPointerException if telephonyDisplayInfo is null.
*/
public void setTelephonyDisplayInfo(Object telephonyDisplayInfo) {
Preconditions.checkNotNull(telephonyDisplayInfo);
this.telephonyDisplayInfo = telephonyDisplayInfo;
for (PhoneStateListener listener :
getListenersForFlags(PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED)) {
listener.onDisplayInfoChanged((TelephonyDisplayInfo) telephonyDisplayInfo);
}
if (VERSION.SDK_INT >= S) {
for (DisplayInfoListener listener : getCallbackForListener(DisplayInfoListener.class)) {
listener.onDisplayInfoChanged((TelephonyDisplayInfo) telephonyDisplayInfo);
}
}
}
@Implementation(minSdk = R)
@HiddenApi
protected boolean isDataConnectionAllowed() {
checkReadPhoneStatePermission();
return isDataConnectionAllowed;
}
public void setIsDataConnectionAllowed(boolean isDataConnectionAllowed) {
this.isDataConnectionAllowed = isDataConnectionAllowed;
}
@Implementation(minSdk = O)
public void sendVisualVoicemailSms(
String number, int port, String text, PendingIntent sentIntent) {
lastVisualVoicemailSmsParams = new VisualVoicemailSmsParams(number, port, text, sentIntent);
}
public VisualVoicemailSmsParams getLastSentVisualVoicemailSmsParams() {
return lastVisualVoicemailSmsParams;
}
/**
* Implementation for {@link
* TelephonyManager#setVisualVoicemailSmsFilterSettings(VisualVoicemailSmsFilterSettings)}.
*
* @param settings The settings for the filter, or null to disable the filter.
*/
@Implementation(minSdk = O)
public void setVisualVoicemailSmsFilterSettings(VisualVoicemailSmsFilterSettings settings) {
visualVoicemailSmsFilterSettings = settings;
}
/** Returns the last set {@link VisualVoicemailSmsFilterSettings}. */
public VisualVoicemailSmsFilterSettings getVisualVoicemailSmsFilterSettings() {
return visualVoicemailSmsFilterSettings;
}
/** Testable parameters from calls to {@link #sendVisualVoicemailSms}. */
public static class VisualVoicemailSmsParams {
private final String destinationAddress;
private final int port;
private final String text;
private final PendingIntent sentIntent;
public VisualVoicemailSmsParams(
String destinationAddress, int port, String text, PendingIntent sentIntent) {
this.destinationAddress = destinationAddress;
this.port = port;
this.text = text;
this.sentIntent = sentIntent;
}
public String getDestinationAddress() {
return destinationAddress;
}
public int getPort() {
return port;
}
public String getText() {
return text;
}
public PendingIntent getSentIntent() {
return sentIntent;
}
}
}