diff options
Diffstat (limited to 'telephony')
54 files changed, 4980 insertions, 256 deletions
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index 1d3ad81..8e4f6fc 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -136,14 +136,14 @@ public class PhoneNumberUtils Uri uri = intent.getData(); String scheme = uri.getScheme(); - if (scheme.equals("tel")) { + if (scheme.equals("tel") || scheme.equals("sip")) { return uri.getSchemeSpecificPart(); } // TODO: We don't check for SecurityException here (requires - // READ_PHONE_STATE permission). + // CALL_PRIVILEGED permission). if (scheme.equals("voicemail")) { - return TelephonyManager.getDefault().getVoiceMailNumber(); + return TelephonyManager.getDefault().getCompleteVoiceMailNumber(); } if (context == null) { @@ -1692,6 +1692,22 @@ public class PhoneNumberUtils } /** + * Determines if the specified number is actually a URI + * (i.e. a SIP address) rather than a regular PSTN phone number, + * based on whether or not the number contains an "@" character. + * + * @hide + * @param number + * @return true if number contains @ + */ + public static boolean isUriNumber(String number) { + // Note we allow either "@" or "%40" to indicate a URI, in case + // the passed-in string is URI-escaped. (Neither "@" nor "%40" + // will ever be found in a legal PSTN number.) + return number != null && (number.contains("@") || number.contains("%40")); + } + + /** * This function handles the plus code conversion within NANP CDMA network * If the number format is * 1)+1NANP,remove +, diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index ab63017..27e08d4 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -177,7 +177,7 @@ public class TelephonyManager { /** * Returns the unique device ID, for example, the IMEI for GSM and the MEID - * for CDMA phones. Return null if device ID is not available. + * or ESN for CDMA phones. Return null if device ID is not available. * * <p>Requires Permission: * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} @@ -665,6 +665,25 @@ public class TelephonyManager { } /** + * Returns the complete voice mail number. Return null if it is unavailable. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#CALL_PRIVILEGED CALL_PRIVILEGED} + * + * @hide + */ + public String getCompleteVoiceMailNumber() { + try { + return getSubscriberInfo().getCompleteVoiceMailNumber(); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** * Returns the voice mail count. Return 0 if unavailable. * <p> * Requires Permission: diff --git a/telephony/java/android/telephony/gsm/GsmCellLocation.java b/telephony/java/android/telephony/gsm/GsmCellLocation.java index fa1f985..313bc82 100644 --- a/telephony/java/android/telephony/gsm/GsmCellLocation.java +++ b/telephony/java/android/telephony/gsm/GsmCellLocation.java @@ -25,6 +25,7 @@ import android.telephony.CellLocation; public class GsmCellLocation extends CellLocation { private int mLac; private int mCid; + private int mPsc; /** * Empty constructor. Initializes the LAC and CID to -1. @@ -32,6 +33,7 @@ public class GsmCellLocation extends CellLocation { public GsmCellLocation() { mLac = -1; mCid = -1; + mPsc = -1; } /** @@ -40,6 +42,7 @@ public class GsmCellLocation extends CellLocation { public GsmCellLocation(Bundle bundle) { mLac = bundle.getInt("lac", mLac); mCid = bundle.getInt("cid", mCid); + mPsc = bundle.getInt("psc", mPsc); } /** @@ -57,11 +60,22 @@ public class GsmCellLocation extends CellLocation { } /** + * On a UMTS network, returns the primary scrambling code of the serving + * cell. + * + * @return primary scrambling code for UMTS, -1 if unknown or GSM + */ + public int getPsc() { + return mPsc; + } + + /** * Invalidate this object. The location area code and the cell id are set to -1. */ public void setStateInvalid() { mLac = -1; mCid = -1; + mPsc = -1; } /** @@ -72,6 +86,14 @@ public class GsmCellLocation extends CellLocation { mCid = cid; } + /** + * Set the primary scrambling code. + * @hide + */ + public void setPsc(int psc) { + mPsc = psc; + } + @Override public int hashCode() { return mLac ^ mCid; @@ -91,12 +113,13 @@ public class GsmCellLocation extends CellLocation { return false; } - return equalsHandlesNulls(mLac, s.mLac) && equalsHandlesNulls(mCid, s.mCid); + return equalsHandlesNulls(mLac, s.mLac) && equalsHandlesNulls(mCid, s.mCid) + && equalsHandlesNulls(mPsc, s.mPsc); } @Override public String toString() { - return "["+ mLac + "," + mCid + "]"; + return "["+ mLac + "," + mCid + "," + mPsc + "]"; } /** @@ -118,12 +141,13 @@ public class GsmCellLocation extends CellLocation { public void fillInNotifierBundle(Bundle m) { m.putInt("lac", mLac); m.putInt("cid", mCid); + m.putInt("psc", mPsc); } /** * @hide */ public boolean isEmpty() { - return (mLac == -1 && mCid == -1); + return (mLac == -1 && mCid == -1 && mPsc == -1); } } diff --git a/telephony/java/android/telephony/gsm/SmsMessage.java b/telephony/java/android/telephony/gsm/SmsMessage.java index 37ef912..0c63c37 100644 --- a/telephony/java/android/telephony/gsm/SmsMessage.java +++ b/telephony/java/android/telephony/gsm/SmsMessage.java @@ -304,9 +304,9 @@ public class SmsMessage { int septets = GsmAlphabet.countGsmSeptets(messageBody, !use7bitOnly); ret[1] = septets; if (septets > MAX_USER_DATA_SEPTETS) { - ret[0] = (septets / MAX_USER_DATA_SEPTETS_WITH_HEADER) + 1; - ret[2] = MAX_USER_DATA_SEPTETS_WITH_HEADER - - (septets % MAX_USER_DATA_SEPTETS_WITH_HEADER); + ret[0] = (septets + (MAX_USER_DATA_SEPTETS_WITH_HEADER - 1)) / + MAX_USER_DATA_SEPTETS_WITH_HEADER; + ret[2] = (ret[0] * MAX_USER_DATA_SEPTETS_WITH_HEADER) - septets; } else { ret[0] = 1; ret[2] = MAX_USER_DATA_SEPTETS - septets; @@ -318,9 +318,9 @@ public class SmsMessage { ret[1] = messageBody.length(); if (octets > MAX_USER_DATA_BYTES) { // 6 is the size of the user data header - ret[0] = (octets / MAX_USER_DATA_BYTES_WITH_HEADER) + 1; - ret[2] = (MAX_USER_DATA_BYTES_WITH_HEADER - - (octets % MAX_USER_DATA_BYTES_WITH_HEADER))/2; + ret[0] = (octets + (MAX_USER_DATA_BYTES_WITH_HEADER - 1)) / + MAX_USER_DATA_BYTES_WITH_HEADER; + ret[2] = ((ret[0] * MAX_USER_DATA_BYTES_WITH_HEADER) - octets) / 2; } else { ret[0] = 1; ret[2] = (MAX_USER_DATA_BYTES - octets)/2; diff --git a/telephony/java/com/android/internal/telephony/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java new file mode 100644 index 0000000..b09df82 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/CallManager.java @@ -0,0 +1,1766 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +import com.android.internal.telephony.sip.SipPhone; + +import android.content.Context; +import android.media.AudioManager; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Message; +import android.os.RegistrantList; +import android.os.Registrant; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + + +/** + * @hide + * + * CallManager class provides an abstract layer for PhoneApp to access + * and control calls. It implements Phone interface. + * + * CallManager provides call and connection control as well as + * channel capability. + * + * There are three categories of APIs CallManager provided + * + * 1. Call control and operation, such as dial() and hangup() + * 2. Channel capabilities, such as CanConference() + * 3. Register notification + * + * + */ +public final class CallManager { + + private static final String LOG_TAG ="CallManager"; + private static final boolean DBG = true; + private static final boolean VDBG = true; + + private static final int EVENT_DISCONNECT = 100; + private static final int EVENT_PRECISE_CALL_STATE_CHANGED = 101; + private static final int EVENT_NEW_RINGING_CONNECTION = 102; + private static final int EVENT_UNKNOWN_CONNECTION = 103; + private static final int EVENT_INCOMING_RING = 104; + private static final int EVENT_RINGBACK_TONE = 105; + private static final int EVENT_IN_CALL_VOICE_PRIVACY_ON = 106; + private static final int EVENT_IN_CALL_VOICE_PRIVACY_OFF = 107; + private static final int EVENT_CALL_WAITING = 108; + private static final int EVENT_DISPLAY_INFO = 109; + private static final int EVENT_SIGNAL_INFO = 110; + private static final int EVENT_CDMA_OTA_STATUS_CHANGE = 111; + private static final int EVENT_RESEND_INCALL_MUTE = 112; + private static final int EVENT_MMI_INITIATE = 113; + private static final int EVENT_MMI_COMPLETE = 114; + private static final int EVENT_ECM_TIMER_RESET = 115; + private static final int EVENT_SUBSCRIPTION_INFO_READY = 116; + private static final int EVENT_SUPP_SERVICE_FAILED = 117; + private static final int EVENT_SERVICE_STATE_CHANGED = 118; + private static final int EVENT_POST_DIAL_CHARACTER = 119; + + // Singleton instance + private static final CallManager INSTANCE = new CallManager(); + + // list of registered phones, which are PhoneBase objs + private final ArrayList<Phone> mPhones; + + // list of supported ringing calls + private final ArrayList<Call> mRingingCalls; + + // list of supported background calls + private final ArrayList<Call> mBackgroundCalls; + + // list of supported foreground calls + private final ArrayList<Call> mForegroundCalls; + + // empty connection list + private final ArrayList<Connection> emptyConnections = new ArrayList<Connection>(); + + // default phone as the first phone registered, which is PhoneBase obj + private Phone mDefaultPhone; + + // state registrants + protected final RegistrantList mPreciseCallStateRegistrants + = new RegistrantList(); + + protected final RegistrantList mNewRingingConnectionRegistrants + = new RegistrantList(); + + protected final RegistrantList mIncomingRingRegistrants + = new RegistrantList(); + + protected final RegistrantList mDisconnectRegistrants + = new RegistrantList(); + + protected final RegistrantList mMmiRegistrants + = new RegistrantList(); + + protected final RegistrantList mUnknownConnectionRegistrants + = new RegistrantList(); + + protected final RegistrantList mRingbackToneRegistrants + = new RegistrantList(); + + protected final RegistrantList mInCallVoicePrivacyOnRegistrants + = new RegistrantList(); + + protected final RegistrantList mInCallVoicePrivacyOffRegistrants + = new RegistrantList(); + + protected final RegistrantList mCallWaitingRegistrants + = new RegistrantList(); + + protected final RegistrantList mDisplayInfoRegistrants + = new RegistrantList(); + + protected final RegistrantList mSignalInfoRegistrants + = new RegistrantList(); + + protected final RegistrantList mCdmaOtaStatusChangeRegistrants + = new RegistrantList(); + + protected final RegistrantList mResendIncallMuteRegistrants + = new RegistrantList(); + + protected final RegistrantList mMmiInitiateRegistrants + = new RegistrantList(); + + protected final RegistrantList mMmiCompleteRegistrants + = new RegistrantList(); + + protected final RegistrantList mEcmTimerResetRegistrants + = new RegistrantList(); + + protected final RegistrantList mSubscriptionInfoReadyRegistrants + = new RegistrantList(); + + protected final RegistrantList mSuppServiceFailedRegistrants + = new RegistrantList(); + + protected final RegistrantList mServiceStateChangedRegistrants + = new RegistrantList(); + + protected final RegistrantList mPostDialCharacterRegistrants + = new RegistrantList(); + + private CallManager() { + mPhones = new ArrayList<Phone>(); + mRingingCalls = new ArrayList<Call>(); + mBackgroundCalls = new ArrayList<Call>(); + mForegroundCalls = new ArrayList<Call>(); + mDefaultPhone = null; + } + + /** + * get singleton instance of CallManager + * @return CallManager + */ + public static CallManager getInstance() { + return INSTANCE; + } + + /** + * Get the corresponding PhoneBase obj + * + * @param phone a Phone object + * @return the corresponding PhoneBase obj in Phone if Phone + * is a PhoneProxy obj + * or the Phone itself if Phone is not a PhoneProxy obj + */ + private static Phone getPhoneBase(Phone phone) { + if (phone instanceof PhoneProxy) { + return phone.getForegroundCall().getPhone(); + } + return phone; + } + + /** + * Check if two phones refer to the same PhoneBase obj + * + * Note: PhoneBase, not PhoneProxy, is to be used inside of CallManager + * + * Both PhoneBase and PhoneProxy implement Phone interface, so + * they have same phone APIs, such as dial(). The real implementation, for + * example in GSM, is in GSMPhone as extend from PhoneBase, so that + * foregroundCall.getPhone() returns GSMPhone obj. On the other hand, + * PhoneFactory.getDefaultPhone() returns PhoneProxy obj, which has a class + * member of GSMPhone. + * + * So for phone returned by PhoneFacotry, which is used by PhoneApp, + * phone.getForegroundCall().getPhone() != phone + * but + * isSamePhone(phone, phone.getForegroundCall().getPhone()) == true + * + * @param p1 is the first Phone obj + * @param p2 is the second Phone obj + * @return true if p1 and p2 refer to the same phone + */ + public static boolean isSamePhone(Phone p1, Phone p2) { + return (getPhoneBase(p1) == getPhoneBase(p2)); + } + + /** + * Returns all the registered phone objects. + * @return all the registered phone objects. + */ + public List<Phone> getAllPhones() { + return Collections.unmodifiableList(mPhones); + } + + /** + * Get current coarse-grained voice call state. + * If the Call Manager has an active call and call waiting occurs, + * then the phone state is RINGING not OFFHOOK + * + */ + public Phone.State getState() { + Phone.State s = Phone.State.IDLE; + + for (Phone phone : mPhones) { + if (phone.getState() == Phone.State.RINGING) { + s = Phone.State.RINGING; + } else if (phone.getState() == Phone.State.OFFHOOK) { + if (s == Phone.State.IDLE) s = Phone.State.OFFHOOK; + } + } + return s; + } + + /** + * @return the service state of CallManager, which represents the + * highest priority state of all the service states of phones + * + * The priority is defined as + * + * STATE_IN_SERIVCE > STATE_OUT_OF_SERIVCE > STATE_EMERGENCY > STATE_POWER_OFF + * + */ + + public int getServiceState() { + int resultState = ServiceState.STATE_OUT_OF_SERVICE; + + for (Phone phone : mPhones) { + int serviceState = phone.getServiceState().getState(); + if (serviceState == ServiceState.STATE_IN_SERVICE) { + // IN_SERVICE has the highest priority + resultState = serviceState; + break; + } else if (serviceState == ServiceState.STATE_OUT_OF_SERVICE) { + // OUT_OF_SERVICE replaces EMERGENCY_ONLY and POWER_OFF + // Note: EMERGENCY_ONLY is not in use at this moment + if ( resultState == ServiceState.STATE_EMERGENCY_ONLY || + resultState == ServiceState.STATE_POWER_OFF) { + resultState = serviceState; + } + } else if (serviceState == ServiceState.STATE_EMERGENCY_ONLY) { + if (resultState == ServiceState.STATE_POWER_OFF) { + resultState = serviceState; + } + } + } + return resultState; + } + + /** + * Register phone to CallManager + * @param phone to be registered + * @return true if register successfully + */ + public boolean registerPhone(Phone phone) { + Phone basePhone = getPhoneBase(phone); + + if (basePhone != null && !mPhones.contains(basePhone)) { + + if (VDBG) { + Log.d(LOG_TAG, "registerPhone(" + + phone.getPhoneName() + " " + phone + ")"); + } + + if (mPhones.isEmpty()) { + mDefaultPhone = basePhone; + } + mPhones.add(basePhone); + mRingingCalls.add(basePhone.getRingingCall()); + mBackgroundCalls.add(basePhone.getBackgroundCall()); + mForegroundCalls.add(basePhone.getForegroundCall()); + registerForPhoneStates(basePhone); + return true; + } + return false; + } + + /** + * unregister phone from CallManager + * @param phone to be unregistered + */ + public void unregisterPhone(Phone phone) { + Phone basePhone = getPhoneBase(phone); + + if (basePhone != null && mPhones.contains(basePhone)) { + + if (VDBG) { + Log.d(LOG_TAG, "unregisterPhone(" + + phone.getPhoneName() + " " + phone + ")"); + } + + mPhones.remove(basePhone); + mRingingCalls.remove(basePhone.getRingingCall()); + mBackgroundCalls.remove(basePhone.getBackgroundCall()); + mForegroundCalls.remove(basePhone.getForegroundCall()); + unregisterForPhoneStates(basePhone); + if (basePhone == mDefaultPhone) { + if (mPhones.isEmpty()) { + mDefaultPhone = null; + } else { + mDefaultPhone = mPhones.get(0); + } + } + } + } + + /** + * return the default phone or null if no phone available + */ + public Phone getDefaultPhone() { + return mDefaultPhone; + } + + /** + * @return the phone associated with the foreground call + */ + public Phone getFgPhone() { + return getActiveFgCall().getPhone(); + } + + /** + * @return the phone associated with the background call + */ + public Phone getBgPhone() { + return getFirstActiveBgCall().getPhone(); + } + + /** + * @return the phone associated with the ringing call + */ + public Phone getRingingPhone() { + return getFirstActiveRingingCall().getPhone(); + } + + public void setAudioMode() { + Context context = getContext(); + if (context == null) return; + AudioManager audioManager = (AudioManager) + context.getSystemService(Context.AUDIO_SERVICE); + + int mode = AudioManager.MODE_NORMAL; + switch (getState()) { + case RINGING: + mode = AudioManager.MODE_RINGTONE; + break; + case OFFHOOK: + Phone fgPhone = getFgPhone(); + if (!(fgPhone instanceof SipPhone)) { + mode = AudioManager.MODE_IN_CALL; + } + break; + } + // calling audioManager.setMode() multiple times in a short period of + // time seems to break the audio recorder in in-call mode + if (audioManager.getMode() != mode) audioManager.setMode(mode); + } + + private Context getContext() { + Phone defaultPhone = getDefaultPhone(); + return ((defaultPhone == null) ? null : defaultPhone.getContext()); + } + + private void registerForPhoneStates(Phone phone) { + // for common events supported by all phones + phone.registerForPreciseCallStateChanged(mHandler, EVENT_PRECISE_CALL_STATE_CHANGED, null); + phone.registerForDisconnect(mHandler, EVENT_DISCONNECT, null); + phone.registerForNewRingingConnection(mHandler, EVENT_NEW_RINGING_CONNECTION, null); + phone.registerForUnknownConnection(mHandler, EVENT_UNKNOWN_CONNECTION, null); + phone.registerForIncomingRing(mHandler, EVENT_INCOMING_RING, null); + phone.registerForRingbackTone(mHandler, EVENT_RINGBACK_TONE, null); + phone.registerForInCallVoicePrivacyOn(mHandler, EVENT_IN_CALL_VOICE_PRIVACY_ON, null); + phone.registerForInCallVoicePrivacyOff(mHandler, EVENT_IN_CALL_VOICE_PRIVACY_OFF, null); + phone.registerForDisplayInfo(mHandler, EVENT_DISPLAY_INFO, null); + phone.registerForSignalInfo(mHandler, EVENT_SIGNAL_INFO, null); + phone.registerForResendIncallMute(mHandler, EVENT_RESEND_INCALL_MUTE, null); + phone.registerForMmiInitiate(mHandler, EVENT_MMI_INITIATE, null); + phone.registerForMmiComplete(mHandler, EVENT_MMI_COMPLETE, null); + phone.registerForSuppServiceFailed(mHandler, EVENT_SUPP_SERVICE_FAILED, null); + phone.registerForServiceStateChanged(mHandler, EVENT_SERVICE_STATE_CHANGED, null); + + // for events supported only by GSM and CDMA phone + if (phone.getPhoneType() == Phone.PHONE_TYPE_GSM || + phone.getPhoneType() == Phone.PHONE_TYPE_CDMA) { + phone.setOnPostDialCharacter(mHandler, EVENT_POST_DIAL_CHARACTER, null); + } + + // for events supported only by CDMA phone + if (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA ){ + phone.registerForCdmaOtaStatusChange(mHandler, EVENT_CDMA_OTA_STATUS_CHANGE, null); + phone.registerForSubscriptionInfoReady(mHandler, EVENT_SUBSCRIPTION_INFO_READY, null); + phone.registerForCallWaiting(mHandler, EVENT_CALL_WAITING, null); + phone.registerForEcmTimerReset(mHandler, EVENT_ECM_TIMER_RESET, null); + } + } + + private void unregisterForPhoneStates(Phone phone) { + // for common events supported by all phones + phone.unregisterForPreciseCallStateChanged(mHandler); + phone.unregisterForDisconnect(mHandler); + phone.unregisterForNewRingingConnection(mHandler); + phone.unregisterForUnknownConnection(mHandler); + phone.unregisterForIncomingRing(mHandler); + phone.unregisterForRingbackTone(mHandler); + phone.unregisterForInCallVoicePrivacyOn(mHandler); + phone.unregisterForInCallVoicePrivacyOff(mHandler); + phone.unregisterForDisplayInfo(mHandler); + phone.unregisterForSignalInfo(mHandler); + phone.unregisterForResendIncallMute(mHandler); + phone.unregisterForMmiInitiate(mHandler); + phone.unregisterForMmiComplete(mHandler); + phone.unregisterForSuppServiceFailed(mHandler); + phone.unregisterForServiceStateChanged(mHandler); + + // for events supported only by GSM and CDMA phone + if (phone.getPhoneType() == Phone.PHONE_TYPE_GSM || + phone.getPhoneType() == Phone.PHONE_TYPE_CDMA) { + phone.setOnPostDialCharacter(null, EVENT_POST_DIAL_CHARACTER, null); + } + + // for events supported only by CDMA phone + if (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA ){ + phone.unregisterForCdmaOtaStatusChange(mHandler); + phone.unregisterForSubscriptionInfoReady(mHandler); + phone.unregisterForCallWaiting(mHandler); + phone.unregisterForEcmTimerReset(mHandler); + } + } + + /** + * Answers a ringing or waiting call. + * + * Active call, if any, go on hold. + * If active call can't be held, i.e., a background call of the same channel exists, + * the active call will be hang up. + * + * Answering occurs asynchronously, and final notification occurs via + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()}. + * + * @exception CallStateException when call is not ringing or waiting + */ + public void acceptCall(Call ringingCall) throws CallStateException { + Phone ringingPhone = ringingCall.getPhone(); + + if (VDBG) { + Log.d(LOG_TAG, "acceptCall(" +ringingCall + " from " + ringingCall.getPhone() + ")"); + Log.d(LOG_TAG, this.toString()); + } + + if ( hasActiveFgCall() ) { + Phone activePhone = getActiveFgCall().getPhone(); + boolean hasBgCall = ! (activePhone.getBackgroundCall().isIdle()); + boolean sameChannel = (activePhone == ringingPhone); + + if (DBG) { + Log.d(LOG_TAG, "hasBgCall: "+ hasBgCall + "sameChannel:" + sameChannel); + } + + if (sameChannel && hasBgCall) { + getActiveFgCall().hangup(); + } else if (!sameChannel && !hasBgCall) { + activePhone.switchHoldingAndActive(); + } else if (!sameChannel && hasBgCall) { + getActiveFgCall().hangup(); + } + } + + ringingPhone.acceptCall(); + + if (VDBG) { + Log.d(LOG_TAG, "End acceptCall(" +ringingCall + ")"); + Log.d(LOG_TAG, this.toString()); + } + } + + /** + * Reject (ignore) a ringing call. In GSM, this means UDUB + * (User Determined User Busy). Reject occurs asynchronously, + * and final notification occurs via + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()}. + * + * @exception CallStateException when no call is ringing or waiting + */ + public void rejectCall(Call ringingCall) throws CallStateException { + if (VDBG) { + Log.d(LOG_TAG, "rejectCall(" +ringingCall + ")"); + Log.d(LOG_TAG, this.toString()); + } + + Phone ringingPhone = ringingCall.getPhone(); + + ringingPhone.rejectCall(); + + if (VDBG) { + Log.d(LOG_TAG, "End rejectCall(" +ringingCall + ")"); + Log.d(LOG_TAG, this.toString()); + } + } + + /** + * Places active call on hold, and makes held call active. + * Switch occurs asynchronously and may fail. + * + * There are 4 scenarios + * 1. only active call but no held call, aka, hold + * 2. no active call but only held call, aka, unhold + * 3. both active and held calls from same phone, aka, swap + * 4. active and held calls from different phones, aka, phone swap + * + * Final notification occurs via + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()}. + * + * @exception CallStateException if active call is ringing, waiting, or + * dialing/alerting, or heldCall can't be active. + * In these cases, this operation may not be performed. + */ + public void switchHoldingAndActive(Call heldCall) throws CallStateException { + Phone activePhone = null; + Phone heldPhone = null; + + if (VDBG) { + Log.d(LOG_TAG, "switchHoldingAndActive(" +heldCall + ")"); + Log.d(LOG_TAG, this.toString()); + } + + if (hasActiveFgCall()) { + activePhone = getActiveFgCall().getPhone(); + } + + if (heldCall != null) { + heldPhone = heldCall.getPhone(); + } + + if (activePhone != null) { + activePhone.switchHoldingAndActive(); + } + + if (heldPhone != null && heldPhone != activePhone) { + heldPhone.switchHoldingAndActive(); + } + + if (VDBG) { + Log.d(LOG_TAG, "End switchHoldingAndActive(" +heldCall + ")"); + Log.d(LOG_TAG, this.toString()); + } + } + + /** + * Hangup foreground call and resume the specific background call + * + * Note: this is noop if there is no foreground call or the heldCall is null + * + * @param heldCall to become foreground + * @throws CallStateException + */ + public void hangupForegroundResumeBackground(Call heldCall) throws CallStateException { + Phone foregroundPhone = null; + Phone backgroundPhone = null; + + if (VDBG) { + Log.d(LOG_TAG, "hangupForegroundResumeBackground(" +heldCall + ")"); + Log.d(LOG_TAG, this.toString()); + } + + if (hasActiveFgCall()) { + foregroundPhone = getFgPhone(); + if (heldCall != null) { + backgroundPhone = heldCall.getPhone(); + if (foregroundPhone == backgroundPhone) { + getActiveFgCall().hangup(); + } else { + // the call to be hangup and resumed belongs to different phones + getActiveFgCall().hangup(); + switchHoldingAndActive(heldCall); + } + } + } + + if (VDBG) { + Log.d(LOG_TAG, "End hangupForegroundResumeBackground(" +heldCall + ")"); + Log.d(LOG_TAG, this.toString()); + } + } + + /** + * Whether or not the phone can conference in the current phone + * state--that is, one call holding and one call active. + * @return true if the phone can conference; false otherwise. + */ + public boolean canConference(Call heldCall) { + Phone activePhone = null; + Phone heldPhone = null; + + if (hasActiveFgCall()) { + activePhone = getActiveFgCall().getPhone(); + } + + if (heldCall != null) { + heldPhone = heldCall.getPhone(); + } + + return heldPhone.getClass().equals(activePhone.getClass()); + } + + /** + * Conferences holding and active. Conference occurs asynchronously + * and may fail. Final notification occurs via + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()}. + * + * @exception CallStateException if canConference() would return false. + * In these cases, this operation may not be performed. + */ + public void conference(Call heldCall) throws CallStateException { + + if (VDBG) { + Log.d(LOG_TAG, "conference(" +heldCall + ")"); + Log.d(LOG_TAG, this.toString()); + } + + + Phone fgPhone = getFgPhone(); + if (fgPhone instanceof SipPhone) { + ((SipPhone) fgPhone).conference(heldCall); + } else if (canConference(heldCall)) { + fgPhone.conference(); + } else { + throw(new CallStateException("Can't conference foreground and selected background call")); + } + + if (VDBG) { + Log.d(LOG_TAG, "End conference(" +heldCall + ")"); + Log.d(LOG_TAG, this.toString()); + } + + } + + /** + * Initiate a new voice connection. This happens asynchronously, so you + * cannot assume the audio path is connected (or a call index has been + * assigned) until PhoneStateChanged notification has occurred. + * + * @exception CallStateException if a new outgoing call is not currently + * possible because no more call slots exist or a call exists that is + * dialing, alerting, ringing, or waiting. Other errors are + * handled asynchronously. + */ + public Connection dial(Phone phone, String dialString) throws CallStateException { + Phone basePhone = getPhoneBase(phone); + Connection result; + + if (VDBG) { + Log.d(LOG_TAG, " dial(" + basePhone + ", "+ dialString + ")"); + Log.d(LOG_TAG, this.toString()); + } + + if ( hasActiveFgCall() ) { + Phone activePhone = getActiveFgCall().getPhone(); + boolean hasBgCall = !(activePhone.getBackgroundCall().isIdle()); + + if (DBG) { + Log.d(LOG_TAG, "hasBgCall: "+ hasBgCall + " sameChannel:" + (activePhone == basePhone)); + } + + if (activePhone != basePhone) { + if (hasBgCall) { + Log.d(LOG_TAG, "Hangup"); + getActiveFgCall().hangup(); + } else { + Log.d(LOG_TAG, "Switch"); + activePhone.switchHoldingAndActive(); + } + } + } + + result = basePhone.dial(dialString); + + if (VDBG) { + Log.d(LOG_TAG, "End dial(" + basePhone + ", "+ dialString + ")"); + Log.d(LOG_TAG, this.toString()); + } + + return result; + } + + /** + * Initiate a new voice connection. This happens asynchronously, so you + * cannot assume the audio path is connected (or a call index has been + * assigned) until PhoneStateChanged notification has occurred. + * + * @exception CallStateException if a new outgoing call is not currently + * possible because no more call slots exist or a call exists that is + * dialing, alerting, ringing, or waiting. Other errors are + * handled asynchronously. + */ + public Connection dial(Phone phone, String dialString, UUSInfo uusInfo) throws CallStateException { + return phone.dial(dialString, uusInfo); + } + + /** + * clear disconnect connection for each phone + */ + public void clearDisconnected() { + for(Phone phone : mPhones) { + phone.clearDisconnected(); + } + } + + /** + * Whether or not the phone can do explicit call transfer in the current + * phone state--that is, one call holding and one call active. + * @return true if the phone can do explicit call transfer; false otherwise. + */ + public boolean canTransfer(Call heldCall) { + Phone activePhone = null; + Phone heldPhone = null; + + if (hasActiveFgCall()) { + activePhone = getActiveFgCall().getPhone(); + } + + if (heldCall != null) { + heldPhone = heldCall.getPhone(); + } + + return (heldPhone == activePhone && activePhone.canTransfer()); + } + + /** + * Connects the held call and active call + * Disconnects the subscriber from both calls + * + * Explicit Call Transfer occurs asynchronously + * and may fail. Final notification occurs via + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()}. + * + * @exception CallStateException if canTransfer() would return false. + * In these cases, this operation may not be performed. + */ + public void explicitCallTransfer(Call heldCall) throws CallStateException { + if (VDBG) { + Log.d(LOG_TAG, " explicitCallTransfer(" + heldCall + ")"); + Log.d(LOG_TAG, this.toString()); + } + + if (canTransfer(heldCall)) { + heldCall.getPhone().explicitCallTransfer(); + } + + if (VDBG) { + Log.d(LOG_TAG, "End explicitCallTransfer(" + heldCall + ")"); + Log.d(LOG_TAG, this.toString()); + } + + } + + /** + * Returns a list of MMI codes that are pending for a phone. (They have initiated + * but have not yet completed). + * Presently there is only ever one. + * + * Use <code>registerForMmiInitiate</code> + * and <code>registerForMmiComplete</code> for change notification. + * @return null if phone doesn't have or support mmi code + */ + public List<? extends MmiCode> getPendingMmiCodes(Phone phone) { + Log.e(LOG_TAG, "getPendingMmiCodes not implemented"); + return null; + } + + /** + * Sends user response to a USSD REQUEST message. An MmiCode instance + * representing this response is sent to handlers registered with + * registerForMmiInitiate. + * + * @param ussdMessge Message to send in the response. + * @return false if phone doesn't support ussd service + */ + public boolean sendUssdResponse(Phone phone, String ussdMessge) { + Log.e(LOG_TAG, "sendUssdResponse not implemented"); + return false; + } + + /** + * Mutes or unmutes the microphone for the active call. The microphone + * is automatically unmuted if a call is answered, dialed, or resumed + * from a holding state. + * + * @param muted true to mute the microphone, + * false to activate the microphone. + */ + + public void setMute(boolean muted) { + if (VDBG) { + Log.d(LOG_TAG, " setMute(" + muted + ")"); + Log.d(LOG_TAG, this.toString()); + } + + if (hasActiveFgCall()) { + getActiveFgCall().getPhone().setMute(muted); + } + + if (VDBG) { + Log.d(LOG_TAG, "End setMute(" + muted + ")"); + Log.d(LOG_TAG, this.toString()); + } + } + + /** + * Gets current mute status. Use + * {@link #registerForPreciseCallStateChanged(android.os.Handler, int, + * java.lang.Object) registerForPreciseCallStateChanged()} + * as a change notifcation, although presently phone state changed is not + * fired when setMute() is called. + * + * @return true is muting, false is unmuting + */ + public boolean getMute() { + if (hasActiveFgCall()) { + return getActiveFgCall().getPhone().getMute(); + } + return false; + } + + /** + * Enables or disables echo suppression. + */ + public void setEchoSuppressionEnabled(boolean enabled) { + if (VDBG) { + Log.d(LOG_TAG, " setEchoSuppression(" + enabled + ")"); + Log.d(LOG_TAG, this.toString()); + } + + if (hasActiveFgCall()) { + getActiveFgCall().getPhone().setEchoSuppressionEnabled(enabled); + } + + if (VDBG) { + Log.d(LOG_TAG, "End setEchoSuppression(" + enabled + ")"); + Log.d(LOG_TAG, this.toString()); + } + } + + /** + * Play a DTMF tone on the active call. + * + * @param c should be one of 0-9, '*' or '#'. Other values will be + * silently ignored. + * @return false if no active call or the active call doesn't support + * dtmf tone + */ + public boolean sendDtmf(char c) { + boolean result = false; + + if (VDBG) { + Log.d(LOG_TAG, " sendDtmf(" + c + ")"); + Log.d(LOG_TAG, this.toString()); + } + + if (hasActiveFgCall()) { + getActiveFgCall().getPhone().sendDtmf(c); + result = true; + } + + if (VDBG) { + Log.d(LOG_TAG, "End sendDtmf(" + c + ")"); + Log.d(LOG_TAG, this.toString()); + } + return result; + } + + /** + * Start to paly a DTMF tone on the active call. + * or there is a playing DTMF tone. + * @param c should be one of 0-9, '*' or '#'. Other values will be + * silently ignored. + * + * @return false if no active call or the active call doesn't support + * dtmf tone + */ + public boolean startDtmf(char c) { + boolean result = false; + + if (VDBG) { + Log.d(LOG_TAG, " startDtmf(" + c + ")"); + Log.d(LOG_TAG, this.toString()); + } + + if (hasActiveFgCall()) { + getActiveFgCall().getPhone().startDtmf(c); + result = true; + } + + if (VDBG) { + Log.d(LOG_TAG, "End startDtmf(" + c + ")"); + Log.d(LOG_TAG, this.toString()); + } + + return result; + } + + /** + * Stop the playing DTMF tone. Ignored if there is no playing DTMF + * tone or no active call. + */ + public void stopDtmf() { + if (VDBG) { + Log.d(LOG_TAG, " stopDtmf()" ); + Log.d(LOG_TAG, this.toString()); + } + + if (hasActiveFgCall()) getFgPhone().stopDtmf(); + + if (VDBG) { + Log.d(LOG_TAG, "End stopDtmf()"); + Log.d(LOG_TAG, this.toString()); + } + } + + /** + * send burst DTMF tone, it can send the string as single character or multiple character + * ignore if there is no active call or not valid digits string. + * Valid digit means only includes characters ISO-LATIN characters 0-9, *, # + * The difference between sendDtmf and sendBurstDtmf is sendDtmf only sends one character, + * this api can send single character and multiple character, also, this api has response + * back to caller. + * + * @param dtmfString is string representing the dialing digit(s) in the active call + * @param on the DTMF ON length in milliseconds, or 0 for default + * @param off the DTMF OFF length in milliseconds, or 0 for default + * @param onComplete is the callback message when the action is processed by BP + * + */ + public boolean sendBurstDtmf(String dtmfString, int on, int off, Message onComplete) { + if (hasActiveFgCall()) { + getActiveFgCall().getPhone().sendBurstDtmf(dtmfString, on, off, onComplete); + return true; + } + return false; + } + + /** + * Notifies when a voice connection has disconnected, either due to local + * or remote hangup or error. + * + * Messages received from this will have the following members:<p> + * <ul><li>Message.obj will be an AsyncResult</li> + * <li>AsyncResult.userObj = obj</li> + * <li>AsyncResult.result = a Connection object that is + * no longer connected.</li></ul> + */ + public void registerForDisconnect(Handler h, int what, Object obj) { + mDisconnectRegistrants.addUnique(h, what, obj); + } + + /** + * Unregisters for voice disconnection notification. + * Extraneous calls are tolerated silently + */ + public void unregisterForDisconnect(Handler h){ + mDisconnectRegistrants.remove(h); + } + + /** + * Register for getting notifications for change in the Call State {@link Call.State} + * This is called PreciseCallState because the call state is more precise than the + * {@link Phone.State} which can be obtained using the {@link PhoneStateListener} + * + * Resulting events will have an AsyncResult in <code>Message.obj</code>. + * AsyncResult.userData will be set to the obj argument here. + * The <em>h</em> parameter is held only by a weak reference. + */ + public void registerForPreciseCallStateChanged(Handler h, int what, Object obj){ + mPreciseCallStateRegistrants.addUnique(h, what, obj); + } + + /** + * Unregisters for voice call state change notifications. + * Extraneous calls are tolerated silently. + */ + public void unregisterForPreciseCallStateChanged(Handler h){ + mPreciseCallStateRegistrants.remove(h); + } + + /** + * Notifies when a previously untracked non-ringing/waiting connection has appeared. + * This is likely due to some other entity (eg, SIM card application) initiating a call. + */ + public void registerForUnknownConnection(Handler h, int what, Object obj){ + mUnknownConnectionRegistrants.addUnique(h, what, obj); + } + + /** + * Unregisters for unknown connection notifications. + */ + public void unregisterForUnknownConnection(Handler h){ + mUnknownConnectionRegistrants.remove(h); + } + + + /** + * Notifies when a new ringing or waiting connection has appeared.<p> + * + * Messages received from this: + * Message.obj will be an AsyncResult + * AsyncResult.userObj = obj + * AsyncResult.result = a Connection. <p> + * Please check Connection.isRinging() to make sure the Connection + * has not dropped since this message was posted. + * If Connection.isRinging() is true, then + * Connection.getCall() == Phone.getRingingCall() + */ + public void registerForNewRingingConnection(Handler h, int what, Object obj){ + mNewRingingConnectionRegistrants.addUnique(h, what, obj); + } + + /** + * Unregisters for new ringing connection notification. + * Extraneous calls are tolerated silently + */ + + public void unregisterForNewRingingConnection(Handler h){ + mNewRingingConnectionRegistrants.remove(h); + } + + /** + * Notifies when an incoming call rings.<p> + * + * Messages received from this: + * Message.obj will be an AsyncResult + * AsyncResult.userObj = obj + * AsyncResult.result = a Connection. <p> + */ + public void registerForIncomingRing(Handler h, int what, Object obj){ + mIncomingRingRegistrants.addUnique(h, what, obj); + } + + /** + * Unregisters for ring notification. + * Extraneous calls are tolerated silently + */ + + public void unregisterForIncomingRing(Handler h){ + mIncomingRingRegistrants.remove(h); + } + + /** + * Notifies when out-band ringback tone is needed.<p> + * + * Messages received from this: + * Message.obj will be an AsyncResult + * AsyncResult.userObj = obj + * AsyncResult.result = boolean, true to start play ringback tone + * and false to stop. <p> + */ + public void registerForRingbackTone(Handler h, int what, Object obj){ + mRingbackToneRegistrants.addUnique(h, what, obj); + } + + /** + * Unregisters for ringback tone notification. + */ + + public void unregisterForRingbackTone(Handler h){ + mRingbackToneRegistrants.remove(h); + } + + /** + * Registers the handler to reset the uplink mute state to get + * uplink audio. + */ + public void registerForResendIncallMute(Handler h, int what, Object obj){ + mResendIncallMuteRegistrants.addUnique(h, what, obj); + } + + /** + * Unregisters for resend incall mute notifications. + */ + public void unregisterForResendIncallMute(Handler h){ + mResendIncallMuteRegistrants.remove(h); + } + + /** + * Register for notifications of initiation of a new MMI code request. + * MMI codes for GSM are discussed in 3GPP TS 22.030.<p> + * + * Example: If Phone.dial is called with "*#31#", then the app will + * be notified here.<p> + * + * The returned <code>Message.obj</code> will contain an AsyncResult. + * + * <code>obj.result</code> will be an "MmiCode" object. + */ + public void registerForMmiInitiate(Handler h, int what, Object obj){ + mMmiInitiateRegistrants.addUnique(h, what, obj); + } + + /** + * Unregisters for new MMI initiate notification. + * Extraneous calls are tolerated silently + */ + public void unregisterForMmiInitiate(Handler h){ + mMmiInitiateRegistrants.remove(h); + } + + /** + * Register for notifications that an MMI request has completed + * its network activity and is in its final state. This may mean a state + * of COMPLETE, FAILED, or CANCELLED. + * + * <code>Message.obj</code> will contain an AsyncResult. + * <code>obj.result</code> will be an "MmiCode" object + */ + public void registerForMmiComplete(Handler h, int what, Object obj){ + mMmiCompleteRegistrants.addUnique(h, what, obj); + } + + /** + * Unregisters for MMI complete notification. + * Extraneous calls are tolerated silently + */ + public void unregisterForMmiComplete(Handler h){ + mMmiCompleteRegistrants.remove(h); + } + + /** + * Registration point for Ecm timer reset + * @param h handler to notify + * @param what user-defined message code + * @param obj placed in Message.obj + */ + public void registerForEcmTimerReset(Handler h, int what, Object obj){ + mEcmTimerResetRegistrants.addUnique(h, what, obj); + } + + /** + * Unregister for notification for Ecm timer reset + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForEcmTimerReset(Handler h){ + mEcmTimerResetRegistrants.remove(h); + } + + /** + * Register for ServiceState changed. + * Message.obj will contain an AsyncResult. + * AsyncResult.result will be a ServiceState instance + */ + public void registerForServiceStateChanged(Handler h, int what, Object obj){ + mServiceStateChangedRegistrants.addUnique(h, what, obj); + } + + /** + * Unregisters for ServiceStateChange notification. + * Extraneous calls are tolerated silently + */ + public void unregisterForServiceStateChanged(Handler h){ + mServiceStateChangedRegistrants.remove(h); + } + + /** + * Register for notifications when a supplementary service attempt fails. + * Message.obj will contain an AsyncResult. + * + * @param h Handler that receives the notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForSuppServiceFailed(Handler h, int what, Object obj){ + mSuppServiceFailedRegistrants.addUnique(h, what, obj); + } + + /** + * Unregister for notifications when a supplementary service attempt fails. + * Extraneous calls are tolerated silently + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForSuppServiceFailed(Handler h){ + mSuppServiceFailedRegistrants.remove(h); + } + + /** + * Register for notifications when a sInCall VoicePrivacy is enabled + * + * @param h Handler that receives the notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj){ + mInCallVoicePrivacyOnRegistrants.addUnique(h, what, obj); + } + + /** + * Unregister for notifications when a sInCall VoicePrivacy is enabled + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForInCallVoicePrivacyOn(Handler h){ + mInCallVoicePrivacyOnRegistrants.remove(h); + } + + /** + * Register for notifications when a sInCall VoicePrivacy is disabled + * + * @param h Handler that receives the notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj){ + mInCallVoicePrivacyOffRegistrants.addUnique(h, what, obj); + } + + /** + * Unregister for notifications when a sInCall VoicePrivacy is disabled + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForInCallVoicePrivacyOff(Handler h){ + mInCallVoicePrivacyOffRegistrants.remove(h); + } + + /** + * Register for notifications when CDMA call waiting comes + * + * @param h Handler that receives the notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForCallWaiting(Handler h, int what, Object obj){ + mCallWaitingRegistrants.addUnique(h, what, obj); + } + + /** + * Unregister for notifications when CDMA Call waiting comes + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForCallWaiting(Handler h){ + mCallWaitingRegistrants.remove(h); + } + + + /** + * Register for signal information notifications from the network. + * Message.obj will contain an AsyncResult. + * AsyncResult.result will be a SuppServiceNotification instance. + * + * @param h Handler that receives the notification message. + * @param what User-defined message code. + * @param obj User object. + */ + + public void registerForSignalInfo(Handler h, int what, Object obj){ + mSignalInfoRegistrants.addUnique(h, what, obj); + } + + /** + * Unregisters for signal information notifications. + * Extraneous calls are tolerated silently + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForSignalInfo(Handler h){ + mSignalInfoRegistrants.remove(h); + } + + /** + * Register for display information notifications from the network. + * Message.obj will contain an AsyncResult. + * AsyncResult.result will be a SuppServiceNotification instance. + * + * @param h Handler that receives the notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForDisplayInfo(Handler h, int what, Object obj){ + mDisplayInfoRegistrants.addUnique(h, what, obj); + } + + /** + * Unregisters for display information notifications. + * Extraneous calls are tolerated silently + * + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForDisplayInfo(Handler h) { + mDisplayInfoRegistrants.remove(h); + } + + /** + * Register for notifications when CDMA OTA Provision status change + * + * @param h Handler that receives the notification message. + * @param what User-defined message code. + * @param obj User object. + */ + public void registerForCdmaOtaStatusChange(Handler h, int what, Object obj){ + mCdmaOtaStatusChangeRegistrants.addUnique(h, what, obj); + } + + /** + * Unregister for notifications when CDMA OTA Provision status change + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForCdmaOtaStatusChange(Handler h){ + mCdmaOtaStatusChangeRegistrants.remove(h); + } + + /** + * Registration point for subscription info ready + * @param h handler to notify + * @param what what code of message when delivered + * @param obj placed in Message.obj + */ + public void registerForSubscriptionInfoReady(Handler h, int what, Object obj){ + mSubscriptionInfoReadyRegistrants.addUnique(h, what, obj); + } + + /** + * Unregister for notifications for subscription info + * @param h Handler to be removed from the registrant list. + */ + public void unregisterForSubscriptionInfoReady(Handler h){ + mSubscriptionInfoReadyRegistrants.remove(h); + } + + /** + * Sets an event to be fired when the telephony system processes + * a post-dial character on an outgoing call.<p> + * + * Messages of type <code>what</code> will be sent to <code>h</code>. + * The <code>obj</code> field of these Message's will be instances of + * <code>AsyncResult</code>. <code>Message.obj.result</code> will be + * a Connection object.<p> + * + * Message.arg1 will be the post dial character being processed, + * or 0 ('\0') if end of string.<p> + * + * If Connection.getPostDialState() == WAIT, + * the application must call + * {@link com.android.internal.telephony.Connection#proceedAfterWaitChar() + * Connection.proceedAfterWaitChar()} or + * {@link com.android.internal.telephony.Connection#cancelPostDial() + * Connection.cancelPostDial()} + * for the telephony system to continue playing the post-dial + * DTMF sequence.<p> + * + * If Connection.getPostDialState() == WILD, + * the application must call + * {@link com.android.internal.telephony.Connection#proceedAfterWildChar + * Connection.proceedAfterWildChar()} + * or + * {@link com.android.internal.telephony.Connection#cancelPostDial() + * Connection.cancelPostDial()} + * for the telephony system to continue playing the + * post-dial DTMF sequence.<p> + * + */ + public void registerForPostDialCharacter(Handler h, int what, Object obj){ + mPostDialCharacterRegistrants.addUnique(h, what, obj); + } + + public void unregisterForPostDialCharacter(Handler h){ + mPostDialCharacterRegistrants.remove(h); + } + + /* APIs to access foregroudCalls, backgroudCalls, and ringingCalls + * 1. APIs to access list of calls + * 2. APIs to check if any active call, which has connection other than + * disconnected ones, pleaser refer to Call.isIdle() + * 3. APIs to return first active call + * 4. APIs to return the connections of first active call + * 5. APIs to return other property of first active call + */ + + /** + * @return list of all ringing calls + */ + public List<Call> getRingingCalls() { + return Collections.unmodifiableList(mRingingCalls); + } + + /** + * @return list of all foreground calls + */ + public List<Call> getForegroundCalls() { + return Collections.unmodifiableList(mForegroundCalls); + } + + /** + * @return list of all background calls + */ + public List<Call> getBackgroundCalls() { + return Collections.unmodifiableList(mBackgroundCalls); + } + + /** + * Return true if there is at least one active foreground call + */ + public boolean hasActiveFgCall() { + return (getFirstActiveCall(mForegroundCalls) != null); + } + + /** + * Return true if there is at least one active background call + */ + public boolean hasActiveBgCall() { + // TODO since hasActiveBgCall may get called often + // better to cache it to improve performance + return (getFirstActiveCall(mBackgroundCalls) != null); + } + + /** + * Return true if there is at least one active ringing call + * + */ + public boolean hasActiveRingingCall() { + return (getFirstActiveCall(mRingingCalls) != null); + } + + /** + * return the active foreground call from foreground calls + * + * Active call means the call is NOT in Call.State.IDLE + * + * 1. If there is active foreground call, return it + * 2. If there is no active foreground call, return the + * foreground call associated with default phone, which state is IDLE. + * 3. If there is no phone registered at all, return null. + * + */ + public Call getActiveFgCall() { + Call call = getFirstNonIdleCall(mForegroundCalls); + if (call == null) { + call = (mDefaultPhone == null) + ? null + : mDefaultPhone.getForegroundCall(); + } + return call; + } + + // Returns the first call that is not in IDLE state. If both active calls + // and disconnecting/disconnected calls exist, return the first active call. + private Call getFirstNonIdleCall(List<Call> calls) { + Call result = null; + for (Call call : calls) { + if (!call.isIdle()) { + return call; + } else if (call.getState() != Call.State.IDLE) { + if (result == null) result = call; + } + } + return result; + } + + /** + * return one active background call from background calls + * + * Active call means the call is NOT idle defined by Call.isIdle() + * + * 1. If there is only one active background call, return it + * 2. If there is more than one active background call, return the first one + * 3. If there is no active background call, return the background call + * associated with default phone, which state is IDLE. + * 4. If there is no background call at all, return null. + * + * Complete background calls list can be get by getBackgroundCalls() + */ + public Call getFirstActiveBgCall() { + Call call = getFirstNonIdleCall(mBackgroundCalls); + if (call == null) { + call = (mDefaultPhone == null) + ? null + : mDefaultPhone.getBackgroundCall(); + } + return call; + } + + /** + * return one active ringing call from ringing calls + * + * Active call means the call is NOT idle defined by Call.isIdle() + * + * 1. If there is only one active ringing call, return it + * 2. If there is more than one active ringing call, return the first one + * 3. If there is no active ringing call, return the ringing call + * associated with default phone, which state is IDLE. + * 4. If there is no ringing call at all, return null. + * + * Complete ringing calls list can be get by getRingingCalls() + */ + public Call getFirstActiveRingingCall() { + Call call = getFirstNonIdleCall(mRingingCalls); + if (call == null) { + call = (mDefaultPhone == null) + ? null + : mDefaultPhone.getRingingCall(); + } + return call; + } + + /** + * @return the state of active foreground call + * return IDLE if there is no active foreground call + */ + public Call.State getActiveFgCallState() { + Call fgCall = getActiveFgCall(); + + if (fgCall != null) { + return fgCall.getState(); + } + + return Call.State.IDLE; + } + + /** + * @return the connections of active foreground call + * return empty list if there is no active foreground call + */ + public List<Connection> getFgCallConnections() { + Call fgCall = getActiveFgCall(); + if ( fgCall != null) { + return fgCall.getConnections(); + } + return emptyConnections; + } + + /** + * @return the connections of active background call + * return empty list if there is no active background call + */ + public List<Connection> getBgCallConnections() { + Call bgCall = getFirstActiveBgCall(); + if ( bgCall != null) { + return bgCall.getConnections(); + } + return emptyConnections; + } + + /** + * @return the latest connection of active foreground call + * return null if there is no active foreground call + */ + public Connection getFgCallLatestConnection() { + Call fgCall = getActiveFgCall(); + if ( fgCall != null) { + return fgCall.getLatestConnection(); + } + return null; + } + + /** + * @return true if there is at least one Foreground call in disconnected state + */ + public boolean hasDisconnectedFgCall() { + return (getFirstCallOfState(mForegroundCalls, Call.State.DISCONNECTED) != null); + } + + /** + * @return true if there is at least one background call in disconnected state + */ + public boolean hasDisconnectedBgCall() { + return (getFirstCallOfState(mBackgroundCalls, Call.State.DISCONNECTED) != null); + } + + /** + * @return the first active call from a call list + */ + private Call getFirstActiveCall(ArrayList<Call> calls) { + for (Call call : calls) { + if (!call.isIdle()) { + return call; + } + } + return null; + } + + /** + * @return the first call in a the Call.state from a call list + */ + private Call getFirstCallOfState(ArrayList<Call> calls, Call.State state) { + for (Call call : calls) { + if (call.getState() == state) { + return call; + } + } + return null; + } + + + + + private Handler mHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + + switch (msg.what) { + case EVENT_DISCONNECT: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_DISCONNECT)"); + mDisconnectRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_PRECISE_CALL_STATE_CHANGED: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_PRECISE_CALL_STATE_CHANGED)"); + mPreciseCallStateRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_NEW_RINGING_CONNECTION: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_NEW_RINGING_CONNECTION)"); + mNewRingingConnectionRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_UNKNOWN_CONNECTION: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_UNKNOWN_CONNECTION)"); + mUnknownConnectionRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_INCOMING_RING: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_INCOMING_RING)"); + // The event may come from RIL who's not aware of an ongoing fg call + if (!hasActiveFgCall()) { + mIncomingRingRegistrants.notifyRegistrants((AsyncResult) msg.obj); + } + break; + case EVENT_RINGBACK_TONE: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_RINGBACK_TONE)"); + mRingbackToneRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_IN_CALL_VOICE_PRIVACY_ON: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_IN_CALL_VOICE_PRIVACY_ON)"); + mInCallVoicePrivacyOnRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_IN_CALL_VOICE_PRIVACY_OFF: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_IN_CALL_VOICE_PRIVACY_OFF)"); + mInCallVoicePrivacyOffRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_CALL_WAITING: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_CALL_WAITING)"); + mCallWaitingRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_DISPLAY_INFO: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_DISPLAY_INFO)"); + mDisplayInfoRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_SIGNAL_INFO: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_SIGNAL_INFO)"); + mSignalInfoRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_CDMA_OTA_STATUS_CHANGE: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_CDMA_OTA_STATUS_CHANGE)"); + mCdmaOtaStatusChangeRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_RESEND_INCALL_MUTE: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_RESEND_INCALL_MUTE)"); + mResendIncallMuteRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_MMI_INITIATE: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_MMI_INITIATE)"); + mMmiInitiateRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_MMI_COMPLETE: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_MMI_COMPLETE)"); + mMmiCompleteRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_ECM_TIMER_RESET: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_ECM_TIMER_RESET)"); + mEcmTimerResetRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_SUBSCRIPTION_INFO_READY: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_SUBSCRIPTION_INFO_READY)"); + mSubscriptionInfoReadyRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_SUPP_SERVICE_FAILED: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_SUPP_SERVICE_FAILED)"); + mSuppServiceFailedRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_SERVICE_STATE_CHANGED: + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_SERVICE_STATE_CHANGED)"); + mServiceStateChangedRegistrants.notifyRegistrants((AsyncResult) msg.obj); + break; + case EVENT_POST_DIAL_CHARACTER: + // we need send the character that is being processed in msg.arg1 + // so can't use notifyRegistrants() + if (VDBG) Log.d(LOG_TAG, " handleMessage (EVENT_POST_DIAL_CHARACTER)"); + for(int i=0; i < mPostDialCharacterRegistrants.size(); i++) { + Message notifyMsg; + notifyMsg = ((Registrant)mPostDialCharacterRegistrants.get(i)).messageForRegistrant(); + notifyMsg.obj = msg.obj; + notifyMsg.arg1 = msg.arg1; + notifyMsg.sendToTarget(); + } + break; + } + } + }; + + @Override + public String toString() { + Call call; + StringBuilder b = new StringBuilder(); + + b.append("########### Dump CallManager ############"); + b.append("\nCallManager state = " + getState()); + call = getActiveFgCall(); + b.append("\n - Foreground: " + getActiveFgCallState()); + b.append(" from " + call.getPhone()); + b.append("\n Conn: ").append(getFgCallConnections()); + call = getFirstActiveBgCall(); + b.append("\n - Background: " + call.getState()); + b.append(" from " + call.getPhone()); + b.append("\n Conn: ").append(getBgCallConnections()); + call = getFirstActiveRingingCall(); + b.append("\n - Ringing: " +call.getState()); + b.append(" from " + call.getPhone()); + + for (Phone phone : getAllPhones()) { + if (phone != null) { + b.append("\n Phone: " + phone + ", name = " + phone.getPhoneName() + + ", state = " + phone.getState()); + call = phone.getForegroundCall(); + b.append("\n - Foreground: ").append(call); + call = phone.getBackgroundCall(); + b.append(" Background: ").append(call); + call = phone.getRingingCall(); + b.append(" Ringing: ").append(call); + } + } + b.append("\n########## End Dump CallManager ##########"); + return b.toString(); + } +} diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java index cf89848..360d35e 100644 --- a/telephony/java/com/android/internal/telephony/CallerInfo.java +++ b/telephony/java/com/android/internal/telephony/CallerInfo.java @@ -26,9 +26,9 @@ import static android.provider.ContactsContract.RawContacts; import android.text.TextUtils; import android.telephony.TelephonyManager; import android.telephony.PhoneNumberUtils; -import android.util.Config; import android.util.Log; + /** * Looks up caller information for the given phone number. * @@ -36,6 +36,7 @@ import android.util.Log; */ public class CallerInfo { private static final String TAG = "CallerInfo"; + private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); public static final String UNKNOWN_NUMBER = "-1"; public static final String PRIVATE_NUMBER = "-2"; @@ -128,7 +129,7 @@ public class CallerInfo { info.isCachedPhotoCurrent = false; info.contactExists = false; - if (Config.LOGV) Log.v(TAG, "construct callerInfo from cursor"); + if (VDBG) Log.v(TAG, "construct callerInfo from cursor"); if (cursor != null) { if (cursor.moveToFirst()) { @@ -166,31 +167,30 @@ public class CallerInfo { // Look for the person ID. // TODO: This is pretty ugly now, see bug 2269240 for - // more details. With tel: URI the contact id is in - // col "_id" while when we use a - // content://contacts/data/phones URI, the contact id - // is col "contact_id". As a work around we use the - // type of the contact url to figure out which column - // we should look at to get the contact_id. - - final String mimeType = context.getContentResolver().getType(contactRef); + // more details. The column to use depends upon the type of URL, + // for content://com.android.contacts/data/phones the "contact_id" + // column is used. For content/com.andriod.contacts/phone_lookup" + // the "_ID" column is used. If it is neither we leave columnIndex + // at -1 and no person ID will be available. columnIndex = -1; - if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) { - // content://com.android.contacts/data/phones URL + String url = contactRef.toString(); + if (url.startsWith("content://com.android.contacts/data/phones")) { + if (VDBG) Log.v(TAG, + "URL path starts with 'data/phones' using RawContacts.CONTACT_ID"); columnIndex = cursor.getColumnIndex(RawContacts.CONTACT_ID); - } else { - // content://com.android.contacts/phone_lookup URL - // TODO: mime type is null here so we cannot test - // if we have the right url type. phone_lookup URL - // should resolve to a mime type. + } else if (url.startsWith("content://com.android.contacts/phone_lookup")) { + if (VDBG) Log.v(TAG, + "URL path starts with 'phone_lookup' using PhoneLookup._ID"); columnIndex = cursor.getColumnIndex(PhoneLookup._ID); + } else { + Log.e(TAG, "Bad contact URL '" + url + "'"); } if (columnIndex != -1) { info.person_id = cursor.getLong(columnIndex); } else { - Log.e(TAG, "Column missing for " + contactRef); + Log.e(TAG, "person_id column missing for " + contactRef); } // look for the custom ringtone, create from the string stored diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java index 798a5a5..25ca559 100644 --- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java +++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java @@ -24,9 +24,10 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.provider.ContactsContract.CommonDataKinds.SipAddress; +import android.provider.ContactsContract.Data; import android.provider.ContactsContract.PhoneLookup; import android.telephony.PhoneNumberUtils; -import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; @@ -36,7 +37,7 @@ import android.util.Log; public class CallerInfoAsyncQuery { - private static final boolean DBG = false; + private static final boolean DBG = true; // STOPSHIP: disable debugging before ship private static final String LOG_TAG = "CallerInfoAsyncQuery"; private static final int EVENT_NEW_QUERY = 1; @@ -189,7 +190,7 @@ public class CallerInfoAsyncQuery { */ @Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - if (DBG) log("query complete for token: " + token); + if (DBG) log("##### onQueryComplete() ##### query complete for token: " + token); //get the cookie and notify the listener. CookieWrapper cw = (CookieWrapper) cookie; @@ -227,6 +228,8 @@ public class CallerInfoAsyncQuery { mCallerInfo = new CallerInfo().markAsVoiceMail(); } else { mCallerInfo = CallerInfo.getCallerInfo(mQueryContext, mQueryUri, cursor); + if (DBG) log("==> Got mCallerInfo: " + mCallerInfo); + // Use the number entered by the user for display. if (!TextUtils.isEmpty(cw.number)) { mCallerInfo.phoneNumber = PhoneNumberUtils.formatNumber(cw.number); @@ -238,7 +241,7 @@ public class CallerInfoAsyncQuery { //notify that we can clean up the queue after this. CookieWrapper endMarker = new CookieWrapper(); endMarker.event = EVENT_END_OF_QUEUE; - startQuery (token, endMarker, null, null, null, null, null); + startQuery(token, endMarker, null, null, null, null, null); } //notify the listener that the query is complete. @@ -274,24 +277,82 @@ public class CallerInfoAsyncQuery { cw.cookie = cookie; cw.event = EVENT_NEW_QUERY; - c.mHandler.startQuery (token, cw, contactRef, null, null, null, null); + c.mHandler.startQuery(token, cw, contactRef, null, null, null, null); return c; } /** - * Factory method to start query with a number + * Factory method to start the query based on a number. + * + * Note: if the number contains an "@" character we treat it + * as a SIP address, and look it up directly in the Data table + * rather than using the PhoneLookup table. + * TODO: But eventually we should expose two separate methods, one for + * numbers and one for SIP addresses, and then have + * PhoneUtils.startGetCallerInfo() decide which one to call based on + * the phone type of the incoming connection. */ public static CallerInfoAsyncQuery startQuery(int token, Context context, String number, OnQueryCompleteListener listener, Object cookie) { - //construct the URI object and start Query. - Uri contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); + if (DBG) { + log("##### CallerInfoAsyncQuery startQuery()... #####"); + log("- number: " + number); + log("- cookie: " + cookie); + } + + // Construct the URI object and query params, and start the query. + + Uri contactRef; + String selection; + String[] selectionArgs; + + if (PhoneNumberUtils.isUriNumber(number)) { + // "number" is really a SIP address. + if (DBG) log(" - Treating number as a SIP address: " + number); + + // We look up SIP addresses directly in the Data table: + contactRef = Data.CONTENT_URI; + + // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent. + // + // Also note we use "upper(data1)" in the WHERE clause, and + // uppercase the incoming SIP address, in order to do a + // case-insensitive match. + // + // TODO: need to confirm that the use of upper() doesn't + // prevent us from using the index! (Linear scan of the whole + // contacts DB can be very slow.) + // + // TODO: May also need to normalize by adding "sip:" as a + // prefix, if we start storing SIP addresses that way in the + // database. + + selection = "upper(" + Data.DATA1 + ")=?" + + " AND " + + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'"; + selectionArgs = new String[] { number.toUpperCase() }; + + } else { + // "number" is a regular phone number. Use the PhoneLookup table: + contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); + selection = null; + selectionArgs = null; + } + + if (DBG) { + log("==> contactRef: " + contactRef); + log("==> selection: " + selection); + if (selectionArgs != null) { + for (int i = 0; i < selectionArgs.length; i++) { + log("==> selectionArgs[" + i + "]: " + selectionArgs[i]); + } + } + } CallerInfoAsyncQuery c = new CallerInfoAsyncQuery(); c.allocate(context, contactRef); - if (DBG) log("starting query for number: " + number + " handler: " + c.toString()); - //create cookieWrapper, start query CookieWrapper cw = new CookieWrapper(); cw.listener = listener; @@ -307,10 +368,15 @@ public class CallerInfoAsyncQuery { cw.event = EVENT_NEW_QUERY; } - c.mHandler.startQuery (token, cw, contactRef, null, null, null, null); - + c.mHandler.startQuery(token, + cw, // cookie + contactRef, // uri + null, // projection + selection, // selection + selectionArgs, // selectionArgs + null); // orderBy return c; - } + } /** * Method to add listeners to a currently running query @@ -326,7 +392,7 @@ public class CallerInfoAsyncQuery { cw.cookie = cookie; cw.event = EVENT_ADD_LISTENER; - mHandler.startQuery (token, cw, null, null, null, null, null); + mHandler.startQuery(token, cw, null, null, null, null, null); } /** diff --git a/telephony/java/com/android/internal/telephony/Connection.java b/telephony/java/com/android/internal/telephony/Connection.java index 11d0b1b..07f90cd 100644 --- a/telephony/java/com/android/internal/telephony/Connection.java +++ b/telephony/java/com/android/internal/telephony/Connection.java @@ -39,6 +39,12 @@ public abstract class Connection { CONGESTION, /* outgoing call to congested network */ MMI, /* not presently used; dial() returns null */ INVALID_NUMBER, /* invalid dial string */ + NUMBER_UNREACHABLE, /* cannot reach the peer */ + SERVER_UNREACHABLE, /* cannot reach the server */ + INVALID_CREDENTIALS, /* invalid credentials */ + OUT_OF_NETWORK, /* calling from out of network is not allowed */ + SERVER_ERROR, /* server error */ + TIMED_OUT, /* client timed out */ LOST_SIGNAL, LIMIT_EXCEEDED, /* eg GSM ACM limit exceeded */ INCOMING_REJECTED, /* an incoming call that was rejected */ diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java index 3b9e6cc..265bf7e 100644 --- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java @@ -33,7 +33,7 @@ import java.util.ArrayList; * */ public abstract class DataConnectionTracker extends Handler { - protected static final boolean DBG = true; + protected static final boolean DBG = false; protected final String LOG_TAG = "DataConnectionTracker"; /** diff --git a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java index 4da4b6a..057ba0a 100644 --- a/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java +++ b/telephony/java/com/android/internal/telephony/DefaultPhoneNotifier.java @@ -102,7 +102,8 @@ public class DefaultPhoneNotifier implements PhoneNotifier { sender.getActiveApnTypes(), sender.getInterfaceName(null), ((telephony!=null) ? telephony.getNetworkType() : - TelephonyManager.NETWORK_TYPE_UNKNOWN)); + TelephonyManager.NETWORK_TYPE_UNKNOWN), + sender.getGateway(null)); } catch (RemoteException ex) { // system process is dead } diff --git a/telephony/java/com/android/internal/telephony/GsmAlphabet.java b/telephony/java/com/android/internal/telephony/GsmAlphabet.java index ebdd220..30ee77c 100644 --- a/telephony/java/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/java/com/android/internal/telephony/GsmAlphabet.java @@ -23,7 +23,7 @@ import android.util.Log; /** * This class implements the character set mapping between - * the GSM SMS 7-bit alphabet specifed in TS 23.038 6.2.1 + * the GSM SMS 7-bit alphabet specified in TS 23.038 6.2.1 * and UTF-16 * * {@hide} @@ -171,7 +171,7 @@ public class GsmAlphabet { * array cannot contain more than 255 septets. * * @param data The text string to encode. - * @param header Optional header (includeing length byte) that precedes + * @param header Optional header (including length byte) that precedes * the encoded data, padded to septet boundary. * @return Byte array containing header and encoded data. */ @@ -204,7 +204,7 @@ public class GsmAlphabet { * the packed septets. The returned array cannot contain more than 255 * septets. * - * @param data the data string to endcode + * @param data the data string to encode * @throws EncodeException if String is too large to encode */ public static byte[] stringToGsm7BitPacked(String data) @@ -223,7 +223,7 @@ public class GsmAlphabet { * * @param data the text to convert to septets * @param startingSeptetOffset the number of padding septets to put before - * the character data at the begining of the array + * the character data at the beginning of the array * @param throwException If true, throws EncodeException on invalid char. * If false, replaces unencodable char with GSM alphabet space char. * @@ -257,7 +257,7 @@ public class GsmAlphabet { } /** - * Pack a 7-bit char into its appropirate place in a byte array + * Pack a 7-bit char into its appropriate place in a byte array * * @param bitOffset the bit offset that the septet should be packed at * (septet index * 7) diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl index e74b9e4..5cba2e1 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl @@ -59,6 +59,11 @@ interface IPhoneSubInfo { String getVoiceMailNumber(); /** + * Retrieves the complete voice mail number. + */ + String getCompleteVoiceMailNumber(); + + /** * Retrieves the alpha identifier associated with the voice mail number. */ String getVoiceMailAlphaTag(); diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 5bf8e58..87e4b52 100644 --- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -32,7 +32,8 @@ interface ITelephonyRegistry { void notifyCallForwardingChanged(boolean cfi); void notifyDataActivity(int state); void notifyDataConnection(int state, boolean isDataConnectivityPossible, - String reason, String apn, in String[] apnTypes, String interfaceName, int networkType); + String reason, String apn, in String[] apnTypes, String interfaceName, int networkType, + String gateway); void notifyDataConnectionFailed(String reason); void notifyCellLocation(in Bundle cellLocation); } diff --git a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java index 8a5a6ae..5fef6de 100644 --- a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java +++ b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManager.java @@ -57,7 +57,7 @@ public abstract class IccSmsInterfaceManager extends ISms.Stub { * @param destPort the port to deliver the message to * @param data the body of the message to send * @param sentIntent if not NULL this <code>PendingIntent</code> is - * broadcast when the message is sucessfully sent, or failed. + * broadcast when the message is successfully sent, or failed. * The result code will be <code>Activity.RESULT_OK<code> for success, * or one of these errors:<br> * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> @@ -67,7 +67,7 @@ public abstract class IccSmsInterfaceManager extends ISms.Stub { * the extra "errorCode" containing a radio technology specific value, * generally only useful for troubleshooting.<br> * The per-application based SMS control checks sentIntent. If sentIntent - * is NULL the caller will be checked against all unknown applicaitons, + * is NULL the caller will be checked against all unknown applications, * which cause smaller number of SMS to be sent in checking period. * @param deliveryIntent if not NULL this <code>PendingIntent</code> is * broadcast when the message is delivered to the recipient. The @@ -94,7 +94,7 @@ public abstract class IccSmsInterfaceManager extends ISms.Stub { * the current default SMSC * @param text the body of the message to send * @param sentIntent if not NULL this <code>PendingIntent</code> is - * broadcast when the message is sucessfully sent, or failed. + * broadcast when the message is successfully sent, or failed. * The result code will be <code>Activity.RESULT_OK<code> for success, * or one of these errors:<br> * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> @@ -140,7 +140,7 @@ public abstract class IccSmsInterfaceManager extends ISms.Stub { * <code>RESULT_ERROR_RADIO_OFF</code> * <code>RESULT_ERROR_NULL_PDU</code>. * The per-application based SMS control checks sentIntent. If sentIntent - * is NULL the caller will be checked against all unknown applicaitons, + * is NULL the caller will be checked against all unknown applications, * which cause smaller number of SMS to be sent in checking period. * @param deliveryIntents if not null, an <code>ArrayList</code> of * <code>PendingIntent</code>s (one for each message part) that is diff --git a/telephony/java/com/android/internal/telephony/MccTable.java b/telephony/java/com/android/internal/telephony/MccTable.java index b73c2f7..5539057 100644 --- a/telephony/java/com/android/internal/telephony/MccTable.java +++ b/telephony/java/com/android/internal/telephony/MccTable.java @@ -174,7 +174,7 @@ mcc_table = [ (438, 'tm', 2, 'Turkmenistan'), (440, 'jp', 2, 'Asia/Tokyo', 'ja', 14, 'Japan'), (441, 'jp', 2, 'Asia/Tokyo', 'ja', 14, 'Japan'), - (450, 'kr', 2, 'Korea (Republic of)'), + (450, 'kr', 2, 'Asia/Seoul', 'ko', 13, 'Korea (Republic of)'), (452, 'vn', 2, 'Viet Nam (Socialist Republic of)'), (454, 'hk', 2, '"Hong Kong, China"'), (455, 'mo', 2, '"Macao, China"'), @@ -378,6 +378,7 @@ public final class MccTable "", "Africa/Johannesburg", "Asia/Beijing", + "Asia/Seoul", "Asia/Singapore", "Asia/Tokyo", "Australia/Sydney", @@ -399,7 +400,8 @@ public final class MccTable * AUTO GENERATED (by the Python code above) */ private static final String[] LANG_STRINGS = { - "", "cs", "de", "en", "es", "fr", "it", "ja", "nl", "zh" + "", "cs", "de", "en", "es", "fr", "it", "ja", "ko", "nl", + "zh" }; /** @@ -446,13 +448,13 @@ public final class MccTable * default language 4 bits */ private static final int[] IND_CODES = { - 0x67720400, 0x6e6c6c68, 0x62650400, 0x667204b5, 0x6d630400, 0x61640400, - 0x657304a4, 0x68750400, 0x62610400, 0x68720400, 0x72730400, 0x697404d6, - 0x766104d6, 0x726f0400, 0x63680502, 0x637a6cc1, 0x736b0400, 0x61746ce2, - 0x67626c93, 0x67626c93, 0x646b0400, 0x73650400, 0x6e6f0400, 0x66690400, + 0x67720400, 0x6e6c6c79, 0x62650400, 0x667204c5, 0x6d630400, 0x61640400, + 0x657304b4, 0x68750400, 0x62610400, 0x68720400, 0x72730400, 0x697404e6, + 0x766104e6, 0x726f0400, 0x63680512, 0x637a6cd1, 0x736b0400, 0x61746cf2, + 0x67626ca3, 0x67626ca3, 0x646b0400, 0x73650400, 0x6e6f0400, 0x66690400, 0x6c740400, 0x6c760400, 0x65650400, 0x72750400, 0x75610400, 0x62790400, - 0x6d640400, 0x706c04f0, 0x64656c72, 0x67690400, 0x70740400, 0x6c750400, - 0x69650483, 0x69730400, 0x616c0400, 0x6d740400, 0x63790400, 0x67650400, + 0x6d640400, 0x706c0500, 0x64656c82, 0x67690400, 0x70740400, 0x6c750400, + 0x69650493, 0x69730400, 0x616c0400, 0x6d740400, 0x63790400, 0x67650400, 0x616d0400, 0x62670400, 0x74720400, 0x666f0400, 0x67650400, 0x676c0400, 0x736d0400, 0x736c0400, 0x6d6b0400, 0x6c690400, 0x6d650400, 0x63615e00, 0x706d0400, 0x75735e03, 0x75735e03, 0x75735e03, 0x75735e03, 0x75735e03, @@ -465,11 +467,11 @@ public final class MccTable 0x6c620400, 0x6a6f0400, 0x73790400, 0x69710400, 0x6b770400, 0x73610400, 0x79650400, 0x6f6d0400, 0x70730400, 0x61650400, 0x696c0400, 0x62680400, 0x71610400, 0x6d6e0400, 0x6e700400, 0x61650400, 0x61650400, 0x69720400, - 0x757a0400, 0x746a0400, 0x6b670400, 0x746d0400, 0x6a707447, 0x6a707447, - 0x6b720400, 0x766e0400, 0x686b0400, 0x6d6f0400, 0x6b680400, 0x6c610400, - 0x636e6c29, 0x636e6c29, 0x74770400, 0x6b700400, 0x62640400, 0x6d760400, - 0x6d790400, 0x61755c53, 0x69640400, 0x746c0400, 0x70680400, 0x74680400, - 0x73675c33, 0x626e0400, 0x6e7a0513, 0x6d700400, 0x67750400, 0x6e720400, + 0x757a0400, 0x746a0400, 0x6b670400, 0x746d0400, 0x6a707457, 0x6a707457, + 0x6b726c38, 0x766e0400, 0x686b0400, 0x6d6f0400, 0x6b680400, 0x6c610400, + 0x636e6c2a, 0x636e6c2a, 0x74770400, 0x6b700400, 0x62640400, 0x6d760400, + 0x6d790400, 0x61755c63, 0x69640400, 0x746c0400, 0x70680400, 0x74680400, + 0x73675c43, 0x626e0400, 0x6e7a0523, 0x6d700400, 0x67750400, 0x6e720400, 0x70670400, 0x746f0400, 0x73620400, 0x76750400, 0x666a0400, 0x77660400, 0x61730400, 0x6b690400, 0x6e630400, 0x70660400, 0x636b0400, 0x77730400, 0x666d0400, 0x6d680400, 0x70770400, 0x65670400, 0x647a0400, 0x6d610400, diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java index 3c9a1f8..e426e94 100644 --- a/telephony/java/com/android/internal/telephony/Phone.java +++ b/telephony/java/com/android/internal/telephony/Phone.java @@ -103,6 +103,7 @@ public interface Phone { static final String DATA_APN_KEY = "apn"; static final String DATA_IFACE_NAME_KEY = "iface"; + static final String DATA_GATEWAY_KEY = "gateway"; static final String NETWORK_UNAVAILABLE_KEY = "networkUnvailable"; static final String PHONE_IN_ECM_STATE = "phoneinECMState"; @@ -177,6 +178,7 @@ public interface Phone { static final int PHONE_TYPE_NONE = RILConstants.NO_PHONE; static final int PHONE_TYPE_GSM = RILConstants.GSM_PHONE; static final int PHONE_TYPE_CDMA = RILConstants.CDMA_PHONE; + static final int PHONE_TYPE_SIP = RILConstants.SIP_PHONE; // Used for preferred network type // Note NT_* substitute RILConstants.NETWORK_MODE_* above the Phone @@ -1167,6 +1169,11 @@ public interface Phone { boolean getMute(); /** + * Enables or disables echo suppression. + */ + void setEchoSuppressionEnabled(boolean enabled); + + /** * Invokes RIL_REQUEST_OEM_HOOK_RAW on RIL implementation. * * @param data The data for the request. diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java index c3c8f5e..1674ad6 100644 --- a/telephony/java/com/android/internal/telephony/PhoneBase.java +++ b/telephony/java/com/android/internal/telephony/PhoneBase.java @@ -286,7 +286,7 @@ public abstract class PhoneBase extends Handler implements Phone { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences.Editor editor = sp.edit(); editor.putBoolean(DNS_SERVER_CHECK_DISABLED_KEY, b); - editor.commit(); + editor.apply(); } /** @@ -505,6 +505,10 @@ public abstract class PhoneBase extends Handler implements Phone { mCM.unregisterForResendIncallMute(h); } + public void setEchoSuppressionEnabled(boolean enabled) { + // no need for regular phone + } + /** * Subclasses of Phone probably want to replace this with a * version scoped to their packages diff --git a/telephony/java/com/android/internal/telephony/PhoneFactory.java b/telephony/java/com/android/internal/telephony/PhoneFactory.java index 803b736..2e391cb 100644 --- a/telephony/java/com/android/internal/telephony/PhoneFactory.java +++ b/telephony/java/com/android/internal/telephony/PhoneFactory.java @@ -24,6 +24,8 @@ import android.util.Log; import com.android.internal.telephony.cdma.CDMAPhone; import com.android.internal.telephony.gsm.GSMPhone; +import com.android.internal.telephony.sip.SipPhone; +import com.android.internal.telephony.sip.SipPhoneFactory; /** * {@hide} @@ -175,4 +177,13 @@ public class PhoneFactory { return phone; } } + + /** + * Makes a {@link SipPhone} object. + * @param sipUri the local SIP URI the phone runs on + * @return the {@code SipPhone} object or null if the SIP URI is not valid + */ + public static SipPhone makeSipPhone(String sipUri) { + return SipPhoneFactory.makePhone(sipUri, sContext, sPhoneNotifier); + } } diff --git a/telephony/java/com/android/internal/telephony/PhoneProxy.java b/telephony/java/com/android/internal/telephony/PhoneProxy.java index 5e7dcb0..77f1e6c 100644 --- a/telephony/java/com/android/internal/telephony/PhoneProxy.java +++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java @@ -568,6 +568,10 @@ public class PhoneProxy extends Handler implements Phone { return mActivePhone.getMute(); } + public void setEchoSuppressionEnabled(boolean enabled) { + mActivePhone.setEchoSuppressionEnabled(enabled); + } + public void invokeOemRilRequestRaw(byte[] data, Message response) { mActivePhone.invokeOemRilRequestRaw(data, response); } diff --git a/telephony/java/com/android/internal/telephony/PhoneSubInfo.java b/telephony/java/com/android/internal/telephony/PhoneSubInfo.java index 4f71bb1..a45cad1 100644 --- a/telephony/java/com/android/internal/telephony/PhoneSubInfo.java +++ b/telephony/java/com/android/internal/telephony/PhoneSubInfo.java @@ -21,6 +21,7 @@ import java.io.PrintWriter; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; +import android.telephony.PhoneNumberUtils; import android.util.Log; public class PhoneSubInfo extends IPhoneSubInfo.Stub { @@ -29,6 +30,9 @@ public class PhoneSubInfo extends IPhoneSubInfo.Stub { private Context mContext; private static final String READ_PHONE_STATE = android.Manifest.permission.READ_PHONE_STATE; + private static final String CALL_PRIVILEGED = + // TODO Add core/res/AndriodManifest.xml#READ_PRIVILEGED_PHONE_STATE + android.Manifest.permission.CALL_PRIVILEGED; public PhoneSubInfo(Phone phone) { mPhone = phone; @@ -101,7 +105,22 @@ public class PhoneSubInfo extends IPhoneSubInfo.Stub { */ public String getVoiceMailNumber() { mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE"); - return (String) mPhone.getVoiceMailNumber(); + String number = PhoneNumberUtils.extractNetworkPortion(mPhone.getVoiceMailNumber()); + Log.d(LOG_TAG, "VM: PhoneSubInfo.getVoiceMailNUmber: "); // + number); + return number; + } + + /** + * Retrieves the complete voice mail number. + * + * @hide + */ + public String getCompleteVoiceMailNumber() { + mContext.enforceCallingOrSelfPermission(CALL_PRIVILEGED, + "Requires CALL_PRIVILEGED"); + String number = mPhone.getVoiceMailNumber(); + Log.d(LOG_TAG, "VM: PhoneSubInfo.getCompleteVoiceMailNUmber: "); // + number); + return number; } /** diff --git a/telephony/java/com/android/internal/telephony/PhoneSubInfoProxy.java b/telephony/java/com/android/internal/telephony/PhoneSubInfoProxy.java index 202ded2..7009893 100644 --- a/telephony/java/com/android/internal/telephony/PhoneSubInfoProxy.java +++ b/telephony/java/com/android/internal/telephony/PhoneSubInfoProxy.java @@ -82,6 +82,13 @@ public class PhoneSubInfoProxy extends IPhoneSubInfo.Stub { } /** + * Retrieves the complete voice mail number. + */ + public String getCompleteVoiceMailNumber() { + return mPhoneSubInfo.getCompleteVoiceMailNumber(); + } + + /** * Retrieves the alpha identifier associated with the voice mail number. */ public String getVoiceMailAlphaTag() { diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 71a80e0..888f721 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -79,6 +79,7 @@ public interface RILConstants { int NO_PHONE = 0; int GSM_PHONE = 1; int CDMA_PHONE = 2; + int SIP_PHONE = 3; int CDM_TTY_MODE_DISABLED = 0; int CDM_TTY_MODE_ENABLED = 1; diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java index ca526a5..917e1d8 100644 --- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java @@ -32,9 +32,11 @@ import android.database.Cursor; import android.database.SQLException; import android.net.Uri; import android.os.AsyncResult; +import android.os.Environment; import android.os.Handler; import android.os.Message; import android.os.PowerManager; +import android.os.StatFs; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.provider.Settings; @@ -44,10 +46,6 @@ import android.util.Config; import android.util.Log; import android.view.WindowManager; -import com.android.internal.telephony.CommandsInterface; -import com.android.internal.telephony.SmsMessageBase; -import com.android.internal.telephony.SmsResponse; -import com.android.internal.telephony.WapPushOverSms; import com.android.internal.util.HexDump; import java.io.ByteArrayOutputStream; @@ -75,7 +73,7 @@ public abstract class SMSDispatcher extends Handler { private static final int DEFAULT_SMS_MAX_COUNT = 100; /** Default timeout for SMS sent query */ - private static final int DEFAULT_SMS_TIMOUEOUT = 6000; + private static final int DEFAULT_SMS_TIMEOUT = 6000; protected static final String[] RAW_PROJECTION = new String[] { "pdu", @@ -83,8 +81,6 @@ public abstract class SMSDispatcher extends Handler { "destination_port", }; - static final int MAIL_SEND_SMS = 1; - static final protected int EVENT_NEW_SMS = 1; static final protected int EVENT_SEND_SMS_COMPLETE = 2; @@ -143,7 +139,7 @@ public abstract class SMSDispatcher extends Handler { private SmsCounter mCounter; - private ArrayList mSTrackers = new ArrayList(MO_MSG_QUEUE_LIMIT); + private ArrayList<SmsTracker> mSTrackers = new ArrayList<SmsTracker>(MO_MSG_QUEUE_LIMIT); /** Wake lock to ensure device stays awake while dispatching the SMS intent. */ private PowerManager.WakeLock mWakeLock; @@ -154,10 +150,6 @@ public abstract class SMSDispatcher extends Handler { */ private final int WAKE_LOCK_TIMEOUT = 5000; - private static SmsMessage mSmsMessage; - private static SmsMessageBase mSmsMessageBase; - private SmsMessageBase.SubmitPduBase mSubmitPduBase; - protected boolean mStorageAvailable = true; protected boolean mReportMemoryStatusPending = false; @@ -250,11 +242,9 @@ public abstract class SMSDispatcher extends Handler { // Register for device storage intents. Use these to notify the RIL // that storage for SMS is or is not available. - // TODO: Revisit this for a later release. Storage reporting should - // rely more on application indication. IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW); - filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); + filter.addAction(Intent.ACTION_DEVICE_STORAGE_FULL); + filter.addAction(Intent.ACTION_DEVICE_STORAGE_NOT_FULL); mContext.registerReceiver(mResultReceiver, filter); } @@ -345,7 +335,7 @@ public abstract class SMSDispatcher extends Handler { msg.obj = null; if (mSTrackers.isEmpty() == false) { try { - SmsTracker sTracker = (SmsTracker)mSTrackers.remove(0); + SmsTracker sTracker = mSTrackers.remove(0); sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED); } catch (CanceledException ex) { Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED"); @@ -358,7 +348,7 @@ public abstract class SMSDispatcher extends Handler { case EVENT_SEND_CONFIRMED_SMS: if (mSTrackers.isEmpty() == false) { - SmsTracker sTracker = (SmsTracker)mSTrackers.remove(mSTrackers.size() - 1); + SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1); if (isMultipartTracker(sTracker)) { sendMultipartSms(sTracker); } else { @@ -372,7 +362,7 @@ public abstract class SMSDispatcher extends Handler { if (mSTrackers.isEmpty() == false) { // Remove the latest one. try { - SmsTracker sTracker = (SmsTracker)mSTrackers.remove(mSTrackers.size() - 1); + SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1); sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED); } catch (CanceledException ex) { Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED"); @@ -679,7 +669,7 @@ public abstract class SMSDispatcher extends Handler { * @param destPort the port to deliver the message to * @param data the body of the message to send * @param sentIntent if not NULL this <code>PendingIntent</code> is - * broadcast when the message is sucessfully sent, or failed. + * broadcast when the message is successfully sent, or failed. * The result code will be <code>Activity.RESULT_OK<code> for success, * or one of these errors:<br> * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> @@ -689,7 +679,7 @@ public abstract class SMSDispatcher extends Handler { * the extra "errorCode" containing a radio technology specific value, * generally only useful for troubleshooting.<br> * The per-application based SMS control checks sentIntent. If sentIntent - * is NULL the caller will be checked against all unknown applicaitons, + * is NULL the caller will be checked against all unknown applications, * which cause smaller number of SMS to be sent in checking period. * @param deliveryIntent if not NULL this <code>PendingIntent</code> is * broadcast when the message is delivered to the recipient. The @@ -706,7 +696,7 @@ public abstract class SMSDispatcher extends Handler { * the current default SMSC * @param text the body of the message to send * @param sentIntent if not NULL this <code>PendingIntent</code> is - * broadcast when the message is sucessfully sent, or failed. + * broadcast when the message is successfully sent, or failed. * The result code will be <code>Activity.RESULT_OK<code> for success, * or one of these errors:<br> * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> @@ -742,7 +732,7 @@ public abstract class SMSDispatcher extends Handler { * <code>RESULT_ERROR_RADIO_OFF</code> * <code>RESULT_ERROR_NULL_PDU</code>. * The per-application based SMS control checks sentIntent. If sentIntent - * is NULL the caller will be checked against all unknown applicaitons, + * is NULL the caller will be checked against all unknown applications, * which cause smaller number of SMS to be sent in checking period. * @param deliveryIntents if not null, an <code>ArrayList</code> of * <code>PendingIntent</code>s (one for each message part) that is @@ -758,17 +748,17 @@ public abstract class SMSDispatcher extends Handler { * Send a SMS * * @param smsc the SMSC to send the message through, or NULL for the - * defatult SMSC + * default SMSC * @param pdu the raw PDU to send * @param sentIntent if not NULL this <code>Intent</code> is - * broadcast when the message is sucessfully sent, or failed. + * broadcast when the message is successfully sent, or failed. * The result code will be <code>Activity.RESULT_OK<code> for success, * or one of these errors: * <code>RESULT_ERROR_GENERIC_FAILURE</code> * <code>RESULT_ERROR_RADIO_OFF</code> * <code>RESULT_ERROR_NULL_PDU</code>. * The per-application based SMS control checks sentIntent. If sentIntent - * is NULL the caller will be checked against all unknown applicaitons, + * is NULL the caller will be checked against all unknown applications, * which cause smaller number of SMS to be sent in checking period. * @param deliveryIntent if not NULL this <code>Intent</code> is * broadcast when the message is delivered to the recipient. The @@ -837,7 +827,7 @@ public abstract class SMSDispatcher extends Handler { mSTrackers.add(tracker); sendMessageDelayed ( obtainMessage(EVENT_ALERT_TIMEOUT, d), - DEFAULT_SMS_TIMOUEOUT); + DEFAULT_SMS_TIMEOUT); } protected String getAppNameByIntent(PendingIntent intent) { @@ -931,7 +921,7 @@ public abstract class SMSDispatcher extends Handler { } /** - * Keeps track of an SMS that has been sent to the RIL, until it it has + * Keeps track of an SMS that has been sent to the RIL, until it has * successfully been sent, or we're done trying. * */ @@ -972,27 +962,26 @@ public abstract class SMSDispatcher extends Handler { } }; - private BroadcastReceiver mResultReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_LOW)) { - mStorageAvailable = false; - mCm.reportSmsMemoryStatus(false, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE)); - } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_OK)) { - mStorageAvailable = true; - mCm.reportSmsMemoryStatus(true, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE)); - } else { - // Assume the intent is one of the SMS receive intents that - // was sent as an ordered broadcast. Check result and ACK. - int rc = getResultCode(); - boolean success = (rc == Activity.RESULT_OK) - || (rc == Intents.RESULT_SMS_HANDLED); - - // For a multi-part message, this only ACKs the last part. - // Previous parts were ACK'd as they were received. - acknowledgeLastIncomingSms(success, rc, null); - } + private BroadcastReceiver mResultReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_FULL)) { + mStorageAvailable = false; + mCm.reportSmsMemoryStatus(false, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE)); + } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)) { + mStorageAvailable = true; + mCm.reportSmsMemoryStatus(true, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE)); + } else { + // Assume the intent is one of the SMS receive intents that + // was sent as an ordered broadcast. Check result and ACK. + int rc = getResultCode(); + boolean success = (rc == Activity.RESULT_OK) + || (rc == Intents.RESULT_SMS_HANDLED); + + // For a multi-part message, this only ACKs the last part. + // Previous parts were ACK'd as they were received. + acknowledgeLastIncomingSms(success, rc, null); } - - }; + } + }; } diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java index 1c81a07..f31bf24 100755 --- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java +++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java @@ -158,7 +158,7 @@ public class CDMAPhone extends PhoneBase { mDataConnection = new CdmaDataConnectionTracker (this); mRuimCard = new RuimCard(this); mRuimPhoneBookInterfaceManager = new RuimPhoneBookInterfaceManager(this); - mRuimSmsInterfaceManager = new RuimSmsInterfaceManager(this); + mRuimSmsInterfaceManager = new RuimSmsInterfaceManager(this, mSMS); mSubInfo = new PhoneSubInfo(this); mEriManager = new EriManager(this, context, EriManager.ERI_FROM_XML); mCcatService = CatService.getInstance(mCM, mRuimRecords, mContext, @@ -453,9 +453,14 @@ public class CDMAPhone extends PhoneBase { return mMeid; } - //returns MEID in CDMA + //returns MEID or ESN in CDMA public String getDeviceId() { - return getMeid(); + String id = getMeid(); + if ((id == null) || id.matches("^0*$")) { + Log.d(LOG_TAG, "getDeviceId(): MEID is not initialized use ESN"); + id = getEsn(); + } + return id; } public String getDeviceSvn() { @@ -1406,7 +1411,7 @@ public class CDMAPhone extends PhoneBase { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences.Editor editor = sp.edit(); editor.putString(VM_NUMBER_CDMA, number); - editor.commit(); + editor.apply(); } /** diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaCall.java b/telephony/java/com/android/internal/telephony/cdma/CdmaCall.java index c3bb01f..4ad61bb 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaCall.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaCall.java @@ -75,8 +75,7 @@ public final class CdmaCall extends Call { public Phone getPhone() { - //TODO, see GsmCall - return null; + return owner.phone; } public boolean isMultiparty() { diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java index ed93aea..dceff2a 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java @@ -28,21 +28,20 @@ import android.database.SQLException; import android.os.AsyncResult; import android.os.Message; import android.os.SystemProperties; +import android.preference.PreferenceManager; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; -import android.preference.PreferenceManager; -import android.util.Config; -import android.util.Log; import android.telephony.SmsManager; import android.telephony.SmsMessage.MessageClass; +import android.util.Config; +import android.util.Log; -import com.android.internal.telephony.TelephonyProperties; import com.android.internal.telephony.CommandsInterface; +import com.android.internal.telephony.SMSDispatcher; import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; -import com.android.internal.telephony.SMSDispatcher; import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails; -import com.android.internal.telephony.cdma.SmsMessage; +import com.android.internal.telephony.TelephonyProperties; import com.android.internal.telephony.cdma.sms.SmsEnvelope; import com.android.internal.telephony.cdma.sms.UserData; import com.android.internal.util.HexDump; @@ -51,20 +50,16 @@ import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.lang.Boolean; final class CdmaSMSDispatcher extends SMSDispatcher { private static final String TAG = "CDMA"; - private CDMAPhone mCdmaPhone; - private byte[] mLastDispatchedSmsFingerprint; private byte[] mLastAcknowledgedSmsFingerprint; CdmaSMSDispatcher(CDMAPhone phone) { super(phone); - mCdmaPhone = phone; } /** @@ -129,10 +124,10 @@ final class CdmaSMSDispatcher extends SMSDispatcher { Log.d(TAG, "Voicemail count=" + voicemailCount); // Store the voicemail count in preferences. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences( - ((CDMAPhone) mPhone).getContext()); + mPhone.getContext()); SharedPreferences.Editor editor = sp.edit(); editor.putInt(CDMAPhone.VM_COUNT_CDMA, voicemailCount); - editor.commit(); + editor.apply(); ((CDMAPhone) mPhone).updateMessageWaitingIndicator(voicemailCount); handled = true; } else if (((SmsEnvelope.TELESERVICE_WMT == teleService) || @@ -176,7 +171,7 @@ final class CdmaSMSDispatcher extends SMSDispatcher { * TODO(cleanup): Why are we using a getter method for this * (and for so many other sms fields)? Trivial getters and * setters like this are direct violations of the style guide. - * If the purpose is to protect agaist writes (by not + * If the purpose is to protect against writes (by not * providing a setter) then any protection is illusory (and * hence bad) for cases where the values are not primitives, * such as this call for the header. Since this is an issue @@ -440,7 +435,7 @@ final class CdmaSMSDispatcher extends SMSDispatcher { protected void sendSms(SmsTracker tracker) { HashMap map = tracker.mData; - byte smsc[] = (byte[]) map.get("smsc"); + // byte smsc[] = (byte[]) map.get("smsc"); // unused for CDMA byte pdu[] = (byte[]) map.get("pdu"); Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker); diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java index cfcfd98..e97549d 100644 --- a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java +++ b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java @@ -27,6 +27,7 @@ import com.android.internal.telephony.IccConstants; import com.android.internal.telephony.IccSmsInterfaceManager; import com.android.internal.telephony.IccUtils; import com.android.internal.telephony.PhoneProxy; +import com.android.internal.telephony.SMSDispatcher; import com.android.internal.telephony.SmsRawData; import java.util.ArrayList; @@ -81,9 +82,9 @@ public class RuimSmsInterfaceManager extends IccSmsInterfaceManager { } }; - public RuimSmsInterfaceManager(CDMAPhone phone) { + public RuimSmsInterfaceManager(CDMAPhone phone, SMSDispatcher dispatcher) { super(phone); - mDispatcher = new CdmaSMSDispatcher(phone); + mDispatcher = dispatcher; } public void dispose() { diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index f869dbd..d563dc8 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -59,7 +59,7 @@ import static android.telephony.SmsMessage.MessageClass; /** * TODO(cleanup): internally returning null in many places makes * debugging very hard (among many other reasons) and should be made - * more meaningful (replaced with execptions for example). Null + * more meaningful (replaced with exceptions for example). Null * returns should only occur at the very outside of the module/class * scope. */ @@ -299,7 +299,7 @@ public class SmsMessage extends SmsMessageBase { * @param destAddr Address of the recipient. * @param message String representation of the message payload. * @param statusReportRequested Indicates whether a report is requested for this message. - * @param headerData Array containing the data for the User Data Header, preceded + * @param smsHeader Array containing the data for the User Data Header, preceded * by the Element Identifiers. * @return a <code>SubmitPdu</code> containing the encoded SC * address, if applicable, and the encoded message. @@ -367,7 +367,7 @@ public class SmsMessage extends SmsMessageBase { * Get an SMS-SUBMIT PDU for a data message to a destination address & port * * @param destAddr the address of the destination for the message - * @param userDara the data for the message + * @param userData the data for the message * @param statusReportRequested Indicates whether a report is requested for this message. * @return a <code>SubmitPdu</code> containing the encoded SC * address, if applicable, and the encoded message. @@ -458,7 +458,7 @@ public class SmsMessage extends SmsMessageBase { */ public static TextEncodingDetails calculateLength(CharSequence messageBody, boolean use7bitOnly) { - return BearerData.calcTextEncodingDetails(messageBody.toString(), use7bitOnly); + return BearerData.calcTextEncodingDetails(messageBody, use7bitOnly); } /** @@ -626,7 +626,7 @@ public class SmsMessage extends SmsMessageBase { * incrementing within the range 1..65535 remembering the state * via a persistent system property. (See C.S0015-B, v2.0, * 4.3.1.5) Since this routine is expected to be accessed via via - * binder-call, and hence should be threadsafe, it has been + * binder-call, and hence should be thread-safe, it has been * synchronized. */ private synchronized static int getNextMessageId() { diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index c7032ac..ab79fe9 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -405,7 +405,8 @@ public final class BearerData { /** * Calculate the message text encoding length, fragmentation, and other details. * - * @param force ignore (but still count) illegal characters if true + * @param msg message text + * @param force7BitEncoding ignore (but still count) illegal characters if true * @return septet count, or -1 on failure */ public static TextEncodingDetails calcTextEncodingDetails(CharSequence msg, @@ -427,9 +428,10 @@ public final class BearerData { ted.codeUnitCount = msg.length(); int octets = ted.codeUnitCount * 2; if (octets > MAX_USER_DATA_BYTES) { - ted.msgCount = (octets / MAX_USER_DATA_BYTES_WITH_HEADER) + 1; - ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES_WITH_HEADER - - (octets % MAX_USER_DATA_BYTES_WITH_HEADER))/2; + ted.msgCount = (octets + (MAX_USER_DATA_BYTES_WITH_HEADER - 1)) / + MAX_USER_DATA_BYTES_WITH_HEADER; + ted.codeUnitsRemaining = ((ted.msgCount * + MAX_USER_DATA_BYTES_WITH_HEADER) - octets) / 2; } else { ted.msgCount = 1; ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES - octets)/2; @@ -802,9 +804,8 @@ public final class BearerData { * Create serialized representation for BearerData object. * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) * - * @param bearerData an instance of BearerData. - * - * @return data byta array of raw encoded SMS bearer data. + * @param bData an instance of BearerData. + * @return byte array of raw encoded SMS bearer data. */ public static byte[] encode(BearerData bData) { bData.hasUserDataHeader = ((bData.userData != null) && diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java index d93852c..189d97db 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java @@ -56,7 +56,7 @@ public class UserData { * 0, with the resulting code of 0x20. * * Note this mapping is also equivalent to that used by both the - * IS5 and the IS-91 encodings. For the former this is defined + * IA5 and the IS-91 encodings. For the former this is defined * using CCITT Rec. T.50 Tables 1 and 3. For the latter IS 637 B, * Table 4.3.1.4.1-1 -- and note the encoding uses only 6 bits, * and hence only maps entries up to the '_' character. diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java index 49de5f9..3959c67 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java +++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java @@ -117,10 +117,6 @@ public class GSMPhone extends PhoneBase { Thread debugPortThread; ServerSocket debugSocket; - private int mReportedRadioResets; - private int mReportedAttemptedConnects; - private int mReportedSuccessfulConnects; - private String mImei; private String mImeiSv; private String mVmNumber; @@ -151,7 +147,7 @@ public class GSMPhone extends PhoneBase { mSimCard = new SimCard(this); if (!unitTestMode) { mSimPhoneBookIntManager = new SimPhoneBookInterfaceManager(this); - mSimSmsIntManager = new SimSmsInterfaceManager(this); + mSimSmsIntManager = new SimSmsInterfaceManager(this, mSMS); mSubInfo = new PhoneSubInfo(this); } mStkService = CatService.getInstance(mCM, mSIMRecords, mContext, @@ -807,7 +803,7 @@ public class GSMPhone extends PhoneBase { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences.Editor editor = sp.edit(); editor.putString(VM_NUMBER, number); - editor.commit(); + editor.apply(); setVmSimImsi(getSubscriberId()); } @@ -830,7 +826,7 @@ public class GSMPhone extends PhoneBase { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences.Editor editor = sp.edit(); editor.putString(VM_SIM_IMSI, imsi); - editor.commit(); + editor.apply(); } public String getVoiceMailAlphaTag() { diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmCall.java b/telephony/java/com/android/internal/telephony/gsm/GsmCall.java index 9542d20..58124a2 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmCall.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmCall.java @@ -70,8 +70,7 @@ class GsmCall extends Call { public Phone getPhone() { - //TODO - return null; + return owner.phone; } public boolean diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java index 5f651e7..e7d57bc 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java @@ -442,8 +442,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { if ((state == State.IDLE || state == State.SCANNING) && (gprsState == ServiceState.STATE_IN_SERVICE || noAutoAttach) && mGsmPhone.mSIMRecords.getRecordsLoaded() - && (mGsmPhone.mSST.isConcurrentVoiceAndData() || - phone.getState() == Phone.State.IDLE ) + && phone.getState() == Phone.State.IDLE && isDataAllowed() && !mIsPsRestricted && desiredPowerState ) { @@ -1158,10 +1157,8 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { // No try for permanent failure if (cause.isPermanentFail()) { notifyNoData(cause); - if (!mRequestedApnType.equals(Phone.APN_TYPE_DEFAULT)) { - phone.notifyDataConnection(Phone.REASON_APN_FAILED); - onEnableApn(apnTypeToId(mRequestedApnType), DISABLED); - } + phone.notifyDataConnection(Phone.REASON_APN_FAILED); + onEnableApn(apnTypeToId(mRequestedApnType), DISABLED); return; } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java index d720516..3079a64 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java @@ -27,13 +27,12 @@ import android.telephony.ServiceState; import android.util.Config; import android.util.Log; -import com.android.internal.telephony.IccUtils; -import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails; -import com.android.internal.telephony.gsm.SmsMessage; import com.android.internal.telephony.CommandsInterface; +import com.android.internal.telephony.IccUtils; import com.android.internal.telephony.SMSDispatcher; import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; +import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails; import java.util.ArrayList; import java.util.HashMap; @@ -97,20 +96,20 @@ final class GsmSMSDispatcher extends SMSDispatcher { if (sms.isTypeZero()) { // As per 3GPP TS 23.040 9.2.3.9, Type Zero messages should not be // Displayed/Stored/Notified. They should only be acknowledged. - Log.d(TAG, "Received short message type 0, Dont display or store it. Send Ack"); + Log.d(TAG, "Received short message type 0, Don't display or store it. Send Ack"); return Intents.RESULT_SMS_HANDLED; } // Special case the message waiting indicator messages if (sms.isMWISetMessage()) { mGsmPhone.updateMessageWaitingIndicator(true); - handled |= sms.isMwiDontStore(); + handled = sms.isMwiDontStore(); if (Config.LOGD) { Log.d(TAG, "Received voice mail indicator set SMS shouldStore=" + !handled); } } else if (sms.isMWIClearMessage()) { mGsmPhone.updateMessageWaitingIndicator(false); - handled |= sms.isMwiDontStore(); + handled = sms.isMwiDontStore(); if (Config.LOGD) { Log.d(TAG, "Received voice mail indicator clear SMS shouldStore=" + !handled); } @@ -301,7 +300,7 @@ final class GsmSMSDispatcher extends SMSDispatcher { map.put("smsc", pdus.encodedScAddress); map.put("pdu", pdus.encodedMessage); - SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent); + SmsTracker tracker = SmsTrackerFactory(map, sentIntent, deliveryIntent); sendSms(tracker); } } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java index d539f6f..6ddb312 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java @@ -358,8 +358,14 @@ final class GsmServiceStateTracker extends ServiceStateTracker { EVENT_SIM_RECORDS_LOADED, null); mNeedToRegForSimLoaded = false; } - // restore the previous network selection. - phone.restoreSavedNetworkSelection(null); + + boolean skipRestoringSelection = phone.getContext().getResources().getBoolean( + com.android.internal.R.bool.skip_restoring_network_selection); + + if (!skipRestoringSelection) { + // restore the previous network selection. + phone.restoreSavedNetworkSelection(null); + } pollState(); // Signal strength polling stops when radio is off queueNextSignalStrengthPoll(); @@ -650,6 +656,7 @@ final class GsmServiceStateTracker extends ServiceStateTracker { int lac = -1; int cid = -1; int regState = -1; + int psc = -1; if (states.length > 0) { try { regState = Integer.parseInt(states[0]); @@ -661,6 +668,11 @@ final class GsmServiceStateTracker extends ServiceStateTracker { cid = Integer.parseInt(states[2], 16); } } + if (states.length > 14) { + if (states[14] != null && states[14].length() > 0) { + psc = Integer.parseInt(states[14], 16); + } + } } catch (NumberFormatException ex) { Log.w(LOG_TAG, "error parsing RegistrationState: " + ex); } @@ -677,6 +689,7 @@ final class GsmServiceStateTracker extends ServiceStateTracker { // LAC and CID are -1 if not avail newCellLoc.setLacAndCid(lac, cid); + newCellLoc.setPsc(psc); break; case EVENT_POLL_STATE_GPRS: diff --git a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java index 2028ca4..67ecc77 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java +++ b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java @@ -25,6 +25,7 @@ import android.util.Log; import com.android.internal.telephony.IccConstants; import com.android.internal.telephony.IccSmsInterfaceManager; import com.android.internal.telephony.IccUtils; +import com.android.internal.telephony.SMSDispatcher; import com.android.internal.telephony.SmsRawData; import java.util.ArrayList; @@ -78,9 +79,9 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { } }; - public SimSmsInterfaceManager(GSMPhone phone) { + public SimSmsInterfaceManager(GSMPhone phone, SMSDispatcher dispatcher) { super(phone); - mDispatcher = new GsmSMSDispatcher(phone); + mDispatcher = dispatcher; } public void dispose() { diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java index 4fd62fb..9a3c476 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java @@ -800,9 +800,10 @@ public class SmsMessage extends SmsMessageBase{ int septets = GsmAlphabet.countGsmSeptets(msgBody, !use7bitOnly); ted.codeUnitCount = septets; if (septets > MAX_USER_DATA_SEPTETS) { - ted.msgCount = (septets / MAX_USER_DATA_SEPTETS_WITH_HEADER) + 1; - ted.codeUnitsRemaining = MAX_USER_DATA_SEPTETS_WITH_HEADER - - (septets % MAX_USER_DATA_SEPTETS_WITH_HEADER); + ted.msgCount = (septets + (MAX_USER_DATA_SEPTETS_WITH_HEADER - 1)) / + MAX_USER_DATA_SEPTETS_WITH_HEADER; + ted.codeUnitsRemaining = (ted.msgCount * + MAX_USER_DATA_SEPTETS_WITH_HEADER) - septets; } else { ted.msgCount = 1; ted.codeUnitsRemaining = MAX_USER_DATA_SEPTETS - septets; @@ -812,9 +813,10 @@ public class SmsMessage extends SmsMessageBase{ int octets = msgBody.length() * 2; ted.codeUnitCount = msgBody.length(); if (octets > MAX_USER_DATA_BYTES) { - ted.msgCount = (octets / MAX_USER_DATA_BYTES_WITH_HEADER) + 1; - ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES_WITH_HEADER - - (octets % MAX_USER_DATA_BYTES_WITH_HEADER))/2; + ted.msgCount = (octets + (MAX_USER_DATA_BYTES_WITH_HEADER - 1)) / + MAX_USER_DATA_BYTES_WITH_HEADER; + ted.codeUnitsRemaining = ((ted.msgCount * + MAX_USER_DATA_BYTES_WITH_HEADER) - octets) / 2; } else { ted.msgCount = 1; ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES - octets)/2; diff --git a/telephony/java/com/android/internal/telephony/sip/SipCallBase.java b/telephony/java/com/android/internal/telephony/sip/SipCallBase.java new file mode 100644 index 0000000..9050a43 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/sip/SipCallBase.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.sip; + +import com.android.internal.telephony.Call; +import com.android.internal.telephony.CallStateException; +import com.android.internal.telephony.Connection; +import com.android.internal.telephony.Phone; + +import android.net.sip.SipManager; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +abstract class SipCallBase extends Call { + protected List<Connection> connections = new ArrayList<Connection>(); + + protected abstract void setState(State newState); + + public List<Connection> getConnections() { + // FIXME should return Collections.unmodifiableList(); + return connections; + } + + public boolean isMultiparty() { + return connections.size() > 1; + } + + public String toString() { + return state.toString() + ":" + super.toString(); + } + + void clearDisconnected() { + for (Iterator<Connection> it = connections.iterator(); it.hasNext(); ) { + Connection c = it.next(); + if (c.getState() == State.DISCONNECTED) it.remove(); + } + + if (connections.isEmpty()) setState(State.IDLE); + } +} diff --git a/telephony/java/com/android/internal/telephony/sip/SipCommandInterface.java b/telephony/java/com/android/internal/telephony/sip/SipCommandInterface.java new file mode 100644 index 0000000..ed578c8 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/sip/SipCommandInterface.java @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.sip; + +import android.content.Context; +import android.os.Handler; +import android.os.Message; + +import com.android.internal.telephony.BaseCommands; +import com.android.internal.telephony.CommandsInterface; +import com.android.internal.telephony.UUSInfo; +import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; + +/** + * SIP doesn't need CommandsInterface. The class does nothing but made to work + * with PhoneBase's constructor. + */ +class SipCommandInterface extends BaseCommands implements CommandsInterface { + SipCommandInterface(Context context) { + super(context); + } + + @Override public void setOnNITZTime(Handler h, int what, Object obj) { + } + + public void getIccCardStatus(Message result) { + } + + public void supplyIccPin(String pin, Message result) { + } + + public void supplyIccPuk(String puk, String newPin, Message result) { + } + + public void supplyIccPin2(String pin, Message result) { + } + + public void supplyIccPuk2(String puk, String newPin2, Message result) { + } + + public void changeIccPin(String oldPin, String newPin, Message result) { + } + + public void changeIccPin2(String oldPin2, String newPin2, Message result) { + } + + public void changeBarringPassword(String facility, String oldPwd, + String newPwd, Message result) { + } + + public void supplyNetworkDepersonalization(String netpin, Message result) { + } + + public void getCurrentCalls(Message result) { + } + + @Deprecated public void getPDPContextList(Message result) { + } + + public void getDataCallList(Message result) { + } + + public void dial(String address, int clirMode, Message result) { + } + + public void dial(String address, int clirMode, UUSInfo uusInfo, + Message result) { + } + + public void getIMSI(Message result) { + } + + public void getIMEI(Message result) { + } + + public void getIMEISV(Message result) { + } + + + public void hangupConnection (int gsmIndex, Message result) { + } + + public void hangupWaitingOrBackground (Message result) { + } + + public void hangupForegroundResumeBackground (Message result) { + } + + public void switchWaitingOrHoldingAndActive (Message result) { + } + + public void conference (Message result) { + } + + + public void setPreferredVoicePrivacy(boolean enable, Message result) { + } + + public void getPreferredVoicePrivacy(Message result) { + } + + public void separateConnection (int gsmIndex, Message result) { + } + + public void acceptCall (Message result) { + } + + public void rejectCall (Message result) { + } + + public void explicitCallTransfer (Message result) { + } + + public void getLastCallFailCause (Message result) { + } + + /** @deprecated */ + public void getLastPdpFailCause (Message result) { + } + + public void getLastDataCallFailCause (Message result) { + } + + public void setMute (boolean enableMute, Message response) { + } + + public void getMute (Message response) { + } + + public void getSignalStrength (Message result) { + } + + public void getRegistrationState (Message result) { + } + + public void getGPRSRegistrationState (Message result) { + } + + public void getOperator(Message result) { + } + + public void sendDtmf(char c, Message result) { + } + + public void startDtmf(char c, Message result) { + } + + public void stopDtmf(Message result) { + } + + public void sendBurstDtmf(String dtmfString, int on, int off, + Message result) { + } + + public void sendSMS (String smscPDU, String pdu, Message result) { + } + + public void sendCdmaSms(byte[] pdu, Message result) { + } + + public void deleteSmsOnSim(int index, Message response) { + } + + public void deleteSmsOnRuim(int index, Message response) { + } + + public void writeSmsToSim(int status, String smsc, String pdu, Message response) { + } + + public void writeSmsToRuim(int status, String pdu, Message response) { + } + + public void setupDefaultPDP(String apn, String user, String password, + Message result) { + } + + public void deactivateDefaultPDP(int cid, Message result) { + } + + public void setupDataCall(String radioTechnology, String profile, + String apn, String user, String password, String authType, + Message result) { + } + + public void deactivateDataCall(int cid, Message result) { + } + + public void setRadioPower(boolean on, Message result) { + } + + public void setSuppServiceNotifications(boolean enable, Message result) { + } + + public void acknowledgeLastIncomingGsmSms(boolean success, int cause, + Message result) { + } + + public void acknowledgeLastIncomingCdmaSms(boolean success, int cause, + Message result) { + } + + + public void iccIO (int command, int fileid, String path, int p1, int p2, + int p3, String data, String pin2, Message result) { + } + + public void getCLIR(Message result) { + } + + public void setCLIR(int clirMode, Message result) { + } + + public void queryCallWaiting(int serviceClass, Message response) { + } + + public void setCallWaiting(boolean enable, int serviceClass, + Message response) { + } + + public void setNetworkSelectionModeAutomatic(Message response) { + } + + public void setNetworkSelectionModeManual( + String operatorNumeric, Message response) { + } + + public void getNetworkSelectionMode(Message response) { + } + + public void getAvailableNetworks(Message response) { + } + + public void setCallForward(int action, int cfReason, int serviceClass, + String number, int timeSeconds, Message response) { + } + + public void queryCallForwardStatus(int cfReason, int serviceClass, + String number, Message response) { + } + + public void queryCLIP(Message response) { + } + + public void getBasebandVersion (Message response) { + } + + public void queryFacilityLock (String facility, String password, + int serviceClass, Message response) { + } + + public void setFacilityLock (String facility, boolean lockState, + String password, int serviceClass, Message response) { + } + + public void sendUSSD (String ussdString, Message response) { + } + + public void cancelPendingUssd (Message response) { + } + + public void resetRadio(Message result) { + } + + public void invokeOemRilRequestRaw(byte[] data, Message response) { + } + + public void invokeOemRilRequestStrings(String[] strings, Message response) { + } + + public void setBandMode (int bandMode, Message response) { + } + + public void queryAvailableBandMode (Message response) { + } + + public void sendTerminalResponse(String contents, Message response) { + } + + public void sendEnvelope(String contents, Message response) { + } + + public void handleCallSetupRequestFromSim( + boolean accept, Message response) { + } + + public void setPreferredNetworkType(int networkType , Message response) { + } + + public void getPreferredNetworkType(Message response) { + } + + public void getNeighboringCids(Message response) { + } + + public void setLocationUpdates(boolean enable, Message response) { + } + + public void getSmscAddress(Message result) { + } + + public void setSmscAddress(String address, Message result) { + } + + public void reportSmsMemoryStatus(boolean available, Message result) { + } + + public void reportStkServiceIsRunning(Message result) { + } + + public void getGsmBroadcastConfig(Message response) { + } + + public void setGsmBroadcastConfig(SmsBroadcastConfigInfo[] config, Message response) { + } + + public void setGsmBroadcastActivation(boolean activate, Message response) { + } + + // ***** Methods for CDMA support + public void getDeviceIdentity(Message response) { + } + + public void getCDMASubscription(Message response) { + } + + public void setPhoneType(int phoneType) { //Set by CDMAPhone and GSMPhone constructor + } + + public void queryCdmaRoamingPreference(Message response) { + } + + public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) { + } + + public void setCdmaSubscription(int cdmaSubscription , Message response) { + } + + public void queryTTYMode(Message response) { + } + + public void setTTYMode(int ttyMode, Message response) { + } + + public void sendCDMAFeatureCode(String FeatureCode, Message response) { + } + + public void getCdmaBroadcastConfig(Message response) { + } + + public void setCdmaBroadcastConfig(int[] configValuesArray, Message response) { + } + + public void setCdmaBroadcastActivation(boolean activate, Message response) { + } + + public void exitEmergencyCallbackMode(Message response) { + } +} diff --git a/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java b/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java new file mode 100644 index 0000000..dc4b27b --- /dev/null +++ b/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.sip; + +import com.android.internal.telephony.Call; +import com.android.internal.telephony.Connection; +import com.android.internal.telephony.Phone; + +import android.net.sip.SipAudioCall; +import android.os.SystemClock; +import android.util.Log; +import android.telephony.PhoneNumberUtils; + +abstract class SipConnectionBase extends Connection { + private static final String LOG_TAG = "SIP_CONN"; + + private SipAudioCall mSipAudioCall; + + private String dialString; // outgoing calls only + private String postDialString; // outgoing calls only + private int nextPostDialChar; // index into postDialString + private boolean isIncoming; + + /* + * These time/timespan values are based on System.currentTimeMillis(), + * i.e., "wall clock" time. + */ + private long createTime; + private long connectTime; + private long disconnectTime; + + /* + * These time/timespan values are based on SystemClock.elapsedRealTime(), + * i.e., time since boot. They are appropriate for comparison and + * calculating deltas. + */ + private long connectTimeReal; + private long duration; + private long holdingStartTime; // The time when the Connection last transitioned + // into HOLDING + + private DisconnectCause mCause = DisconnectCause.NOT_DISCONNECTED; + private PostDialState postDialState = PostDialState.NOT_STARTED; + + SipConnectionBase(String calleeSipUri) { + dialString = calleeSipUri; + + postDialString = PhoneNumberUtils.extractPostDialPortion(dialString); + + isIncoming = false; + createTime = System.currentTimeMillis(); + } + + protected void setState(Call.State state) { + switch (state) { + case ACTIVE: + if (connectTime == 0) { + connectTimeReal = SystemClock.elapsedRealtime(); + connectTime = System.currentTimeMillis(); + } + break; + case DISCONNECTED: + duration = SystemClock.elapsedRealtime() - connectTimeReal; + disconnectTime = System.currentTimeMillis(); + break; + case HOLDING: + holdingStartTime = SystemClock.elapsedRealtime(); + break; + } + } + + @Override + public long getCreateTime() { + return createTime; + } + + @Override + public long getConnectTime() { + return connectTime; + } + + @Override + public long getDisconnectTime() { + return disconnectTime; + } + + @Override + public long getDurationMillis() { + if (connectTimeReal == 0) { + return 0; + } else if (duration == 0) { + return SystemClock.elapsedRealtime() - connectTimeReal; + } else { + return duration; + } + } + + @Override + public long getHoldDurationMillis() { + if (getState() != Call.State.HOLDING) { + // If not holding, return 0 + return 0; + } else { + return SystemClock.elapsedRealtime() - holdingStartTime; + } + } + + @Override + public DisconnectCause getDisconnectCause() { + return mCause; + } + + void setDisconnectCause(DisconnectCause cause) { + mCause = cause; + } + + @Override + public PostDialState getPostDialState() { + return postDialState; + } + + @Override + public void proceedAfterWaitChar() { + // TODO + } + + @Override + public void proceedAfterWildChar(String str) { + // TODO + } + + @Override + public void cancelPostDial() { + // TODO + } + + protected abstract Phone getPhone(); + + @Override + public String getRemainingPostDialString() { + if (postDialState == PostDialState.CANCELLED + || postDialState == PostDialState.COMPLETE + || postDialString == null + || postDialString.length() <= nextPostDialChar) { + return ""; + } + + return postDialString.substring(nextPostDialChar); + } + + private void log(String msg) { + Log.d(LOG_TAG, "[SipConn] " + msg); + } + + @Override + public int getNumberPresentation() { + // TODO: add PRESENTATION_URL + return Connection.PRESENTATION_ALLOWED; + } +} diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java new file mode 100755 index 0000000..67f13bd --- /dev/null +++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java @@ -0,0 +1,872 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.sip; + +import android.content.Context; +import android.net.rtp.AudioGroup; +import android.net.sip.SipAudioCall; +import android.net.sip.SipErrorCode; +import android.net.sip.SipException; +import android.net.sip.SipManager; +import android.net.sip.SipProfile; +import android.net.sip.SipSession; +import android.os.AsyncResult; +import android.os.Message; +import android.telephony.PhoneNumberUtils; +import android.telephony.ServiceState; +import android.util.Log; + +import com.android.internal.telephony.Call; +import com.android.internal.telephony.CallStateException; +import com.android.internal.telephony.Connection; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneNotifier; +import com.android.internal.telephony.UUSInfo; + +import java.text.ParseException; +import java.util.List; + +/** + * {@hide} + */ +public class SipPhone extends SipPhoneBase { + private static final String LOG_TAG = "SipPhone"; + private static final boolean DEBUG = true; + private static final int TIMEOUT_MAKE_CALL = 15; // in seconds + private static final int TIMEOUT_ANSWER_CALL = 8; // in seconds + private static final int TIMEOUT_HOLD_CALL = 15; // in seconds + + // A call that is ringing or (call) waiting + private SipCall ringingCall = new SipCall(); + private SipCall foregroundCall = new SipCall(); + private SipCall backgroundCall = new SipCall(); + + private SipManager mSipManager; + private SipProfile mProfile; + + SipPhone (Context context, PhoneNotifier notifier, SipProfile profile) { + super(context, notifier); + + if (DEBUG) Log.d(LOG_TAG, "new SipPhone: " + profile.getUriString()); + ringingCall = new SipCall(); + foregroundCall = new SipCall(); + backgroundCall = new SipCall(); + mProfile = profile; + mSipManager = SipManager.newInstance(context); + } + + public String getPhoneName() { + return "SIP:" + getUriString(mProfile); + } + + public String getSipUri() { + return mProfile.getUriString(); + } + + public boolean equals(SipPhone phone) { + return getSipUri().equals(phone.getSipUri()); + } + + public boolean canTake(Object incomingCall) { + synchronized (SipPhone.class) { + if (!(incomingCall instanceof SipAudioCall)) return false; + if (ringingCall.getState().isAlive()) return false; + + // FIXME: is it true that we cannot take any incoming call if + // both foreground and background are active + if (foregroundCall.getState().isAlive() + && backgroundCall.getState().isAlive()) { + return false; + } + + try { + SipAudioCall sipAudioCall = (SipAudioCall) incomingCall; + if (DEBUG) Log.d(LOG_TAG, "+++ taking call from: " + + sipAudioCall.getPeerProfile().getUriString()); + String localUri = sipAudioCall.getLocalProfile().getUriString(); + if (localUri.equals(mProfile.getUriString())) { + boolean makeCallWait = foregroundCall.getState().isAlive(); + ringingCall.initIncomingCall(sipAudioCall, makeCallWait); + if (sipAudioCall.getState() + != SipSession.State.INCOMING_CALL) { + // Peer cancelled the call! + if (DEBUG) Log.d(LOG_TAG, " call cancelled !!"); + ringingCall.reset(); + } + return true; + } + } catch (Exception e) { + // Peer may cancel the call at any time during the time we hook + // up ringingCall with sipAudioCall. Clean up ringingCall when + // that happens. + ringingCall.reset(); + } + return false; + } + } + + public void acceptCall() throws CallStateException { + synchronized (SipPhone.class) { + if ((ringingCall.getState() == Call.State.INCOMING) || + (ringingCall.getState() == Call.State.WAITING)) { + if (DEBUG) Log.d(LOG_TAG, "acceptCall"); + // Always unmute when answering a new call + setMute(false); + ringingCall.acceptCall(); + } else { + throw new CallStateException("phone not ringing"); + } + } + } + + public void rejectCall() throws CallStateException { + synchronized (SipPhone.class) { + if (ringingCall.getState().isRinging()) { + if (DEBUG) Log.d(LOG_TAG, "rejectCall"); + ringingCall.rejectCall(); + } else { + throw new CallStateException("phone not ringing"); + } + } + } + + public Connection dial(String dialString, UUSInfo uusinfo) throws CallStateException { + return dial(dialString); + } + + public Connection dial(String dialString) throws CallStateException { + synchronized (SipPhone.class) { + return dialInternal(dialString); + } + } + + private Connection dialInternal(String dialString) + throws CallStateException { + clearDisconnected(); + + if (!canDial()) { + throw new CallStateException("cannot dial in current state"); + } + if (foregroundCall.getState() == SipCall.State.ACTIVE) { + switchHoldingAndActive(); + } + if (foregroundCall.getState() != SipCall.State.IDLE) { + //we should have failed in !canDial() above before we get here + throw new CallStateException("cannot dial in current state"); + } + + setMute(false); + try { + Connection c = foregroundCall.dial(dialString); + return c; + } catch (SipException e) { + Log.e(LOG_TAG, "dial()", e); + throw new CallStateException("dial error: " + e); + } + } + + public void switchHoldingAndActive() throws CallStateException { + if (DEBUG) Log.d(LOG_TAG, " ~~~~~~ switch fg and bg"); + synchronized (SipPhone.class) { + foregroundCall.switchWith(backgroundCall); + if (backgroundCall.getState().isAlive()) backgroundCall.hold(); + if (foregroundCall.getState().isAlive()) foregroundCall.unhold(); + } + } + + public boolean canConference() { + return true; + } + + public void conference() throws CallStateException { + synchronized (SipPhone.class) { + if ((foregroundCall.getState() != SipCall.State.ACTIVE) + || (foregroundCall.getState() != SipCall.State.ACTIVE)) { + throw new CallStateException("wrong state to merge calls: fg=" + + foregroundCall.getState() + ", bg=" + + backgroundCall.getState()); + } + foregroundCall.merge(backgroundCall); + } + } + + public void conference(Call that) throws CallStateException { + synchronized (SipPhone.class) { + if (!(that instanceof SipCall)) { + throw new CallStateException("expect " + SipCall.class + + ", cannot merge with " + that.getClass()); + } + foregroundCall.merge((SipCall) that); + } + } + + public boolean canTransfer() { + return false; + } + + public void explicitCallTransfer() throws CallStateException { + //mCT.explicitCallTransfer(); + } + + public void clearDisconnected() { + synchronized (SipPhone.class) { + ringingCall.clearDisconnected(); + foregroundCall.clearDisconnected(); + backgroundCall.clearDisconnected(); + + updatePhoneState(); + notifyPreciseCallStateChanged(); + } + } + + public void sendDtmf(char c) { + if (!PhoneNumberUtils.is12Key(c)) { + Log.e(LOG_TAG, + "sendDtmf called with invalid character '" + c + "'"); + } else if (foregroundCall.getState().isAlive()) { + synchronized (SipPhone.class) { + foregroundCall.sendDtmf(c); + } + } + } + + public void startDtmf(char c) { + if (!PhoneNumberUtils.is12Key(c)) { + Log.e(LOG_TAG, + "startDtmf called with invalid character '" + c + "'"); + } else { + sendDtmf(c); + } + } + + public void stopDtmf() { + // no op + } + + public void sendBurstDtmf(String dtmfString) { + Log.e(LOG_TAG, "[SipPhone] sendBurstDtmf() is a CDMA method"); + } + + public void getOutgoingCallerIdDisplay(Message onComplete) { + // FIXME: what to reply? + AsyncResult.forMessage(onComplete, null, null); + onComplete.sendToTarget(); + } + + public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode, + Message onComplete) { + // FIXME: what's this for SIP? + AsyncResult.forMessage(onComplete, null, null); + onComplete.sendToTarget(); + } + + public void getCallWaiting(Message onComplete) { + // FIXME: what to reply? + AsyncResult.forMessage(onComplete, null, null); + onComplete.sendToTarget(); + } + + public void setCallWaiting(boolean enable, Message onComplete) { + // FIXME: what to reply? + Log.e(LOG_TAG, "call waiting not supported"); + } + + @Override + public void setEchoSuppressionEnabled(boolean enabled) { + synchronized (SipPhone.class) { + AudioGroup audioGroup = foregroundCall.getAudioGroup(); + if (audioGroup == null) return; + int mode = audioGroup.getMode(); + audioGroup.setMode(enabled + ? AudioGroup.MODE_ECHO_SUPPRESSION + : AudioGroup.MODE_NORMAL); + if (DEBUG) Log.d(LOG_TAG, String.format( + "audioGroup mode change: %d --> %d", mode, + audioGroup.getMode())); + } + } + + public void setMute(boolean muted) { + synchronized (SipPhone.class) { + foregroundCall.setMute(muted); + } + } + + public boolean getMute() { + return foregroundCall.getMute(); + } + + public Call getForegroundCall() { + return foregroundCall; + } + + public Call getBackgroundCall() { + return backgroundCall; + } + + public Call getRingingCall() { + return ringingCall; + } + + public ServiceState getServiceState() { + // FIXME: we may need to provide this when data connectivity is lost + // or when server is down + return super.getServiceState(); + } + + private String getUriString(SipProfile p) { + // SipProfile.getUriString() may contain "SIP:" and port + return p.getUserName() + "@" + getSipDomain(p); + } + + private String getSipDomain(SipProfile p) { + String domain = p.getSipDomain(); + // TODO: move this to SipProfile + if (domain.endsWith(":5060")) { + return domain.substring(0, domain.length() - 5); + } else { + return domain; + } + } + + private class SipCall extends SipCallBase { + void reset() { + connections.clear(); + setState(Call.State.IDLE); + } + + void switchWith(SipCall that) { + synchronized (SipPhone.class) { + SipCall tmp = new SipCall(); + tmp.takeOver(this); + this.takeOver(that); + that.takeOver(tmp); + } + } + + private void takeOver(SipCall that) { + connections = that.connections; + state = that.state; + for (Connection c : connections) { + ((SipConnection) c).changeOwner(this); + } + } + + @Override + public Phone getPhone() { + return SipPhone.this; + } + + @Override + public List<Connection> getConnections() { + synchronized (SipPhone.class) { + // FIXME should return Collections.unmodifiableList(); + return connections; + } + } + + Connection dial(String originalNumber) throws SipException { + String calleeSipUri = originalNumber; + if (!calleeSipUri.contains("@")) { + calleeSipUri += "@" + getSipDomain(mProfile); + } + try { + SipProfile callee = + new SipProfile.Builder(calleeSipUri).build(); + SipConnection c = new SipConnection(this, callee); + connections.add(c); + c.dial(); + setState(Call.State.DIALING); + return c; + } catch (ParseException e) { + // TODO: notify someone + throw new SipException("dial", e); + } + } + + @Override + public void hangup() throws CallStateException { + synchronized (SipPhone.class) { + if (state.isAlive()) { + if (DEBUG) Log.d(LOG_TAG, "hang up call: " + getState() + + ": " + this + " on phone " + getPhone()); + setState(State.DISCONNECTING); + CallStateException excp = null; + for (Connection c : connections) { + try { + c.hangup(); + } catch (CallStateException e) { + excp = e; + } + } + if (excp != null) throw excp; + } else { + if (DEBUG) Log.d(LOG_TAG, "hang up dead call: " + getState() + + ": " + this + " on phone " + getPhone()); + } + } + } + + void initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) { + SipProfile callee = sipAudioCall.getPeerProfile(); + SipConnection c = new SipConnection(this, callee); + connections.add(c); + + Call.State newState = makeCallWait ? State.WAITING : State.INCOMING; + c.initIncomingCall(sipAudioCall, newState); + + setState(newState); + notifyNewRingingConnectionP(c); + } + + void rejectCall() throws CallStateException { + hangup(); + } + + void acceptCall() throws CallStateException { + if (this != ringingCall) { + throw new CallStateException("acceptCall() in a non-ringing call"); + } + if (connections.size() != 1) { + throw new CallStateException("acceptCall() in a conf call"); + } + ((SipConnection) connections.get(0)).acceptCall(); + } + + void hold() throws CallStateException { + setState(State.HOLDING); + AudioGroup audioGroup = getAudioGroup(); + if (audioGroup != null) { + audioGroup.setMode(AudioGroup.MODE_ON_HOLD); + } + for (Connection c : connections) ((SipConnection) c).hold(); + } + + void unhold() throws CallStateException { + setState(State.ACTIVE); + AudioGroup audioGroup = new AudioGroup(); + for (Connection c : connections) { + ((SipConnection) c).unhold(audioGroup); + } + } + + void setMute(boolean muted) { + AudioGroup audioGroup = getAudioGroup(); + if (audioGroup == null) return; + audioGroup.setMode( + muted ? AudioGroup.MODE_MUTED : AudioGroup.MODE_NORMAL); + } + + boolean getMute() { + AudioGroup audioGroup = getAudioGroup(); + if (audioGroup == null) return false; + return (audioGroup.getMode() == AudioGroup.MODE_MUTED); + } + + void merge(SipCall that) throws CallStateException { + AudioGroup audioGroup = getAudioGroup(); + for (Connection c : that.connections) { + SipConnection conn = (SipConnection) c; + add(conn); + if (conn.getState() == Call.State.HOLDING) { + conn.unhold(audioGroup); + } + } + that.setState(Call.State.IDLE); + } + + private void add(SipConnection conn) { + SipCall call = conn.getCall(); + if (call == this) return; + if (call != null) call.connections.remove(conn); + + connections.add(conn); + conn.changeOwner(this); + } + + void sendDtmf(char c) { + AudioGroup audioGroup = getAudioGroup(); + if (audioGroup == null) return; + audioGroup.sendDtmf(convertDtmf(c)); + } + + private int convertDtmf(char c) { + int code = c - '0'; + if ((code < 0) || (code > 9)) { + switch (c) { + case '*': return 10; + case '#': return 11; + case 'A': return 12; + case 'B': return 13; + case 'C': return 14; + case 'D': return 15; + default: + throw new IllegalArgumentException( + "invalid DTMF char: " + (int) c); + } + } + return code; + } + + @Override + protected void setState(State newState) { + if (state != newState) { + if (DEBUG) Log.v(LOG_TAG, "+***+ call state changed: " + state + + " --> " + newState + ": " + this + ": on phone " + + getPhone() + " " + connections.size()); + + if (newState == Call.State.ALERTING) { + state = newState; // need in ALERTING to enable ringback + SipPhone.this.startRingbackTone(); + } else if (state == Call.State.ALERTING) { + SipPhone.this.stopRingbackTone(); + } + state = newState; + updatePhoneState(); + notifyPreciseCallStateChanged(); + } + } + + void onConnectionStateChanged(SipConnection conn) { + // this can be called back when a conf call is formed + if (state != State.ACTIVE) { + setState(conn.getState()); + } + } + + void onConnectionEnded(SipConnection conn) { + // set state to DISCONNECTED only when all conns are disconnected + if (state != State.DISCONNECTED) { + boolean allConnectionsDisconnected = true; + if (DEBUG) Log.d(LOG_TAG, "---check connections: " + + connections.size()); + for (Connection c : connections) { + if (DEBUG) Log.d(LOG_TAG, " state=" + c.getState() + ": " + + c); + if (c.getState() != State.DISCONNECTED) { + allConnectionsDisconnected = false; + break; + } + } + if (allConnectionsDisconnected) setState(State.DISCONNECTED); + } + notifyDisconnectP(conn); + } + + private AudioGroup getAudioGroup() { + if (connections.isEmpty()) return null; + return ((SipConnection) connections.get(0)).getAudioGroup(); + } + } + + private class SipConnection extends SipConnectionBase { + private SipCall mOwner; + private SipAudioCall mSipAudioCall; + private Call.State mState = Call.State.IDLE; + private SipProfile mPeer; + private boolean mIncoming = false; + + private SipAudioCallAdapter mAdapter = new SipAudioCallAdapter() { + @Override + protected void onCallEnded(DisconnectCause cause) { + if (getDisconnectCause() != DisconnectCause.LOCAL) { + setDisconnectCause(cause); + } + synchronized (SipPhone.class) { + setState(Call.State.DISCONNECTED); + SipAudioCall sipAudioCall = mSipAudioCall; + mSipAudioCall = null; + String sessionState = (sipAudioCall == null) + ? "" + : (sipAudioCall.getState() + ", "); + if (DEBUG) Log.d(LOG_TAG, "--- connection ended: " + + mPeer.getUriString() + ": " + sessionState + + "cause: " + getDisconnectCause() + ", on phone " + + getPhone()); + if (sipAudioCall != null) { + sipAudioCall.setListener(null); + sipAudioCall.close(); + } + mOwner.onConnectionEnded(SipConnection.this); + } + } + + @Override + public void onCallEstablished(SipAudioCall call) { + onChanged(call); + if (mState == Call.State.ACTIVE) call.startAudio(); + } + + @Override + public void onCallHeld(SipAudioCall call) { + onChanged(call); + if (mState == Call.State.HOLDING) call.startAudio(); + } + + @Override + public void onChanged(SipAudioCall call) { + synchronized (SipPhone.class) { + Call.State newState = getCallStateFrom(call); + if (mState == newState) return; + if (newState == Call.State.INCOMING) { + setState(mOwner.getState()); // INCOMING or WAITING + } else { + if (mOwner == ringingCall) { + if (ringingCall.getState() == Call.State.WAITING) { + try { + switchHoldingAndActive(); + } catch (CallStateException e) { + // disconnect the call. + onCallEnded(DisconnectCause.LOCAL); + return; + } + } + foregroundCall.switchWith(ringingCall); + } + setState(newState); + } + mOwner.onConnectionStateChanged(SipConnection.this); + if (DEBUG) Log.v(LOG_TAG, "+***+ connection state changed: " + + mPeer.getUriString() + ": " + mState + + " on phone " + getPhone()); + } + } + + @Override + protected void onError(DisconnectCause cause) { + if (DEBUG) Log.d(LOG_TAG, "SIP error: " + cause); + if (mSipAudioCall.isInCall() + && (cause != DisconnectCause.LOST_SIGNAL)) { + // Don't end the call when in a call. + return; + } + + onCallEnded(cause); + } + }; + + public SipConnection(SipCall owner, SipProfile callee) { + super(getUriString(callee)); + mOwner = owner; + mPeer = callee; + } + + void initIncomingCall(SipAudioCall sipAudioCall, Call.State newState) { + setState(newState); + mSipAudioCall = sipAudioCall; + sipAudioCall.setListener(mAdapter); // call back to set state + mIncoming = true; + } + + void acceptCall() throws CallStateException { + try { + mSipAudioCall.answerCall(TIMEOUT_ANSWER_CALL); + } catch (SipException e) { + throw new CallStateException("acceptCall(): " + e); + } + } + + void changeOwner(SipCall owner) { + mOwner = owner; + } + + AudioGroup getAudioGroup() { + if (mSipAudioCall == null) return null; + return mSipAudioCall.getAudioGroup(); + } + + void dial() throws SipException { + setState(Call.State.DIALING); + mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null, + TIMEOUT_MAKE_CALL); + mSipAudioCall.setRingbackToneEnabled(false); + mSipAudioCall.setListener(mAdapter); + } + + void hold() throws CallStateException { + setState(Call.State.HOLDING); + try { + mSipAudioCall.holdCall(TIMEOUT_HOLD_CALL); + } catch (SipException e) { + throw new CallStateException("hold(): " + e); + } + } + + void unhold(AudioGroup audioGroup) throws CallStateException { + mSipAudioCall.setAudioGroup(audioGroup); + setState(Call.State.ACTIVE); + try { + mSipAudioCall.continueCall(TIMEOUT_HOLD_CALL); + } catch (SipException e) { + throw new CallStateException("unhold(): " + e); + } + } + + @Override + protected void setState(Call.State state) { + if (state == mState) return; + super.setState(state); + mState = state; + } + + @Override + public Call.State getState() { + return mState; + } + + @Override + public boolean isIncoming() { + return mIncoming; + } + + @Override + public String getAddress() { + return getUriString(mPeer); + } + + @Override + public SipCall getCall() { + return mOwner; + } + + @Override + protected Phone getPhone() { + return mOwner.getPhone(); + } + + @Override + public void hangup() throws CallStateException { + synchronized (SipPhone.class) { + if (DEBUG) Log.d(LOG_TAG, "hangup conn: " + mPeer.getUriString() + + ": " + mState + ": on phone " + + getPhone().getPhoneName()); + if (!mState.isAlive()) return; + try { + SipAudioCall sipAudioCall = mSipAudioCall; + if (sipAudioCall != null) { + sipAudioCall.setListener(null); + sipAudioCall.endCall(); + } + } catch (SipException e) { + throw new CallStateException("hangup(): " + e); + } finally { + mAdapter.onCallEnded(((mState == Call.State.INCOMING) + || (mState == Call.State.WAITING)) + ? DisconnectCause.INCOMING_REJECTED + : DisconnectCause.LOCAL); + } + } + } + + @Override + public void separate() throws CallStateException { + synchronized (SipPhone.class) { + SipCall call = (SipCall) SipPhone.this.getBackgroundCall(); + if (call.getState() != Call.State.IDLE) { + throw new CallStateException( + "cannot put conn back to a call in non-idle state: " + + call.getState()); + } + if (DEBUG) Log.d(LOG_TAG, "separate conn: " + + mPeer.getUriString() + " from " + mOwner + " back to " + + call); + + AudioGroup audioGroup = call.getAudioGroup(); // may be null + call.add(this); + mSipAudioCall.setAudioGroup(audioGroup); + call.hold(); + } + } + + @Override + public UUSInfo getUUSInfo() { + return null; + } + } + + private static Call.State getCallStateFrom(SipAudioCall sipAudioCall) { + if (sipAudioCall.isOnHold()) return Call.State.HOLDING; + int sessionState = sipAudioCall.getState(); + switch (sessionState) { + case SipSession.State.READY_TO_CALL: return Call.State.IDLE; + case SipSession.State.INCOMING_CALL: + case SipSession.State.INCOMING_CALL_ANSWERING: return Call.State.INCOMING; + case SipSession.State.OUTGOING_CALL: return Call.State.DIALING; + case SipSession.State.OUTGOING_CALL_RING_BACK: return Call.State.ALERTING; + case SipSession.State.OUTGOING_CALL_CANCELING: return Call.State.DISCONNECTING; + case SipSession.State.IN_CALL: return Call.State.ACTIVE; + default: + Log.w(LOG_TAG, "illegal connection state: " + sessionState); + return Call.State.DISCONNECTED; + } + } + + private abstract class SipAudioCallAdapter extends SipAudioCall.Listener { + protected abstract void onCallEnded(Connection.DisconnectCause cause); + protected abstract void onError(Connection.DisconnectCause cause); + + @Override + public void onCallEnded(SipAudioCall call) { + onCallEnded(call.isInCall() + ? Connection.DisconnectCause.NORMAL + : Connection.DisconnectCause.INCOMING_MISSED); + } + + @Override + public void onCallBusy(SipAudioCall call) { + onCallEnded(Connection.DisconnectCause.BUSY); + } + + @Override + public void onError(SipAudioCall call, int errorCode, + String errorMessage) { + switch (errorCode) { + case SipErrorCode.SERVER_UNREACHABLE: + onError(Connection.DisconnectCause.SERVER_UNREACHABLE); + break; + case SipErrorCode.PEER_NOT_REACHABLE: + onError(Connection.DisconnectCause.NUMBER_UNREACHABLE); + break; + case SipErrorCode.INVALID_REMOTE_URI: + onError(Connection.DisconnectCause.INVALID_NUMBER); + break; + case SipErrorCode.TIME_OUT: + case SipErrorCode.TRANSACTION_TERMINTED: + onError(Connection.DisconnectCause.TIMED_OUT); + break; + case SipErrorCode.DATA_CONNECTION_LOST: + onError(Connection.DisconnectCause.LOST_SIGNAL); + break; + case SipErrorCode.INVALID_CREDENTIALS: + onError(Connection.DisconnectCause.INVALID_CREDENTIALS); + break; + case SipErrorCode.CROSS_DOMAIN_AUTHENTICATION: + onError(Connection.DisconnectCause.OUT_OF_NETWORK); + break; + case SipErrorCode.SERVER_ERROR: + onError(Connection.DisconnectCause.SERVER_ERROR); + break; + case SipErrorCode.SOCKET_ERROR: + case SipErrorCode.CLIENT_ERROR: + default: + Log.w(LOG_TAG, "error: " + SipErrorCode.toString(errorCode) + + ": " + errorMessage); + onError(Connection.DisconnectCause.ERROR_UNSPECIFIED); + } + } + } +} diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java new file mode 100755 index 0000000..5499966 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.sip; + +import android.content.Context; +import android.os.AsyncResult; +import android.os.Handler; +import android.os.Message; +import android.os.Registrant; +import android.os.RegistrantList; +import android.os.SystemProperties; +import android.telephony.CellLocation; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; +import android.util.Log; + +import com.android.internal.telephony.Call; +import com.android.internal.telephony.CallStateException; +import com.android.internal.telephony.Connection; +import com.android.internal.telephony.DataConnection; +import com.android.internal.telephony.IccCard; +import com.android.internal.telephony.IccFileHandler; +import com.android.internal.telephony.IccPhoneBookInterfaceManager; +import com.android.internal.telephony.IccSmsInterfaceManager; +import com.android.internal.telephony.MmiCode; +import com.android.internal.telephony.Phone; +import com.android.internal.telephony.PhoneBase; +import com.android.internal.telephony.PhoneNotifier; +import com.android.internal.telephony.PhoneSubInfo; +import com.android.internal.telephony.TelephonyProperties; + +import java.util.ArrayList; +import java.util.List; + +abstract class SipPhoneBase extends PhoneBase { + private static final String LOG_TAG = "SipPhone"; + + private RegistrantList mRingbackRegistrants = new RegistrantList(); + private State state = State.IDLE; + + public SipPhoneBase(Context context, PhoneNotifier notifier) { + super(notifier, context, new SipCommandInterface(context), false); + } + + public abstract Call getForegroundCall(); + + public abstract Call getBackgroundCall(); + + public abstract Call getRingingCall(); + + void migrateFrom(SipPhoneBase from) { + migrate(mRingbackRegistrants, from.mRingbackRegistrants); + migrate(mPreciseCallStateRegistrants, from.mPreciseCallStateRegistrants); + migrate(mNewRingingConnectionRegistrants, from.mNewRingingConnectionRegistrants); + migrate(mIncomingRingRegistrants, from.mIncomingRingRegistrants); + migrate(mDisconnectRegistrants, from.mDisconnectRegistrants); + migrate(mServiceStateRegistrants, from.mServiceStateRegistrants); + migrate(mMmiCompleteRegistrants, from.mMmiCompleteRegistrants); + migrate(mMmiRegistrants, from.mMmiRegistrants); + migrate(mUnknownConnectionRegistrants, from.mUnknownConnectionRegistrants); + migrate(mSuppServiceFailedRegistrants, from.mSuppServiceFailedRegistrants); + } + + static void migrate(RegistrantList to, RegistrantList from) { + from.removeCleared(); + for (int i = 0, n = from.size(); i < n; i++) { + to.add((Registrant) from.get(i)); + } + } + + @Override + public void registerForRingbackTone(Handler h, int what, Object obj) { + mRingbackRegistrants.addUnique(h, what, obj); + } + + @Override + public void unregisterForRingbackTone(Handler h) { + mRingbackRegistrants.remove(h); + } + + protected void startRingbackTone() { + AsyncResult result = new AsyncResult(null, new Boolean(true), null); + mRingbackRegistrants.notifyRegistrants(result); + } + + protected void stopRingbackTone() { + AsyncResult result = new AsyncResult(null, new Boolean(false), null); + mRingbackRegistrants.notifyRegistrants(result); + } + + public ServiceState getServiceState() { + // FIXME: we may need to provide this when data connectivity is lost + // or when server is down + ServiceState s = new ServiceState(); + s.setState(ServiceState.STATE_IN_SERVICE); + return s; + } + + public CellLocation getCellLocation() { + return null; + } + + public State getState() { + return state; + } + + public int getPhoneType() { + return Phone.PHONE_TYPE_SIP; + } + + public SignalStrength getSignalStrength() { + return new SignalStrength(); + } + + public boolean getMessageWaitingIndicator() { + return false; + } + + public boolean getCallForwardingIndicator() { + return false; + } + + public List<? extends MmiCode> getPendingMmiCodes() { + return new ArrayList<MmiCode>(0); + } + + public DataState getDataConnectionState() { + return DataState.DISCONNECTED; + } + + public DataState getDataConnectionState(String apnType) { + return DataState.DISCONNECTED; + } + + public DataActivityState getDataActivityState() { + return DataActivityState.NONE; + } + + /** + * Notify any interested party of a Phone state change {@link Phone.State} + */ + void notifyPhoneStateChanged() { + mNotifier.notifyPhoneState(this); + } + + /** + * Notify registrants of a change in the call state. This notifies changes in {@link Call.State} + * Use this when changes in the precise call state are needed, else use notifyPhoneStateChanged. + */ + void notifyPreciseCallStateChanged() { + /* we'd love it if this was package-scoped*/ + super.notifyPreciseCallStateChangedP(); + } + + void notifyNewRingingConnection(Connection c) { + super.notifyNewRingingConnectionP(c); + } + + void notifyDisconnect(Connection cn) { + mDisconnectRegistrants.notifyResult(cn); + } + + void notifyUnknownConnection() { + mUnknownConnectionRegistrants.notifyResult(this); + } + + void notifySuppServiceFailed(SuppService code) { + mSuppServiceFailedRegistrants.notifyResult(code); + } + + void notifyServiceStateChanged(ServiceState ss) { + super.notifyServiceStateChangedP(ss); + } + + public void notifyCallForwardingIndicator() { + mNotifier.notifyCallForwardingChanged(this); + } + + public boolean canDial() { + int serviceState = getServiceState().getState(); + Log.v(LOG_TAG, "canDial(): serviceState = " + serviceState); + if (serviceState == ServiceState.STATE_POWER_OFF) return false; + + String disableCall = SystemProperties.get( + TelephonyProperties.PROPERTY_DISABLE_CALL, "false"); + Log.v(LOG_TAG, "canDial(): disableCall = " + disableCall); + if (disableCall.equals("true")) return false; + + Log.v(LOG_TAG, "canDial(): ringingCall: " + getRingingCall().getState()); + Log.v(LOG_TAG, "canDial(): foregndCall: " + getForegroundCall().getState()); + Log.v(LOG_TAG, "canDial(): backgndCall: " + getBackgroundCall().getState()); + return !getRingingCall().isRinging() + && (!getForegroundCall().getState().isAlive() + || !getBackgroundCall().getState().isAlive()); + } + + public boolean handleInCallMmiCommands(String dialString) + throws CallStateException { + return false; + } + + boolean isInCall() { + Call.State foregroundCallState = getForegroundCall().getState(); + Call.State backgroundCallState = getBackgroundCall().getState(); + Call.State ringingCallState = getRingingCall().getState(); + + return (foregroundCallState.isAlive() || backgroundCallState.isAlive() + || ringingCallState.isAlive()); + } + + public boolean handlePinMmi(String dialString) { + return false; + } + + public void sendUssdResponse(String ussdMessge) { + } + + public void registerForSuppServiceNotification( + Handler h, int what, Object obj) { + } + + public void unregisterForSuppServiceNotification(Handler h) { + } + + public void setRadioPower(boolean power) { + } + + public String getVoiceMailNumber() { + return null; + } + + public String getVoiceMailAlphaTag() { + return null; + } + + public String getDeviceId() { + return null; + } + + public String getDeviceSvn() { + return null; + } + + public String getEsn() { + Log.e(LOG_TAG, "[SipPhone] getEsn() is a CDMA method"); + return "0"; + } + + public String getMeid() { + Log.e(LOG_TAG, "[SipPhone] getMeid() is a CDMA method"); + return "0"; + } + + public String getSubscriberId() { + return null; + } + + public String getIccSerialNumber() { + return null; + } + + public String getLine1Number() { + return null; + } + + public String getLine1AlphaTag() { + return null; + } + + public void setLine1Number(String alphaTag, String number, Message onComplete) { + // FIXME: what to reply for SIP? + AsyncResult.forMessage(onComplete, null, null); + onComplete.sendToTarget(); + } + + public void setVoiceMailNumber(String alphaTag, String voiceMailNumber, + Message onComplete) { + // FIXME: what to reply for SIP? + AsyncResult.forMessage(onComplete, null, null); + onComplete.sendToTarget(); + } + + public void getCallForwardingOption(int commandInterfaceCFReason, Message onComplete) { + } + + public void setCallForwardingOption(int commandInterfaceCFAction, + int commandInterfaceCFReason, String dialingNumber, + int timerSeconds, Message onComplete) { + } + + public void getOutgoingCallerIdDisplay(Message onComplete) { + // FIXME: what to reply? + AsyncResult.forMessage(onComplete, null, null); + onComplete.sendToTarget(); + } + + public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode, + Message onComplete) { + // FIXME: what's this for SIP? + AsyncResult.forMessage(onComplete, null, null); + onComplete.sendToTarget(); + } + + public void getCallWaiting(Message onComplete) { + AsyncResult.forMessage(onComplete, null, null); + onComplete.sendToTarget(); + } + + public void setCallWaiting(boolean enable, Message onComplete) { + Log.e(LOG_TAG, "call waiting not supported"); + } + + public boolean getIccRecordsLoaded() { + return false; + } + + public IccCard getIccCard() { + return null; + } + + public void getAvailableNetworks(Message response) { + } + + public void setNetworkSelectionModeAutomatic(Message response) { + } + + public void selectNetworkManually( + com.android.internal.telephony.gsm.NetworkInfo network, + Message response) { + } + + public void getNeighboringCids(Message response) { + } + + public void setOnPostDialCharacter(Handler h, int what, Object obj) { + } + + public void getDataCallList(Message response) { + } + + public List<DataConnection> getCurrentDataConnectionList () { + return null; + } + + public void updateServiceLocation() { + } + + public void enableLocationUpdates() { + } + + public void disableLocationUpdates() { + } + + public boolean getDataRoamingEnabled() { + return false; + } + + public void setDataRoamingEnabled(boolean enable) { + } + + public boolean enableDataConnectivity() { + return false; + } + + public boolean disableDataConnectivity() { + return false; + } + + public boolean isDataConnectivityPossible() { + return false; + } + + boolean updateCurrentCarrierInProvider() { + return false; + } + + public void saveClirSetting(int commandInterfaceCLIRMode) { + } + + public PhoneSubInfo getPhoneSubInfo(){ + return null; + } + + public IccSmsInterfaceManager getIccSmsInterfaceManager(){ + return null; + } + + public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(){ + return null; + } + + public IccFileHandler getIccFileHandler(){ + return null; + } + + public void activateCellBroadcastSms(int activate, Message response) { + Log.e(LOG_TAG, "Error! This functionality is not implemented for SIP."); + } + + public void getCellBroadcastSmsConfig(Message response) { + Log.e(LOG_TAG, "Error! This functionality is not implemented for SIP."); + } + + public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response){ + Log.e(LOG_TAG, "Error! This functionality is not implemented for SIP."); + } + + void updatePhoneState() { + State oldState = state; + + if (getRingingCall().isRinging()) { + state = State.RINGING; + } else if (getForegroundCall().isIdle() + && getBackgroundCall().isIdle()) { + state = State.IDLE; + } else { + state = State.OFFHOOK; + } + + if (state != oldState) { + Log.d(LOG_TAG, " ^^^ new phone state: " + state); + notifyPhoneStateChanged(); + } + } +} diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java new file mode 100644 index 0000000..611e3ea --- /dev/null +++ b/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.sip; + +import com.android.internal.telephony.PhoneNotifier; + +import android.content.Context; +import android.net.sip.SipProfile; +import android.util.Log; + +import java.text.ParseException; + +/** + * {@hide} + */ +public class SipPhoneFactory { + /** + * Makes a {@link SipPhone} object. + * @param sipUri the local SIP URI the phone runs on + * @param context {@code Context} needed to create a Phone object + * @param phoneNotifier {@code PhoneNotifier} needed to create a Phone + * object + * @return the {@code SipPhone} object or null if the SIP URI is not valid + */ + public static SipPhone makePhone(String sipUri, Context context, + PhoneNotifier phoneNotifier) { + try { + SipProfile profile = new SipProfile.Builder(sipUri).build(); + return new SipPhone(context, phoneNotifier, profile); + } catch (ParseException e) { + Log.w("SipPhoneFactory", "makePhone", e); + return null; + } + } +} diff --git a/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java b/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java index a120f52..9c72e5a 100644 --- a/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java +++ b/telephony/java/com/android/internal/telephony/test/SimulatedCommands.java @@ -844,7 +844,6 @@ public final class SimulatedCommands extends BaseCommands ret[11] = null; ret[12] = null; ret[13] = null; - ret[14] = null; resultSuccess(result, ret); } diff --git a/telephony/mockril/Android.mk b/telephony/mockril/Android.mk new file mode 100644 index 0000000..7c39cb1 --- /dev/null +++ b/telephony/mockril/Android.mk @@ -0,0 +1,31 @@ +# +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# + +LOCAL_PATH:=$(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_JAVA_LIBRARIES := core framework + +LOCAL_STATIC_JAVA_LIBRARIES := librilproto-java + +LOCAL_MODULE_TAGS := debug +LOCAL_MODULE := mockrilcontroller + +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/telephony/mockril/src/com/android/internal/telephony/mockril/MockRilController.java b/telephony/mockril/src/com/android/internal/telephony/mockril/MockRilController.java new file mode 100644 index 0000000..99f0abe --- /dev/null +++ b/telephony/mockril/src/com/android/internal/telephony/mockril/MockRilController.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2010, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.mockril; + +import android.os.Bundle; +import android.util.Log; +import android.telephony.PhoneNumberUtils; + +import com.android.internal.communication.MsgHeader; +import com.android.internal.communication.Msg; +import com.android.internal.telephony.RilChannel; +import com.android.internal.telephony.ril_proto.RilCtrlCmds; +import com.android.internal.telephony.ril_proto.RilCmds; +import com.google.protobuf.micro.MessageMicro; + +import java.io.IOException; + +/** + * Contain a list of commands to control Mock RIL. Before using these commands the devices + * needs to be set with Mock RIL. Refer to hardware/ril/mockril/README.txt for details. + * + */ +public class MockRilController { + private static final String TAG = "MockRILController"; + private RilChannel mRilChannel = null; + private Msg mMessage = null; + + public MockRilController() throws IOException { + mRilChannel = RilChannel.makeRilChannel(); + } + + /** + * Close the channel after the communication is done. + * This method has to be called after the test is finished. + */ + public void closeChannel() { + mRilChannel.close(); + } + + /** + * Send commands and return true on success + * @param cmd for MsgHeader + * @param token for MsgHeader + * @param status for MsgHeader + * @param pbData for Msg data + * @return true if command is sent successfully, false if it fails + */ + private boolean sendCtrlCommand(int cmd, long token, int status, MessageMicro pbData) { + try { + Msg.send(mRilChannel, cmd, token, status, pbData); + } catch (IOException e) { + Log.v(TAG, "send command : %d failed: " + e.getStackTrace()); + return false; + } + return true; + } + + /** + * Get control response + * @return Msg if response is received, else return null. + */ + private Msg getCtrlResponse() { + Msg response = null; + try { + response = Msg.recv(mRilChannel); + } catch (IOException e) { + Log.v(TAG, "receive response for getRadioState() error: " + e.getStackTrace()); + return null; + } + return response; + } + + /** + * @return the radio state if it is valid, otherwise return -1 + */ + public int getRadioState() { + if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_GET_RADIO_STATE, 0, 0, null)) { + return -1; + } + Msg response = getCtrlResponse(); + if (response == null) { + Log.v(TAG, "failed to get response"); + return -1; + } + response.printHeader(TAG); + RilCtrlCmds.CtrlRspRadioState resp = + response.getDataAs(RilCtrlCmds.CtrlRspRadioState.class); + int state = resp.getState(); + if ((state >= RilCmds.RADIOSTATE_OFF) && (state <= RilCmds.RADIOSTATE_NV_READY)) + return state; + else + return -1; + } + + /** + * Set the radio state of mock ril to the given state + * @param state for given radio state + * @return true if the state is set successful, false if it fails + */ + public boolean setRadioState(int state) { + RilCtrlCmds.CtrlReqRadioState req = new RilCtrlCmds.CtrlReqRadioState(); + if (state < 0 || state > RilCmds.RADIOSTATE_NV_READY) { + Log.v(TAG, "the give radio state is not valid."); + return false; + } + req.setState(state); + if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_RADIO_STATE, 0, 0, req)) { + Log.v(TAG, "send set radio state request failed."); + return false; + } + Msg response = getCtrlResponse(); + if (response == null) { + Log.v(TAG, "failed to get response for setRadioState"); + return false; + } + response.printHeader(TAG); + RilCtrlCmds.CtrlRspRadioState resp = + response.getDataAs(RilCtrlCmds.CtrlRspRadioState.class); + int curstate = resp.getState(); + return curstate == state; + } + + /** + * Start an incoming call for the given phone number + * + * @param phoneNumber is the number to show as incoming call + * @return true if the incoming call is started successfully, false if it fails. + */ + public boolean startIncomingCall(String phoneNumber) { + RilCtrlCmds.CtrlReqSetMTCall req = new RilCtrlCmds.CtrlReqSetMTCall(); + + req.setPhoneNumber(phoneNumber); + if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_MT_CALL, 0, 0, req)) { + Log.v(TAG, "send CMD_SET_MT_CALL request failed"); + return false; + } + return true; + } + + /** + * Hang up a connection remotelly for the given call fail cause + * + * @param connectionID is the connection to be hung up + * @param failCause is the call fail cause defined in ril.h + * @return true if the hangup is successful, false if it fails + */ + public boolean hangupRemote(int connectionId, int failCause) { + RilCtrlCmds.CtrlHangupConnRemote req = new RilCtrlCmds.CtrlHangupConnRemote(); + req.setConnectionId(connectionId); + req.setCallFailCause(failCause); + + if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_HANGUP_CONN_REMOTE, 0, 0, req)) { + Log.v(TAG, "send CTRL_CMD_HANGUP_CONN_REMOTE request failed"); + return false; + } + return true; + } + + /** + * Set call transition flag to the Mock Ril + * + * @param flag is a boolean value for the call transiton flag + * true: call transition: dialing->alert, alert->active is controlled + * false: call transition is automatically handled by Mock Ril + * @return true if the request is successful, false if it failed to set the flag + */ + public boolean setCallTransitionFlag(boolean flag) { + RilCtrlCmds.CtrlSetCallTransitionFlag req = new RilCtrlCmds.CtrlSetCallTransitionFlag(); + + req.setFlag(flag); + + if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_CALL_TRANSITION_FLAG, 0, 0, req)) { + Log.v(TAG, "send CTRL_CMD_SET_CALL_TRANSITION_FLAG request failed"); + return false; + } + return true; + } + + /** + * Set the dialing call to alert if the call transition flag is true + * + * @return true if the call transition is successful, false if it fails + */ + public boolean setDialCallToAlert() { + if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_CALL_ALERT, 0, 0, null)) { + Log.v(TAG, "send CTRL_CMD_SET_CALL_ALERT request failed"); + return false; + } + return true; + } + + /** + * Set the alert call to active if the call transition flag is true + * + * @return true if the call transition is successful, false if it fails + */ + public boolean setAlertCallToActive() { + if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_CALL_ACTIVE, 0, 0, null)) { + Log.v(TAG, "send CTRL_CMD_SET_CALL_ACTIVE request failed"); + return false; + } + return true; + } +} diff --git a/telephony/tests/telephonytests/Android.mk b/telephony/tests/telephonytests/Android.mk index 45e265a..98e4403 100644 --- a/telephony/tests/telephonytests/Android.mk +++ b/telephony/tests/telephonytests/Android.mk @@ -5,6 +5,8 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_STATIC_JAVA_LIBRARIES := librilproto-java + LOCAL_JAVA_LIBRARIES := android.test.runner LOCAL_PACKAGE_NAME := FrameworksTelephonyTests diff --git a/telephony/tests/telephonytests/AndroidManifest.xml b/telephony/tests/telephonytests/AndroidManifest.xml index 6a97423..ba1d957 100644 --- a/telephony/tests/telephonytests/AndroidManifest.xml +++ b/telephony/tests/telephonytests/AndroidManifest.xml @@ -32,6 +32,13 @@ android:targetPackage="com.android.frameworks.telephonytests" android:label="Frameworks Telephony Tests"> </instrumentation> + + <instrumentation android:name=".TelephonyMockRilTestRunner" + android:targetPackage="com.android.frameworks.telephonytests" + android:label="Test Runner for Mock Ril Tests" + /> + <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <uses-permission android:name="android.permission.INTERNET" /> </manifest> diff --git a/telephony/tests/telephonytests/src/android/telephony/PhoneNumberUtilsTest.java b/telephony/tests/telephonytests/src/android/telephony/PhoneNumberUtilsTest.java index 2165023..67df510 100644 --- a/telephony/tests/telephonytests/src/android/telephony/PhoneNumberUtilsTest.java +++ b/telephony/tests/telephonytests/src/android/telephony/PhoneNumberUtilsTest.java @@ -16,13 +16,16 @@ package android.telephony; +import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import android.text.SpannableStringBuilder; import android.telephony.PhoneNumberUtils; +import android.telephony.TelephonyManager; +import android.content.Context; import junit.framework.TestCase; -public class PhoneNumberUtilsTest extends TestCase { +public class PhoneNumberUtilsTest extends AndroidTestCase { @SmallTest public void testExtractNetworkPortion() throws Exception { @@ -482,4 +485,22 @@ public class PhoneNumberUtilsTest extends TestCase { PhoneNumberUtils.cdmaCheckAndProcessPlusCodeByNumberFormat("+18475797000", PhoneNumberUtils.FORMAT_UNKNOWN,PhoneNumberUtils.FORMAT_UNKNOWN)); } + + /** + * Basic checks for the VoiceMail number. + */ + @SmallTest + public void testWithNumberNotEqualToVoiceMail() throws Exception { + assertFalse(PhoneNumberUtils.isVoiceMailNumber("911")); + assertFalse(PhoneNumberUtils.isVoiceMailNumber("tel:911")); + assertFalse(PhoneNumberUtils.isVoiceMailNumber("+18001234567")); + assertFalse(PhoneNumberUtils.isVoiceMailNumber("")); + assertFalse(PhoneNumberUtils.isVoiceMailNumber(null)); + // This test fails on a device without a sim card + /*TelephonyManager mTelephonyManager = + (TelephonyManager)getContext().getSystemService(Context.TELEPHONY_SERVICE); + String mVoiceMailNumber = mTelephonyManager.getDefault().getVoiceMailNumber(); + assertTrue(PhoneNumberUtils.isVoiceMailNumber(mVoiceMailNumber)); + */ + } } diff --git a/telephony/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java b/telephony/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java new file mode 100644 index 0000000..9192f57 --- /dev/null +++ b/telephony/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2010, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.frameworks.telephonytests; + +import android.os.Bundle; + +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; +import android.util.Log; + +import java.io.IOException; + +import com.android.internal.telephony.RilChannel; +import com.android.internal.telephony.mockril.MockRilTest; + +import junit.framework.TestSuite; + +public class TelephonyMockRilTestRunner extends InstrumentationTestRunner { + + public RilChannel mMockRilChannel; + + @Override + public TestSuite getAllTests() { + log("getAllTests E"); + TestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(MockRilTest.class); + log("getAllTests X"); + return suite; + } + + @Override + public ClassLoader getLoader() { + log("getLoader EX"); + return TelephonyMockRilTestRunner.class.getClassLoader(); + } + + @Override + public void onCreate(Bundle icicle) { + log("onCreate E"); + try { + mMockRilChannel = RilChannel.makeRilChannel(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + log("onCreate X"); + + super.onCreate(icicle); + } + + @Override + public void onDestroy() { + // I've not seen this called + log("onDestroy EX"); + super.onDestroy(); + } + + @Override + public void onStart() { + // Called when the instrumentation thread is started. + // At the moment we don't need the thread so return + // which will shut down this unused thread. + log("onStart EX"); + super.onStart(); + } + + @Override + public void finish(int resultCode, Bundle results) { + // Called when complete so I ask the mMockRilChannel to quit. + log("finish E"); + mMockRilChannel.close(); + log("finish X"); + super.finish(resultCode, results); + } + + private void log(String s) { + Log.e("TelephonyMockRilTestRunner", s); + } +} diff --git a/telephony/tests/telephonytests/src/com/android/telephonytest/unit/CallerInfoUnitTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/CallerInfoTest.java index 0f24f15..1e5dafb 100644 --- a/telephony/tests/telephonytests/src/com/android/telephonytest/unit/CallerInfoUnitTest.java +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/CallerInfoTest.java @@ -14,7 +14,8 @@ * limitations under the License. */ -package com.android.telephonytest.unit; +package com.android.internal.telephony; + import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; @@ -33,7 +34,7 @@ import android.util.StringBuilderPrinter; * */ -public class CallerInfoUnitTest extends AndroidTestCase { +public class CallerInfoTest extends AndroidTestCase { private CallerInfo mInfo; private Context mContext; diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java new file mode 100644 index 0000000..b214887 --- /dev/null +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +import android.telephony.SmsMessage; +import android.telephony.TelephonyManager; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS; + +public class SmsMessageBodyTest extends AndroidTestCase { + + private static final String sAsciiChars = "@$_ !\"#%&'()*+,-./0123456789" + + ":;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n\r"; + private static final String sGsmBasicChars = "\u00a3\u00a5\u00e8\u00e9" + + "\u00f9\u00ec\u00f2\u00c7\u00d8\u00f8\u00c5\u00e5\u0394\u03a6" + + "\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u00c6\u00e6" + + "\u00df\u00c9\u00a4\u00a1\u00c4\u00d6\u00d1\u00dc\u00a7\u00bf" + + "\u00e4\u00f6\u00f1\u00fc\u00e0"; + private static final String sGsmExtendedAsciiChars = "{|}\\[~]^\f"; + private static final String sGsmExtendedEuroSymbol = "\u20ac"; + private static final String sUnicodeChars = "\u4e00\u4e01\u4e02\u4e03" + + "\u4e04\u4e05\u4e06\u4e07\u4e08\u4e09\u4e0a\u4e0b\u4e0c\u4e0d" + + "\u4e0e\u4e0f\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048" + + "\u30a1\u30a2\u30a3\u30a4\u30a5\u30a6\u30a7\u30a8" + + "\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18" + + "\uff70\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78" + + "\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408" + + "\u00a2\u00a9\u00ae\u2122"; + + private static final int sTestLengthCount = 12; + + private static final int[] sSeptetTestLengths = + { 0, 1, 2, 80, 159, 160, 161, 240, 305, 306, 307, 320}; + + private static final int[] sUnicodeTestLengths = + { 0, 1, 2, 35, 69, 70, 71, 100, 133, 134, 135, 160}; + + private static final int[] sTestMsgCounts = + { 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3}; + + private static final int[] sSeptetUnitsRemaining = + {160, 159, 158, 80, 1, 0, 145, 66, 1, 0, 152, 139}; + + private static final int[] sUnicodeUnitsRemaining = + { 70, 69, 68, 35, 1, 0, 63, 34, 1, 0, 66, 41}; + + + @SmallTest + public void testCalcLengthAscii() throws Exception { + StringBuilder sb = new StringBuilder(320); + int[] values = {0, 0, 0, SmsMessage.ENCODING_7BIT}; + int startPos = 0; + int asciiCharsLen = sAsciiChars.length(); + + for (int i = 0; i < sTestLengthCount; i++) { + int len = sSeptetTestLengths[i]; + assertTrue(sb.length() <= len); + + while (sb.length() < len) { + int addCount = len - sb.length(); + int endPos = (asciiCharsLen - startPos > addCount) ? + (startPos + addCount) : asciiCharsLen; + sb.append(sAsciiChars, startPos, endPos); + startPos = (endPos == asciiCharsLen) ? 0 : endPos; + } + assertEquals(len, sb.length()); + + String testStr = sb.toString(); + values[0] = sTestMsgCounts[i]; + values[1] = len; + values[2] = sSeptetUnitsRemaining[i]; + + callGsmLengthMethods(testStr, false, values); + callGsmLengthMethods(testStr, true, values); + callCdmaLengthMethods(testStr, false, values); + callCdmaLengthMethods(testStr, true, values); + } + } + + @SmallTest + public void testCalcLength7bitGsm() throws Exception { + // TODO + } + + @SmallTest + public void testCalcLength7bitGsmExtended() throws Exception { + // TODO + } + + @SmallTest + public void testCalcLengthUnicode() throws Exception { + StringBuilder sb = new StringBuilder(160); + int[] values = {0, 0, 0, SmsMessage.ENCODING_16BIT}; + int[] values7bit = {1, 0, 0, SmsMessage.ENCODING_7BIT}; + int startPos = 0; + int unicodeCharsLen = sUnicodeChars.length(); + + // start with length 1: empty string uses ENCODING_7BIT + for (int i = 1; i < sTestLengthCount; i++) { + int len = sUnicodeTestLengths[i]; + assertTrue(sb.length() <= len); + + while (sb.length() < len) { + int addCount = len - sb.length(); + int endPos = (unicodeCharsLen - startPos > addCount) ? + (startPos + addCount) : unicodeCharsLen; + sb.append(sUnicodeChars, startPos, endPos); + startPos = (endPos == unicodeCharsLen) ? 0 : endPos; + } + assertEquals(len, sb.length()); + + String testStr = sb.toString(); + values[0] = sTestMsgCounts[i]; + values[1] = len; + values[2] = sUnicodeUnitsRemaining[i]; + values7bit[1] = len; + values7bit[2] = MAX_USER_DATA_SEPTETS - len; + + callGsmLengthMethods(testStr, false, values); + callCdmaLengthMethods(testStr, false, values); + callGsmLengthMethods(testStr, true, values7bit); + callCdmaLengthMethods(testStr, true, values7bit); + } + } + + private void callGsmLengthMethods(CharSequence msgBody, boolean use7bitOnly, + int[] expectedValues) + { + // deprecated GSM-specific method + int[] values = android.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly); + assertEquals("msgCount", expectedValues[0], values[0]); + assertEquals("codeUnitCount", expectedValues[1], values[1]); + assertEquals("codeUnitsRemaining", expectedValues[2], values[2]); + assertEquals("codeUnitSize", expectedValues[3], values[3]); + + int activePhone = TelephonyManager.getDefault().getPhoneType(); + if (TelephonyManager.PHONE_TYPE_GSM == activePhone) { + values = android.telephony.SmsMessage.calculateLength(msgBody, use7bitOnly); + assertEquals("msgCount", expectedValues[0], values[0]); + assertEquals("codeUnitCount", expectedValues[1], values[1]); + assertEquals("codeUnitsRemaining", expectedValues[2], values[2]); + assertEquals("codeUnitSize", expectedValues[3], values[3]); + } + + SmsMessageBase.TextEncodingDetails ted = + com.android.internal.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly); + assertEquals("msgCount", expectedValues[0], ted.msgCount); + assertEquals("codeUnitCount", expectedValues[1], ted.codeUnitCount); + assertEquals("codeUnitsRemaining", expectedValues[2], ted.codeUnitsRemaining); + assertEquals("codeUnitSize", expectedValues[3], ted.codeUnitSize); + } + + private void callCdmaLengthMethods(CharSequence msgBody, boolean use7bitOnly, + int[] expectedValues) + { + int activePhone = TelephonyManager.getDefault().getPhoneType(); + if (TelephonyManager.PHONE_TYPE_CDMA == activePhone) { + int[] values = android.telephony.SmsMessage.calculateLength(msgBody, use7bitOnly); + assertEquals("msgCount", expectedValues[0], values[0]); + assertEquals("codeUnitCount", expectedValues[1], values[1]); + assertEquals("codeUnitsRemaining", expectedValues[2], values[2]); + assertEquals("codeUnitSize", expectedValues[3], values[3]); + } + + SmsMessageBase.TextEncodingDetails ted = + com.android.internal.telephony.cdma.SmsMessage.calculateLength(msgBody, use7bitOnly); + assertEquals("msgCount", expectedValues[0], ted.msgCount); + assertEquals("codeUnitCount", expectedValues[1], ted.codeUnitCount); + assertEquals("codeUnitsRemaining", expectedValues[2], ted.codeUnitsRemaining); + assertEquals("codeUnitSize", expectedValues[3], ted.codeUnitSize); + + ted = com.android.internal.telephony.cdma.sms.BearerData.calcTextEncodingDetails(msgBody, use7bitOnly); + assertEquals("msgCount", expectedValues[0], ted.msgCount); + assertEquals("codeUnitCount", expectedValues[1], ted.codeUnitCount); + assertEquals("codeUnitsRemaining", expectedValues[2], ted.codeUnitsRemaining); + assertEquals("codeUnitSize", expectedValues[3], ted.codeUnitSize); + } +} diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java new file mode 100644 index 0000000..3149ee1 --- /dev/null +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony.mockril; + +import android.util.Log; +import android.test.InstrumentationTestCase; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import com.android.internal.communication.MsgHeader; +import com.android.internal.communication.Msg; +import com.android.internal.telephony.RilChannel; +import com.android.internal.telephony.ril_proto.RilCtrlCmds; +import com.android.internal.telephony.ril_proto.RilCmds; + +import com.android.frameworks.telephonytests.TelephonyMockRilTestRunner; +import com.google.protobuf.micro.InvalidProtocolBufferMicroException; + +// Test suite for test ril +public class MockRilTest extends InstrumentationTestCase { + private static final String TAG = "MockRilTest"; + + RilChannel mMockRilChannel; + TelephonyMockRilTestRunner mRunner; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mRunner = (TelephonyMockRilTestRunner)getInstrumentation(); + mMockRilChannel = mRunner.mMockRilChannel; + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + static void log(String s) { + Log.v(TAG, s); + } + + /** + * Test Case 1: Test protobuf serialization and deserialization + * @throws InvalidProtocolBufferMicroException + */ + public void testProtobufSerDes() throws InvalidProtocolBufferMicroException { + log("testProtobufSerdes E"); + + RilCtrlCmds.CtrlRspRadioState rs = new RilCtrlCmds.CtrlRspRadioState(); + assertTrue(String.format("expected rs.state == 0 was %d", rs.getState()), + rs.getState() == 0); + rs.setState(1); + assertTrue(String.format("expected rs.state == 1 was %d", rs.getState()), + rs.getState() == 1); + + byte[] rs_ser = rs.toByteArray(); + RilCtrlCmds.CtrlRspRadioState rsNew = RilCtrlCmds.CtrlRspRadioState.parseFrom(rs_ser); + assertTrue(String.format("expected rsNew.state == 1 was %d", rs.getState()), + rs.getState() == 1); + + log("testProtobufSerdes X"); + } + + /** + * Test case 2: Test echo command works using writeMsg & readMsg + */ + public void testEchoMsg() throws IOException { + log("testEchoMsg E"); + + MsgHeader mh = new MsgHeader(); + mh.setCmd(0); + mh.setToken(1); + mh.setStatus(2); + ByteBuffer data = ByteBuffer.allocate(3); + data.put((byte)3); + data.put((byte)4); + data.put((byte)5); + Msg.send(mMockRilChannel, mh, data); + + Msg respMsg = Msg.recv(mMockRilChannel); + assertTrue(String.format("expected mhd.header.cmd == 0 was %d",respMsg.getCmd()), + respMsg.getCmd() == 0); + assertTrue(String.format("expected mhd.header.token == 1 was %d",respMsg.getToken()), + respMsg.getToken() == 1); + assertTrue(String.format("expected mhd.header.status == 2 was %d", respMsg.getStatus()), + respMsg.getStatus() == 2); + assertTrue(String.format("expected mhd.data[0] == 3 was %d", respMsg.getData(0)), + respMsg.getData(0) == 3); + assertTrue(String.format("expected mhd.data[1] == 4 was %d", respMsg.getData(1)), + respMsg.getData(1) == 4); + assertTrue(String.format("expected mhd.data[2] == 5 was %d", respMsg.getData(2)), + respMsg.getData(2) == 5); + + log("testEchoMsg X"); + } + + /** + * Test case 3: Test get as + */ + public void testGetAs() { + log("testGetAs E"); + + // Use a message header as the protobuf data content + MsgHeader mh = new MsgHeader(); + mh.setCmd(12345); + mh.setToken(9876); + mh.setStatus(7654); + mh.setLengthData(4321); + byte[] data = mh.toByteArray(); + MsgHeader mhResult = Msg.getAs(MsgHeader.class, data); + + assertTrue(String.format("expected cmd == 12345 was %d", mhResult.getCmd()), + mhResult.getCmd() == 12345); + assertTrue(String.format("expected token == 9876 was %d", mhResult.getToken()), + mhResult.getToken() == 9876); + assertTrue(String.format("expected status == 7654 was %d", mhResult.getStatus()), + mhResult.getStatus() == 7654); + assertTrue(String.format("expected lengthData == 4321 was %d", mhResult.getLengthData()), + mhResult.getLengthData() == 4321); + + Msg msg = Msg.obtain(); + msg.setData(ByteBuffer.wrap(data)); + + mhResult = msg.getDataAs(MsgHeader.class); + + assertTrue(String.format("expected cmd == 12345 was %d", mhResult.getCmd()), + mhResult.getCmd() == 12345); + assertTrue(String.format("expected token == 9876 was %d", mhResult.getToken()), + mhResult.getToken() == 9876); + assertTrue(String.format("expected status == 7654 was %d", mhResult.getStatus()), + mhResult.getStatus() == 7654); + assertTrue(String.format("expected lengthData == 4321 was %d", mhResult.getLengthData()), + mhResult.getLengthData() == 4321); + + log("testGetAs X"); + } + + /** + * Test case 3: test get radio state + */ + public void testGetRadioState() throws IOException { + log("testGetRadioState E"); + + Msg.send(mMockRilChannel, 1, 9876, 0, null); + + Msg resp = Msg.recv(mMockRilChannel); + //resp.printHeader("testGetRadioState"); + + assertTrue(String.format("expected cmd == 1 was %d", resp.getCmd()), + resp.getCmd() == 1); + assertTrue(String.format("expected token == 9876 was %d", resp.getToken()), + resp.getToken() == 9876); + assertTrue(String.format("expected status == 0 was %d", resp.getStatus()), + resp.getStatus() == 0); + + RilCtrlCmds.CtrlRspRadioState rsp = resp.getDataAs(RilCtrlCmds.CtrlRspRadioState.class); + + int state = rsp.getState(); + log("testGetRadioState state=" + state); + assertTrue(String.format("expected RadioState >= 0 && RadioState <= 9 was %d", state), + ((state >= 0) && (state <= 9))); + + log("testGetRadioState X"); + } + + /** + * Test case 5: test set radio state + */ + public void testSetRadioState() throws IOException { + log("testSetRadioState E"); + + RilCtrlCmds.CtrlReqRadioState cmdrs = new RilCtrlCmds.CtrlReqRadioState(); + assertEquals(0, cmdrs.getState()); + + cmdrs.setState(RilCmds.RADIOSTATE_SIM_NOT_READY); + assertEquals(2, cmdrs.getState()); + + Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_RADIO_STATE, 0, 0, cmdrs); + + Msg resp = Msg.recv(mMockRilChannel); + log("get response status :" + resp.getStatus()); + log("get response for command: " + resp.getCmd()); + log("get command token: " + resp.getToken()); + + RilCtrlCmds.CtrlRspRadioState rsp = resp.getDataAs(RilCtrlCmds.CtrlRspRadioState.class); + + int state = rsp.getState(); + log("get response for testSetRadioState: " + state); + assertTrue(RilCmds.RADIOSTATE_SIM_NOT_READY == state); + } + + /** + * Test case 6: test start incoming call and hangup it. + */ + public void testStartIncomingCallAndHangup() throws IOException { + log("testStartIncomingCallAndHangup"); + RilCtrlCmds.CtrlReqSetMTCall cmd = new RilCtrlCmds.CtrlReqSetMTCall(); + String incomingCall = "6502889108"; + // set the MT call + cmd.setPhoneNumber(incomingCall); + Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_MT_CALL, 0, 0, cmd); + // get response + Msg resp = Msg.recv(mMockRilChannel); + log("Get response status: " + resp.getStatus()); + assertTrue("The ril is not in a proper state to set MT calls.", + resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK); + + // allow the incoming call alerting for some time + try { + Thread.sleep(5000); + } catch (InterruptedException e) {} + + // we are playing a trick to assume the current is 1 + RilCtrlCmds.CtrlHangupConnRemote hangupCmd = new RilCtrlCmds.CtrlHangupConnRemote(); + hangupCmd.setConnectionId(1); + hangupCmd.setCallFailCause(16); // normal hangup + Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_HANGUP_CONN_REMOTE, 0, 0, hangupCmd); + + // get response + resp = Msg.recv(mMockRilChannel); + log("Get response for hangup connection: " + resp.getStatus()); + assertTrue("CTRL_CMD_HANGUP_CONN_REMOTE failed", + resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK); + } + + /** + * Test case 7: test set call transition flag + */ + public void testSetCallTransitionFlag() throws IOException { + log("testSetCallTransitionFlag"); + // Set flag to true: + RilCtrlCmds.CtrlSetCallTransitionFlag cmd = new RilCtrlCmds.CtrlSetCallTransitionFlag(); + cmd.setFlag(true); + Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_CALL_TRANSITION_FLAG, 0, 0, cmd); + + Msg resp = Msg.recv(mMockRilChannel); + log("Get response status: " + resp.getStatus()); + assertTrue("Set call transition flag failed", + resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK); + + // add a dialing call + RilCtrlCmds.CtrlReqAddDialingCall cmdDialCall = new RilCtrlCmds.CtrlReqAddDialingCall(); + String phoneNumber = "5102345678"; + cmdDialCall.setPhoneNumber(phoneNumber); + Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_ADD_DIALING_CALL, 0, 0, cmdDialCall); + resp = Msg.recv(mMockRilChannel); + log("Get response status for adding a dialing call: " + resp.getStatus()); + assertTrue("add dialing call failed", + resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK); + try { + Thread.sleep(5000); + } catch (InterruptedException e) {} + + // send command to force call state change + Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_CALL_ALERT, 0, 0, null); + resp = Msg.recv(mMockRilChannel); + log("Get response status: " + resp.getStatus()); + assertTrue("Set call alert failed", + resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK); + + try { + Thread.sleep(2000); + } catch (InterruptedException e) {} + + // send command to force call state change + Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_CALL_ACTIVE, 0, 0, null); + resp = Msg.recv(mMockRilChannel); + log("Get response status: " + resp.getStatus()); + assertTrue("Set call active failed", + resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK); + + // hangup the active all remotely + RilCtrlCmds.CtrlHangupConnRemote hangupCmd = new RilCtrlCmds.CtrlHangupConnRemote(); + hangupCmd.setConnectionId(1); + hangupCmd.setCallFailCause(16); // normal hangup + Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_HANGUP_CONN_REMOTE, 0, 0, hangupCmd); + resp = Msg.recv(mMockRilChannel); + log("Get response for hangup connection: " + resp.getStatus()); + assertTrue("CTRL_CMD_HANGUP_CONN_REMOTE failed", + resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK); + + // set the flag to false + cmd.setFlag(false); + Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_CALL_TRANSITION_FLAG, 0, 0, cmd); + resp = Msg.recv(mMockRilChannel); + assertTrue("Set call transition flag failed", + resp.getStatus() == RilCtrlCmds.CTRL_STATUS_OK); + } +} diff --git a/telephony/tests/telephonytests/src/com/android/telephonytest/unit/PhoneNumberUtilsUnitTest.java b/telephony/tests/telephonytests/src/com/android/telephonytest/unit/PhoneNumberUtilsUnitTest.java deleted file mode 100644 index 2d3c548..0000000 --- a/telephony/tests/telephonytests/src/com/android/telephonytest/unit/PhoneNumberUtilsUnitTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.telephonytest.unit; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; -import android.util.Log; - -import android.telephony.PhoneNumberUtils; -import android.telephony.TelephonyManager; - -/* - * Check the PhoneNumberUtils utility class works as expected. - * - */ - -public class PhoneNumberUtilsUnitTest extends AndroidTestCase { - private String mVoiceMailNumber; - private static final String TAG = "PhoneNumberUtilsUnitTest"; - - @Override - protected void setUp() throws Exception { - super.setUp(); - // FIXME: Why are we getting a security exception here? The - // permission is declared in the manifest.... - // mVoiceMailNumber = TelephonyManager.getDefault().getVoiceMailNumber(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - /** - * Basic checks for the VoiceMail number. - * Assumes READ_PHONE_STATE permission and we don't have it. - */ - // TODO: Figure out why we don't have the permission declared in the manifest. - @SmallTest - public void testWithNumberNotEqualToVoiceMail() throws Exception { - assertFalse(PhoneNumberUtils.isVoiceMailNumber("911")); - assertFalse(PhoneNumberUtils.isVoiceMailNumber("tel:911")); - assertFalse(PhoneNumberUtils.isVoiceMailNumber("+18001234567")); - assertFalse(PhoneNumberUtils.isVoiceMailNumber("")); - assertFalse(PhoneNumberUtils.isVoiceMailNumber(null)); - // FIXME: - // assertTrue(PhoneNumberUtils.isVoiceMailNumber(mVoiceMailNumber)); - } - -} |