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