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