• 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.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 
MainThreadRequest(Object argument)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
handleMessage(Message msg)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      */
sendRequest(int command, Object argument)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      */
sendRequestAsync(int command)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      */
init(PhoneApp app, Phone phone)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() */
PhoneInterfaceManager(PhoneApp app, Phone phone)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 
publish()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 
dial(String number)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 
call(String number)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 
showCallScreenInternal(boolean specifyInitialDialpadState, boolean initialDialpadState)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.
showCallScreen()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.)
showCallScreenWithDialpad(boolean showDialpad)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      */
endCall()332     public boolean endCall() {
333         enforceCallPermission();
334         return (Boolean) sendRequest(CMD_END_CALL, null);
335     }
336 
answerRingingCall()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      */
answerRingingCallInternal()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 
silenceRinger()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      */
silenceRingerInternal()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 
isOffhook()407     public boolean isOffhook() {
408         return (mPhone.getState() == Phone.State.OFFHOOK);
409     }
410 
isRinging()411     public boolean isRinging() {
412         return (mPhone.getState() == Phone.State.RINGING);
413     }
414 
isIdle()415     public boolean isIdle() {
416         return (mPhone.getState() == Phone.State.IDLE);
417     }
418 
isSimPinEnabled()419     public boolean isSimPinEnabled() {
420         enforceReadPermission();
421         return (PhoneApp.getInstance().isSimPinEnabled());
422     }
423 
supplyPin(String pin)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 
supplyPuk(String puk, String pin)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 
UnlockSim(IccCard simCard)455         public UnlockSim(IccCard simCard) {
456             mSimCard = simCard;
457         }
458 
459         @Override
run()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          */
unlockSim(String puk, String pin)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 
updateServiceLocation()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 
isRadioOn()529     public boolean isRadioOn() {
530         return mPhone.getServiceState().getState() != ServiceState.STATE_POWER_OFF;
531     }
532 
toggleRadioOnOff()533     public void toggleRadioOnOff() {
534         enforceModifyPermission();
535         mPhone.setRadioPower(!isRadioOn());
536     }
setRadio(boolean turnOn)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 
enableDataConnectivity()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 
enableApnType(String type)553     public int enableApnType(String type) {
554         enforceModifyPermission();
555         return mPhone.enableApnType(type);
556     }
557 
disableApnType(String type)558     public int disableApnType(String type) {
559         enforceModifyPermission();
560         return mPhone.disableApnType(type);
561     }
562 
disableDataConnectivity()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 
isDataConnectivityPossible()571     public boolean isDataConnectivityPossible() {
572         return mPhone.isDataConnectivityPossible();
573     }
574 
handlePinMmi(String dialString)575     public boolean handlePinMmi(String dialString) {
576         enforceModifyPermission();
577         return (Boolean) sendRequest(CMD_HANDLE_PIN_MMI, dialString);
578     }
579 
cancelMissedCallsNotification()580     public void cancelMissedCallsNotification() {
581         enforceModifyPermission();
582         mApp.notificationMgr.cancelMissedCallNotification();
583     }
584 
getCallState()585     public int getCallState() {
586         return DefaultPhoneNotifier.convertCallState(mPhone.getState());
587     }
588 
getDataState()589     public int getDataState() {
590         return DefaultPhoneNotifier.convertDataState(mPhone.getDataConnectionState());
591     }
592 
getDataActivity()593     public int getDataActivity() {
594         return DefaultPhoneNotifier.convertDataActivityState(mPhone.getDataActivityState());
595     }
596 
getCellLocation()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 
enableLocationUpdates()613     public void enableLocationUpdates() {
614         mApp.enforceCallingOrSelfPermission(
615                 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
616         mPhone.enableLocationUpdates();
617     }
618 
disableLocationUpdates()619     public void disableLocationUpdates() {
620         mApp.enforceCallingOrSelfPermission(
621                 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
622         mPhone.disableLocationUpdates();
623     }
624 
625     @SuppressWarnings("unchecked")
getNeighboringCellInfo()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      */
enforceReadPermission()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      */
enforceModifyPermission()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      */
enforceCallPermission()679     private void enforceCallPermission() {
680         mApp.enforceCallingOrSelfPermission(android.Manifest.permission.CALL_PHONE, null);
681     }
682 
683 
createTelUrl(String number)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 
log(String msg)694     private void log(String msg) {
695         Log.d(LOG_TAG, "[PhoneIntfMgr] " + msg);
696     }
697 
getActivePhoneType()698     public int getActivePhoneType() {
699         return mPhone.getPhoneType();
700     }
701 
702     /**
703      * Returns the CDMA ERI icon index to display
704      */
getCdmaEriIconIndex()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      */
getCdmaEriIconMode()714     public int getCdmaEriIconMode() {
715         return mPhone.getCdmaEriIconMode();
716     }
717 
718     /**
719      * Returns the CDMA ERI text,
720      */
getCdmaEriText()721     public String getCdmaEriText() {
722         return mPhone.getCdmaEriText();
723     }
724 
725     /**
726      * Returns true if CDMA provisioning needs to run.
727      */
needsOtaServiceProvisioning()728     public boolean needsOtaServiceProvisioning() {
729         return mPhone.needsOtaServiceProvisioning();
730     }
731 
732     /**
733      * Returns the unread count of voicemails
734      */
getVoiceMessageCount()735     public int getVoiceMessageCount() {
736         return mPhone.getVoiceMessageCount();
737     }
738 
739     /**
740      * Returns the network type
741      */
getNetworkType()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      */
hasIccCard()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      */
getLteOnCdmaMode()794     public int getLteOnCdmaMode() {
795         return mPhone.getLteOnCdmaMode();
796     }
797 }
798