blob: cf86e7a5184380cae2eab76a5e7be27ce45b8406 [file] [log] [blame]
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001/*
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
17package com.android.phone;
18
19import android.app.ActivityManager;
20import android.app.AppOpsManager;
Gabriel Peal805a72e2014-03-20 09:20:43 -070021import android.bluetooth.IBluetoothHeadsetPhone;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070022import android.content.ActivityNotFoundException;
23import android.content.Context;
24import android.content.Intent;
25import android.net.ConnectivityManager;
26import android.net.Uri;
27import android.os.AsyncResult;
28import android.os.Binder;
29import android.os.Bundle;
30import android.os.Handler;
Gabriel Peal805a72e2014-03-20 09:20:43 -070031import android.os.IBinder;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070032import android.os.Looper;
33import android.os.Message;
34import android.os.Process;
Gabriel Peal805a72e2014-03-20 09:20:43 -070035import android.os.RemoteException;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070036import android.os.ServiceManager;
37import android.os.UserHandle;
38import android.telephony.NeighboringCellInfo;
39import android.telephony.CellInfo;
40import android.telephony.ServiceState;
41import android.text.TextUtils;
42import android.util.Log;
43
Gabriel Peal805a72e2014-03-20 09:20:43 -070044import com.android.internal.telephony.CallManager;
45import com.android.internal.telephony.CommandException;
46import com.android.internal.telephony.Connection;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070047import com.android.internal.telephony.DefaultPhoneNotifier;
48import com.android.internal.telephony.IccCard;
49import com.android.internal.telephony.ITelephony;
Gabriel Peal805a72e2014-03-20 09:20:43 -070050import com.android.internal.telephony.ITelephonyListener;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070051import com.android.internal.telephony.Phone;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070052import com.android.internal.telephony.PhoneConstants;
Gabriel Peal805a72e2014-03-20 09:20:43 -070053import com.android.services.telephony.common.Call;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070054
Gabriel Peal805a72e2014-03-20 09:20:43 -070055import com.android.internal.util.HexDump;
56
Santos Cordon7d4ddf62013-07-10 11:58:08 -070057import java.util.ArrayList;
Gabriel Peal805a72e2014-03-20 09:20:43 -070058import java.util.HashMap;
59import java.util.Iterator;
60import java.util.List;
61import java.util.Map;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070062
63/**
64 * Implementation of the ITelephony interface.
65 */
Gabriel Peal805a72e2014-03-20 09:20:43 -070066public class PhoneInterfaceManager extends ITelephony.Stub implements CallModeler.Listener {
Santos Cordon7d4ddf62013-07-10 11:58:08 -070067 private static final String LOG_TAG = "PhoneInterfaceManager";
68 private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
69 private static final boolean DBG_LOC = false;
70
71 // Message codes used with mMainThreadHandler
72 private static final int CMD_HANDLE_PIN_MMI = 1;
73 private static final int CMD_HANDLE_NEIGHBORING_CELL = 2;
74 private static final int EVENT_NEIGHBORING_CELL_DONE = 3;
75 private static final int CMD_ANSWER_RINGING_CALL = 4;
76 private static final int CMD_END_CALL = 5; // not used yet
77 private static final int CMD_SILENCE_RINGER = 6;
78
79 /** The singleton instance. */
80 private static PhoneInterfaceManager sInstance;
81
82 PhoneGlobals mApp;
83 Phone mPhone;
84 CallManager mCM;
85 AppOpsManager mAppOps;
86 MainThreadHandler mMainThreadHandler;
Santos Cordon406c0342013-08-28 00:07:47 -070087 CallHandlerServiceProxy mCallHandlerService;
Gabriel Peal805a72e2014-03-20 09:20:43 -070088 CallModeler mCallModeler;
89 DTMFTonePlayer mDtmfTonePlayer;
90 Handler mDtmfStopHandler = new Handler();
91 Runnable mDtmfStopRunnable;
92
93 private final List<ITelephonyListener> mListeners = new ArrayList<ITelephonyListener>();
94 private final Map<IBinder, TelephonyListenerDeathRecipient> mDeathRecipients =
95 new HashMap<IBinder, TelephonyListenerDeathRecipient>();
Santos Cordon7d4ddf62013-07-10 11:58:08 -070096
97 /**
98 * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
99 * request after sending. The main thread will notify the request when it is complete.
100 */
101 private static final class MainThreadRequest {
102 /** The argument to use for the request */
103 public Object argument;
104 /** The result of the request that is run on the main thread */
105 public Object result;
106
107 public MainThreadRequest(Object argument) {
108 this.argument = argument;
109 }
110 }
111
112 /**
113 * A handler that processes messages on the main thread in the phone process. Since many
114 * of the Phone calls are not thread safe this is needed to shuttle the requests from the
115 * inbound binder threads to the main thread in the phone process. The Binder thread
116 * may provide a {@link MainThreadRequest} object in the msg.obj field that they are waiting
117 * on, which will be notified when the operation completes and will contain the result of the
118 * request.
119 *
120 * <p>If a MainThreadRequest object is provided in the msg.obj field,
121 * note that request.result must be set to something non-null for the calling thread to
122 * unblock.
123 */
124 private final class MainThreadHandler extends Handler {
125 @Override
126 public void handleMessage(Message msg) {
127 MainThreadRequest request;
128 Message onCompleted;
129 AsyncResult ar;
130
131 switch (msg.what) {
132 case CMD_HANDLE_PIN_MMI:
133 request = (MainThreadRequest) msg.obj;
134 request.result = Boolean.valueOf(
135 mPhone.handlePinMmi((String) request.argument));
136 // Wake up the requesting thread
137 synchronized (request) {
138 request.notifyAll();
139 }
140 break;
141
142 case CMD_HANDLE_NEIGHBORING_CELL:
143 request = (MainThreadRequest) msg.obj;
144 onCompleted = obtainMessage(EVENT_NEIGHBORING_CELL_DONE,
145 request);
146 mPhone.getNeighboringCids(onCompleted);
147 break;
148
149 case EVENT_NEIGHBORING_CELL_DONE:
150 ar = (AsyncResult) msg.obj;
151 request = (MainThreadRequest) ar.userObj;
152 if (ar.exception == null && ar.result != null) {
153 request.result = ar.result;
154 } else {
155 // create an empty list to notify the waiting thread
156 request.result = new ArrayList<NeighboringCellInfo>();
157 }
158 // Wake up the requesting thread
159 synchronized (request) {
160 request.notifyAll();
161 }
162 break;
163
164 case CMD_ANSWER_RINGING_CALL:
165 answerRingingCallInternal();
166 break;
167
168 case CMD_SILENCE_RINGER:
169 silenceRingerInternal();
170 break;
171
172 case CMD_END_CALL:
173 request = (MainThreadRequest) msg.obj;
174 boolean hungUp = false;
175 int phoneType = mPhone.getPhoneType();
176 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
177 // CDMA: If the user presses the Power button we treat it as
178 // ending the complete call session
179 hungUp = PhoneUtils.hangupRingingAndActive(mPhone);
180 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
181 // GSM: End the call as per the Phone state
182 hungUp = PhoneUtils.hangup(mCM);
183 } else {
184 throw new IllegalStateException("Unexpected phone type: " + phoneType);
185 }
186 if (DBG) log("CMD_END_CALL: " + (hungUp ? "hung up!" : "no call to hang up"));
187 request.result = hungUp;
188 // Wake up the requesting thread
189 synchronized (request) {
190 request.notifyAll();
191 }
192 break;
193
194 default:
195 Log.w(LOG_TAG, "MainThreadHandler: unexpected message code: " + msg.what);
196 break;
197 }
198 }
199 }
200
201 /**
202 * Posts the specified command to be executed on the main thread,
203 * waits for the request to complete, and returns the result.
204 * @see #sendRequestAsync
205 */
206 private Object sendRequest(int command, Object argument) {
207 if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
208 throw new RuntimeException("This method will deadlock if called from the main thread.");
209 }
210
211 MainThreadRequest request = new MainThreadRequest(argument);
212 Message msg = mMainThreadHandler.obtainMessage(command, request);
213 msg.sendToTarget();
214
215 // Wait for the request to complete
216 synchronized (request) {
217 while (request.result == null) {
218 try {
219 request.wait();
220 } catch (InterruptedException e) {
221 // Do nothing, go back and wait until the request is complete
222 }
223 }
224 }
225 return request.result;
226 }
227
228 /**
229 * Asynchronous ("fire and forget") version of sendRequest():
230 * Posts the specified command to be executed on the main thread, and
231 * returns immediately.
232 * @see #sendRequest
233 */
234 private void sendRequestAsync(int command) {
235 mMainThreadHandler.sendEmptyMessage(command);
236 }
237
238 /**
239 * Initialize the singleton PhoneInterfaceManager instance.
240 * This is only done once, at startup, from PhoneApp.onCreate().
241 */
Santos Cordon406c0342013-08-28 00:07:47 -0700242 /* package */ static PhoneInterfaceManager init(PhoneGlobals app, Phone phone,
Gabriel Peal805a72e2014-03-20 09:20:43 -0700243 CallHandlerServiceProxy callHandlerService, CallModeler callModeler,
244 DTMFTonePlayer dtmfTonePlayer) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700245 synchronized (PhoneInterfaceManager.class) {
246 if (sInstance == null) {
Gabriel Peal805a72e2014-03-20 09:20:43 -0700247 sInstance = new PhoneInterfaceManager(app, phone, callHandlerService, callModeler,
248 dtmfTonePlayer);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700249 } else {
250 Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
251 }
252 return sInstance;
253 }
254 }
255
256 /** Private constructor; @see init() */
Santos Cordon406c0342013-08-28 00:07:47 -0700257 private PhoneInterfaceManager(PhoneGlobals app, Phone phone,
Gabriel Peal805a72e2014-03-20 09:20:43 -0700258 CallHandlerServiceProxy callHandlerService, CallModeler callModeler,
259 DTMFTonePlayer dtmfTonePlayer) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700260 mApp = app;
261 mPhone = phone;
262 mCM = PhoneGlobals.getInstance().mCM;
263 mAppOps = (AppOpsManager)app.getSystemService(Context.APP_OPS_SERVICE);
264 mMainThreadHandler = new MainThreadHandler();
Santos Cordon406c0342013-08-28 00:07:47 -0700265 mCallHandlerService = callHandlerService;
Gabriel Peal805a72e2014-03-20 09:20:43 -0700266 mCallModeler = callModeler;
267 mCallModeler.addListener(this);
268 mDtmfTonePlayer = dtmfTonePlayer;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700269 publish();
270 }
271
272 private void publish() {
273 if (DBG) log("publish: " + this);
274
275 ServiceManager.addService("phone", this);
276 }
277
278 //
279 // Implementation of the ITelephony interface.
280 //
281
282 public void dial(String number) {
283 if (DBG) log("dial: " + number);
284 // No permission check needed here: This is just a wrapper around the
285 // ACTION_DIAL intent, which is available to any app since it puts up
286 // the UI before it does anything.
287
288 String url = createTelUrl(number);
289 if (url == null) {
290 return;
291 }
292
293 // PENDING: should we just silently fail if phone is offhook or ringing?
294 PhoneConstants.State state = mCM.getState();
295 if (state != PhoneConstants.State.OFFHOOK && state != PhoneConstants.State.RINGING) {
296 Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
297 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
298 mApp.startActivity(intent);
299 }
300 }
301
302 public void call(String callingPackage, String number) {
303 if (DBG) log("call: " + number);
304
305 // This is just a wrapper around the ACTION_CALL intent, but we still
306 // need to do a permission check since we're calling startActivity()
307 // from the context of the phone app.
308 enforceCallPermission();
309
310 if (mAppOps.noteOp(AppOpsManager.OP_CALL_PHONE, Binder.getCallingUid(), callingPackage)
311 != AppOpsManager.MODE_ALLOWED) {
312 return;
313 }
314
315 String url = createTelUrl(number);
316 if (url == null) {
317 return;
318 }
319
320 Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(url));
321 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
322 mApp.startActivity(intent);
323 }
324
325 private boolean showCallScreenInternal(boolean specifyInitialDialpadState,
Makoto Onukibcf20992013-09-12 17:59:30 -0700326 boolean showDialpad) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700327 if (!PhoneGlobals.sVoiceCapable) {
328 // Never allow the InCallScreen to appear on data-only devices.
329 return false;
330 }
331 if (isIdle()) {
332 return false;
333 }
334 // If the phone isn't idle then go to the in-call screen
335 long callingId = Binder.clearCallingIdentity();
Santos Cordon406c0342013-08-28 00:07:47 -0700336
Makoto Onukibcf20992013-09-12 17:59:30 -0700337 mCallHandlerService.bringToForeground(showDialpad);
Santos Cordon406c0342013-08-28 00:07:47 -0700338
339 Binder.restoreCallingIdentity(callingId);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700340 return true;
341 }
342
343 // Show the in-call screen without specifying the initial dialpad state.
344 public boolean showCallScreen() {
345 return showCallScreenInternal(false, false);
346 }
347
348 // The variation of showCallScreen() that specifies the initial dialpad state.
349 // (Ideally this would be called showCallScreen() too, just with a different
350 // signature, but AIDL doesn't allow that.)
351 public boolean showCallScreenWithDialpad(boolean showDialpad) {
352 return showCallScreenInternal(true, showDialpad);
353 }
354
355 /**
356 * End a call based on call state
357 * @return true is a call was ended
358 */
359 public boolean endCall() {
360 enforceCallPermission();
361 return (Boolean) sendRequest(CMD_END_CALL, null);
362 }
363
364 public void answerRingingCall() {
365 if (DBG) log("answerRingingCall...");
366 // TODO: there should eventually be a separate "ANSWER_PHONE" permission,
367 // but that can probably wait till the big TelephonyManager API overhaul.
368 // For now, protect this call with the MODIFY_PHONE_STATE permission.
369 enforceModifyPermission();
370 sendRequestAsync(CMD_ANSWER_RINGING_CALL);
371 }
372
373 /**
374 * Make the actual telephony calls to implement answerRingingCall().
375 * This should only be called from the main thread of the Phone app.
376 * @see #answerRingingCall
377 *
378 * TODO: it would be nice to return true if we answered the call, or
379 * false if there wasn't actually a ringing incoming call, or some
380 * other error occurred. (In other words, pass back the return value
381 * from PhoneUtils.answerCall() or PhoneUtils.answerAndEndActive().)
382 * But that would require calling this method via sendRequest() rather
383 * than sendRequestAsync(), and right now we don't actually *need* that
384 * return value, so let's just return void for now.
385 */
386 private void answerRingingCallInternal() {
387 final boolean hasRingingCall = !mPhone.getRingingCall().isIdle();
388 if (hasRingingCall) {
389 final boolean hasActiveCall = !mPhone.getForegroundCall().isIdle();
390 final boolean hasHoldingCall = !mPhone.getBackgroundCall().isIdle();
391 if (hasActiveCall && hasHoldingCall) {
392 // Both lines are in use!
393 // TODO: provide a flag to let the caller specify what
394 // policy to use if both lines are in use. (The current
395 // behavior is hardwired to "answer incoming, end ongoing",
396 // which is how the CALL button is specced to behave.)
397 PhoneUtils.answerAndEndActive(mCM, mCM.getFirstActiveRingingCall());
398 return;
399 } else {
400 // answerCall() will automatically hold the current active
401 // call, if there is one.
402 PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
403 return;
404 }
405 } else {
406 // No call was ringing.
407 return;
408 }
409 }
410
411 public void silenceRinger() {
412 if (DBG) log("silenceRinger...");
413 // TODO: find a more appropriate permission to check here.
414 // (That can probably wait till the big TelephonyManager API overhaul.
415 // For now, protect this call with the MODIFY_PHONE_STATE permission.)
416 enforceModifyPermission();
417 sendRequestAsync(CMD_SILENCE_RINGER);
418 }
419
420 /**
421 * Internal implemenation of silenceRinger().
422 * This should only be called from the main thread of the Phone app.
423 * @see #silenceRinger
424 */
425 private void silenceRingerInternal() {
426 if ((mCM.getState() == PhoneConstants.State.RINGING)
427 && mApp.notifier.isRinging()) {
428 // Ringer is actually playing, so silence it.
429 if (DBG) log("silenceRingerInternal: silencing...");
430 mApp.notifier.silenceRinger();
431 }
432 }
433
434 public boolean isOffhook() {
435 return (mCM.getState() == PhoneConstants.State.OFFHOOK);
436 }
437
438 public boolean isRinging() {
439 return (mCM.getState() == PhoneConstants.State.RINGING);
440 }
441
442 public boolean isIdle() {
443 return (mCM.getState() == PhoneConstants.State.IDLE);
444 }
445
446 public boolean isSimPinEnabled() {
447 enforceReadPermission();
448 return (PhoneGlobals.getInstance().isSimPinEnabled());
449 }
450
451 public boolean supplyPin(String pin) {
Wink Saville9de0f752013-10-22 19:04:03 -0700452 int [] resultArray = supplyPinReportResult(pin);
453 return (resultArray[0] == PhoneConstants.PIN_RESULT_SUCCESS) ? true : false;
454 }
455
456 public boolean supplyPuk(String puk, String pin) {
457 int [] resultArray = supplyPukReportResult(puk, pin);
458 return (resultArray[0] == PhoneConstants.PIN_RESULT_SUCCESS) ? true : false;
459 }
460
461 /** {@hide} */
462 public int[] supplyPinReportResult(String pin) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700463 enforceModifyPermission();
464 final UnlockSim checkSimPin = new UnlockSim(mPhone.getIccCard());
465 checkSimPin.start();
466 return checkSimPin.unlockSim(null, pin);
467 }
468
Wink Saville9de0f752013-10-22 19:04:03 -0700469 /** {@hide} */
470 public int[] supplyPukReportResult(String puk, String pin) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700471 enforceModifyPermission();
472 final UnlockSim checkSimPuk = new UnlockSim(mPhone.getIccCard());
473 checkSimPuk.start();
474 return checkSimPuk.unlockSim(puk, pin);
475 }
476
477 /**
Wink Saville9de0f752013-10-22 19:04:03 -0700478 * Helper thread to turn async call to SimCard#supplyPin into
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700479 * a synchronous one.
480 */
481 private static class UnlockSim extends Thread {
482
483 private final IccCard mSimCard;
484
485 private boolean mDone = false;
Wink Saville9de0f752013-10-22 19:04:03 -0700486 private int mResult = PhoneConstants.PIN_GENERAL_FAILURE;
487 private int mRetryCount = -1;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700488
489 // For replies from SimCard interface
490 private Handler mHandler;
491
492 // For async handler to identify request type
493 private static final int SUPPLY_PIN_COMPLETE = 100;
494
495 public UnlockSim(IccCard simCard) {
496 mSimCard = simCard;
497 }
498
499 @Override
500 public void run() {
501 Looper.prepare();
502 synchronized (UnlockSim.this) {
503 mHandler = new Handler() {
504 @Override
505 public void handleMessage(Message msg) {
506 AsyncResult ar = (AsyncResult) msg.obj;
507 switch (msg.what) {
508 case SUPPLY_PIN_COMPLETE:
509 Log.d(LOG_TAG, "SUPPLY_PIN_COMPLETE");
510 synchronized (UnlockSim.this) {
Wink Saville9de0f752013-10-22 19:04:03 -0700511 mRetryCount = msg.arg1;
512 if (ar.exception != null) {
513 if (ar.exception instanceof CommandException &&
514 ((CommandException)(ar.exception)).getCommandError()
515 == CommandException.Error.PASSWORD_INCORRECT) {
516 mResult = PhoneConstants.PIN_PASSWORD_INCORRECT;
517 } else {
518 mResult = PhoneConstants.PIN_GENERAL_FAILURE;
519 }
520 } else {
521 mResult = PhoneConstants.PIN_RESULT_SUCCESS;
522 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700523 mDone = true;
524 UnlockSim.this.notifyAll();
525 }
526 break;
527 }
528 }
529 };
530 UnlockSim.this.notifyAll();
531 }
532 Looper.loop();
533 }
534
535 /*
536 * Use PIN or PUK to unlock SIM card
537 *
538 * If PUK is null, unlock SIM card with PIN
539 *
540 * If PUK is not null, unlock SIM card with PUK and set PIN code
541 */
Wink Saville9de0f752013-10-22 19:04:03 -0700542 synchronized int[] unlockSim(String puk, String pin) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700543
544 while (mHandler == null) {
545 try {
546 wait();
547 } catch (InterruptedException e) {
548 Thread.currentThread().interrupt();
549 }
550 }
551 Message callback = Message.obtain(mHandler, SUPPLY_PIN_COMPLETE);
552
553 if (puk == null) {
554 mSimCard.supplyPin(pin, callback);
555 } else {
556 mSimCard.supplyPuk(puk, pin, callback);
557 }
558
559 while (!mDone) {
560 try {
561 Log.d(LOG_TAG, "wait for done");
562 wait();
563 } catch (InterruptedException e) {
564 // Restore the interrupted status
565 Thread.currentThread().interrupt();
566 }
567 }
568 Log.d(LOG_TAG, "done");
Wink Saville9de0f752013-10-22 19:04:03 -0700569 int[] resultArray = new int[2];
570 resultArray[0] = mResult;
571 resultArray[1] = mRetryCount;
572 return resultArray;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700573 }
574 }
575
576 public void updateServiceLocation() {
577 // No permission check needed here: this call is harmless, and it's
578 // needed for the ServiceState.requestStateUpdate() call (which is
579 // already intentionally exposed to 3rd parties.)
580 mPhone.updateServiceLocation();
581 }
582
583 public boolean isRadioOn() {
584 return mPhone.getServiceState().getVoiceRegState() != ServiceState.STATE_POWER_OFF;
585 }
586
587 public void toggleRadioOnOff() {
588 enforceModifyPermission();
589 mPhone.setRadioPower(!isRadioOn());
590 }
591 public boolean setRadio(boolean turnOn) {
592 enforceModifyPermission();
593 if ((mPhone.getServiceState().getVoiceRegState() != ServiceState.STATE_POWER_OFF) != turnOn) {
594 toggleRadioOnOff();
595 }
596 return true;
597 }
598 public boolean setRadioPower(boolean turnOn) {
599 enforceModifyPermission();
600 mPhone.setRadioPower(turnOn);
601 return true;
602 }
603
604 public boolean enableDataConnectivity() {
605 enforceModifyPermission();
606 ConnectivityManager cm =
607 (ConnectivityManager)mApp.getSystemService(Context.CONNECTIVITY_SERVICE);
608 cm.setMobileDataEnabled(true);
609 return true;
610 }
611
612 public int enableApnType(String type) {
613 enforceModifyPermission();
614 return mPhone.enableApnType(type);
615 }
616
617 public int disableApnType(String type) {
618 enforceModifyPermission();
619 return mPhone.disableApnType(type);
620 }
621
622 public boolean disableDataConnectivity() {
623 enforceModifyPermission();
624 ConnectivityManager cm =
625 (ConnectivityManager)mApp.getSystemService(Context.CONNECTIVITY_SERVICE);
626 cm.setMobileDataEnabled(false);
627 return true;
628 }
629
630 public boolean isDataConnectivityPossible() {
631 return mPhone.isDataConnectivityPossible();
632 }
633
634 public boolean handlePinMmi(String dialString) {
635 enforceModifyPermission();
636 return (Boolean) sendRequest(CMD_HANDLE_PIN_MMI, dialString);
637 }
638
639 public void cancelMissedCallsNotification() {
640 enforceModifyPermission();
641 mApp.notificationMgr.cancelMissedCallNotification();
642 }
643
644 public int getCallState() {
645 return DefaultPhoneNotifier.convertCallState(mCM.getState());
646 }
647
648 public int getDataState() {
649 return DefaultPhoneNotifier.convertDataState(mPhone.getDataConnectionState());
650 }
651
652 public int getDataActivity() {
653 return DefaultPhoneNotifier.convertDataActivityState(mPhone.getDataActivityState());
654 }
655
656 @Override
657 public Bundle getCellLocation() {
658 try {
659 mApp.enforceCallingOrSelfPermission(
660 android.Manifest.permission.ACCESS_FINE_LOCATION, null);
661 } catch (SecurityException e) {
662 // If we have ACCESS_FINE_LOCATION permission, skip the check for ACCESS_COARSE_LOCATION
663 // A failure should throw the SecurityException from ACCESS_COARSE_LOCATION since this
664 // is the weaker precondition
665 mApp.enforceCallingOrSelfPermission(
666 android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
667 }
668
669 if (checkIfCallerIsSelfOrForegoundUser()) {
670 if (DBG_LOC) log("getCellLocation: is active user");
671 Bundle data = new Bundle();
672 mPhone.getCellLocation().fillInNotifierBundle(data);
673 return data;
674 } else {
675 if (DBG_LOC) log("getCellLocation: suppress non-active user");
676 return null;
677 }
678 }
679
680 @Override
681 public void enableLocationUpdates() {
682 mApp.enforceCallingOrSelfPermission(
683 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
684 mPhone.enableLocationUpdates();
685 }
686
687 @Override
688 public void disableLocationUpdates() {
689 mApp.enforceCallingOrSelfPermission(
690 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
691 mPhone.disableLocationUpdates();
692 }
693
694 @Override
695 @SuppressWarnings("unchecked")
696 public List<NeighboringCellInfo> getNeighboringCellInfo(String callingPackage) {
697 try {
698 mApp.enforceCallingOrSelfPermission(
699 android.Manifest.permission.ACCESS_FINE_LOCATION, null);
700 } catch (SecurityException e) {
701 // If we have ACCESS_FINE_LOCATION permission, skip the check
702 // for ACCESS_COARSE_LOCATION
703 // A failure should throw the SecurityException from
704 // ACCESS_COARSE_LOCATION since this is the weaker precondition
705 mApp.enforceCallingOrSelfPermission(
706 android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
707 }
708
709 if (mAppOps.noteOp(AppOpsManager.OP_NEIGHBORING_CELLS, Binder.getCallingUid(),
710 callingPackage) != AppOpsManager.MODE_ALLOWED) {
711 return null;
712 }
713 if (checkIfCallerIsSelfOrForegoundUser()) {
714 if (DBG_LOC) log("getNeighboringCellInfo: is active user");
715
716 ArrayList<NeighboringCellInfo> cells = null;
717
718 try {
719 cells = (ArrayList<NeighboringCellInfo>) sendRequest(
720 CMD_HANDLE_NEIGHBORING_CELL, null);
721 } catch (RuntimeException e) {
722 Log.e(LOG_TAG, "getNeighboringCellInfo " + e);
723 }
724 return cells;
725 } else {
726 if (DBG_LOC) log("getNeighboringCellInfo: suppress non-active user");
727 return null;
728 }
729 }
730
731
732 @Override
733 public List<CellInfo> getAllCellInfo() {
734 try {
735 mApp.enforceCallingOrSelfPermission(
736 android.Manifest.permission.ACCESS_FINE_LOCATION, null);
737 } catch (SecurityException e) {
738 // If we have ACCESS_FINE_LOCATION permission, skip the check for ACCESS_COARSE_LOCATION
739 // A failure should throw the SecurityException from ACCESS_COARSE_LOCATION since this
740 // is the weaker precondition
741 mApp.enforceCallingOrSelfPermission(
742 android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
743 }
744
745 if (checkIfCallerIsSelfOrForegoundUser()) {
746 if (DBG_LOC) log("getAllCellInfo: is active user");
747 return mPhone.getAllCellInfo();
748 } else {
749 if (DBG_LOC) log("getAllCellInfo: suppress non-active user");
750 return null;
751 }
752 }
753
754 public void setCellInfoListRate(int rateInMillis) {
755 mPhone.setCellInfoListRate(rateInMillis);
756 }
757
758 //
759 // Internal helper methods.
760 //
761
762 private boolean checkIfCallerIsSelfOrForegoundUser() {
763 boolean ok;
764
765 boolean self = Binder.getCallingUid() == Process.myUid();
766 if (!self) {
767 // Get the caller's user id then clear the calling identity
768 // which will be restored in the finally clause.
769 int callingUser = UserHandle.getCallingUserId();
770 long ident = Binder.clearCallingIdentity();
771
772 try {
773 // With calling identity cleared the current user is the foreground user.
774 int foregroundUser = ActivityManager.getCurrentUser();
775 ok = (foregroundUser == callingUser);
776 if (DBG_LOC) {
777 log("checkIfCallerIsSelfOrForegoundUser: foregroundUser=" + foregroundUser
778 + " callingUser=" + callingUser + " ok=" + ok);
779 }
780 } catch (Exception ex) {
781 if (DBG_LOC) loge("checkIfCallerIsSelfOrForegoundUser: Exception ex=" + ex);
782 ok = false;
783 } finally {
784 Binder.restoreCallingIdentity(ident);
785 }
786 } else {
787 if (DBG_LOC) log("checkIfCallerIsSelfOrForegoundUser: is self");
788 ok = true;
789 }
790 if (DBG_LOC) log("checkIfCallerIsSelfOrForegoundUser: ret=" + ok);
791 return ok;
792 }
793
794 /**
795 * Make sure the caller has the READ_PHONE_STATE permission.
796 *
797 * @throws SecurityException if the caller does not have the required permission
798 */
799 private void enforceReadPermission() {
800 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE, null);
801 }
802
803 /**
804 * Make sure the caller has the MODIFY_PHONE_STATE permission.
805 *
806 * @throws SecurityException if the caller does not have the required permission
807 */
808 private void enforceModifyPermission() {
809 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
810 }
811
812 /**
813 * Make sure the caller has the CALL_PHONE permission.
814 *
815 * @throws SecurityException if the caller does not have the required permission
816 */
817 private void enforceCallPermission() {
818 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.CALL_PHONE, null);
819 }
820
Gabriel Peal805a72e2014-03-20 09:20:43 -0700821 /**
822 * Make sure the caller has the READ_PRIVILEGED_PHONE_STATE permission.
823 *
824 * @throws SecurityException if the caller does not have the required permission
825 */
826 private void enforcePrivilegedPhoneStatePermission() {
827 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
828 null);
829 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700830
831 private String createTelUrl(String number) {
832 if (TextUtils.isEmpty(number)) {
833 return null;
834 }
835
836 StringBuilder buf = new StringBuilder("tel:");
837 buf.append(number);
838 return buf.toString();
839 }
840
841 private void log(String msg) {
842 Log.d(LOG_TAG, "[PhoneIntfMgr] " + msg);
843 }
844
845 private void loge(String msg) {
846 Log.e(LOG_TAG, "[PhoneIntfMgr] " + msg);
847 }
848
849 public int getActivePhoneType() {
850 return mPhone.getPhoneType();
851 }
852
853 /**
854 * Returns the CDMA ERI icon index to display
855 */
856 public int getCdmaEriIconIndex() {
857 return mPhone.getCdmaEriIconIndex();
858 }
859
860 /**
861 * Returns the CDMA ERI icon mode,
862 * 0 - ON
863 * 1 - FLASHING
864 */
865 public int getCdmaEriIconMode() {
866 return mPhone.getCdmaEriIconMode();
867 }
868
869 /**
870 * Returns the CDMA ERI text,
871 */
872 public String getCdmaEriText() {
873 return mPhone.getCdmaEriText();
874 }
875
876 /**
877 * Returns true if CDMA provisioning needs to run.
878 */
879 public boolean needsOtaServiceProvisioning() {
880 return mPhone.needsOtaServiceProvisioning();
881 }
882
883 /**
884 * Returns the unread count of voicemails
885 */
886 public int getVoiceMessageCount() {
887 return mPhone.getVoiceMessageCount();
888 }
889
890 /**
891 * Returns the data network type
892 *
893 * @Deprecated to be removed Q3 2013 use {@link #getDataNetworkType}.
894 */
895 @Override
896 public int getNetworkType() {
897 return mPhone.getServiceState().getDataNetworkType();
898 }
899
900 /**
901 * Returns the data network type
902 */
903 @Override
904 public int getDataNetworkType() {
905 return mPhone.getServiceState().getDataNetworkType();
906 }
907
908 /**
909 * Returns the data network type
910 */
911 @Override
912 public int getVoiceNetworkType() {
913 return mPhone.getServiceState().getVoiceNetworkType();
914 }
915
916 /**
917 * @return true if a ICC card is present
918 */
919 public boolean hasIccCard() {
920 return mPhone.getIccCard().hasIccCard();
921 }
922
923 /**
924 * Return if the current radio is LTE on CDMA. This
925 * is a tri-state return value as for a period of time
926 * the mode may be unknown.
927 *
928 * @return {@link Phone#LTE_ON_CDMA_UNKNOWN}, {@link Phone#LTE_ON_CDMA_FALSE}
929 * or {@link PHone#LTE_ON_CDMA_TRUE}
930 */
931 public int getLteOnCdmaMode() {
932 return mPhone.getLteOnCdmaMode();
933 }
Gabriel Peal805a72e2014-03-20 09:20:43 -0700934
935 @Override
936 public void toggleHold() {
937 enforceModifyPermission();
938
939 try {
940 PhoneUtils.switchHoldingAndActive(mCM.getFirstActiveBgCall());
941 } catch (Exception e) {
942 Log.e(LOG_TAG, "Error during toggleHold().", e);
943 }
944 }
945
946 @Override
947 public void merge() {
948 enforceModifyPermission();
949
950 try {
951 if (PhoneUtils.okToMergeCalls(mCM)) {
952 PhoneUtils.mergeCalls(mCM);
953 }
954 } catch (Exception e) {
955 Log.e(LOG_TAG, "Error during merge().", e);
956 }
957 }
958
959 @Override
960 public void swap() {
961 enforceModifyPermission();
962
963 try {
964 PhoneUtils.swap();
965 } catch (Exception e) {
966 Log.e(LOG_TAG, "Error during swap().", e);
967 }
968 }
969
970 @Override
971 public void mute(boolean onOff) {
972 enforceModifyPermission();
973
974 try {
975 PhoneUtils.setMute(onOff);
976 } catch (Exception e) {
977 Log.e(LOG_TAG, "Error during mute().", e);
978 }
979 }
980
981 @Override
982 public void playDtmfTone(char digit, boolean timedShortTone) {
983 enforceModifyPermission();
984
985 synchronized (mDtmfStopHandler) {
986 try {
987 mDtmfTonePlayer.playDtmfTone(digit, timedShortTone);
988 } catch (Exception e) {
989 Log.e(LOG_TAG, "Error playing DTMF tone.", e);
990 }
991
992 if (mDtmfStopRunnable != null) {
993 mDtmfStopHandler.removeCallbacks(mDtmfStopRunnable);
994 }
995 mDtmfStopRunnable = new Runnable() {
996 @Override
997 public void run() {
998 synchronized (mDtmfStopHandler) {
999 if (mDtmfStopRunnable == this) {
1000 mDtmfTonePlayer.stopDtmfTone();
1001 mDtmfStopRunnable = null;
1002 }
1003 }
1004 }
1005 };
1006 mDtmfStopHandler.postDelayed(mDtmfStopRunnable, 5000);
1007 }
1008 }
1009
1010 @Override
1011 public void stopDtmfTone() {
1012 enforceModifyPermission();
1013
1014 synchronized (mDtmfStopHandler) {
1015 try {
1016 mDtmfTonePlayer.stopDtmfTone();
1017 } catch (Exception e) {
1018 Log.e(LOG_TAG, "Error stopping DTMF tone.", e);
1019 }
1020
1021 if (mDtmfStopRunnable != null) {
1022 mDtmfStopHandler.removeCallbacks(mDtmfStopRunnable);
1023 mDtmfStopRunnable = null;
1024 }
1025 }
1026 }
1027
1028 @Override
1029 public void addListener(ITelephonyListener listener) {
1030 enforcePrivilegedPhoneStatePermission();
1031
1032 if (listener == null) {
1033 throw new IllegalArgumentException("Listener must not be null.");
1034 }
1035
1036 synchronized (mListeners) {
1037 IBinder listenerBinder = listener.asBinder();
1038 for (ITelephonyListener l : mListeners) {
1039 if (l.asBinder().equals(listenerBinder)) {
1040 Log.w(LOG_TAG, "Listener already registered. Ignoring.");
1041 return;
1042 }
1043 }
1044 mListeners.add(listener);
1045 mDeathRecipients.put(listener.asBinder(),
1046 new TelephonyListenerDeathRecipient(listener.asBinder()));
1047
1048 // update the new listener so they get the full call state immediately
1049 for (Call call : mCallModeler.getFullList()) {
1050 try {
1051 notifyListenerOfCallLocked(call, listener);
1052 } catch (RemoteException e) {
1053 Log.e(LOG_TAG, "Error updating new listener. Ignoring.");
1054 removeListenerInternal(listener);
1055 }
1056 }
1057 }
1058 }
1059
1060 @Override
1061 public void removeListener(ITelephonyListener listener) {
1062 enforcePrivilegedPhoneStatePermission();
1063
1064 if (listener == null) {
1065 throw new IllegalArgumentException("Listener must not be null.");
1066 }
1067
1068 removeListenerInternal(listener);
1069 }
1070
1071 private void removeListenerInternal(ITelephonyListener listener) {
1072 IBinder listenerBinder = listener.asBinder();
1073
1074 synchronized (mListeners) {
1075 for (Iterator<ITelephonyListener> it = mListeners.iterator(); it.hasNext(); ) {
1076 ITelephonyListener nextListener = it.next();
1077 if (nextListener.asBinder().equals(listenerBinder)) {
1078 TelephonyListenerDeathRecipient dr = mDeathRecipients.get(listener.asBinder());
1079 if (dr != null) {
1080 dr.unlinkDeathRecipient();
1081 }
1082 it.remove();
1083 }
1084 }
1085 }
1086 }
1087
1088 /** CallModeler.Listener implementation **/
1089
1090 @Override
1091 public void onDisconnect(Call call) {
1092 notifyListenersOfCall(call);
1093 }
1094
1095 @Override
1096 public void onIncoming(Call call) {
1097 notifyListenersOfCall(call);
1098 }
1099
1100 @Override
1101 public void onUpdate(List<Call> calls) {
1102 for (Call call : calls) {
1103 notifyListenersOfCall(call);
1104 }
1105 }
1106
1107 @Override
1108 public void onPostDialAction(
1109 Connection.PostDialState state, int callId, String remainingChars, char c) { }
1110
1111 private void notifyListenersOfCall(Call call) {
1112 synchronized (mListeners) {
1113 for (Iterator<ITelephonyListener> it = mListeners.iterator(); it.hasNext(); ) {
1114 ITelephonyListener listener = it.next();
1115 try {
1116 notifyListenerOfCallLocked(call, listener);
1117 } catch (RemoteException e) {
1118 TelephonyListenerDeathRecipient deathRecipient =
1119 mDeathRecipients.get(listener.asBinder());
1120 if (deathRecipient != null) {
1121 deathRecipient.unlinkDeathRecipient();
1122 }
1123 it.remove();
1124 }
1125 }
1126 }
1127 }
1128
1129 private void notifyListenerOfCallLocked(final Call call,final ITelephonyListener listener)
1130 throws RemoteException {
1131 if (Binder.isProxy(listener)) {
1132 listener.onUpdate(call.getCallId(), call.getState(), call.getNumber());
1133 } else {
1134 mMainThreadHandler.post(new Runnable() {
1135
1136 @Override
1137 public void run() {
1138 try {
1139 listener.onUpdate(call.getCallId(), call.getState(), call.getNumber());
1140 } catch (RemoteException e) {
1141 Log.wtf(LOG_TAG, "Local binder call failed with RemoteException.", e);
1142 }
1143 }
1144 });
1145 }
1146
1147 }
1148
1149 private class TelephonyListenerDeathRecipient implements Binder.DeathRecipient {
1150 private final IBinder mBinder;
1151
1152 public TelephonyListenerDeathRecipient(IBinder listener) {
1153 mBinder = listener;
1154 try {
1155 mBinder.linkToDeath(this, 0);
1156 } catch (RemoteException e) {
1157 unlinkDeathRecipient();
1158 }
1159 }
1160
1161 @Override
1162 public void binderDied() {
1163 synchronized (mListeners) {
1164 if (mListeners.contains(mBinder)) {
1165 mListeners.remove(mBinder);
1166 Log.w(LOG_TAG, "ITelephonyListener died. Removing.");
1167 } else {
1168 Log.w(LOG_TAG, "TelephonyListener binder died but the listener " +
1169 "is not registered.");
1170 }
1171 }
1172 }
1173
1174 public void unlinkDeathRecipient() {
1175 mBinder.unlinkToDeath(this, 0);
1176 }
1177 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001178}