blob: 31efd0fd6031aa44147a48c15c64229f6c971c08 [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) {
Jack Yu3128d9e2017-01-16 10:15:34 -0800755 enforceModifyPermission();
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700756 mPhone.setCellInfoListRate(rateInMillis);
757 }
758
759 //
760 // Internal helper methods.
761 //
762
763 private boolean checkIfCallerIsSelfOrForegoundUser() {
764 boolean ok;
765
766 boolean self = Binder.getCallingUid() == Process.myUid();
767 if (!self) {
768 // Get the caller's user id then clear the calling identity
769 // which will be restored in the finally clause.
770 int callingUser = UserHandle.getCallingUserId();
771 long ident = Binder.clearCallingIdentity();
772
773 try {
774 // With calling identity cleared the current user is the foreground user.
775 int foregroundUser = ActivityManager.getCurrentUser();
776 ok = (foregroundUser == callingUser);
777 if (DBG_LOC) {
778 log("checkIfCallerIsSelfOrForegoundUser: foregroundUser=" + foregroundUser
779 + " callingUser=" + callingUser + " ok=" + ok);
780 }
781 } catch (Exception ex) {
782 if (DBG_LOC) loge("checkIfCallerIsSelfOrForegoundUser: Exception ex=" + ex);
783 ok = false;
784 } finally {
785 Binder.restoreCallingIdentity(ident);
786 }
787 } else {
788 if (DBG_LOC) log("checkIfCallerIsSelfOrForegoundUser: is self");
789 ok = true;
790 }
791 if (DBG_LOC) log("checkIfCallerIsSelfOrForegoundUser: ret=" + ok);
792 return ok;
793 }
794
795 /**
796 * Make sure the caller has the READ_PHONE_STATE permission.
797 *
798 * @throws SecurityException if the caller does not have the required permission
799 */
800 private void enforceReadPermission() {
801 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE, null);
802 }
803
804 /**
805 * Make sure the caller has the MODIFY_PHONE_STATE permission.
806 *
807 * @throws SecurityException if the caller does not have the required permission
808 */
809 private void enforceModifyPermission() {
810 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
811 }
812
813 /**
814 * Make sure the caller has the CALL_PHONE permission.
815 *
816 * @throws SecurityException if the caller does not have the required permission
817 */
818 private void enforceCallPermission() {
819 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.CALL_PHONE, null);
820 }
821
Gabriel Peal805a72e2014-03-20 09:20:43 -0700822 /**
823 * Make sure the caller has the READ_PRIVILEGED_PHONE_STATE permission.
824 *
825 * @throws SecurityException if the caller does not have the required permission
826 */
827 private void enforcePrivilegedPhoneStatePermission() {
828 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
829 null);
830 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700831
832 private String createTelUrl(String number) {
833 if (TextUtils.isEmpty(number)) {
834 return null;
835 }
836
837 StringBuilder buf = new StringBuilder("tel:");
838 buf.append(number);
839 return buf.toString();
840 }
841
842 private void log(String msg) {
843 Log.d(LOG_TAG, "[PhoneIntfMgr] " + msg);
844 }
845
846 private void loge(String msg) {
847 Log.e(LOG_TAG, "[PhoneIntfMgr] " + msg);
848 }
849
850 public int getActivePhoneType() {
851 return mPhone.getPhoneType();
852 }
853
854 /**
855 * Returns the CDMA ERI icon index to display
856 */
857 public int getCdmaEriIconIndex() {
858 return mPhone.getCdmaEriIconIndex();
859 }
860
861 /**
862 * Returns the CDMA ERI icon mode,
863 * 0 - ON
864 * 1 - FLASHING
865 */
866 public int getCdmaEriIconMode() {
867 return mPhone.getCdmaEriIconMode();
868 }
869
870 /**
871 * Returns the CDMA ERI text,
872 */
873 public String getCdmaEriText() {
874 return mPhone.getCdmaEriText();
875 }
876
877 /**
878 * Returns true if CDMA provisioning needs to run.
879 */
880 public boolean needsOtaServiceProvisioning() {
881 return mPhone.needsOtaServiceProvisioning();
882 }
883
884 /**
885 * Returns the unread count of voicemails
886 */
887 public int getVoiceMessageCount() {
888 return mPhone.getVoiceMessageCount();
889 }
890
891 /**
892 * Returns the data network type
893 *
894 * @Deprecated to be removed Q3 2013 use {@link #getDataNetworkType}.
895 */
896 @Override
897 public int getNetworkType() {
898 return mPhone.getServiceState().getDataNetworkType();
899 }
900
901 /**
902 * Returns the data network type
903 */
904 @Override
905 public int getDataNetworkType() {
906 return mPhone.getServiceState().getDataNetworkType();
907 }
908
909 /**
910 * Returns the data network type
911 */
912 @Override
913 public int getVoiceNetworkType() {
914 return mPhone.getServiceState().getVoiceNetworkType();
915 }
916
917 /**
918 * @return true if a ICC card is present
919 */
920 public boolean hasIccCard() {
921 return mPhone.getIccCard().hasIccCard();
922 }
923
924 /**
925 * Return if the current radio is LTE on CDMA. This
926 * is a tri-state return value as for a period of time
927 * the mode may be unknown.
928 *
929 * @return {@link Phone#LTE_ON_CDMA_UNKNOWN}, {@link Phone#LTE_ON_CDMA_FALSE}
930 * or {@link PHone#LTE_ON_CDMA_TRUE}
931 */
932 public int getLteOnCdmaMode() {
933 return mPhone.getLteOnCdmaMode();
934 }
Gabriel Peal805a72e2014-03-20 09:20:43 -0700935
936 @Override
937 public void toggleHold() {
938 enforceModifyPermission();
939
940 try {
941 PhoneUtils.switchHoldingAndActive(mCM.getFirstActiveBgCall());
942 } catch (Exception e) {
943 Log.e(LOG_TAG, "Error during toggleHold().", e);
944 }
945 }
946
947 @Override
948 public void merge() {
949 enforceModifyPermission();
950
951 try {
952 if (PhoneUtils.okToMergeCalls(mCM)) {
953 PhoneUtils.mergeCalls(mCM);
954 }
955 } catch (Exception e) {
956 Log.e(LOG_TAG, "Error during merge().", e);
957 }
958 }
959
960 @Override
961 public void swap() {
962 enforceModifyPermission();
963
964 try {
965 PhoneUtils.swap();
966 } catch (Exception e) {
967 Log.e(LOG_TAG, "Error during swap().", e);
968 }
969 }
970
971 @Override
972 public void mute(boolean onOff) {
973 enforceModifyPermission();
974
975 try {
976 PhoneUtils.setMute(onOff);
977 } catch (Exception e) {
978 Log.e(LOG_TAG, "Error during mute().", e);
979 }
980 }
981
982 @Override
983 public void playDtmfTone(char digit, boolean timedShortTone) {
984 enforceModifyPermission();
985
986 synchronized (mDtmfStopHandler) {
987 try {
988 mDtmfTonePlayer.playDtmfTone(digit, timedShortTone);
989 } catch (Exception e) {
990 Log.e(LOG_TAG, "Error playing DTMF tone.", e);
991 }
992
993 if (mDtmfStopRunnable != null) {
994 mDtmfStopHandler.removeCallbacks(mDtmfStopRunnable);
995 }
996 mDtmfStopRunnable = new Runnable() {
997 @Override
998 public void run() {
999 synchronized (mDtmfStopHandler) {
1000 if (mDtmfStopRunnable == this) {
1001 mDtmfTonePlayer.stopDtmfTone();
1002 mDtmfStopRunnable = null;
1003 }
1004 }
1005 }
1006 };
1007 mDtmfStopHandler.postDelayed(mDtmfStopRunnable, 5000);
1008 }
1009 }
1010
1011 @Override
1012 public void stopDtmfTone() {
1013 enforceModifyPermission();
1014
1015 synchronized (mDtmfStopHandler) {
1016 try {
1017 mDtmfTonePlayer.stopDtmfTone();
1018 } catch (Exception e) {
1019 Log.e(LOG_TAG, "Error stopping DTMF tone.", e);
1020 }
1021
1022 if (mDtmfStopRunnable != null) {
1023 mDtmfStopHandler.removeCallbacks(mDtmfStopRunnable);
1024 mDtmfStopRunnable = null;
1025 }
1026 }
1027 }
1028
1029 @Override
1030 public void addListener(ITelephonyListener listener) {
1031 enforcePrivilegedPhoneStatePermission();
1032
1033 if (listener == null) {
1034 throw new IllegalArgumentException("Listener must not be null.");
1035 }
1036
1037 synchronized (mListeners) {
1038 IBinder listenerBinder = listener.asBinder();
1039 for (ITelephonyListener l : mListeners) {
1040 if (l.asBinder().equals(listenerBinder)) {
1041 Log.w(LOG_TAG, "Listener already registered. Ignoring.");
1042 return;
1043 }
1044 }
1045 mListeners.add(listener);
1046 mDeathRecipients.put(listener.asBinder(),
1047 new TelephonyListenerDeathRecipient(listener.asBinder()));
1048
1049 // update the new listener so they get the full call state immediately
1050 for (Call call : mCallModeler.getFullList()) {
1051 try {
1052 notifyListenerOfCallLocked(call, listener);
1053 } catch (RemoteException e) {
1054 Log.e(LOG_TAG, "Error updating new listener. Ignoring.");
1055 removeListenerInternal(listener);
1056 }
1057 }
1058 }
1059 }
1060
1061 @Override
1062 public void removeListener(ITelephonyListener listener) {
1063 enforcePrivilegedPhoneStatePermission();
1064
1065 if (listener == null) {
1066 throw new IllegalArgumentException("Listener must not be null.");
1067 }
1068
1069 removeListenerInternal(listener);
1070 }
1071
1072 private void removeListenerInternal(ITelephonyListener listener) {
1073 IBinder listenerBinder = listener.asBinder();
1074
1075 synchronized (mListeners) {
1076 for (Iterator<ITelephonyListener> it = mListeners.iterator(); it.hasNext(); ) {
1077 ITelephonyListener nextListener = it.next();
1078 if (nextListener.asBinder().equals(listenerBinder)) {
1079 TelephonyListenerDeathRecipient dr = mDeathRecipients.get(listener.asBinder());
1080 if (dr != null) {
1081 dr.unlinkDeathRecipient();
1082 }
1083 it.remove();
1084 }
1085 }
1086 }
1087 }
1088
1089 /** CallModeler.Listener implementation **/
1090
1091 @Override
1092 public void onDisconnect(Call call) {
1093 notifyListenersOfCall(call);
1094 }
1095
1096 @Override
1097 public void onIncoming(Call call) {
1098 notifyListenersOfCall(call);
1099 }
1100
1101 @Override
1102 public void onUpdate(List<Call> calls) {
1103 for (Call call : calls) {
1104 notifyListenersOfCall(call);
1105 }
1106 }
1107
1108 @Override
1109 public void onPostDialAction(
1110 Connection.PostDialState state, int callId, String remainingChars, char c) { }
1111
1112 private void notifyListenersOfCall(Call call) {
1113 synchronized (mListeners) {
1114 for (Iterator<ITelephonyListener> it = mListeners.iterator(); it.hasNext(); ) {
1115 ITelephonyListener listener = it.next();
1116 try {
1117 notifyListenerOfCallLocked(call, listener);
1118 } catch (RemoteException e) {
1119 TelephonyListenerDeathRecipient deathRecipient =
1120 mDeathRecipients.get(listener.asBinder());
1121 if (deathRecipient != null) {
1122 deathRecipient.unlinkDeathRecipient();
1123 }
1124 it.remove();
1125 }
1126 }
1127 }
1128 }
1129
1130 private void notifyListenerOfCallLocked(final Call call,final ITelephonyListener listener)
1131 throws RemoteException {
1132 if (Binder.isProxy(listener)) {
1133 listener.onUpdate(call.getCallId(), call.getState(), call.getNumber());
1134 } else {
1135 mMainThreadHandler.post(new Runnable() {
1136
1137 @Override
1138 public void run() {
1139 try {
1140 listener.onUpdate(call.getCallId(), call.getState(), call.getNumber());
1141 } catch (RemoteException e) {
1142 Log.wtf(LOG_TAG, "Local binder call failed with RemoteException.", e);
1143 }
1144 }
1145 });
1146 }
1147
1148 }
1149
1150 private class TelephonyListenerDeathRecipient implements Binder.DeathRecipient {
1151 private final IBinder mBinder;
1152
1153 public TelephonyListenerDeathRecipient(IBinder listener) {
1154 mBinder = listener;
1155 try {
1156 mBinder.linkToDeath(this, 0);
1157 } catch (RemoteException e) {
1158 unlinkDeathRecipient();
1159 }
1160 }
1161
1162 @Override
1163 public void binderDied() {
1164 synchronized (mListeners) {
1165 if (mListeners.contains(mBinder)) {
1166 mListeners.remove(mBinder);
1167 Log.w(LOG_TAG, "ITelephonyListener died. Removing.");
1168 } else {
1169 Log.w(LOG_TAG, "TelephonyListener binder died but the listener " +
1170 "is not registered.");
1171 }
1172 }
1173 }
1174
1175 public void unlinkDeathRecipient() {
1176 mBinder.unlinkToDeath(this, 0);
1177 }
1178 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -07001179}