• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
MainThreadRequest(Object argument)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
handleMessage(Message msg)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      */
sendRequest(int command, Object argument)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      */
sendRequestAsync(int command)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      */
init(PhoneApp app, Phone phone)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() */
PhoneInterfaceManager(PhoneApp app, Phone phone)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 
publish()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 
dial(String number)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 
call(String number)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 
showCallScreenInternal(boolean specifyInitialDialpadState, boolean initialDialpadState)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.
showCallScreen()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.)
showCallScreenWithDialpad(boolean showDialpad)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      */
endCall()333     public boolean endCall() {
334         enforceCallPermission();
335         return (Boolean) sendRequest(CMD_END_CALL, null);
336     }
337 
answerRingingCall()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      */
answerRingingCallInternal()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 
silenceRinger()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      */
silenceRingerInternal()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 
isOffhook()408     public boolean isOffhook() {
409         return (mCM.getState() == Phone.State.OFFHOOK);
410     }
411 
isRinging()412     public boolean isRinging() {
413         return (mCM.getState() == Phone.State.RINGING);
414     }
415 
isIdle()416     public boolean isIdle() {
417         return (mCM.getState() == Phone.State.IDLE);
418     }
419 
isSimPinEnabled()420     public boolean isSimPinEnabled() {
421         enforceReadPermission();
422         return (PhoneApp.getInstance().isSimPinEnabled());
423     }
424 
supplyPin(String pin)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 
supplyPuk(String puk, String pin)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 
UnlockSim(IccCard simCard)456         public UnlockSim(IccCard simCard) {
457             mSimCard = simCard;
458         }
459 
460         @Override
run()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          */
unlockSim(String puk, String pin)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 
updateServiceLocation()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 
isRadioOn()530     public boolean isRadioOn() {
531         return mPhone.getServiceState().getState() != ServiceState.STATE_POWER_OFF;
532     }
533 
toggleRadioOnOff()534     public void toggleRadioOnOff() {
535         enforceModifyPermission();
536         mPhone.setRadioPower(!isRadioOn());
537     }
setRadio(boolean turnOn)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 
enableDataConnectivity()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 
enableApnType(String type)554     public int enableApnType(String type) {
555         enforceModifyPermission();
556         return mPhone.enableApnType(type);
557     }
558 
disableApnType(String type)559     public int disableApnType(String type) {
560         enforceModifyPermission();
561         return mPhone.disableApnType(type);
562     }
563 
disableDataConnectivity()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 
isDataConnectivityPossible()572     public boolean isDataConnectivityPossible() {
573         return mPhone.isDataConnectivityPossible();
574     }
575 
handlePinMmi(String dialString)576     public boolean handlePinMmi(String dialString) {
577         enforceModifyPermission();
578         return (Boolean) sendRequest(CMD_HANDLE_PIN_MMI, dialString);
579     }
580 
cancelMissedCallsNotification()581     public void cancelMissedCallsNotification() {
582         enforceModifyPermission();
583         mApp.notificationMgr.cancelMissedCallNotification();
584     }
585 
getCallState()586     public int getCallState() {
587         return DefaultPhoneNotifier.convertCallState(mCM.getState());
588     }
589 
getDataState()590     public int getDataState() {
591         return DefaultPhoneNotifier.convertDataState(mPhone.getDataConnectionState());
592     }
593 
getDataActivity()594     public int getDataActivity() {
595         return DefaultPhoneNotifier.convertDataActivityState(mPhone.getDataActivityState());
596     }
597 
getCellLocation()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 
enableLocationUpdates()614     public void enableLocationUpdates() {
615         mApp.enforceCallingOrSelfPermission(
616                 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
617         mPhone.enableLocationUpdates();
618     }
619 
disableLocationUpdates()620     public void disableLocationUpdates() {
621         mApp.enforceCallingOrSelfPermission(
622                 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
623         mPhone.disableLocationUpdates();
624     }
625 
626     @SuppressWarnings("unchecked")
getNeighboringCellInfo()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 
getAllCellInfo()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      */
enforceReadPermission()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      */
enforceModifyPermission()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      */
enforceCallPermission()696     private void enforceCallPermission() {
697         mApp.enforceCallingOrSelfPermission(android.Manifest.permission.CALL_PHONE, null);
698     }
699 
700 
createTelUrl(String number)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 
log(String msg)711     private void log(String msg) {
712         Log.d(LOG_TAG, "[PhoneIntfMgr] " + msg);
713     }
714 
getActivePhoneType()715     public int getActivePhoneType() {
716         return mPhone.getPhoneType();
717     }
718 
719     /**
720      * Returns the CDMA ERI icon index to display
721      */
getCdmaEriIconIndex()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      */
getCdmaEriIconMode()731     public int getCdmaEriIconMode() {
732         return mPhone.getCdmaEriIconMode();
733     }
734 
735     /**
736      * Returns the CDMA ERI text,
737      */
getCdmaEriText()738     public String getCdmaEriText() {
739         return mPhone.getCdmaEriText();
740     }
741 
742     /**
743      * Returns true if CDMA provisioning needs to run.
744      */
needsOtaServiceProvisioning()745     public boolean needsOtaServiceProvisioning() {
746         return mPhone.needsOtaServiceProvisioning();
747     }
748 
749     /**
750      * Returns the unread count of voicemails
751      */
getVoiceMessageCount()752     public int getVoiceMessageCount() {
753         return mPhone.getVoiceMessageCount();
754     }
755 
756     /**
757      * Returns the network type
758      */
getNetworkType()759     public int getNetworkType() {
760         return mPhone.getServiceState().getNetworkType();
761     }
762 
763     /**
764      * @return true if a ICC card is present
765      */
hasIccCard()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      */
getLteOnCdmaMode()778     public int getLteOnCdmaMode() {
779         return mPhone.getLteOnCdmaMode();
780     }
781 }
782