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