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