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