Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.phone;
     18 
     19 import android.content.Intent;
     20 import android.net.Uri;
     21 import android.os.AsyncResult;
     22 import android.os.Binder;
     23 import android.os.Bundle;
     24 import android.os.Handler;
     25 import android.os.Looper;
     26 import android.os.Message;
     27 import android.os.ServiceManager;
     28 import android.telephony.NeighboringCellInfo;
     29 import android.telephony.ServiceState;
     30 import android.telephony.TelephonyManager;
     31 import android.text.TextUtils;
     32 import android.util.Log;
     33 
     34 import com.android.internal.telephony.DefaultPhoneNotifier;
     35 import com.android.internal.telephony.IccCard;
     36 import com.android.internal.telephony.ITelephony;
     37 import com.android.internal.telephony.Phone;
     38 
     39 import java.util.List;
     40 import java.util.ArrayList;
     41 
     42 /**
     43  * Implementation of the ITelephony interface.
     44  */
     45 public class PhoneInterfaceManager extends ITelephony.Stub {
     46     private static final String LOG_TAG = "PhoneInterfaceManager";
     47     private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
     48 
     49     // Message codes used with mMainThreadHandler
     50     private static final int CMD_HANDLE_PIN_MMI = 1;
     51     private static final int CMD_HANDLE_NEIGHBORING_CELL = 2;
     52     private static final int EVENT_NEIGHBORING_CELL_DONE = 3;
     53     private static final int CMD_ANSWER_RINGING_CALL = 4;
     54     private static final int CMD_END_CALL = 5;  // not used yet
     55     private static final int CMD_SILENCE_RINGER = 6;
     56 
     57     PhoneApp mApp;
     58     Phone mPhone;
     59     MainThreadHandler mMainThreadHandler;
     60 
     61     /**
     62      * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
     63      * request after sending. The main thread will notify the request when it is complete.
     64      */
     65     private static final class MainThreadRequest {
     66         /** The argument to use for the request */
     67         public Object argument;
     68         /** The result of the request that is run on the main thread */
     69         public Object result;
     70 
     71         public MainThreadRequest(Object argument) {
     72             this.argument = argument;
     73         }
     74     }
     75 
     76     /**
     77      * A handler that processes messages on the main thread in the phone process. Since many
     78      * of the Phone calls are not thread safe this is needed to shuttle the requests from the
     79      * inbound binder threads to the main thread in the phone process.  The Binder thread
     80      * may provide a {@link MainThreadRequest} object in the msg.obj field that they are waiting
     81      * on, which will be notified when the operation completes and will contain the result of the
     82      * request.
     83      *
     84      * <p>If a MainThreadRequest object is provided in the msg.obj field,
     85      * note that request.result must be set to something non-null for the calling thread to
     86      * unblock.
     87      */
     88     private final class MainThreadHandler extends Handler {
     89         @Override
     90         public void handleMessage(Message msg) {
     91             MainThreadRequest request;
     92             Message onCompleted;
     93             AsyncResult ar;
     94 
     95             switch (msg.what) {
     96                 case CMD_HANDLE_PIN_MMI:
     97                     request = (MainThreadRequest) msg.obj;
     98                     request.result = Boolean.valueOf(
     99                             mPhone.handlePinMmi((String) request.argument));
    100                     // Wake up the requesting thread
    101                     synchronized (request) {
    102                         request.notifyAll();
    103                     }
    104                     break;
    105 
    106                 case CMD_HANDLE_NEIGHBORING_CELL:
    107                     request = (MainThreadRequest) msg.obj;
    108                     onCompleted = obtainMessage(EVENT_NEIGHBORING_CELL_DONE,
    109                             request);
    110                     mPhone.getNeighboringCids(onCompleted);
    111                     break;
    112 
    113                 case EVENT_NEIGHBORING_CELL_DONE:
    114                     ar = (AsyncResult) msg.obj;
    115                     request = (MainThreadRequest) ar.userObj;
    116                     if (ar.exception == null && ar.result != null) {
    117                         request.result = ar.result;
    118                     } else {
    119                         // create an empty list to notify the waiting thread
    120                         request.result = new ArrayList<NeighboringCellInfo>();
    121                     }
    122                     // Wake up the requesting thread
    123                     synchronized (request) {
    124                         request.notifyAll();
    125                     }
    126                     break;
    127 
    128                 case CMD_ANSWER_RINGING_CALL:
    129                     answerRingingCallInternal();
    130                     break;
    131 
    132                 case CMD_SILENCE_RINGER:
    133                     silenceRingerInternal();
    134                     break;
    135 
    136                 case CMD_END_CALL:
    137                     request = (MainThreadRequest) msg.obj;
    138                     boolean hungUp = false;
    139                     int phoneType = mPhone.getPhoneType();
    140                     if (phoneType == Phone.PHONE_TYPE_CDMA) {
    141                         // CDMA: If the user presses the Power button we treat it as
    142                         // ending the complete call session
    143                         hungUp = PhoneUtils.hangupRingingAndActive(mPhone);
    144                     } else if (phoneType == Phone.PHONE_TYPE_GSM) {
    145                         // GSM: End the call as per the Phone state
    146                         hungUp = PhoneUtils.hangup(mPhone);
    147                     } else {
    148                         throw new IllegalStateException("Unexpected phone type: " + phoneType);
    149                     }
    150                     if (DBG) log("CMD_END_CALL: " + (hungUp ? "hung up!" : "no call to hang up"));
    151                     request.result = hungUp;
    152                     // Wake up the requesting thread
    153                     synchronized (request) {
    154                         request.notifyAll();
    155                     }
    156                     break;
    157 
    158                 default:
    159                     Log.w(LOG_TAG, "MainThreadHandler: unexpected message code: " + msg.what);
    160                     break;
    161             }
    162         }
    163     }
    164 
    165     /**
    166      * Posts the specified command to be executed on the main thread,
    167      * waits for the request to complete, and returns the result.
    168      * @see sendRequestAsync
    169      */
    170     private Object sendRequest(int command, Object argument) {
    171         if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
    172             throw new RuntimeException("This method will deadlock if called from the main thread.");
    173         }
    174 
    175         MainThreadRequest request = new MainThreadRequest(argument);
    176         Message msg = mMainThreadHandler.obtainMessage(command, request);
    177         msg.sendToTarget();
    178 
    179         // Wait for the request to complete
    180         synchronized (request) {
    181             while (request.result == null) {
    182                 try {
    183                     request.wait();
    184                 } catch (InterruptedException e) {
    185                     // Do nothing, go back and wait until the request is complete
    186                 }
    187             }
    188         }
    189         return request.result;
    190     }
    191 
    192     /**
    193      * Asynchronous ("fire and forget") version of sendRequest():
    194      * Posts the specified command to be executed on the main thread, and
    195      * returns immediately.
    196      * @see sendRequest
    197      */
    198     private void sendRequestAsync(int command) {
    199         mMainThreadHandler.sendEmptyMessage(command);
    200     }
    201 
    202     public PhoneInterfaceManager(PhoneApp app, Phone phone) {
    203         mApp = app;
    204         mPhone = phone;
    205         mMainThreadHandler = new MainThreadHandler();
    206         publish();
    207     }
    208 
    209     private void publish() {
    210         if (DBG) log("publish: " + this);
    211 
    212         ServiceManager.addService("phone", this);
    213     }
    214 
    215     //
    216     // Implementation of the ITelephony interface.
    217     //
    218 
    219     public void dial(String number) {
    220         if (DBG) log("dial: " + number);
    221         // No permission check needed here: This is just a wrapper around the
    222         // ACTION_DIAL intent, which is available to any app since it puts up
    223         // the UI before it does anything.
    224 
    225         String url = createTelUrl(number);
    226         if (url == null) {
    227             return;
    228         }
    229 
    230         // PENDING: should we just silently fail if phone is offhook or ringing?
    231         Phone.State state = mPhone.getState();
    232         if (state != Phone.State.OFFHOOK && state != Phone.State.RINGING) {
    233             Intent  intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
    234             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    235             mApp.startActivity(intent);
    236         }
    237     }
    238 
    239     public void call(String number) {
    240         if (DBG) log("call: " + number);
    241 
    242         // This is just a wrapper around the ACTION_CALL intent, but we still
    243         // need to do a permission check since we're calling startActivity()
    244         // from the context of the phone app.
    245         enforceCallPermission();
    246 
    247         String url = createTelUrl(number);
    248         if (url == null) {
    249             return;
    250         }
    251 
    252         Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(url));
    253         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    254         intent.setClassName(mApp, PhoneApp.getCallScreenClassName());
    255         mApp.startActivity(intent);
    256     }
    257 
    258     private boolean showCallScreenInternal(boolean specifyInitialDialpadState,
    259                                            boolean initialDialpadState) {
    260         if (isIdle()) {
    261             return false;
    262         }
    263         // If the phone isn't idle then go to the in-call screen
    264         long callingId = Binder.clearCallingIdentity();
    265         try {
    266             Intent intent;
    267             if (specifyInitialDialpadState) {
    268                 intent = PhoneApp.createInCallIntent(initialDialpadState);
    269             } else {
    270                 intent = PhoneApp.createInCallIntent();
    271             }
    272             mApp.startActivity(intent);
    273         } finally {
    274             Binder.restoreCallingIdentity(callingId);
    275         }
    276         return true;
    277     }
    278 
    279     // Show the in-call screen without specifying the initial dialpad state.
    280     public boolean showCallScreen() {
    281         return showCallScreenInternal(false, false);
    282     }
    283 
    284     // The variation of showCallScreen() that specifies the initial dialpad state.
    285     // (Ideally this would be called showCallScreen() too, just with a different
    286     // signature, but AIDL doesn't allow that.)
    287     public boolean showCallScreenWithDialpad(boolean showDialpad) {
    288         return showCallScreenInternal(true, showDialpad);
    289     }
    290 
    291     /**
    292      * End a call based on call state
    293      * @return true is a call was ended
    294      */
    295     public boolean endCall() {
    296         enforceCallPermission();
    297         return (Boolean) sendRequest(CMD_END_CALL, null);
    298     }
    299 
    300     public void answerRingingCall() {
    301         if (DBG) log("answerRingingCall...");
    302         // TODO: there should eventually be a separate "ANSWER_PHONE" permission,
    303         // but that can probably wait till the big TelephonyManager API overhaul.
    304         // For now, protect this call with the MODIFY_PHONE_STATE permission.
    305         enforceModifyPermission();
    306         sendRequestAsync(CMD_ANSWER_RINGING_CALL);
    307     }
    308 
    309     /**
    310      * Make the actual telephony calls to implement answerRingingCall().
    311      * This should only be called from the main thread of the Phone app.
    312      * @see answerRingingCall
    313      *
    314      * TODO: it would be nice to return true if we answered the call, or
    315      * false if there wasn't actually a ringing incoming call, or some
    316      * other error occurred.  (In other words, pass back the return value
    317      * from PhoneUtils.answerCall() or PhoneUtils.answerAndEndActive().)
    318      * But that would require calling this method via sendRequest() rather
    319      * than sendRequestAsync(), and right now we don't actually *need* that
    320      * return value, so let's just return void for now.
    321      */
    322     private void answerRingingCallInternal() {
    323         final boolean hasRingingCall = !mPhone.getRingingCall().isIdle();
    324         if (hasRingingCall) {
    325             final boolean hasActiveCall = !mPhone.getForegroundCall().isIdle();
    326             final boolean hasHoldingCall = !mPhone.getBackgroundCall().isIdle();
    327             if (hasActiveCall && hasHoldingCall) {
    328                 // Both lines are in use!
    329                 // TODO: provide a flag to let the caller specify what
    330                 // policy to use if both lines are in use.  (The current
    331                 // behavior is hardwired to "answer incoming, end ongoing",
    332                 // which is how the CALL button is specced to behave.)
    333                 PhoneUtils.answerAndEndActive(mPhone);
    334                 return;
    335             } else {
    336                 // answerCall() will automatically hold the current active
    337                 // call, if there is one.
    338                 PhoneUtils.answerCall(mPhone);
    339                 return;
    340             }
    341         } else {
    342             // No call was ringing.
    343             return;
    344         }
    345     }
    346 
    347     public void silenceRinger() {
    348         if (DBG) log("silenceRinger...");
    349         // TODO: find a more appropriate permission to check here.
    350         // (That can probably wait till the big TelephonyManager API overhaul.
    351         // For now, protect this call with the MODIFY_PHONE_STATE permission.)
    352         enforceModifyPermission();
    353         sendRequestAsync(CMD_SILENCE_RINGER);
    354     }
    355 
    356     /**
    357      * Internal implemenation of silenceRinger().
    358      * This should only be called from the main thread of the Phone app.
    359      * @see silenceRinger
    360      */
    361     private void silenceRingerInternal() {
    362         if ((mPhone.getState() == Phone.State.RINGING)
    363             && mApp.notifier.isRinging()) {
    364             // Ringer is actually playing, so silence it.
    365             if (DBG) log("silenceRingerInternal: silencing...");
    366             PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_IDLE);
    367             mApp.notifier.silenceRinger();
    368         }
    369     }
    370 
    371     public boolean isOffhook() {
    372         return (mPhone.getState() == Phone.State.OFFHOOK);
    373     }
    374 
    375     public boolean isRinging() {
    376         return (mPhone.getState() == Phone.State.RINGING);
    377     }
    378 
    379     public boolean isIdle() {
    380         return (mPhone.getState() == Phone.State.IDLE);
    381     }
    382 
    383     public boolean isSimPinEnabled() {
    384         enforceReadPermission();
    385         return (PhoneApp.getInstance().isSimPinEnabled());
    386     }
    387 
    388     public boolean supplyPin(String pin) {
    389         enforceModifyPermission();
    390         final CheckSimPin checkSimPin = new CheckSimPin(mPhone.getIccCard());
    391         checkSimPin.start();
    392         return checkSimPin.checkPin(pin);
    393     }
    394 
    395     /**
    396      * Helper thread to turn async call to {@link SimCard#supplyPin} into
    397      * a synchronous one.
    398      */
    399     private static class CheckSimPin extends Thread {
    400 
    401         private final IccCard mSimCard;
    402 
    403         private boolean mDone = false;
    404         private boolean mResult = false;
    405 
    406         // For replies from SimCard interface
    407         private Handler mHandler;
    408 
    409         // For async handler to identify request type
    410         private static final int SUPPLY_PIN_COMPLETE = 100;
    411 
    412         public CheckSimPin(IccCard simCard) {
    413             mSimCard = simCard;
    414         }
    415 
    416         @Override
    417         public void run() {
    418             Looper.prepare();
    419             synchronized (CheckSimPin.this) {
    420                 mHandler = new Handler() {
    421                     @Override
    422                     public void handleMessage(Message msg) {
    423                         AsyncResult ar = (AsyncResult) msg.obj;
    424                         switch (msg.what) {
    425                             case SUPPLY_PIN_COMPLETE:
    426                                 Log.d(LOG_TAG, "SUPPLY_PIN_COMPLETE");
    427                                 synchronized (CheckSimPin.this) {
    428                                     mResult = (ar.exception == null);
    429                                     mDone = true;
    430                                     CheckSimPin.this.notifyAll();
    431                                 }
    432                                 break;
    433                         }
    434                     }
    435                 };
    436                 CheckSimPin.this.notifyAll();
    437             }
    438             Looper.loop();
    439         }
    440 
    441         synchronized boolean checkPin(String pin) {
    442 
    443             while (mHandler == null) {
    444                 try {
    445                     wait();
    446                 } catch (InterruptedException e) {
    447                     Thread.currentThread().interrupt();
    448                 }
    449             }
    450             Message callback = Message.obtain(mHandler, SUPPLY_PIN_COMPLETE);
    451 
    452             mSimCard.supplyPin(pin, callback);
    453 
    454             while (!mDone) {
    455                 try {
    456                     Log.d(LOG_TAG, "wait for done");
    457                     wait();
    458                 } catch (InterruptedException e) {
    459                     // Restore the interrupted status
    460                     Thread.currentThread().interrupt();
    461                 }
    462             }
    463             Log.d(LOG_TAG, "done");
    464             return mResult;
    465         }
    466     }
    467 
    468     public void updateServiceLocation() {
    469         // No permission check needed here: this call is harmless, and it's
    470         // needed for the ServiceState.requestStateUpdate() call (which is
    471         // already intentionally exposed to 3rd parties.)
    472         mPhone.updateServiceLocation();
    473     }
    474 
    475     public boolean isRadioOn() {
    476         return mPhone.getServiceState().getState() != ServiceState.STATE_POWER_OFF;
    477     }
    478 
    479     public void toggleRadioOnOff() {
    480         enforceModifyPermission();
    481         mPhone.setRadioPower(!isRadioOn());
    482     }
    483     public boolean setRadio(boolean turnOn) {
    484         enforceModifyPermission();
    485         if ((mPhone.getServiceState().getState() != ServiceState.STATE_POWER_OFF) != turnOn) {
    486             toggleRadioOnOff();
    487         }
    488         return true;
    489     }
    490 
    491     public boolean enableDataConnectivity() {
    492         enforceModifyPermission();
    493         return mPhone.enableDataConnectivity();
    494     }
    495 
    496     public int enableApnType(String type) {
    497         enforceModifyPermission();
    498         return mPhone.enableApnType(type);
    499     }
    500 
    501     public int disableApnType(String type) {
    502         enforceModifyPermission();
    503         return mPhone.disableApnType(type);
    504     }
    505 
    506     public boolean disableDataConnectivity() {
    507         enforceModifyPermission();
    508         return mPhone.disableDataConnectivity();
    509     }
    510 
    511     public boolean isDataConnectivityPossible() {
    512         return mPhone.isDataConnectivityPossible();
    513     }
    514 
    515     public boolean handlePinMmi(String dialString) {
    516         enforceModifyPermission();
    517         return (Boolean) sendRequest(CMD_HANDLE_PIN_MMI, dialString);
    518     }
    519 
    520     public void cancelMissedCallsNotification() {
    521         enforceModifyPermission();
    522         NotificationMgr.getDefault().cancelMissedCallNotification();
    523     }
    524 
    525     public int getCallState() {
    526         return DefaultPhoneNotifier.convertCallState(mPhone.getState());
    527     }
    528 
    529     public int getDataState() {
    530         return DefaultPhoneNotifier.convertDataState(mPhone.getDataConnectionState());
    531     }
    532 
    533     public int getDataActivity() {
    534         return DefaultPhoneNotifier.convertDataActivityState(mPhone.getDataActivityState());
    535     }
    536 
    537     public Bundle getCellLocation() {
    538         try {
    539             mApp.enforceCallingOrSelfPermission(
    540                 android.Manifest.permission.ACCESS_FINE_LOCATION, null);
    541         } catch (SecurityException e) {
    542             // If we have ACCESS_FINE_LOCATION permission, skip the check for ACCESS_COARSE_LOCATION
    543             // A failure should throw the SecurityException from ACCESS_COARSE_LOCATION since this
    544             // is the weaker precondition
    545             mApp.enforceCallingOrSelfPermission(
    546                 android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
    547         }
    548         Bundle data = new Bundle();
    549         mPhone.getCellLocation().fillInNotifierBundle(data);
    550         return data;
    551     }
    552 
    553     public void enableLocationUpdates() {
    554         mApp.enforceCallingOrSelfPermission(
    555                 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
    556         mPhone.enableLocationUpdates();
    557     }
    558 
    559     public void disableLocationUpdates() {
    560         mApp.enforceCallingOrSelfPermission(
    561                 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
    562         mPhone.disableLocationUpdates();
    563     }
    564 
    565     @SuppressWarnings("unchecked")
    566     public List<NeighboringCellInfo> getNeighboringCellInfo() {
    567         try {
    568             mApp.enforceCallingOrSelfPermission(
    569                     android.Manifest.permission.ACCESS_FINE_LOCATION, null);
    570         } catch (SecurityException e) {
    571             // If we have ACCESS_FINE_LOCATION permission, skip the check
    572             // for ACCESS_COARSE_LOCATION
    573             // A failure should throw the SecurityException from
    574             // ACCESS_COARSE_LOCATION since this is the weaker precondition
    575             mApp.enforceCallingOrSelfPermission(
    576                     android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
    577         }
    578 
    579         ArrayList<NeighboringCellInfo> cells = null;
    580 
    581         try {
    582             cells = (ArrayList<NeighboringCellInfo>) sendRequest(
    583                     CMD_HANDLE_NEIGHBORING_CELL, null);
    584         } catch (RuntimeException e) {
    585             Log.e(LOG_TAG, "getNeighboringCellInfo " + e);
    586         }
    587 
    588         return (List <NeighboringCellInfo>) cells;
    589     }
    590 
    591 
    592     //
    593     // Internal helper methods.
    594     //
    595 
    596     /**
    597      * Make sure the caller has the READ_PHONE_STATE permission.
    598      *
    599      * @throws SecurityException if the caller does not have the required permission
    600      */
    601     private void enforceReadPermission() {
    602         mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE, null);
    603     }
    604 
    605     /**
    606      * Make sure the caller has the MODIFY_PHONE_STATE permission.
    607      *
    608      * @throws SecurityException if the caller does not have the required permission
    609      */
    610     private void enforceModifyPermission() {
    611         mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
    612     }
    613 
    614     /**
    615      * Make sure the caller has the CALL_PHONE permission.
    616      *
    617      * @throws SecurityException if the caller does not have the required permission
    618      */
    619     private void enforceCallPermission() {
    620         mApp.enforceCallingOrSelfPermission(android.Manifest.permission.CALL_PHONE, null);
    621     }
    622 
    623 
    624     private String createTelUrl(String number) {
    625         if (TextUtils.isEmpty(number)) {
    626             return null;
    627         }
    628 
    629         StringBuilder buf = new StringBuilder("tel:");
    630         buf.append(number);
    631         return buf.toString();
    632     }
    633 
    634     private void log(String msg) {
    635         Log.d(LOG_TAG, "[PhoneIntfMgr] " + msg);
    636     }
    637 
    638     public int getActivePhoneType() {
    639         return mPhone.getPhoneType();
    640     }
    641 
    642     /**
    643      * Returns the CDMA ERI icon index to display
    644      */
    645     public int getCdmaEriIconIndex() {
    646         return mPhone.getCdmaEriIconIndex();
    647     }
    648 
    649     /**
    650      * Returns the CDMA ERI icon mode,
    651      * 0 - ON
    652      * 1 - FLASHING
    653      */
    654     public int getCdmaEriIconMode() {
    655         return mPhone.getCdmaEriIconMode();
    656     }
    657 
    658     /**
    659      * Returns the CDMA ERI text,
    660      */
    661     public String getCdmaEriText() {
    662         return mPhone.getCdmaEriText();
    663     }
    664 
    665     /**
    666      * Returns true if CDMA provisioning needs to run.
    667      */
    668     public boolean getCdmaNeedsProvisioning() {
    669         if (getActivePhoneType() == Phone.PHONE_TYPE_GSM) {
    670             return false;
    671         }
    672 
    673         boolean needsProvisioning = false;
    674         String cdmaMin = mPhone.getCdmaMin();
    675         try {
    676             needsProvisioning = OtaUtils.needsActivation(cdmaMin);
    677         } catch (IllegalArgumentException e) {
    678             // shouldn't get here unless hardware is misconfigured
    679             Log.e(LOG_TAG, "CDMA MIN string " + ((cdmaMin == null) ? "was null" : "was too short"));
    680         }
    681         return needsProvisioning;
    682     }
    683 
    684     /**
    685      * Returns the unread count of voicemails
    686      */
    687     public int getVoiceMessageCount() {
    688         return mPhone.getVoiceMessageCount();
    689     }
    690 
    691     /**
    692      * Returns the network type
    693      */
    694     public int getNetworkType() {
    695         int radiotech = mPhone.getServiceState().getRadioTechnology();
    696         switch(radiotech) {
    697             case ServiceState.RADIO_TECHNOLOGY_GPRS:
    698                 return TelephonyManager.NETWORK_TYPE_GPRS;
    699             case ServiceState.RADIO_TECHNOLOGY_EDGE:
    700                 return TelephonyManager.NETWORK_TYPE_EDGE;
    701             case ServiceState.RADIO_TECHNOLOGY_UMTS:
    702                 return TelephonyManager.NETWORK_TYPE_UMTS;
    703             case ServiceState.RADIO_TECHNOLOGY_HSDPA:
    704                 return TelephonyManager.NETWORK_TYPE_HSDPA;
    705             case ServiceState.RADIO_TECHNOLOGY_HSUPA:
    706                 return TelephonyManager.NETWORK_TYPE_HSUPA;
    707             case ServiceState.RADIO_TECHNOLOGY_HSPA:
    708                 return TelephonyManager.NETWORK_TYPE_HSPA;
    709             case ServiceState.RADIO_TECHNOLOGY_IS95A:
    710             case ServiceState.RADIO_TECHNOLOGY_IS95B:
    711                 return TelephonyManager.NETWORK_TYPE_CDMA;
    712             case ServiceState.RADIO_TECHNOLOGY_1xRTT:
    713                 return TelephonyManager.NETWORK_TYPE_1xRTT;
    714             case ServiceState.RADIO_TECHNOLOGY_EVDO_0:
    715                 return TelephonyManager.NETWORK_TYPE_EVDO_0;
    716             case ServiceState.RADIO_TECHNOLOGY_EVDO_A:
    717                 return TelephonyManager.NETWORK_TYPE_EVDO_A;
    718             default:
    719                 return TelephonyManager.NETWORK_TYPE_UNKNOWN;
    720         }
    721     }
    722 
    723     /**
    724      * @return true if a ICC card is present
    725      */
    726     public boolean hasIccCard() {
    727         return mPhone.getIccCard().hasIccCard();
    728     }
    729 }
    730