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