/* * Copyright (C) 2006 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.gsm; import static com.android.internal.telephony.TelephonyProperties.PROPERTY_BASEBAND_VERSION; import static com.android.internal.telephony.gsm.CommandsInterface.CF_ACTION_DISABLE; import static com.android.internal.telephony.gsm.CommandsInterface.CF_ACTION_ENABLE; import static com.android.internal.telephony.gsm.CommandsInterface.CF_ACTION_ERASURE; import static com.android.internal.telephony.gsm.CommandsInterface.CF_ACTION_REGISTRATION; import static com.android.internal.telephony.gsm.CommandsInterface.CF_REASON_ALL; import static com.android.internal.telephony.gsm.CommandsInterface.CF_REASON_ALL_CONDITIONAL; import static com.android.internal.telephony.gsm.CommandsInterface.CF_REASON_BUSY; import static com.android.internal.telephony.gsm.CommandsInterface.CF_REASON_NOT_REACHABLE; import static com.android.internal.telephony.gsm.CommandsInterface.CF_REASON_NO_REPLY; import static com.android.internal.telephony.gsm.CommandsInterface.CF_REASON_UNCONDITIONAL; import static com.android.internal.telephony.gsm.CommandsInterface.SERVICE_CLASS_VOICE; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.database.SQLException; import android.net.Uri; import android.os.AsyncResult; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Registrant; import android.os.RegistrantList; import android.os.SystemProperties; import android.preference.PreferenceManager; import android.provider.Telephony; import android.telephony.CellLocation; import android.telephony.PhoneNumberUtils; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.text.TextUtils; 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.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.SimCard; import com.android.internal.telephony.gsm.SimException; import com.android.internal.telephony.gsm.stk.StkService; import com.android.internal.telephony.test.SimulatedRadioControl; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; /** * {@hide} */ public class GSMPhone extends PhoneBase { // NOTE that LOG_TAG here is "GSM", which means that log messages // from this file will go into the radio log rather than the main // log. (Use "adb logcat -b radio" to see them.) static final String LOG_TAG = "GSM"; private static final boolean LOCAL_DEBUG = false; // Key used to read and write the saved network selection value public static final String NETWORK_SELECTION_KEY = "network_selection_key"; // Key used to read/write "disable data connection on boot" pref (used for testing) public static final String DATA_DISABLED_ON_BOOT_KEY = "disabled_on_boot_key"; // Key used to read/write current ciphering state public static final String CIPHERING_KEY = "ciphering_key"; // Key used to read/write current CLIR setting public static final String CLIR_KEY = "clir_key"; // Key used to read/write voice mail number public static final String VM_NUMBER = "vm_number_key"; // Key used to read/write the SIM IMSI used for storing the voice mail public static final String VM_SIM_IMSI = "vm_sim_imsi_key"; // Key used to read/write "disable DNS server check" pref (used for testing) public static final String DNS_SERVER_CHECK_DISABLED_KEY = "dns_server_check_disabled_key"; //***** Instance Variables CallTracker mCT; ServiceStateTracker mSST; CommandsInterface mCM; SMSDispatcher mSMS; DataConnectionTracker mDataConnection; SIMFileHandler mSIMFileHandler; SIMRecords mSIMRecords; GsmSimCard mSimCard; StkService mStkService; MyHandler h; ArrayList <GsmMmiCode> mPendingMMIs = new ArrayList<GsmMmiCode>(); SimPhoneBookInterfaceManager mSimPhoneBookIntManager; SimSmsInterfaceManager mSimSmsIntManager; PhoneSubInfo mSubInfo; boolean mDnsCheckDisabled = false; Registrant mPostDialHandler; /** List of Registrants to receive Supplementary Service Notifications. */ RegistrantList mSsnRegistrants = new RegistrantList(); Thread debugPortThread; ServerSocket debugSocket; private int mReportedRadioResets; private int mReportedAttemptedConnects; private int mReportedSuccessfulConnects; private String mImei; private String mImeiSv; private String mVmNumber; //***** Event Constants static final int EVENT_RADIO_AVAILABLE = 1; /** Supplemnetary Service Notification received. */ static final int EVENT_SSN = 2; static final int EVENT_SIM_RECORDS_LOADED = 3; static final int EVENT_MMI_DONE = 4; static final int EVENT_RADIO_ON = 5; static final int EVENT_GET_BASEBAND_VERSION_DONE = 6; static final int EVENT_USSD = 7; static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 8; static final int EVENT_GET_IMEI_DONE = 9; static final int EVENT_GET_IMEISV_DONE = 10; static final int EVENT_GET_SIM_STATUS_DONE = 11; static final int EVENT_SET_CALL_FORWARD_DONE = 12; static final int EVENT_GET_CALL_FORWARD_DONE = 13; static final int EVENT_CALL_RING = 14; // Used to intercept the carriere selection calls so that // we can save the values. static final int EVENT_SET_NETWORK_MANUAL_COMPLETE = 15; static final int EVENT_SET_NETWORK_AUTOMATIC_COMPLETE = 16; static final int EVENT_SET_CLIR_COMPLETE = 17; static final int EVENT_REGISTERED_TO_NETWORK = 18; static final int EVENT_SET_VM_NUMBER_DONE = 19; //***** Constructors public GSMPhone (Context context, CommandsInterface ci, PhoneNotifier notifier) { this(context,ci,notifier, false); } public GSMPhone (Context context, CommandsInterface ci, PhoneNotifier notifier, boolean unitTestMode) { super(notifier, context, unitTestMode); h = new MyHandler(); mCM = ci; if (ci instanceof SimulatedRadioControl) { mSimulatedRadioControl = (SimulatedRadioControl) ci; } mCT = new CallTracker(this); mSST = new ServiceStateTracker (this); mSMS = new SMSDispatcher(this); mSIMFileHandler = new SIMFileHandler(this); mSIMRecords = new SIMRecords(this); mDataConnection = new DataConnectionTracker (this); mSimCard = new GsmSimCard(this); if (!unitTestMode) { mSimPhoneBookIntManager = new SimPhoneBookInterfaceManager(this); mSimSmsIntManager = new SimSmsInterfaceManager(this); mSubInfo = new PhoneSubInfo(this); } mStkService = StkService.getInstance(mCM, mSIMRecords, mContext, mSIMFileHandler, mSimCard); mCM.registerForAvailable(h, EVENT_RADIO_AVAILABLE, null); mSIMRecords.registerForRecordsLoaded(h, EVENT_SIM_RECORDS_LOADED, null); mCM.registerForOffOrNotAvailable(h, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null); mCM.registerForOn(h, EVENT_RADIO_ON, null); mCM.setOnUSSD(h, EVENT_USSD, null); mCM.setOnSuppServiceNotification(h, EVENT_SSN, null); mCM.setOnCallRing(h, EVENT_CALL_RING, null); mSST.registerForNetworkAttach(h, EVENT_REGISTERED_TO_NETWORK, null); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); mDnsCheckDisabled = sp.getBoolean(DNS_SERVER_CHECK_DISABLED_KEY, false); if (false) { try { //debugSocket = new LocalServerSocket("com.android.internal.telephony.debug"); debugSocket = new ServerSocket(); debugSocket.setReuseAddress(true); debugSocket.bind (new InetSocketAddress("127.0.0.1", 6666)); debugPortThread = new Thread( new Runnable() { public void run() { for(;;) { try { Socket sock; sock = debugSocket.accept(); Log.i(LOG_TAG, "New connection; resetting radio"); mCM.resetRadio(null); sock.close(); } catch (IOException ex) { Log.w(LOG_TAG, "Exception accepting socket", ex); } } } }, "GSMPhone debug"); debugPortThread.start(); } catch (IOException ex) { Log.w(LOG_TAG, "Failure to open com.android.internal.telephony.debug socket", ex); } } } //***** Overridden from Phone public ServiceState getServiceState() { return mSST.ss; } public CellLocation getCellLocation() { return mSST.cellLoc; } public Phone.State getState() { return mCT.state; } public String getPhoneName() { return "GSM"; } public String[] getActiveApnTypes() { return mDataConnection.getActiveApnTypes(); } public String getActiveApn() { return mDataConnection.getActiveApnString(); } public int getSignalStrengthASU() { return mSST.rssi == 99 ? -1 : mSST.rssi; } public boolean getMessageWaitingIndicator() { return mSIMRecords.getVoiceMessageWaiting(); } public boolean getCallForwardingIndicator() { return mSIMRecords.getVoiceCallForwardingFlag(); } public List<? extends MmiCode> getPendingMmiCodes() { return mPendingMMIs; } public DataState getDataConnectionState() { DataState ret = DataState.DISCONNECTED; if ((SystemProperties.get("adb.connected", "").length() > 0) && (SystemProperties.get("android.net.use-adb-networking", "") .length() > 0)) { // We're connected to an ADB host and we have USB networking // turned on. No matter what the radio state is, // we report data connected ret = DataState.CONNECTED; } else if (mSST.getCurrentGprsState() != ServiceState.STATE_IN_SERVICE) { // If we're out of service, open TCP sockets may still work // but no data will flow ret = DataState.DISCONNECTED; } else { /* mSST.gprsState == ServiceState.STATE_IN_SERVICE */ switch (mDataConnection.state) { case FAILED: case IDLE: ret = DataState.DISCONNECTED; break; case CONNECTED: case DISCONNECTING: if ( mCT.state != Phone.State.IDLE && !mSST.isConcurrentVoiceAndData()) { ret = DataState.SUSPENDED; } else { ret = DataState.CONNECTED; } break; case INITING: case CONNECTING: case SCANNING: ret = DataState.CONNECTING; break; } } return ret; } public DataActivityState getDataActivityState() { DataActivityState ret = DataActivityState.NONE; if (mSST.getCurrentGprsState() == ServiceState.STATE_IN_SERVICE) { switch (mDataConnection.activity) { case DATAIN: ret = DataActivityState.DATAIN; break; case DATAOUT: ret = DataActivityState.DATAOUT; break; case DATAINANDOUT: ret = DataActivityState.DATAINANDOUT; break; } } return ret; } /** * Notify any interested party of a Phone state change. */ /*package*/ void notifyPhoneStateChanged() { mNotifier.notifyPhoneState(this); } /** * Notifies registrants (ie, activities in the Phone app) about * changes to call state (including Phone and Connection changes). */ /*package*/ void notifyCallStateChanged() { /* we'd love it if this was package-scoped*/ super.notifyCallStateChangedP(); } /*package*/ void notifyNewRingingConnection(Connection c) { /* we'd love it if this was package-scoped*/ super.notifyNewRingingConnectionP(c); } /** * Notifiy registrants of a RING event. */ void notifyIncomingRing() { AsyncResult ar = new AsyncResult(null, this, null); mIncomingRingRegistrants.notifyRegistrants(ar); } /*package*/ void notifyDisconnect(Connection cn) { mDisconnectRegistrants.notifyResult(cn); } void notifyUnknownConnection() { mUnknownConnectionRegistrants.notifyResult(this); } void notifySuppServiceFailed(SuppService code) { mSuppServiceFailedRegistrants.notifyResult(code); } /*package*/ void notifyServiceStateChanged(ServiceState ss) { super.notifyServiceStateChangedP(ss); } /*package*/ void notifyLocationChanged() { mNotifier.notifyCellLocation(this); } /*package*/ void notifySignalStrength() { mNotifier.notifySignalStrength(this); } /*package*/ void notifyDataConnection(String reason) { mNotifier.notifyDataConnection(this, reason); } /*package*/ void notifyDataConnectionFailed(String reason) { mNotifier.notifyDataConnectionFailed(this, reason); } /*package*/ void notifyDataActivity() { mNotifier.notifyDataActivity(this); } /*package*/ void updateMessageWaitingIndicator(boolean mwi) { // this also calls notifyMessageWaitingIndicator() mSIMRecords.setVoiceMessageWaiting(1, mwi ? -1 : 0); } /*package*/ void notifyMessageWaitingIndicator() { mNotifier.notifyMessageWaitingChanged(this); } /*package*/ void notifyCallForwardingIndicator() { mNotifier.notifyCallForwardingChanged(this); } /** * Set a system property, unless we're in unit test mode */ /*package*/ void setSystemProperty(String property, String value) { if(getUnitTestMode()) { return; } SystemProperties.set(property, value); } public void registerForSuppServiceNotification( Handler h, int what, Object obj) { mSsnRegistrants.addUnique(h, what, obj); if (mSsnRegistrants.size() == 1) mCM.setSuppServiceNotifications(true, null); } public void unregisterForSuppServiceNotification(Handler h) { mSsnRegistrants.remove(h); if (mSsnRegistrants.size() == 0) mCM.setSuppServiceNotifications(false, null); } public void acceptCall() throws CallStateException { mCT.acceptCall(); } public void rejectCall() throws CallStateException { mCT.rejectCall(); } public void switchHoldingAndActive() throws CallStateException { mCT.switchWaitingOrHoldingAndActive(); } public boolean canConference() { return mCT.canConference(); } public boolean canDial() { return mCT.canDial(); } public void conference() throws CallStateException { mCT.conference(); } public void clearDisconnected() { mCT.clearDisconnected(); } public boolean canTransfer() { return mCT.canTransfer(); } public void explicitCallTransfer() throws CallStateException { mCT.explicitCallTransfer(); } public Call getForegroundCall() { return mCT.foregroundCall; } public Call getBackgroundCall() { return mCT.backgroundCall; } public Call getRingingCall() { return mCT.ringingCall; } private boolean handleCallDeflectionIncallSupplementaryService( String dialString) throws CallStateException { if (dialString.length() > 1) { return false; } if (getRingingCall().getState() != Call.State.IDLE) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 0: rejectCall"); try { mCT.rejectCall(); } catch (CallStateException e) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "reject failed", e); notifySuppServiceFailed(Phone.SuppService.REJECT); } } else if (getBackgroundCall().getState() != Call.State.IDLE) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 0: hangupWaitingOrBackground"); mCT.hangupWaitingOrBackground(); } return true; } private boolean handleCallWaitingIncallSupplementaryService( String dialString) throws CallStateException { int len = dialString.length(); if (len > 2) { return false; } GSMCall call = (GSMCall) getForegroundCall(); try { if (len > 1) { char ch = dialString.charAt(1); int callIndex = ch - '0'; if (callIndex >= 1 && callIndex <= CallTracker.MAX_CONNECTIONS) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 1: hangupConnectionByIndex " + callIndex); mCT.hangupConnectionByIndex(call, callIndex); } } else { if (call.getState() != Call.State.IDLE) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 1: hangup foreground"); //mCT.hangupForegroundResumeBackground(); mCT.hangup(call); } else { if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 1: switchWaitingOrHoldingAndActive"); mCT.switchWaitingOrHoldingAndActive(); } } } catch (CallStateException e) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "hangup failed", e); notifySuppServiceFailed(Phone.SuppService.HANGUP); } return true; } private boolean handleCallHoldIncallSupplementaryService(String dialString) throws CallStateException { int len = dialString.length(); if (len > 2) { return false; } GSMCall call = (GSMCall) getForegroundCall(); if (len > 1) { try { char ch = dialString.charAt(1); int callIndex = ch - '0'; GSMConnection conn = mCT.getConnectionByIndex(call, callIndex); // gsm index starts at 1, up to 5 connections in a call, if (conn != null && callIndex >= 1 && callIndex <= CallTracker.MAX_CONNECTIONS) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 2: separate call "+ callIndex); mCT.separate(conn); } else { if (LOCAL_DEBUG) Log.d(LOG_TAG, "separate: invalid call index "+ callIndex); notifySuppServiceFailed(Phone.SuppService.SEPARATE); } } catch (CallStateException e) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "separate failed", e); notifySuppServiceFailed(Phone.SuppService.SEPARATE); } } else { try { if (getRingingCall().getState() != Call.State.IDLE) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 2: accept ringing call"); mCT.acceptCall(); } else { if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 2: switchWaitingOrHoldingAndActive"); mCT.switchWaitingOrHoldingAndActive(); } } catch (CallStateException e) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "switch failed", e); notifySuppServiceFailed(Phone.SuppService.SWITCH); } } return true; } private boolean handleMultipartyIncallSupplementaryService( String dialString) throws CallStateException { if (dialString.length() > 1) { return false; } if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 3: merge calls"); try { conference(); } catch (CallStateException e) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "conference failed", e); notifySuppServiceFailed(Phone.SuppService.CONFERENCE); } return true; } private boolean handleEctIncallSupplementaryService(String dialString) throws CallStateException { int len = dialString.length(); if (len != 1) { return false; } if (LOCAL_DEBUG) Log.d(LOG_TAG, "MmiCode 4: explicit call transfer"); try { explicitCallTransfer(); } catch (CallStateException e) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "transfer failed", e); notifySuppServiceFailed(Phone.SuppService.TRANSFER); } return true; } private boolean handleCcbsIncallSupplementaryService(String dialString) throws CallStateException { if (dialString.length() > 1) { return false; } Log.i(LOG_TAG, "MmiCode 5: CCBS not supported!"); // Treat it as an "unknown" service. notifySuppServiceFailed(Phone.SuppService.UNKNOWN); return true; } public boolean handleInCallMmiCommands(String dialString) throws CallStateException { if (!isInCall()) { return false; } if (TextUtils.isEmpty(dialString)) { return false; } boolean result = false; char ch = dialString.charAt(0); switch (ch) { case '0': result = handleCallDeflectionIncallSupplementaryService( dialString); break; case '1': result = handleCallWaitingIncallSupplementaryService( dialString); break; case '2': result = handleCallHoldIncallSupplementaryService(dialString); break; case '3': result = handleMultipartyIncallSupplementaryService(dialString); break; case '4': result = handleEctIncallSupplementaryService(dialString); break; case '5': result = handleCcbsIncallSupplementaryService(dialString); break; default: break; } return result; } 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 Connection dial (String dialString) throws CallStateException { // Need to make sure dialString gets parsed properly String newDialString = PhoneNumberUtils.stripSeparators(dialString); // handle in-call MMI first if applicable if (handleInCallMmiCommands(newDialString)) { return null; } // Only look at the Network portion for mmi String networkPortion = PhoneNumberUtils.extractNetworkPortion(newDialString); GsmMmiCode mmi = GsmMmiCode.newFromDialString(networkPortion, this); if (LOCAL_DEBUG) Log.d(LOG_TAG, "dialing w/ mmi '" + mmi + "'..."); if (mmi == null) { return mCT.dial(newDialString); } else if (mmi.isTemporaryModeCLIR()) { return mCT.dial(mmi.dialingNumber, mmi.getCLIRMode()); } else { mPendingMMIs.add(mmi); mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null)); mmi.processCode(); // FIXME should this return null or something else? return null; } } public boolean handlePinMmi(String dialString) { GsmMmiCode mmi = GsmMmiCode.newFromDialString(dialString, this); if (mmi != null && mmi.isPinCommand()) { mPendingMMIs.add(mmi); mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null)); mmi.processCode(); return true; } return false; } public void sendUssdResponse(String ussdMessge) { GsmMmiCode mmi = GsmMmiCode.newFromUssdUserInput(ussdMessge, this); mPendingMMIs.add(mmi); mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null)); mmi.sendUssd(ussdMessge); } public void sendDtmf(char c) { if (!PhoneNumberUtils.is12Key(c)) { Log.e(LOG_TAG, "sendDtmf called with invalid character '" + c + "'"); } else { if (mCT.state == Phone.State.OFFHOOK) { mCM.sendDtmf(c, null); } } } public void startDtmf(char c) { if (!PhoneNumberUtils.is12Key(c)) { Log.e(LOG_TAG, "startDtmf called with invalid character '" + c + "'"); } else { mCM.startDtmf(c, null); } } public void stopDtmf() { mCM.stopDtmf(null); } public void setRadioPower(boolean power) { mSST.setRadioPower(power); } private void storeVoiceMailNumber(String number) { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences.Editor editor = sp.edit(); editor.putString(VM_NUMBER, number); editor.commit(); setVmSimImsi(getSubscriberId()); } public String getVoiceMailNumber() { // Read from the SIM. If its null, try reading from the shared preference area. String number = mSIMRecords.getVoiceMailNumber(); if (TextUtils.isEmpty(number)) { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); number = sp.getString(VM_NUMBER, null); } return number; } private String getVmSimImsi() { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); return sp.getString(VM_SIM_IMSI, null); } private void setVmSimImsi(String imsi) { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences.Editor editor = sp.edit(); editor.putString(VM_SIM_IMSI, imsi); editor.commit(); } public String getVoiceMailAlphaTag() { String ret; ret = mSIMRecords.getVoiceMailAlphaTag(); if (ret == null || ret.length() == 0) { return mContext.getText( com.android.internal.R.string.defaultVoiceMailAlphaTag).toString(); } return ret; } public String getDeviceId() { return mImei; } public String getDeviceSvn() { return mImeiSv; } public String getSubscriberId() { return mSIMRecords.imsi; } public String getSimSerialNumber() { return mSIMRecords.iccid; } public String getLine1Number() { return mSIMRecords.getMsisdnNumber(); } public String getLine1AlphaTag() { String ret; ret = mSIMRecords.getMsisdnAlphaTag(); if (ret == null || ret.length() == 0) { return mContext.getText( com.android.internal.R.string.defaultMsisdnAlphaTag).toString(); } return ret; } public void setLine1Number(String alphaTag, String number, Message onComplete) { mSIMRecords.setMsisdnNumber(alphaTag, number, onComplete); } public void setVoiceMailNumber(String alphaTag, String voiceMailNumber, Message onComplete) { Message resp; mVmNumber = voiceMailNumber; resp = h.obtainMessage(EVENT_SET_VM_NUMBER_DONE, 0, 0, onComplete); mSIMRecords.setVoiceMailNumber(alphaTag, mVmNumber, resp); } private boolean isValidCommandInterfaceCFReason (int commandInterfaceCFReason) { switch (commandInterfaceCFReason) { case CF_REASON_UNCONDITIONAL: case CF_REASON_BUSY: case CF_REASON_NO_REPLY: case CF_REASON_NOT_REACHABLE: case CF_REASON_ALL: case CF_REASON_ALL_CONDITIONAL: return true; default: return false; } } private boolean isValidCommandInterfaceCFAction (int commandInterfaceCFAction) { switch (commandInterfaceCFAction) { case CF_ACTION_DISABLE: case CF_ACTION_ENABLE: case CF_ACTION_REGISTRATION: case CF_ACTION_ERASURE: return true; default: return false; } } private boolean isCfEnable(int action) { return (action == CF_ACTION_ENABLE) || (action == CF_ACTION_REGISTRATION); } public void getCallForwardingOption(int commandInterfaceCFReason, Message onComplete) { if (isValidCommandInterfaceCFReason(commandInterfaceCFReason)) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "requesting call forwarding query."); Message resp; if (commandInterfaceCFReason == CF_REASON_UNCONDITIONAL) { resp = h.obtainMessage(EVENT_GET_CALL_FORWARD_DONE, onComplete); } else { resp = onComplete; } mCM.queryCallForwardStatus(commandInterfaceCFReason,0,null,resp); } } public void setCallForwardingOption(int commandInterfaceCFAction, int commandInterfaceCFReason, String dialingNumber, int timerSeconds, Message onComplete) { if ((isValidCommandInterfaceCFAction(commandInterfaceCFAction)) && (isValidCommandInterfaceCFReason(commandInterfaceCFReason))) { Message resp; if (commandInterfaceCFReason == CF_REASON_UNCONDITIONAL) { resp = h.obtainMessage(EVENT_SET_CALL_FORWARD_DONE, isCfEnable(commandInterfaceCFAction) ? 1 : 0, 0, onComplete); } else { resp = onComplete; } mCM.setCallForward(commandInterfaceCFAction, commandInterfaceCFReason, CommandsInterface.SERVICE_CLASS_VOICE, dialingNumber, timerSeconds, resp); } } public void getOutgoingCallerIdDisplay(Message onComplete) { mCM.getCLIR(onComplete); } public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode, Message onComplete) { mCM.setCLIR(commandInterfaceCLIRMode, h.obtainMessage(EVENT_SET_CLIR_COMPLETE, commandInterfaceCLIRMode, 0, onComplete)); } public void getCallWaiting(Message onComplete) { mCM.queryCallWaiting(CommandsInterface.SERVICE_CLASS_VOICE, onComplete); } public void setCallWaiting(boolean enable, Message onComplete) { mCM.setCallWaiting(enable, CommandsInterface.SERVICE_CLASS_VOICE, onComplete); } public boolean getSimRecordsLoaded() { return mSIMRecords.getRecordsLoaded(); } public SimCard getSimCard() { return mSimCard; } public void getAvailableNetworks(Message response) { mCM.getAvailableNetworks(response); } /** * Small container class used to hold information relevant to * the carrier selection process. operatorNumeric can be "" * if we are looking for automatic selection. */ private static class NetworkSelectMessage { public Message message; public String operatorNumeric; } public void setNetworkSelectionModeAutomatic(Message response) { // wrap the response message in our own message along with // an empty string (to indicate automatic selection) for the // operator's id. NetworkSelectMessage nsm = new NetworkSelectMessage(); nsm.message = response; nsm.operatorNumeric = ""; // get the message Message msg = h.obtainMessage(EVENT_SET_NETWORK_AUTOMATIC_COMPLETE, nsm); if (LOCAL_DEBUG) Log.d(LOG_TAG, "wrapping and sending message to connect automatically"); mCM.setNetworkSelectionModeAutomatic(msg); } public void selectNetworkManually(com.android.internal.telephony.gsm.NetworkInfo network, Message response) { // wrap the response message in our own message along with // the operator's id. NetworkSelectMessage nsm = new NetworkSelectMessage(); nsm.message = response; nsm.operatorNumeric = network.operatorNumeric; // get the message Message msg = h.obtainMessage(EVENT_SET_NETWORK_MANUAL_COMPLETE, nsm); mCM.setNetworkSelectionModeManual(network.operatorNumeric, msg); } /** * Method to retrieve the saved operator id from the Shared Preferences */ private String getSavedNetworkSelection() { // open the shared preferences and search with our key. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); return sp.getString(NETWORK_SELECTION_KEY, ""); } /** * Method to restore the previously saved operator id, or reset to * automatic selection, all depending upon the value in the shared * preferences. */ void restoreSavedNetworkSelection(Message response) { // retrieve the operator id String networkSelection = getSavedNetworkSelection(); // set to auto if the id is empty, otherwise select the network. if (TextUtils.isEmpty(networkSelection)) { mCM.setNetworkSelectionModeAutomatic(response); } else { mCM.setNetworkSelectionModeManual(networkSelection, response); } } public void setPreferredNetworkType(int networkType, Message response) { mCM.setPreferredNetworkType(networkType, response); } public void getPreferredNetworkType(Message response) { mCM.getPreferredNetworkType(response); } public void getNeighboringCids(Message response) { mCM.getNeighboringCids(response); } public void setOnPostDialCharacter(Handler h, int what, Object obj) { mPostDialHandler = new Registrant(h, what, obj); } public void setMute(boolean muted) { mCT.setMute(muted); } public boolean getMute() { return mCT.getMute(); } public void invokeOemRilRequestRaw(byte[] data, Message response) { mCM.invokeOemRilRequestRaw(data, response); } public void invokeOemRilRequestStrings(String[] strings, Message response) { mCM.invokeOemRilRequestStrings(strings, response); } public void getPdpContextList(Message response) { mCM.getPDPContextList(response); } public List<PdpConnection> getCurrentPdpList () { return mDataConnection.getAllPdps(); } /** * Disables the DNS check (i.e., allows "0.0.0.0"). * Useful for lab testing environment. * @param b true disables the check, false enables. */ public void disableDnsCheck(boolean b) { mDnsCheckDisabled = b; SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences.Editor editor = sp.edit(); editor.putBoolean(DNS_SERVER_CHECK_DISABLED_KEY, b); editor.commit(); } /** * Returns true if the DNS check is currently disabled. */ public boolean isDnsCheckDisabled() { return mDnsCheckDisabled; } public void updateServiceLocation(Message response) { mSST.getLacAndCid(response); } public void enableLocationUpdates() { mSST.enableLocationUpdates(); } public void disableLocationUpdates() { mSST.disableLocationUpdates(); } public void setBandMode(int bandMode, Message response) { mCM.setBandMode(bandMode, response); } public void queryAvailableBandMode(Message response) { mCM.queryAvailableBandMode(response); } public boolean getDataRoamingEnabled() { return mDataConnection.getDataOnRoamingEnabled(); } public void setDataRoamingEnabled(boolean enable) { mDataConnection.setDataOnRoamingEnabled(enable); } public boolean enableDataConnectivity() { return mDataConnection.setDataEnabled(true); } public int enableApnType(String type) { return mDataConnection.enableApnType(type); } public int disableApnType(String type) { return mDataConnection.disableApnType(type); } public boolean disableDataConnectivity() { return mDataConnection.setDataEnabled(false); } public String getInterfaceName(String apnType) { return mDataConnection.getInterfaceName(apnType); } public String getIpAddress(String apnType) { return mDataConnection.getIpAddress(apnType); } public String getGateway(String apnType) { return mDataConnection.getGateway(apnType); } public String[] getDnsServers(String apnType) { return mDataConnection.getDnsServers(apnType); } /** * The only circumstances under which we report that data connectivity is not * possible are * <ul> * <li>Data roaming is disallowed and we are roaming.</li> * <li>The current data state is {@code DISCONNECTED} for a reason other than * having explicitly disabled connectivity. In other words, data is not available * because the phone is out of coverage or some like reason.</li> * </ul> * @return {@code true} if data connectivity is possible, {@code false} otherwise. */ public boolean isDataConnectivityPossible() { // TODO: Currently checks if any GPRS connection is active. Should it only // check for "default"? boolean noData = mDataConnection.getDataEnabled() && getDataConnectionState() == DataState.DISCONNECTED; return !noData && getSimCard().getState() == SimCard.State.READY && getServiceState().getState() == ServiceState.STATE_IN_SERVICE && (mDataConnection.getDataOnRoamingEnabled() || !getServiceState().getRoaming()); } /** * Removes the given MMI from the pending list and notifies * registrants that it is complete. * @param mmi MMI that is done */ /*package*/ void onMMIDone(GsmMmiCode mmi) { /* Only notify complete if it's on the pending list. * Otherwise, it's already been handled (eg, previously canceled). * The exception is cancellation of an incoming USSD-REQUEST, which is * not on the list. */ if (mPendingMMIs.remove(mmi) || mmi.isUssdRequest()) { mMmiCompleteRegistrants.notifyRegistrants( new AsyncResult(null, mmi, null)); } } private void onNetworkInitiatedUssd(GsmMmiCode mmi) { mMmiCompleteRegistrants.notifyRegistrants( new AsyncResult(null, mmi, null)); } /** ussdMode is one of CommandsInterface.USSD_MODE_* */ private void onIncomingUSSD (int ussdMode, String ussdMessage) { boolean isUssdError; boolean isUssdRequest; isUssdRequest = (ussdMode == CommandsInterface.USSD_MODE_REQUEST); isUssdError = (ussdMode != CommandsInterface.USSD_MODE_NOTIFY && ussdMode != CommandsInterface.USSD_MODE_REQUEST); // See comments in GsmMmiCode.java // USSD requests aren't finished until one // of these two events happen GsmMmiCode found = null; for (int i = 0, s = mPendingMMIs.size() ; i < s; i++) { if(mPendingMMIs.get(i).isPendingUSSD()) { found = mPendingMMIs.get(i); break; } } if (found != null) { // Complete pending USSD if (isUssdError) { found.onUssdFinishedError(); } else { found.onUssdFinished(ussdMessage, isUssdRequest); } } else { // pending USSD not found // The network may initiate its own USSD request // ignore everything that isnt a Notify or a Request // also, discard if there is no message to present if (!isUssdError && ussdMessage != null) { GsmMmiCode mmi; mmi = GsmMmiCode.newNetworkInitiatedUssd(ussdMessage, isUssdRequest, GSMPhone.this); onNetworkInitiatedUssd(mmi); } } } /** * Make sure the network knows our preferred setting. */ private void syncClirSetting() { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); int clirSetting = sp.getInt(CLIR_KEY, -1); if (clirSetting >= 0) { mCM.setCLIR(clirSetting, null); } } //***** Inner Classes class MyHandler extends Handler { MyHandler() { } MyHandler(Looper l) { super(l); } public void handleMessage (Message msg) { AsyncResult ar; Message onComplete; switch (msg.what) { case EVENT_RADIO_AVAILABLE: { mCM.getBasebandVersion( obtainMessage(EVENT_GET_BASEBAND_VERSION_DONE)); mCM.getIMEI(obtainMessage(EVENT_GET_IMEI_DONE)); mCM.getIMEISV(obtainMessage(EVENT_GET_IMEISV_DONE)); } break; case EVENT_RADIO_ON: break; case EVENT_REGISTERED_TO_NETWORK: syncClirSetting(); break; case EVENT_SIM_RECORDS_LOADED: updateCurrentCarrierInProvider(); // Check if this is a different SIM than the previous one. If so unset the // voice mail number. String imsi = getVmSimImsi(); if (imsi != null && !getSubscriberId().equals(imsi)) { storeVoiceMailNumber(null); setVmSimImsi(null); } break; case EVENT_GET_BASEBAND_VERSION_DONE: ar = (AsyncResult)msg.obj; if (ar.exception != null) { break; } if (LOCAL_DEBUG) Log.d(LOG_TAG, "Baseband version: " + ar.result); setSystemProperty(PROPERTY_BASEBAND_VERSION, (String)ar.result); break; case EVENT_GET_IMEI_DONE: ar = (AsyncResult)msg.obj; if (ar.exception != null) { break; } mImei = (String)ar.result; break; case EVENT_GET_IMEISV_DONE: ar = (AsyncResult)msg.obj; if (ar.exception != null) { break; } mImeiSv = (String)ar.result; break; case EVENT_USSD: ar = (AsyncResult)msg.obj; String[] ussdResult = (String[]) ar.result; if (ussdResult.length > 1) { try { onIncomingUSSD(Integer.parseInt(ussdResult[0]), ussdResult[1]); } catch (NumberFormatException e) { Log.w(LOG_TAG, "error parsing USSD"); } } break; case EVENT_RADIO_OFF_OR_NOT_AVAILABLE: // Some MMI requests (eg USSD) are not completed // within the course of a CommandsInterface request // If the radio shuts off or resets while one of these // is pending, we need to clean up. for (int i = 0, s = mPendingMMIs.size() ; i < s; i++) { if (mPendingMMIs.get(i).isPendingUSSD()) { mPendingMMIs.get(i).onUssdFinishedError(); } } break; case EVENT_SSN: ar = (AsyncResult)msg.obj; SuppServiceNotification not = (SuppServiceNotification) ar.result; mSsnRegistrants.notifyRegistrants(ar); break; case EVENT_SET_CALL_FORWARD_DONE: ar = (AsyncResult)msg.obj; if (ar.exception == null) { mSIMRecords.setVoiceCallForwardingFlag(1, msg.arg1 == 1); } onComplete = (Message) ar.userObj; if (onComplete != null) { AsyncResult.forMessage(onComplete, ar.result, ar.exception); onComplete.sendToTarget(); } break; case EVENT_SET_VM_NUMBER_DONE: ar = (AsyncResult)msg.obj; if (SimVmNotSupportedException.class.isInstance(ar.exception)) { storeVoiceMailNumber(mVmNumber); ar.exception = null; } onComplete = (Message) ar.userObj; if (onComplete != null) { AsyncResult.forMessage(onComplete, ar.result, ar.exception); onComplete.sendToTarget(); } break; case EVENT_GET_CALL_FORWARD_DONE: ar = (AsyncResult)msg.obj; if (ar.exception == null) { handleCfuQueryResult((CallForwardInfo[])ar.result); } onComplete = (Message) ar.userObj; if (onComplete != null) { AsyncResult.forMessage(onComplete, ar.result, ar.exception); onComplete.sendToTarget(); } break; case EVENT_CALL_RING: ar = (AsyncResult)msg.obj; if (ar.exception == null) { notifyIncomingRing(); } break; // handle the select network completion callbacks. case EVENT_SET_NETWORK_MANUAL_COMPLETE: case EVENT_SET_NETWORK_AUTOMATIC_COMPLETE: handleSetSelectNetwork((AsyncResult) msg.obj); break; case EVENT_SET_CLIR_COMPLETE: ar = (AsyncResult)msg.obj; if (ar.exception == null) { saveClirSetting(msg.arg1); } onComplete = (Message) ar.userObj; if (onComplete != null) { AsyncResult.forMessage(onComplete, ar.result, ar.exception); onComplete.sendToTarget(); } break; } } } /** * Sets the "current" field in the telephony provider according to the SIM's operator * * @return true for success; false otherwise. */ boolean updateCurrentCarrierInProvider() { if (mSIMRecords != null) { try { Uri uri = Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "current"); ContentValues map = new ContentValues(); map.put(Telephony.Carriers.NUMERIC, mSIMRecords.getSIMOperatorNumeric()); mContext.getContentResolver().insert(uri, map); return true; } catch (SQLException e) { Log.e(LOG_TAG, "Can't store current operator", e); } } return false; } /** * Used to track the settings upon completion of the network change. */ private void handleSetSelectNetwork(AsyncResult ar) { // look for our wrapper within the asyncresult, skip the rest if it // is null. if (!(ar.userObj instanceof NetworkSelectMessage)) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "unexpected result from user object."); return; } NetworkSelectMessage nsm = (NetworkSelectMessage) ar.userObj; // found the object, now we send off the message we had originally // attached to the request. if (nsm.message != null) { if (LOCAL_DEBUG) Log.d(LOG_TAG, "sending original message to recipient"); AsyncResult.forMessage(nsm.message, ar.result, ar.exception); nsm.message.sendToTarget(); } // open the shared preferences editor, and write the value. // nsm.operatorNumeric is "" if we're in automatic.selection. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences.Editor editor = sp.edit(); editor.putString(NETWORK_SELECTION_KEY, nsm.operatorNumeric); // commit and log the result. if (! editor.commit()) { Log.e(LOG_TAG, "failed to commit network selection preference"); } } /** * Saves CLIR setting so that we can re-apply it as necessary * (in case the RIL resets it across reboots). */ /* package */ void saveClirSetting(int commandInterfaceCLIRMode) { // open the shared preferences editor, and write the value. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences.Editor editor = sp.edit(); editor.putInt(CLIR_KEY, commandInterfaceCLIRMode); // commit and log the result. if (! editor.commit()) { Log.e(LOG_TAG, "failed to commit CLIR preference"); } } private void handleCfuQueryResult(CallForwardInfo[] infos) { if (infos == null || infos.length == 0) { // Assume the default is not active // Set unconditional CFF in SIM to false mSIMRecords.setVoiceCallForwardingFlag(1, false); } else { for (int i = 0, s = infos.length; i < s; i++) { if ((infos[i].serviceClass & SERVICE_CLASS_VOICE) != 0) { mSIMRecords.setVoiceCallForwardingFlag(1, (infos[i].status == 1)); // should only have the one break; } } } } /** * simulateDataConnection * * simulates various data connection states. This messes with * DataConnectionTracker's internal states, but doesn't actually change * the underlying radio connection states. * * @param state Phone.DataState enum. */ public void simulateDataConnection(Phone.DataState state) { DataConnectionTracker.State dcState; switch (state) { case CONNECTED: dcState = DataConnectionTracker.State.CONNECTED; break; case SUSPENDED: dcState = DataConnectionTracker.State.CONNECTED; break; case DISCONNECTED: dcState = DataConnectionTracker.State.FAILED; break; default: dcState = DataConnectionTracker.State.CONNECTING; break; } mDataConnection.setState(dcState); notifyDataConnection(null); } }