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