blob: 9ad47ace210425b3c73056737ace5ab8b187738e [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;
21import android.content.ActivityNotFoundException;
Sailesh Nepalbd76e4e2013-10-27 13:59:44 -070022import android.content.ComponentName;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070023import 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;
31import android.os.Looper;
32import android.os.Message;
33import android.os.Process;
34import android.os.ServiceManager;
35import android.os.UserHandle;
36import android.telephony.NeighboringCellInfo;
37import android.telephony.CellInfo;
38import android.telephony.ServiceState;
39import android.text.TextUtils;
40import android.util.Log;
41
42import com.android.internal.telephony.DefaultPhoneNotifier;
43import com.android.internal.telephony.IccCard;
44import com.android.internal.telephony.ITelephony;
Sailesh Nepalbd76e4e2013-10-27 13:59:44 -070045import com.android.internal.telephony.IThirdPartyCallProvider;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070046import com.android.internal.telephony.Phone;
47import com.android.internal.telephony.CallManager;
Wink Saville9de0f752013-10-22 19:04:03 -070048import com.android.internal.telephony.CommandException;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070049import com.android.internal.telephony.PhoneConstants;
Sailesh Nepalbd76e4e2013-10-27 13:59:44 -070050import com.android.internal.telephony.thirdpartyphone.ThirdPartyPhone;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070051
52import java.util.List;
53import java.util.ArrayList;
54
55/**
56 * Implementation of the ITelephony interface.
57 */
58public class PhoneInterfaceManager extends ITelephony.Stub {
59 private static final String LOG_TAG = "PhoneInterfaceManager";
60 private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
61 private static final boolean DBG_LOC = false;
62
63 // Message codes used with mMainThreadHandler
64 private static final int CMD_HANDLE_PIN_MMI = 1;
65 private static final int CMD_HANDLE_NEIGHBORING_CELL = 2;
66 private static final int EVENT_NEIGHBORING_CELL_DONE = 3;
67 private static final int CMD_ANSWER_RINGING_CALL = 4;
68 private static final int CMD_END_CALL = 5; // not used yet
69 private static final int CMD_SILENCE_RINGER = 6;
Sailesh Nepalbd76e4e2013-10-27 13:59:44 -070070 private static final int CMD_NEW_INCOMING_THIRD_PARTY_CALL = 7;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070071
72 /** The singleton instance. */
73 private static PhoneInterfaceManager sInstance;
74
75 PhoneGlobals mApp;
76 Phone mPhone;
77 CallManager mCM;
78 AppOpsManager mAppOps;
79 MainThreadHandler mMainThreadHandler;
Santos Cordon406c0342013-08-28 00:07:47 -070080 CallHandlerServiceProxy mCallHandlerService;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070081
82 /**
83 * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
84 * request after sending. The main thread will notify the request when it is complete.
85 */
86 private static final class MainThreadRequest {
87 /** The argument to use for the request */
88 public Object argument;
89 /** The result of the request that is run on the main thread */
90 public Object result;
91
92 public MainThreadRequest(Object argument) {
93 this.argument = argument;
94 }
95 }
96
Sailesh Nepalcc0375f2013-11-13 09:15:18 -080097 private static final class IncomingThirdPartyCallArgs {
98 public final ComponentName component;
99 public final String callId;
100 public final String callerDisplayName;
101
102 public IncomingThirdPartyCallArgs(ComponentName component, String callId,
103 String callerDisplayName) {
104 this.component = component;
105 this.callId = callId;
106 this.callerDisplayName = callerDisplayName;
107 }
108 }
109
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700110 /**
111 * A handler that processes messages on the main thread in the phone process. Since many
112 * of the Phone calls are not thread safe this is needed to shuttle the requests from the
113 * inbound binder threads to the main thread in the phone process. The Binder thread
114 * may provide a {@link MainThreadRequest} object in the msg.obj field that they are waiting
115 * on, which will be notified when the operation completes and will contain the result of the
116 * request.
117 *
118 * <p>If a MainThreadRequest object is provided in the msg.obj field,
119 * note that request.result must be set to something non-null for the calling thread to
120 * unblock.
121 */
122 private final class MainThreadHandler extends Handler {
123 @Override
124 public void handleMessage(Message msg) {
125 MainThreadRequest request;
126 Message onCompleted;
127 AsyncResult ar;
128
129 switch (msg.what) {
130 case CMD_HANDLE_PIN_MMI:
131 request = (MainThreadRequest) msg.obj;
132 request.result = Boolean.valueOf(
133 mPhone.handlePinMmi((String) request.argument));
134 // Wake up the requesting thread
135 synchronized (request) {
136 request.notifyAll();
137 }
138 break;
139
140 case CMD_HANDLE_NEIGHBORING_CELL:
141 request = (MainThreadRequest) msg.obj;
142 onCompleted = obtainMessage(EVENT_NEIGHBORING_CELL_DONE,
143 request);
144 mPhone.getNeighboringCids(onCompleted);
145 break;
146
147 case EVENT_NEIGHBORING_CELL_DONE:
148 ar = (AsyncResult) msg.obj;
149 request = (MainThreadRequest) ar.userObj;
150 if (ar.exception == null && ar.result != null) {
151 request.result = ar.result;
152 } else {
153 // create an empty list to notify the waiting thread
154 request.result = new ArrayList<NeighboringCellInfo>();
155 }
156 // Wake up the requesting thread
157 synchronized (request) {
158 request.notifyAll();
159 }
160 break;
161
162 case CMD_ANSWER_RINGING_CALL:
163 answerRingingCallInternal();
164 break;
165
166 case CMD_SILENCE_RINGER:
167 silenceRingerInternal();
168 break;
169
170 case CMD_END_CALL:
171 request = (MainThreadRequest) msg.obj;
172 boolean hungUp = false;
173 int phoneType = mPhone.getPhoneType();
174 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
175 // CDMA: If the user presses the Power button we treat it as
176 // ending the complete call session
177 hungUp = PhoneUtils.hangupRingingAndActive(mPhone);
178 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
179 // GSM: End the call as per the Phone state
180 hungUp = PhoneUtils.hangup(mCM);
181 } else {
182 throw new IllegalStateException("Unexpected phone type: " + phoneType);
183 }
184 if (DBG) log("CMD_END_CALL: " + (hungUp ? "hung up!" : "no call to hang up"));
185 request.result = hungUp;
186 // Wake up the requesting thread
187 synchronized (request) {
188 request.notifyAll();
189 }
190 break;
Sailesh Nepalbd76e4e2013-10-27 13:59:44 -0700191 case CMD_NEW_INCOMING_THIRD_PARTY_CALL: {
192 request = (MainThreadRequest) msg.obj;
Sailesh Nepalcc0375f2013-11-13 09:15:18 -0800193 IncomingThirdPartyCallArgs args = (IncomingThirdPartyCallArgs) request.argument;
Sailesh Nepalbfb68322013-11-07 14:07:41 -0800194 ThirdPartyPhone thirdPartyPhone = (ThirdPartyPhone)
Sailesh Nepalcc0375f2013-11-13 09:15:18 -0800195 PhoneUtils.getThirdPartyPhoneFromComponent(mCM, args.component);
196 thirdPartyPhone.takeIncomingCall(args.callId, args.callerDisplayName);
Sailesh Nepalbd76e4e2013-10-27 13:59:44 -0700197 break;
198 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700199
200 default:
201 Log.w(LOG_TAG, "MainThreadHandler: unexpected message code: " + msg.what);
202 break;
203 }
204 }
205 }
206
207 /**
208 * Posts the specified command to be executed on the main thread,
209 * waits for the request to complete, and returns the result.
210 * @see #sendRequestAsync
211 */
212 private Object sendRequest(int command, Object argument) {
213 if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
214 throw new RuntimeException("This method will deadlock if called from the main thread.");
215 }
216
217 MainThreadRequest request = new MainThreadRequest(argument);
218 Message msg = mMainThreadHandler.obtainMessage(command, request);
219 msg.sendToTarget();
220
221 // Wait for the request to complete
222 synchronized (request) {
223 while (request.result == null) {
224 try {
225 request.wait();
226 } catch (InterruptedException e) {
227 // Do nothing, go back and wait until the request is complete
228 }
229 }
230 }
231 return request.result;
232 }
233
234 /**
235 * Asynchronous ("fire and forget") version of sendRequest():
236 * Posts the specified command to be executed on the main thread, and
237 * returns immediately.
238 * @see #sendRequest
239 */
240 private void sendRequestAsync(int command) {
241 mMainThreadHandler.sendEmptyMessage(command);
242 }
243
244 /**
Sailesh Nepalbd76e4e2013-10-27 13:59:44 -0700245 * Same as {@link #sendRequestAsync(int)} except it takes an argument.
246 * @see {@link #sendRequest(int,Object)}
247 */
248 private void sendRequestAsync(int command, Object argument) {
249 MainThreadRequest request = new MainThreadRequest(argument);
250 Message msg = mMainThreadHandler.obtainMessage(command, request);
251 msg.sendToTarget();
252 }
253
254 /**
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700255 * Initialize the singleton PhoneInterfaceManager instance.
256 * This is only done once, at startup, from PhoneApp.onCreate().
257 */
Santos Cordon406c0342013-08-28 00:07:47 -0700258 /* package */ static PhoneInterfaceManager init(PhoneGlobals app, Phone phone,
259 CallHandlerServiceProxy callHandlerService) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700260 synchronized (PhoneInterfaceManager.class) {
261 if (sInstance == null) {
Santos Cordon406c0342013-08-28 00:07:47 -0700262 sInstance = new PhoneInterfaceManager(app, phone, callHandlerService);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700263 } else {
264 Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
265 }
266 return sInstance;
267 }
268 }
269
270 /** Private constructor; @see init() */
Santos Cordon406c0342013-08-28 00:07:47 -0700271 private PhoneInterfaceManager(PhoneGlobals app, Phone phone,
272 CallHandlerServiceProxy callHandlerService) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700273 mApp = app;
274 mPhone = phone;
275 mCM = PhoneGlobals.getInstance().mCM;
276 mAppOps = (AppOpsManager)app.getSystemService(Context.APP_OPS_SERVICE);
277 mMainThreadHandler = new MainThreadHandler();
Santos Cordon406c0342013-08-28 00:07:47 -0700278 mCallHandlerService = callHandlerService;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700279 publish();
280 }
281
282 private void publish() {
283 if (DBG) log("publish: " + this);
284
285 ServiceManager.addService("phone", this);
286 }
287
288 //
289 // Implementation of the ITelephony interface.
290 //
291
292 public void dial(String number) {
293 if (DBG) log("dial: " + number);
294 // No permission check needed here: This is just a wrapper around the
295 // ACTION_DIAL intent, which is available to any app since it puts up
296 // the UI before it does anything.
297
298 String url = createTelUrl(number);
299 if (url == null) {
300 return;
301 }
302
303 // PENDING: should we just silently fail if phone is offhook or ringing?
304 PhoneConstants.State state = mCM.getState();
305 if (state != PhoneConstants.State.OFFHOOK && state != PhoneConstants.State.RINGING) {
306 Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
307 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
308 mApp.startActivity(intent);
309 }
310 }
311
312 public void call(String callingPackage, String number) {
313 if (DBG) log("call: " + number);
314
315 // This is just a wrapper around the ACTION_CALL intent, but we still
316 // need to do a permission check since we're calling startActivity()
317 // from the context of the phone app.
318 enforceCallPermission();
319
320 if (mAppOps.noteOp(AppOpsManager.OP_CALL_PHONE, Binder.getCallingUid(), callingPackage)
321 != AppOpsManager.MODE_ALLOWED) {
322 return;
323 }
324
325 String url = createTelUrl(number);
326 if (url == null) {
327 return;
328 }
329
330 Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(url));
331 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
332 mApp.startActivity(intent);
333 }
334
335 private boolean showCallScreenInternal(boolean specifyInitialDialpadState,
Makoto Onukibcf20992013-09-12 17:59:30 -0700336 boolean showDialpad) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700337 if (!PhoneGlobals.sVoiceCapable) {
338 // Never allow the InCallScreen to appear on data-only devices.
339 return false;
340 }
341 if (isIdle()) {
342 return false;
343 }
344 // If the phone isn't idle then go to the in-call screen
345 long callingId = Binder.clearCallingIdentity();
Santos Cordon406c0342013-08-28 00:07:47 -0700346
Makoto Onukibcf20992013-09-12 17:59:30 -0700347 mCallHandlerService.bringToForeground(showDialpad);
Santos Cordon406c0342013-08-28 00:07:47 -0700348
349 Binder.restoreCallingIdentity(callingId);
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700350 return true;
351 }
352
353 // Show the in-call screen without specifying the initial dialpad state.
354 public boolean showCallScreen() {
355 return showCallScreenInternal(false, false);
356 }
357
358 // The variation of showCallScreen() that specifies the initial dialpad state.
359 // (Ideally this would be called showCallScreen() too, just with a different
360 // signature, but AIDL doesn't allow that.)
361 public boolean showCallScreenWithDialpad(boolean showDialpad) {
362 return showCallScreenInternal(true, showDialpad);
363 }
364
365 /**
366 * End a call based on call state
367 * @return true is a call was ended
368 */
369 public boolean endCall() {
370 enforceCallPermission();
371 return (Boolean) sendRequest(CMD_END_CALL, null);
372 }
373
374 public void answerRingingCall() {
375 if (DBG) log("answerRingingCall...");
376 // TODO: there should eventually be a separate "ANSWER_PHONE" permission,
377 // but that can probably wait till the big TelephonyManager API overhaul.
378 // For now, protect this call with the MODIFY_PHONE_STATE permission.
379 enforceModifyPermission();
380 sendRequestAsync(CMD_ANSWER_RINGING_CALL);
381 }
382
383 /**
384 * Make the actual telephony calls to implement answerRingingCall().
385 * This should only be called from the main thread of the Phone app.
386 * @see #answerRingingCall
387 *
388 * TODO: it would be nice to return true if we answered the call, or
389 * false if there wasn't actually a ringing incoming call, or some
390 * other error occurred. (In other words, pass back the return value
391 * from PhoneUtils.answerCall() or PhoneUtils.answerAndEndActive().)
392 * But that would require calling this method via sendRequest() rather
393 * than sendRequestAsync(), and right now we don't actually *need* that
394 * return value, so let's just return void for now.
395 */
396 private void answerRingingCallInternal() {
397 final boolean hasRingingCall = !mPhone.getRingingCall().isIdle();
398 if (hasRingingCall) {
399 final boolean hasActiveCall = !mPhone.getForegroundCall().isIdle();
400 final boolean hasHoldingCall = !mPhone.getBackgroundCall().isIdle();
401 if (hasActiveCall && hasHoldingCall) {
402 // Both lines are in use!
403 // TODO: provide a flag to let the caller specify what
404 // policy to use if both lines are in use. (The current
405 // behavior is hardwired to "answer incoming, end ongoing",
406 // which is how the CALL button is specced to behave.)
407 PhoneUtils.answerAndEndActive(mCM, mCM.getFirstActiveRingingCall());
408 return;
409 } else {
410 // answerCall() will automatically hold the current active
411 // call, if there is one.
412 PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
413 return;
414 }
415 } else {
416 // No call was ringing.
417 return;
418 }
419 }
420
421 public void silenceRinger() {
422 if (DBG) log("silenceRinger...");
423 // TODO: find a more appropriate permission to check here.
424 // (That can probably wait till the big TelephonyManager API overhaul.
425 // For now, protect this call with the MODIFY_PHONE_STATE permission.)
426 enforceModifyPermission();
427 sendRequestAsync(CMD_SILENCE_RINGER);
428 }
429
430 /**
431 * Internal implemenation of silenceRinger().
432 * This should only be called from the main thread of the Phone app.
433 * @see #silenceRinger
434 */
435 private void silenceRingerInternal() {
436 if ((mCM.getState() == PhoneConstants.State.RINGING)
437 && mApp.notifier.isRinging()) {
438 // Ringer is actually playing, so silence it.
439 if (DBG) log("silenceRingerInternal: silencing...");
440 mApp.notifier.silenceRinger();
441 }
442 }
443
444 public boolean isOffhook() {
445 return (mCM.getState() == PhoneConstants.State.OFFHOOK);
446 }
447
448 public boolean isRinging() {
449 return (mCM.getState() == PhoneConstants.State.RINGING);
450 }
451
452 public boolean isIdle() {
453 return (mCM.getState() == PhoneConstants.State.IDLE);
454 }
455
456 public boolean isSimPinEnabled() {
457 enforceReadPermission();
458 return (PhoneGlobals.getInstance().isSimPinEnabled());
459 }
460
461 public boolean supplyPin(String pin) {
Wink Saville9de0f752013-10-22 19:04:03 -0700462 int [] resultArray = supplyPinReportResult(pin);
463 return (resultArray[0] == PhoneConstants.PIN_RESULT_SUCCESS) ? true : false;
464 }
465
466 public boolean supplyPuk(String puk, String pin) {
467 int [] resultArray = supplyPukReportResult(puk, pin);
468 return (resultArray[0] == PhoneConstants.PIN_RESULT_SUCCESS) ? true : false;
469 }
470
471 /** {@hide} */
472 public int[] supplyPinReportResult(String pin) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700473 enforceModifyPermission();
474 final UnlockSim checkSimPin = new UnlockSim(mPhone.getIccCard());
475 checkSimPin.start();
476 return checkSimPin.unlockSim(null, pin);
477 }
478
Wink Saville9de0f752013-10-22 19:04:03 -0700479 /** {@hide} */
480 public int[] supplyPukReportResult(String puk, String pin) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700481 enforceModifyPermission();
482 final UnlockSim checkSimPuk = new UnlockSim(mPhone.getIccCard());
483 checkSimPuk.start();
484 return checkSimPuk.unlockSim(puk, pin);
485 }
486
487 /**
Wink Saville9de0f752013-10-22 19:04:03 -0700488 * Helper thread to turn async call to SimCard#supplyPin into
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700489 * a synchronous one.
490 */
491 private static class UnlockSim extends Thread {
492
493 private final IccCard mSimCard;
494
495 private boolean mDone = false;
Wink Saville9de0f752013-10-22 19:04:03 -0700496 private int mResult = PhoneConstants.PIN_GENERAL_FAILURE;
497 private int mRetryCount = -1;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700498
499 // For replies from SimCard interface
500 private Handler mHandler;
501
502 // For async handler to identify request type
503 private static final int SUPPLY_PIN_COMPLETE = 100;
504
505 public UnlockSim(IccCard simCard) {
506 mSimCard = simCard;
507 }
508
509 @Override
510 public void run() {
511 Looper.prepare();
512 synchronized (UnlockSim.this) {
513 mHandler = new Handler() {
514 @Override
515 public void handleMessage(Message msg) {
516 AsyncResult ar = (AsyncResult) msg.obj;
517 switch (msg.what) {
518 case SUPPLY_PIN_COMPLETE:
519 Log.d(LOG_TAG, "SUPPLY_PIN_COMPLETE");
520 synchronized (UnlockSim.this) {
Wink Saville9de0f752013-10-22 19:04:03 -0700521 mRetryCount = msg.arg1;
522 if (ar.exception != null) {
523 if (ar.exception instanceof CommandException &&
524 ((CommandException)(ar.exception)).getCommandError()
525 == CommandException.Error.PASSWORD_INCORRECT) {
526 mResult = PhoneConstants.PIN_PASSWORD_INCORRECT;
527 } else {
528 mResult = PhoneConstants.PIN_GENERAL_FAILURE;
529 }
530 } else {
531 mResult = PhoneConstants.PIN_RESULT_SUCCESS;
532 }
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700533 mDone = true;
534 UnlockSim.this.notifyAll();
535 }
536 break;
537 }
538 }
539 };
540 UnlockSim.this.notifyAll();
541 }
542 Looper.loop();
543 }
544
545 /*
546 * Use PIN or PUK to unlock SIM card
547 *
548 * If PUK is null, unlock SIM card with PIN
549 *
550 * If PUK is not null, unlock SIM card with PUK and set PIN code
551 */
Wink Saville9de0f752013-10-22 19:04:03 -0700552 synchronized int[] unlockSim(String puk, String pin) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700553
554 while (mHandler == null) {
555 try {
556 wait();
557 } catch (InterruptedException e) {
558 Thread.currentThread().interrupt();
559 }
560 }
561 Message callback = Message.obtain(mHandler, SUPPLY_PIN_COMPLETE);
562
563 if (puk == null) {
564 mSimCard.supplyPin(pin, callback);
565 } else {
566 mSimCard.supplyPuk(puk, pin, callback);
567 }
568
569 while (!mDone) {
570 try {
571 Log.d(LOG_TAG, "wait for done");
572 wait();
573 } catch (InterruptedException e) {
574 // Restore the interrupted status
575 Thread.currentThread().interrupt();
576 }
577 }
578 Log.d(LOG_TAG, "done");
Wink Saville9de0f752013-10-22 19:04:03 -0700579 int[] resultArray = new int[2];
580 resultArray[0] = mResult;
581 resultArray[1] = mRetryCount;
582 return resultArray;
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700583 }
584 }
585
586 public void updateServiceLocation() {
587 // No permission check needed here: this call is harmless, and it's
588 // needed for the ServiceState.requestStateUpdate() call (which is
589 // already intentionally exposed to 3rd parties.)
590 mPhone.updateServiceLocation();
591 }
592
593 public boolean isRadioOn() {
594 return mPhone.getServiceState().getVoiceRegState() != ServiceState.STATE_POWER_OFF;
595 }
596
597 public void toggleRadioOnOff() {
598 enforceModifyPermission();
599 mPhone.setRadioPower(!isRadioOn());
600 }
601 public boolean setRadio(boolean turnOn) {
602 enforceModifyPermission();
603 if ((mPhone.getServiceState().getVoiceRegState() != ServiceState.STATE_POWER_OFF) != turnOn) {
604 toggleRadioOnOff();
605 }
606 return true;
607 }
608 public boolean setRadioPower(boolean turnOn) {
609 enforceModifyPermission();
610 mPhone.setRadioPower(turnOn);
611 return true;
612 }
613
614 public boolean enableDataConnectivity() {
615 enforceModifyPermission();
616 ConnectivityManager cm =
617 (ConnectivityManager)mApp.getSystemService(Context.CONNECTIVITY_SERVICE);
618 cm.setMobileDataEnabled(true);
619 return true;
620 }
621
622 public int enableApnType(String type) {
623 enforceModifyPermission();
624 return mPhone.enableApnType(type);
625 }
626
627 public int disableApnType(String type) {
628 enforceModifyPermission();
629 return mPhone.disableApnType(type);
630 }
631
632 public boolean disableDataConnectivity() {
633 enforceModifyPermission();
634 ConnectivityManager cm =
635 (ConnectivityManager)mApp.getSystemService(Context.CONNECTIVITY_SERVICE);
636 cm.setMobileDataEnabled(false);
637 return true;
638 }
639
640 public boolean isDataConnectivityPossible() {
641 return mPhone.isDataConnectivityPossible();
642 }
643
644 public boolean handlePinMmi(String dialString) {
645 enforceModifyPermission();
646 return (Boolean) sendRequest(CMD_HANDLE_PIN_MMI, dialString);
647 }
648
649 public void cancelMissedCallsNotification() {
650 enforceModifyPermission();
651 mApp.notificationMgr.cancelMissedCallNotification();
652 }
653
654 public int getCallState() {
655 return DefaultPhoneNotifier.convertCallState(mCM.getState());
656 }
657
658 public int getDataState() {
659 return DefaultPhoneNotifier.convertDataState(mPhone.getDataConnectionState());
660 }
661
662 public int getDataActivity() {
663 return DefaultPhoneNotifier.convertDataActivityState(mPhone.getDataActivityState());
664 }
665
666 @Override
667 public Bundle getCellLocation() {
668 try {
669 mApp.enforceCallingOrSelfPermission(
670 android.Manifest.permission.ACCESS_FINE_LOCATION, null);
671 } catch (SecurityException e) {
672 // If we have ACCESS_FINE_LOCATION permission, skip the check for ACCESS_COARSE_LOCATION
673 // A failure should throw the SecurityException from ACCESS_COARSE_LOCATION since this
674 // is the weaker precondition
675 mApp.enforceCallingOrSelfPermission(
676 android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
677 }
678
679 if (checkIfCallerIsSelfOrForegoundUser()) {
680 if (DBG_LOC) log("getCellLocation: is active user");
681 Bundle data = new Bundle();
682 mPhone.getCellLocation().fillInNotifierBundle(data);
683 return data;
684 } else {
685 if (DBG_LOC) log("getCellLocation: suppress non-active user");
686 return null;
687 }
688 }
689
690 @Override
691 public void enableLocationUpdates() {
692 mApp.enforceCallingOrSelfPermission(
693 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
694 mPhone.enableLocationUpdates();
695 }
696
697 @Override
698 public void disableLocationUpdates() {
699 mApp.enforceCallingOrSelfPermission(
700 android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
701 mPhone.disableLocationUpdates();
702 }
703
704 @Override
705 @SuppressWarnings("unchecked")
706 public List<NeighboringCellInfo> getNeighboringCellInfo(String callingPackage) {
707 try {
708 mApp.enforceCallingOrSelfPermission(
709 android.Manifest.permission.ACCESS_FINE_LOCATION, null);
710 } catch (SecurityException e) {
711 // If we have ACCESS_FINE_LOCATION permission, skip the check
712 // for ACCESS_COARSE_LOCATION
713 // A failure should throw the SecurityException from
714 // ACCESS_COARSE_LOCATION since this is the weaker precondition
715 mApp.enforceCallingOrSelfPermission(
716 android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
717 }
718
719 if (mAppOps.noteOp(AppOpsManager.OP_NEIGHBORING_CELLS, Binder.getCallingUid(),
720 callingPackage) != AppOpsManager.MODE_ALLOWED) {
721 return null;
722 }
723 if (checkIfCallerIsSelfOrForegoundUser()) {
724 if (DBG_LOC) log("getNeighboringCellInfo: is active user");
725
726 ArrayList<NeighboringCellInfo> cells = null;
727
728 try {
729 cells = (ArrayList<NeighboringCellInfo>) sendRequest(
730 CMD_HANDLE_NEIGHBORING_CELL, null);
731 } catch (RuntimeException e) {
732 Log.e(LOG_TAG, "getNeighboringCellInfo " + e);
733 }
734 return cells;
735 } else {
736 if (DBG_LOC) log("getNeighboringCellInfo: suppress non-active user");
737 return null;
738 }
739 }
740
741
742 @Override
743 public List<CellInfo> getAllCellInfo() {
744 try {
745 mApp.enforceCallingOrSelfPermission(
746 android.Manifest.permission.ACCESS_FINE_LOCATION, null);
747 } catch (SecurityException e) {
748 // If we have ACCESS_FINE_LOCATION permission, skip the check for ACCESS_COARSE_LOCATION
749 // A failure should throw the SecurityException from ACCESS_COARSE_LOCATION since this
750 // is the weaker precondition
751 mApp.enforceCallingOrSelfPermission(
752 android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
753 }
754
755 if (checkIfCallerIsSelfOrForegoundUser()) {
756 if (DBG_LOC) log("getAllCellInfo: is active user");
757 return mPhone.getAllCellInfo();
758 } else {
759 if (DBG_LOC) log("getAllCellInfo: suppress non-active user");
760 return null;
761 }
762 }
763
Sailesh Nepalbd76e4e2013-10-27 13:59:44 -0700764 @Override
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700765 public void setCellInfoListRate(int rateInMillis) {
766 mPhone.setCellInfoListRate(rateInMillis);
767 }
768
Sailesh Nepalbd76e4e2013-10-27 13:59:44 -0700769 @Override
Sailesh Nepalcc0375f2013-11-13 09:15:18 -0800770 public void newIncomingThirdPartyCall(ComponentName component, String callId,
771 String callerDisplayName) {
Sailesh Nepalbd76e4e2013-10-27 13:59:44 -0700772 // TODO(sail): Enforce that the component belongs to the calling package.
773 if (DBG) {
774 log("newIncomingThirdPartyCall: component: " + component + " callId: " + callId);
775 }
776 enforceCallPermission();
Sailesh Nepalcc0375f2013-11-13 09:15:18 -0800777 sendRequestAsync(CMD_NEW_INCOMING_THIRD_PARTY_CALL, new IncomingThirdPartyCallArgs(
778 component, callId, callerDisplayName));
Sailesh Nepalbd76e4e2013-10-27 13:59:44 -0700779 }
780
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700781 //
782 // Internal helper methods.
783 //
784
785 private boolean checkIfCallerIsSelfOrForegoundUser() {
786 boolean ok;
787
788 boolean self = Binder.getCallingUid() == Process.myUid();
789 if (!self) {
790 // Get the caller's user id then clear the calling identity
791 // which will be restored in the finally clause.
792 int callingUser = UserHandle.getCallingUserId();
793 long ident = Binder.clearCallingIdentity();
794
795 try {
796 // With calling identity cleared the current user is the foreground user.
797 int foregroundUser = ActivityManager.getCurrentUser();
798 ok = (foregroundUser == callingUser);
799 if (DBG_LOC) {
800 log("checkIfCallerIsSelfOrForegoundUser: foregroundUser=" + foregroundUser
801 + " callingUser=" + callingUser + " ok=" + ok);
802 }
803 } catch (Exception ex) {
804 if (DBG_LOC) loge("checkIfCallerIsSelfOrForegoundUser: Exception ex=" + ex);
805 ok = false;
806 } finally {
807 Binder.restoreCallingIdentity(ident);
808 }
809 } else {
810 if (DBG_LOC) log("checkIfCallerIsSelfOrForegoundUser: is self");
811 ok = true;
812 }
813 if (DBG_LOC) log("checkIfCallerIsSelfOrForegoundUser: ret=" + ok);
814 return ok;
815 }
816
817 /**
818 * Make sure the caller has the READ_PHONE_STATE permission.
819 *
820 * @throws SecurityException if the caller does not have the required permission
821 */
822 private void enforceReadPermission() {
823 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE, null);
824 }
825
826 /**
827 * Make sure the caller has the MODIFY_PHONE_STATE permission.
828 *
829 * @throws SecurityException if the caller does not have the required permission
830 */
831 private void enforceModifyPermission() {
832 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
833 }
834
835 /**
836 * Make sure the caller has the CALL_PHONE permission.
837 *
838 * @throws SecurityException if the caller does not have the required permission
839 */
840 private void enforceCallPermission() {
841 mApp.enforceCallingOrSelfPermission(android.Manifest.permission.CALL_PHONE, null);
842 }
843
844
845 private String createTelUrl(String number) {
846 if (TextUtils.isEmpty(number)) {
847 return null;
848 }
849
850 StringBuilder buf = new StringBuilder("tel:");
851 buf.append(number);
852 return buf.toString();
853 }
854
855 private void log(String msg) {
856 Log.d(LOG_TAG, "[PhoneIntfMgr] " + msg);
857 }
858
859 private void loge(String msg) {
860 Log.e(LOG_TAG, "[PhoneIntfMgr] " + msg);
861 }
862
863 public int getActivePhoneType() {
864 return mPhone.getPhoneType();
865 }
866
867 /**
868 * Returns the CDMA ERI icon index to display
869 */
870 public int getCdmaEriIconIndex() {
871 return mPhone.getCdmaEriIconIndex();
872 }
873
874 /**
875 * Returns the CDMA ERI icon mode,
876 * 0 - ON
877 * 1 - FLASHING
878 */
879 public int getCdmaEriIconMode() {
880 return mPhone.getCdmaEriIconMode();
881 }
882
883 /**
884 * Returns the CDMA ERI text,
885 */
886 public String getCdmaEriText() {
887 return mPhone.getCdmaEriText();
888 }
889
890 /**
891 * Returns true if CDMA provisioning needs to run.
892 */
893 public boolean needsOtaServiceProvisioning() {
894 return mPhone.needsOtaServiceProvisioning();
895 }
896
897 /**
898 * Returns the unread count of voicemails
899 */
900 public int getVoiceMessageCount() {
901 return mPhone.getVoiceMessageCount();
902 }
903
904 /**
905 * Returns the data network type
906 *
907 * @Deprecated to be removed Q3 2013 use {@link #getDataNetworkType}.
908 */
909 @Override
910 public int getNetworkType() {
911 return mPhone.getServiceState().getDataNetworkType();
912 }
913
914 /**
915 * Returns the data network type
916 */
917 @Override
918 public int getDataNetworkType() {
919 return mPhone.getServiceState().getDataNetworkType();
920 }
921
922 /**
923 * Returns the data network type
924 */
925 @Override
926 public int getVoiceNetworkType() {
927 return mPhone.getServiceState().getVoiceNetworkType();
928 }
929
930 /**
931 * @return true if a ICC card is present
932 */
933 public boolean hasIccCard() {
934 return mPhone.getIccCard().hasIccCard();
935 }
936
937 /**
938 * Return if the current radio is LTE on CDMA. This
939 * is a tri-state return value as for a period of time
940 * the mode may be unknown.
941 *
942 * @return {@link Phone#LTE_ON_CDMA_UNKNOWN}, {@link Phone#LTE_ON_CDMA_FALSE}
943 * or {@link PHone#LTE_ON_CDMA_TRUE}
944 */
945 public int getLteOnCdmaMode() {
946 return mPhone.getLteOnCdmaMode();
947 }
948}