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