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