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