package android.telephony;
+import android.annotation.PrivateApi;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.telephony.Rlog;
import android.util.Log;
import com.android.internal.telephony.IPhoneSubInfo;
import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.ITelephonyListener;
import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.RILConstants;
import java.io.FileInputStream;
import java.io.IOException;
+import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TelephonyManager {
private static final String TAG = "TelephonyManager";
- private static Context sContext;
private static ITelephonyRegistry sRegistry;
+ private final HashMap<CallStateListener,Listener> mListeners
+ = new HashMap<CallStateListener,Listener>();
+ private final Context mContext;
+
+ private static class Listener extends ITelephonyListener.Stub {
+ final CallStateListener mListener;
+ private static final int WHAT = 1;
+
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ mListener.onCallStateChanged(msg.arg1, msg.arg2, (String)msg.obj);
+ }
+ };
+
+ Listener(CallStateListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void onUpdate(final int callId, final int state, final String number) {
+ if (mHandler != null) {
+ mHandler.sendMessage(mHandler.obtainMessage(WHAT, callId, state, number));
+ }
+ }
+
+ void clearQueue() {
+ mHandler.removeMessages(WHAT);
+
+ // Don't accept more incoming binder calls either.
+ mHandler = null;
+ }
+ }
+
/** @hide */
public TelephonyManager(Context context) {
- if (sContext == null) {
- Context appContext = context.getApplicationContext();
- if (appContext != null) {
- sContext = appContext;
- } else {
- sContext = context;
- }
+ Context appContext = context.getApplicationContext();
+ if (appContext != null) {
+ mContext = appContext;
+ } else {
+ mContext = context;
+ }
+ if (sRegistry == null) {
sRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
"telephony.registry"));
}
/** @hide */
private TelephonyManager() {
+ mContext = null;
}
private static TelephonyManager sInstance = new TelephonyManager();
"android.intent.action.PHONE_STATE";
/**
+ * The Phone app sends this intent when a user opts to respond-via-message during an incoming
+ * call. By default, the device's default SMS app consumes this message and sends a text message
+ * to the caller. A third party app can also provide this functionality by consuming this Intent
+ * with a {@link android.app.Service} and sending the message using its own messaging system.
+ * <p>The intent contains a URI (available from {@link android.content.Intent#getData})
+ * describing the recipient, using either the {@code sms:}, {@code smsto:}, {@code mms:},
+ * or {@code mmsto:} URI schema. Each of these URI schema carry the recipient information the
+ * same way: the path part of the URI contains the recipient's phone number or a comma-separated
+ * set of phone numbers if there are multiple recipients. For example, {@code
+ * smsto:2065551234}.</p>
+ *
+ * <p>The intent may also contain extras for the message text (in {@link
+ * android.content.Intent#EXTRA_TEXT}) and a message subject
+ * (in {@link android.content.Intent#EXTRA_SUBJECT}).</p>
+ *
+ * <p class="note"><strong>Note:</strong>
+ * The intent-filter that consumes this Intent needs to be in a {@link android.app.Service}
+ * that requires the
+ * permission {@link android.Manifest.permission#SEND_RESPOND_VIA_MESSAGE}.</p>
+ * <p>For example, the service that receives this intent can be declared in the manifest file
+ * with an intent filter like this:</p>
+ * <pre>
+ * <!-- Service that delivers SMS messages received from the phone "quick response" -->
+ * <service android:name=".HeadlessSmsSendService"
+ * android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+ * android:exported="true" >
+ * <intent-filter>
+ * <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+ * <category android:name="android.intent.category.DEFAULT" />
+ * <data android:scheme="sms" />
+ * <data android:scheme="smsto" />
+ * <data android:scheme="mms" />
+ * <data android:scheme="mmsto" />
+ * </intent-filter>
+ * </service></pre>
+ * <p>
+ * Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_RESPOND_VIA_MESSAGE =
+ "android.intent.action.RESPOND_VIA_MESSAGE";
+
+ /**
* The lookup key used with the {@link #ACTION_PHONE_STATE_CHANGED} broadcast
* for a String containing the new call state.
*
/**
* Returns the current location of the device.
- * Return null if current location is not available.
+ *<p>
+ * If there is only one radio in the device and that radio has an LTE connection,
+ * this method will return null. The implementation must not to try add LTE
+ * identifiers into the existing cdma/gsm classes.
+ *<p>
+ * In the future this call will be deprecated.
+ *<p>
+ * @return Current location of the device or null if not available.
*
* <p>Requires Permission:
* {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
public CellLocation getCellLocation() {
try {
Bundle bundle = getITelephony().getCellLocation();
+ if (bundle.isEmpty()) return null;
CellLocation cl = CellLocation.newFromBundle(bundle);
if (cl.isEmpty())
return null;
}
/**
- * Returns the neighboring cell information of the device.
- *
+ * Returns the neighboring cell information of the device. The getAllCellInfo is preferred
+ * and use this only if getAllCellInfo return nulls or an empty list.
+ *<p>
+ * In the future this call will be deprecated.
+ *<p>
* @return List of NeighboringCellInfo or null if info unavailable.
*
* <p>Requires Permission:
*/
public List<NeighboringCellInfo> getNeighboringCellInfo() {
try {
- return getITelephony().getNeighboringCellInfo();
+ return getITelephony().getNeighboringCellInfo(mContext.getOpPackageName());
} catch (RemoteException ex) {
return null;
} catch (NullPointerException ex) {
* This function returns the type of the phone, depending
* on the network mode.
*
- * @param network mode
+ * @param networkMode
* @return Phone Type
*
* @hide
case RILConstants.NETWORK_MODE_GSM_ONLY:
case RILConstants.NETWORK_MODE_WCDMA_ONLY:
case RILConstants.NETWORK_MODE_GSM_UMTS:
+ case RILConstants.NETWORK_MODE_LTE_GSM_WCDMA:
+ case RILConstants.NETWORK_MODE_LTE_WCDMA:
+ case RILConstants.NETWORK_MODE_LTE_CMDA_EVDO_GSM_WCDMA:
return PhoneConstants.PHONE_TYPE_GSM;
// Use CDMA Phone for the global mode including CDMA
case RILConstants.NETWORK_MODE_GLOBAL:
case RILConstants.NETWORK_MODE_LTE_CDMA_EVDO:
- case RILConstants.NETWORK_MODE_LTE_CMDA_EVDO_GSM_WCDMA:
return PhoneConstants.PHONE_TYPE_CDMA;
case RILConstants.NETWORK_MODE_LTE_ONLY:
cmdline = new String(buffer, 0, count);
}
} catch (IOException e) {
- Log.d(TAG, "No /proc/cmdline exception=" + e);
+ Rlog.d(TAG, "No /proc/cmdline exception=" + e);
} finally {
if (is != null) {
try {
}
}
}
- Log.d(TAG, "/proc/cmdline=" + cmdline);
+ Rlog.d(TAG, "/proc/cmdline=" + cmdline);
return cmdline;
}
}
}
- Log.d(TAG, "getLteOnCdmaMode=" + retVal + " curVal=" + curVal +
+ Rlog.d(TAG, "getLteOnCdmaMode=" + retVal + " curVal=" + curVal +
" product_type='" + productType +
"' lteOnCdmaProductType='" + sLteOnCdmaProductType + "'");
return retVal;
public static final int NETWORK_TYPE_HSPAP = 15;
/**
+ * @return the NETWORK_TYPE_xxxx for current data connection.
+ */
+ public int getNetworkType() {
+ return getDataNetworkType();
+ }
+
+ /**
* Returns a constant indicating the radio technology (network type)
* currently in use on the device for data transmission.
* @return the network type
* @see #NETWORK_TYPE_LTE
* @see #NETWORK_TYPE_EHRPD
* @see #NETWORK_TYPE_HSPAP
+ *
+ * @hide
*/
- public int getNetworkType() {
+ public int getDataNetworkType() {
try{
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.getNetworkType();
+ return telephony.getDataNetworkType();
+ } else {
+ // This can happen when the ITelephony interface is not up yet.
+ return NETWORK_TYPE_UNKNOWN;
+ }
+ } catch(RemoteException ex) {
+ // This shouldn't happen in the normal case
+ return NETWORK_TYPE_UNKNOWN;
+ } catch (NullPointerException ex) {
+ // This could happen before phone restarts due to crashing
+ return NETWORK_TYPE_UNKNOWN;
+ }
+ }
+
+ /**
+ * Returns the NETWORK_TYPE_xxxx for voice
+ *
+ * @hide
+ */
+ public int getVoiceNetworkType() {
+ try{
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getVoiceNetworkType();
} else {
// This can happen when the ITelephony interface is not up yet.
return NETWORK_TYPE_UNKNOWN;
* is a tri-state return value as for a period of time
* the mode may be unknown.
*
- * @return {@link Phone#LTE_ON_CDMA_UNKNOWN}, {@link Phone#LTE_ON_CDMA_FALSE}
- * or {@link Phone#LTE_ON_CDMA_TRUE}
+ * @return {@link PhoneConstants#LTE_ON_CDMA_UNKNOWN}, {@link PhoneConstants#LTE_ON_CDMA_FALSE}
+ * or {@link PhoneConstants#LTE_ON_CDMA_TRUE}
*
* @hide
*/
}
/**
+ * Returns the Group Identifier Level1 for a GSM phone.
+ * Return null if it is unavailable.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ */
+ public String getGroupIdLevel1() {
+ try {
+ return getSubscriberInfo().getGroupIdLevel1();
+ } catch (RemoteException ex) {
+ return null;
+ } catch (NullPointerException ex) {
+ // This could happen before phone restarts due to crashing
+ return null;
+ }
+ }
+
+ /**
* Returns the phone number string for line 1, for example, the MSISDN
* for a GSM phone. Return null if it is unavailable.
* <p>
* LISTEN_ flags.
*/
public void listen(PhoneStateListener listener, int events) {
- String pkgForDebug = sContext != null ? sContext.getPackageName() : "<unknown>";
+ String pkgForDebug = mContext != null ? mContext.getPackageName() : "<unknown>";
try {
- Boolean notifyNow = (getITelephony() != null);
+ Boolean notifyNow = true;
sRegistry.listen(pkgForDebug, listener.callback, events, notifyNow);
} catch (RemoteException ex) {
// system process dead
* @hide pending API review
*/
public boolean isVoiceCapable() {
- if (sContext == null) return true;
- return sContext.getResources().getBoolean(
+ if (mContext == null) return true;
+ return mContext.getResources().getBoolean(
com.android.internal.R.bool.config_voice_capable);
}
* @hide pending API review
*/
public boolean isSmsCapable() {
- if (sContext == null) return true;
- return sContext.getResources().getBoolean(
+ if (mContext == null) return true;
+ return mContext.getResources().getBoolean(
com.android.internal.R.bool.config_sms_capable);
}
/**
- * Returns all observed cell information of the device.
- *
+ * Returns all observed cell information from all radios on the
+ * device including the primary and neighboring cells. This does
+ * not cause or change the rate of PhoneStateListner#onCellInfoChanged.
+ *<p>
+ * The list can include one or more of {@link android.telephony.CellInfoGsm CellInfoGsm},
+ * {@link android.telephony.CellInfoCdma CellInfoCdma},
+ * {@link android.telephony.CellInfoLte CellInfoLte} and
+ * {@link android.telephony.CellInfoWcdma CellInfoCdma} in any combination.
+ * Specifically on devices with multiple radios it is typical to see instances of
+ * one or more of any these in the list. In addition 0, 1 or more CellInfo
+ * objects may return isRegistered() true.
+ *<p>
+ * This is preferred over using getCellLocation although for older
+ * devices this may return null in which case getCellLocation should
+ * be called.
+ *<p>
* @return List of CellInfo or null if info unavailable.
*
- * <p>Requires Permission:
- * (@link android.Manifest.permission#ACCESS_COARSE_UPDATES}
- *
- * @hide pending API review
+ * <p>Requires Permission: {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
*/
public List<CellInfo> getAllCellInfo() {
try {
return null;
}
}
+
+ /**
+ * Sets the minimum time in milli-seconds between {@link PhoneStateListener#onCellInfoChanged
+ * PhoneStateListener.onCellInfoChanged} will be invoked.
+ *<p>
+ * The default, 0, means invoke onCellInfoChanged when any of the reported
+ * information changes. Setting the value to INT_MAX(0x7fffffff) means never issue
+ * A onCellInfoChanged.
+ *<p>
+ * @param rateInMillis the rate
+ *
+ * @hide
+ */
+ public void setCellInfoListRate(int rateInMillis) {
+ try {
+ getITelephony().setCellInfoListRate(rateInMillis);
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ }
+
+ /**
+ * Returns the MMS user agent.
+ */
+ public String getMmsUserAgent() {
+ if (mContext == null) return null;
+ return mContext.getResources().getString(
+ com.android.internal.R.string.config_mms_user_agent);
+ }
+
+ /**
+ * Returns the MMS user agent profile URL.
+ */
+ public String getMmsUAProfUrl() {
+ if (mContext == null) return null;
+ return mContext.getResources().getString(
+ com.android.internal.R.string.config_mms_user_agent_profile_url);
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void dial(String number) {
+ try {
+ getITelephony().dial(number);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#dial", e);
+ }
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void call(String callingPackage, String number) {
+ try {
+ getITelephony().call(callingPackage, number);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#call", e);
+ }
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean showCallScreen() {
+ try {
+ return getITelephony().showCallScreen();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#showCallScreen", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean showCallScreenWithDialpad(boolean showDialpad) {
+ try {
+ return getITelephony().showCallScreenWithDialpad(showDialpad);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#showCallScreenWithDialpad", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean endCall() {
+ try {
+ return getITelephony().endCall();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#endCall", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void answerRingingCall() {
+ try {
+ getITelephony().answerRingingCall();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#answerRingingCall", e);
+ }
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void toggleHold() {
+ try {
+ getITelephony().toggleHold();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#toggleHold", e);
+ }
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void merge() {
+ try {
+ getITelephony().merge();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#merge", e);
+ }
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void swap() {
+ try {
+ getITelephony().swap();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#swap", e);
+ }
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void mute(boolean mute) {
+ try {
+ getITelephony().mute(mute);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#mute", e);
+ }
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void silenceRinger() {
+ try {
+ getITelephony().silenceRinger();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#silenceRinger", e);
+ }
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean isOffhook() {
+ try {
+ return getITelephony().isOffhook();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#isOffhook", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean isRinging() {
+ try {
+ return getITelephony().isRinging();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#isRinging", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean isIdle() {
+ try {
+ return getITelephony().isIdle();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#isIdle", e);
+ }
+ return true;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean isRadioOn() {
+ try {
+ return getITelephony().isRadioOn();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#isRadioOn", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean isSimPinEnabled() {
+ try {
+ return getITelephony().isSimPinEnabled();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#isSimPinEnabled", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void cancelMissedCallsNotification() {
+ try {
+ getITelephony().cancelMissedCallsNotification();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#cancelMissedCallsNotification", e);
+ }
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean supplyPin(String pin) {
+ try {
+ return getITelephony().supplyPin(pin);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#supplyPin", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean supplyPuk(String puk, String pin) {
+ try {
+ return getITelephony().supplyPuk(puk, pin);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#supplyPuk", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public int[] supplyPinReportResult(String pin) {
+ try {
+ return getITelephony().supplyPinReportResult(pin);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#supplyPinReportResult", e);
+ }
+ return new int[0];
+ }
+
+ /** @hide */
+ @PrivateApi
+ public int[] supplyPukReportResult(String puk, String pin) {
+ try {
+ return getITelephony().supplyPukReportResult(puk, pin);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#]", e);
+ }
+ return new int[0];
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean handlePinMmi(String dialString) {
+ try {
+ return getITelephony().handlePinMmi(dialString);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#handlePinMmi", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void toggleRadioOnOff() {
+ try {
+ getITelephony().toggleRadioOnOff();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#toggleRadioOnOff", e);
+ }
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean setRadio(boolean turnOn) {
+ try {
+ return getITelephony().setRadio(turnOn);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#setRadio", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean setRadioPower(boolean turnOn) {
+ try {
+ return getITelephony().setRadioPower(turnOn);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#setRadioPower", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void updateServiceLocation() {
+ try {
+ getITelephony().updateServiceLocation();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#updateServiceLocation", e);
+ }
+ }
+
+ /** @hide */
+ @PrivateApi
+ public int enableApnType(String type) {
+ try {
+ return getITelephony().enableApnType(type);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#enableApnType", e);
+ }
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public int disableApnType(String type) {
+ try {
+ return getITelephony().disableApnType(type);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#disableApnType", e);
+ }
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean enableDataConnectivity() {
+ try {
+ return getITelephony().enableDataConnectivity();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#enableDataConnectivity", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean disableDataConnectivity() {
+ try {
+ return getITelephony().disableDataConnectivity();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#disableDataConnectivity", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean isDataConnectivityPossible() {
+ try {
+ return getITelephony().isDataConnectivityPossible();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#isDataConnectivityPossible", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public boolean needsOtaServiceProvisioning() {
+ try {
+ return getITelephony().needsOtaServiceProvisioning();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#needsOtaServiceProvisioning", e);
+ }
+ return false;
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void playDtmfTone(char digit, boolean timedShortCode) {
+ try {
+ getITelephony().playDtmfTone(digit, timedShortCode);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#playDtmfTone", e);
+ }
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void stopDtmfTone() {
+ try {
+ getITelephony().stopDtmfTone();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#stopDtmfTone", e);
+ }
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void addCallStateListener(CallStateListener listener) {
+ try {
+ if (listener == null) {
+ throw new RuntimeException("Listener can't be null");
+ }
+ if (!mListeners.containsKey(listener)) {
+ final Listener l = new Listener(listener);
+ mListeners.put(listener, l);
+ getITelephony().addListener(l);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#addListener", e);
+ }
+ }
+
+ /** @hide */
+ @PrivateApi
+ public void removeCallStateListener(CallStateListener listener) {
+ try {
+ final Listener l = mListeners.remove(listener);
+ if (l != null) {
+ // Make sure that no callbacks that are already in flight come.
+ l.clearQueue();
+ getITelephony().removeListener(l);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#removeListener", e);
+ }
+ }
}