• 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.text.TextUtils;
31 import android.util.Log;
32 
33 import com.android.internal.telephony.DefaultPhoneNotifier;
34 import com.android.internal.telephony.IccCard;
35 import com.android.internal.telephony.ITelephony;
36 import com.android.internal.telephony.Phone;
37 
38 import static com.android.internal.telephony.RILConstants.GSM_PHONE;
39 import static com.android.internal.telephony.RILConstants.CDMA_PHONE;
40 
41 import java.util.List;
42 import java.util.ArrayList;
43 
44 /**
45  * Implementation of the ITelephony interface.
46  */
47 public class PhoneInterfaceManager extends ITelephony.Stub {
48     private static final String LOG_TAG = "PhoneInterfaceManager";
49     private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
50 
51     // Message codes used with mMainThreadHandler
52     private static final int CMD_HANDLE_PIN_MMI = 1;
53     private static final int CMD_HANDLE_NEIGHBORING_CELL = 2;
54     private static final int EVENT_NEIGHBORING_CELL_DONE = 3;
55     private static final int CMD_ANSWER_RINGING_CALL = 4;
56     private static final int CMD_END_CALL = 5;  // not used yet
57     private static final int CMD_SILENCE_RINGER = 6;
58 
59     PhoneApp mApp;
60     Phone mPhone;
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                 default:
139                     Log.w(LOG_TAG, "MainThreadHandler: unexpected message code: " + msg.what);
140                     break;
141             }
142         }
143     }
144 
145     /**
146      * Posts the specified command to be executed on the main thread,
147      * waits for the request to complete, and returns the result.
148      * @see sendRequestAsync
149      */
sendRequest(int command, Object argument)150     private Object sendRequest(int command, Object argument) {
151         if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
152             throw new RuntimeException("This method will deadlock if called from the main thread.");
153         }
154 
155         MainThreadRequest request = new MainThreadRequest(argument);
156         Message msg = mMainThreadHandler.obtainMessage(command, request);
157         msg.sendToTarget();
158 
159         // Wait for the request to complete
160         synchronized (request) {
161             while (request.result == null) {
162                 try {
163                     request.wait();
164                 } catch (InterruptedException e) {
165                     // Do nothing, go back and wait until the request is complete
166                 }
167             }
168         }
169         return request.result;
170     }
171 
172     /**
173      * Asynchronous ("fire and forget") version of sendRequest():
174      * Posts the specified command to be executed on the main thread, and
175      * returns immediately.
176      * @see sendRequest
177      */
sendRequestAsync(int command)178     private void sendRequestAsync(int command) {
179         mMainThreadHandler.sendEmptyMessage(command);
180     }
181 
PhoneInterfaceManager(PhoneApp app, Phone phone)182     public PhoneInterfaceManager(PhoneApp app, Phone phone) {
183         mApp = app;
184         mPhone = phone;
185         mMainThreadHandler = new MainThreadHandler();
186         publish();
187     }
188 
publish()189     private void publish() {
190         if (DBG) log("publish: " + this);
191 
192         ServiceManager.addService("phone", this);
193     }
194 
195     //
196     // Implementation of the ITelephony interface.
197     //
198 
dial(String number)199     public void dial(String number) {
200         if (DBG) log("dial: " + number);
201         // No permission check needed here: This is just a wrapper around the
202         // ACTION_DIAL intent, which is available to any app since it puts up
203         // the UI before it does anything.
204 
205         String url = createTelUrl(number);
206         if (url == null) {
207             return;
208         }
209 
210         // PENDING: should we just silently fail if phone is offhook or ringing?
211         Phone.State state = mPhone.getState();
212         if (state != Phone.State.OFFHOOK && state != Phone.State.RINGING) {
213             Intent  intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
214             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
215             mApp.startActivity(intent);
216         }
217     }
218 
call(String number)219     public void call(String number) {
220         if (DBG) log("call: " + number);
221 
222         // This is just a wrapper around the ACTION_CALL intent, but we still
223         // need to do a permission check since we're calling startActivity()
224         // from the context of the phone app.
225         enforceCallPermission();
226 
227         String url = createTelUrl(number);
228         if (url == null) {
229             return;
230         }
231 
232         Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(url));
233         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
234         intent.setClassName(mApp, PhoneApp.getCallScreenClassName());
235         mApp.startActivity(intent);
236     }
237 
showCallScreenInternal(boolean specifyInitialDialpadState, boolean initialDialpadState)238     private boolean showCallScreenInternal(boolean specifyInitialDialpadState,
239                                            boolean initialDialpadState) {
240         if (isIdle()) {
241             return false;
242         }
243         // If the phone isn't idle then go to the in-call screen
244         long callingId = Binder.clearCallingIdentity();
245         try {
246             Intent intent;
247             if (specifyInitialDialpadState) {
248                 intent = PhoneApp.createInCallIntent(initialDialpadState);
249             } else {
250                 intent = PhoneApp.createInCallIntent();
251             }
252             mApp.startActivity(intent);
253         } finally {
254             Binder.restoreCallingIdentity(callingId);
255         }
256         return true;
257     }
258 
259     // Show the in-call screen without specifying the initial dialpad state.
showCallScreen()260     public boolean showCallScreen() {
261         return showCallScreenInternal(false, false);
262     }
263 
264     // The variation of showCallScreen() that specifies the initial dialpad state.
265     // (Ideally this would be called showCallScreen() too, just with a different
266     // signature, but AIDL doesn't allow that.)
showCallScreenWithDialpad(boolean showDialpad)267     public boolean showCallScreenWithDialpad(boolean showDialpad) {
268         return showCallScreenInternal(true, showDialpad);
269     }
270 
271     // TODO: Watch out: it's dangerous to call into the telephony code
272     // directly from here (we're running in a random binder thread, but
273     // the telephony code expects to always be called from the phone app
274     // main thread.)  We should instead wrap this in a sendRequest() call
275     // (just like with answerRingingCall() / answerRingingCallInternal()).
endCall()276     public boolean endCall() {
277         enforceCallPermission();
278         boolean hungUp = PhoneUtils.hangup(mPhone);
279         if (DBG) log("endCall: " + (hungUp ? "hung up!" : "no call to hang up"));
280         return hungUp;
281     }
282 
answerRingingCall()283     public void answerRingingCall() {
284         if (DBG) log("answerRingingCall...");
285         // TODO: there should eventually be a separate "ANSWER_PHONE" permission,
286         // but that can probably wait till the big TelephonyManager API overhaul.
287         // For now, protect this call with the MODIFY_PHONE_STATE permission.
288         enforceModifyPermission();
289         sendRequestAsync(CMD_ANSWER_RINGING_CALL);
290     }
291 
292     /**
293      * Make the actual telephony calls to implement answerRingingCall().
294      * This should only be called from the main thread of the Phone app.
295      * @see answerRingingCall
296      *
297      * TODO: it would be nice to return true if we answered the call, or
298      * false if there wasn't actually a ringing incoming call, or some
299      * other error occurred.  (In other words, pass back the return value
300      * from PhoneUtils.answerCall() or PhoneUtils.answerAndEndActive().)
301      * But that would require calling this method via sendRequest() rather
302      * than sendRequestAsync(), and right now we don't actually *need* that
303      * return value, so let's just return void for now.
304      */
answerRingingCallInternal()305     private void answerRingingCallInternal() {
306         final boolean hasRingingCall = !mPhone.getRingingCall().isIdle();
307         if (hasRingingCall) {
308             final boolean hasActiveCall = !mPhone.getForegroundCall().isIdle();
309             final boolean hasHoldingCall = !mPhone.getBackgroundCall().isIdle();
310             if (hasActiveCall && hasHoldingCall) {
311                 // Both lines are in use!
312                 // TODO: provide a flag to let the caller specify what
313                 // policy to use if both lines are in use.  (The current
314                 // behavior is hardwired to "answer incoming, end ongoing",
315                 // which is how the CALL button is specced to behave.)
316                 PhoneUtils.answerAndEndActive(mPhone);
317                 return;
318             } else {
319                 // answerCall() will automatically hold the current active
320                 // call, if there is one.
321                 PhoneUtils.answerCall(mPhone);
322                 return;
323             }
324         } else {
325             // No call was ringing.
326             return;
327         }
328     }
329 
silenceRinger()330     public void silenceRinger() {
331         if (DBG) log("silenceRinger...");
332         // TODO: find a more appropriate permission to check here.
333         // (That can probably wait till the big TelephonyManager API overhaul.
334         // For now, protect this call with the MODIFY_PHONE_STATE permission.)
335         enforceModifyPermission();
336         sendRequestAsync(CMD_SILENCE_RINGER);
337     }
338 
339     /**
340      * Internal implemenation of silenceRinger().
341      * This should only be called from the main thread of the Phone app.
342      * @see silenceRinger
343      */
silenceRingerInternal()344     private void silenceRingerInternal() {
345         if ((mPhone.getState() == Phone.State.RINGING)
346             && mApp.notifier.isRinging()) {
347             // Ringer is actually playing, so silence it.
348             if (DBG) log("silenceRingerInternal: silencing...");
349             PhoneUtils.setAudioControlState(PhoneUtils.AUDIO_IDLE);
350             mApp.notifier.silenceRinger();
351         }
352     }
353 
isOffhook()354     public boolean isOffhook() {
355         return (mPhone.getState() == Phone.State.OFFHOOK);
356     }
357 
isRinging()358     public boolean isRinging() {
359         return (mPhone.getState() == Phone.State.RINGING);
360     }
361 
isIdle()362     public boolean isIdle() {
363         return (mPhone.getState() == Phone.State.IDLE);
364     }
365 
isSimPinEnabled()366     public boolean isSimPinEnabled() {
367         enforceReadPermission();
368         return (PhoneApp.getInstance().isSimPinEnabled());
369     }
370 
supplyPin(String pin)371     public boolean supplyPin(String pin) {
372         enforceModifyPermission();
373         final CheckSimPin checkSimPin = new CheckSimPin(mPhone.getIccCard());
374         checkSimPin.start();
375         return checkSimPin.checkPin(pin);
376     }
377 
378     /**
379      * Helper thread to turn async call to {@link SimCard#supplyPin} into
380      * a synchronous one.
381      */
382     private static class CheckSimPin extends Thread {
383 
384         private final IccCard mSimCard;
385 
386         private boolean mDone = false;
387         private boolean mResult = false;
388 
389         // For replies from SimCard interface
390         private Handler mHandler;
391 
392         // For async handler to identify request type
393         private static final int SUPPLY_PIN_COMPLETE = 100;
394 
CheckSimPin(IccCard simCard)395         public CheckSimPin(IccCard simCard) {
396             mSimCard = simCard;
397         }
398 
399         @Override
run()400         public void run() {
401             Looper.prepare();
402             synchronized (CheckSimPin.this) {
403                 mHandler = new Handler() {
404                     @Override
405                     public void handleMessage(Message msg) {
406                         AsyncResult ar = (AsyncResult) msg.obj;
407                         switch (msg.what) {
408                             case SUPPLY_PIN_COMPLETE:
409                                 Log.d(LOG_TAG, "SUPPLY_PIN_COMPLETE");
410                                 synchronized (CheckSimPin.this) {
411                                     mResult = (ar.exception == null);
412                                     mDone = true;
413                                     CheckSimPin.this.notifyAll();
414                                 }
415                                 break;
416                         }
417                     }
418                 };
419                 CheckSimPin.this.notifyAll();
420             }
421             Looper.loop();
422         }
423 
checkPin(String pin)424         synchronized boolean checkPin(String pin) {
425 
426             while (mHandler == null) {
427                 try {
428                     wait();
429                 } catch (InterruptedException e) {
430                     Thread.currentThread().interrupt();
431                 }
432             }
433             Message callback = Message.obtain(mHandler, SUPPLY_PIN_COMPLETE);
434 
435             mSimCard.supplyPin(pin, callback);
436 
437             while (!mDone) {
438                 try {
439                     Log.d(LOG_TAG, "wait for done");
440                     wait();
441                 } catch (InterruptedException e) {
442                     // Restore the interrupted status
443                     Thread.currentThread().interrupt();
444                 }
445             }
446             Log.d(LOG_TAG, "done");
447             return mResult;
448         }
449     }
450 
updateServiceLocation()451     public void updateServiceLocation() {
452         // No permission check needed here: this call is harmless, and it's
453         // needed for the ServiceState.requestStateUpdate() call (which is
454         // already intentionally exposed to 3rd parties.)
455         mPhone.updateServiceLocation(null);
456     }
457 
isRadioOn()458     public boolean isRadioOn() {
459         return mPhone.getServiceState().getState() != ServiceState.STATE_POWER_OFF;
460     }
461 
toggleRadioOnOff()462     public void toggleRadioOnOff() {
463         enforceModifyPermission();
464         mPhone.setRadioPower(!isRadioOn());
465     }
setRadio(boolean turnOn)466     public boolean setRadio(boolean turnOn) {
467         enforceModifyPermission();
468         if ((mPhone.getServiceState().getState() != ServiceState.STATE_POWER_OFF) != turnOn) {
469             toggleRadioOnOff();
470         }
471         return true;
472     }
473 
enableDataConnectivity()474     public boolean enableDataConnectivity() {
475         enforceModifyPermission();
476         return mPhone.enableDataConnectivity();
477     }
478 
enableApnType(String type)479     public int enableApnType(String type) {
480         enforceModifyPermission();
481         return mPhone.enableApnType(type);
482     }
483 
disableApnType(String type)484     public int disableApnType(String type) {
485         enforceModifyPermission();
486         return mPhone.disableApnType(type);
487     }
488 
disableDataConnectivity()489     public boolean disableDataConnectivity() {
490         enforceModifyPermission();
491         return mPhone.disableDataConnectivity();
492     }
493 
isDataConnectivityPossible()494     public boolean isDataConnectivityPossible() {
495         return mPhone.isDataConnectivityPossible();
496     }
497 
handlePinMmi(String dialString)498     public boolean handlePinMmi(String dialString) {
499         enforceModifyPermission();
500         return (Boolean) sendRequest(CMD_HANDLE_PIN_MMI, dialString);
501     }
502 
cancelMissedCallsNotification()503     public void cancelMissedCallsNotification() {
504         enforceModifyPermission();
505         NotificationMgr.getDefault().cancelMissedCallNotification();
506     }
507 
getCallState()508     public int getCallState() {
509         return DefaultPhoneNotifier.convertCallState(mPhone.getState());
510     }
511 
getDataState()512     public int getDataState() {
513         return DefaultPhoneNotifier.convertDataState(mPhone.getDataConnectionState());
514     }
515 
getDataActivity()516     public int getDataActivity() {
517         return DefaultPhoneNotifier.convertDataActivityState(mPhone.getDataActivityState());
518     }
519 
getCellLocation()520     public Bundle getCellLocation() {
521         try {
522             mApp.enforceCallingOrSelfPermission(
523                 android.Manifest.permission.ACCESS_FINE_LOCATION, null);
524         } catch (SecurityException e) {
525             // If we have ACCESS_FINE_LOCATION permission, skip the check for ACCESS_COARSE_LOCATION
526             // A failure should throw the SecurityException from ACCESS_COARSE_LOCATION since this
527             // is the weaker precondition
528             mApp.enforceCallingOrSelfPermission(
529                 android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
530         }
531         Bundle data = new Bundle();
532         mPhone.getCellLocation().fillInNotifierBundle(data);
533         return data;
534     }
535 
enableLocationUpdates()536     public void enableLocationUpdates() {
537         mApp.enforceCallingOrSelfPermission(
538                 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
539         mPhone.enableLocationUpdates();
540     }
541 
disableLocationUpdates()542     public void disableLocationUpdates() {
543         mApp.enforceCallingOrSelfPermission(
544                 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
545         mPhone.disableLocationUpdates();
546     }
547 
548     @SuppressWarnings("unchecked")
getNeighboringCellInfo()549     public List<NeighboringCellInfo> getNeighboringCellInfo() {
550         try {
551             mApp.enforceCallingOrSelfPermission(
552                     android.Manifest.permission.ACCESS_FINE_LOCATION, null);
553         } catch (SecurityException e) {
554             // If we have ACCESS_FINE_LOCATION permission, skip the check
555             // for ACCESS_COARSE_LOCATION
556             // A failure should throw the SecurityException from
557             // ACCESS_COARSE_LOCATION since this is the weaker precondition
558             mApp.enforceCallingOrSelfPermission(
559                     android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
560         }
561 
562         ArrayList<NeighboringCellInfo> cells = null;
563 
564         try {
565             cells = (ArrayList<NeighboringCellInfo>) sendRequest(
566                     CMD_HANDLE_NEIGHBORING_CELL, null);
567         } catch (RuntimeException e) {
568         }
569 
570         return (List <NeighboringCellInfo>) cells;
571     }
572 
573 
574     //
575     // Internal helper methods.
576     //
577 
578     /**
579      * Make sure the caller has the READ_PHONE_STATE permission.
580      *
581      * @throws SecurityException if the caller does not have the required permission
582      */
enforceReadPermission()583     private void enforceReadPermission() {
584         mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE, null);
585     }
586 
587     /**
588      * Make sure the caller has the MODIFY_PHONE_STATE permission.
589      *
590      * @throws SecurityException if the caller does not have the required permission
591      */
enforceModifyPermission()592     private void enforceModifyPermission() {
593         mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
594     }
595 
596     /**
597      * Make sure the caller has the CALL_PHONE permission.
598      *
599      * @throws SecurityException if the caller does not have the required permission
600      */
enforceCallPermission()601     private void enforceCallPermission() {
602         mApp.enforceCallingOrSelfPermission(android.Manifest.permission.CALL_PHONE, null);
603     }
604 
605 
createTelUrl(String number)606     private String createTelUrl(String number) {
607         if (TextUtils.isEmpty(number)) {
608             return null;
609         }
610 
611         StringBuilder buf = new StringBuilder("tel:");
612         buf.append(number);
613         return buf.toString();
614     }
615 
log(String msg)616     private void log(String msg) {
617         Log.d(LOG_TAG, "[PhoneIntfMgr] " + msg);
618     }
619 
getActivePhoneType()620     public int getActivePhoneType() {
621         if(mPhone.getPhoneName().equals("CDMA")) {
622             return CDMA_PHONE;
623         } else {
624             return GSM_PHONE;
625         }
626     }
627 
628     /**
629      * Returns the CDMA ERI icon index to display
630      */
getCdmaEriIconIndex()631     public int getCdmaEriIconIndex() {
632         return mPhone.getCdmaEriIconIndex();
633     }
634 
635     /**
636      * Returns the CDMA ERI icon mode,
637      * 0 - ON
638      * 1 - FLASHING
639      */
getCdmaEriIconMode()640     public int getCdmaEriIconMode() {
641         return mPhone.getCdmaEriIconMode();
642     }
643 
644     /**
645      * Returns the CDMA ERI text,
646      */
getCdmaEriText()647     public String getCdmaEriText() {
648         return mPhone.getCdmaEriText();
649     }
650 
651     /**
652      * Returns the unread count of voicemails
653      */
getVoiceMessageCount()654     public int getVoiceMessageCount() {
655         return mPhone.getVoiceMessageCount();
656     }
657 
658 }
659