Blanket copy of PhoneApp to services/Telephony.
First phase of splitting out InCallUI from PhoneApp.
Change-Id: I237341c4ff00e96c677caa4580b251ef3432931b
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
new file mode 100644
index 0000000..6600ae5
--- /dev/null
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -0,0 +1,884 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.telephony.NeighboringCellInfo;
+import android.telephony.CellInfo;
+import android.telephony.ServiceState;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.DefaultPhoneNotifier;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.PhoneConstants;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Implementation of the ITelephony interface.
+ */
+public class PhoneInterfaceManager extends ITelephony.Stub {
+ private static final String LOG_TAG = "PhoneInterfaceManager";
+ private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+ private static final boolean DBG_LOC = false;
+
+ // Message codes used with mMainThreadHandler
+ private static final int CMD_HANDLE_PIN_MMI = 1;
+ private static final int CMD_HANDLE_NEIGHBORING_CELL = 2;
+ private static final int EVENT_NEIGHBORING_CELL_DONE = 3;
+ private static final int CMD_ANSWER_RINGING_CALL = 4;
+ private static final int CMD_END_CALL = 5; // not used yet
+ private static final int CMD_SILENCE_RINGER = 6;
+
+ /** The singleton instance. */
+ private static PhoneInterfaceManager sInstance;
+
+ PhoneGlobals mApp;
+ Phone mPhone;
+ CallManager mCM;
+ AppOpsManager mAppOps;
+ MainThreadHandler mMainThreadHandler;
+
+ /**
+ * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
+ * request after sending. The main thread will notify the request when it is complete.
+ */
+ private static final class MainThreadRequest {
+ /** The argument to use for the request */
+ public Object argument;
+ /** The result of the request that is run on the main thread */
+ public Object result;
+
+ public MainThreadRequest(Object argument) {
+ this.argument = argument;
+ }
+ }
+
+ /**
+ * A handler that processes messages on the main thread in the phone process. Since many
+ * of the Phone calls are not thread safe this is needed to shuttle the requests from the
+ * inbound binder threads to the main thread in the phone process. The Binder thread
+ * may provide a {@link MainThreadRequest} object in the msg.obj field that they are waiting
+ * on, which will be notified when the operation completes and will contain the result of the
+ * request.
+ *
+ * <p>If a MainThreadRequest object is provided in the msg.obj field,
+ * note that request.result must be set to something non-null for the calling thread to
+ * unblock.
+ */
+ private final class MainThreadHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ MainThreadRequest request;
+ Message onCompleted;
+ AsyncResult ar;
+
+ switch (msg.what) {
+ case CMD_HANDLE_PIN_MMI:
+ request = (MainThreadRequest) msg.obj;
+ request.result = Boolean.valueOf(
+ mPhone.handlePinMmi((String) request.argument));
+ // Wake up the requesting thread
+ synchronized (request) {
+ request.notifyAll();
+ }
+ break;
+
+ case CMD_HANDLE_NEIGHBORING_CELL:
+ request = (MainThreadRequest) msg.obj;
+ onCompleted = obtainMessage(EVENT_NEIGHBORING_CELL_DONE,
+ request);
+ mPhone.getNeighboringCids(onCompleted);
+ break;
+
+ case EVENT_NEIGHBORING_CELL_DONE:
+ ar = (AsyncResult) msg.obj;
+ request = (MainThreadRequest) ar.userObj;
+ if (ar.exception == null && ar.result != null) {
+ request.result = ar.result;
+ } else {
+ // create an empty list to notify the waiting thread
+ request.result = new ArrayList<NeighboringCellInfo>();
+ }
+ // Wake up the requesting thread
+ synchronized (request) {
+ request.notifyAll();
+ }
+ break;
+
+ case CMD_ANSWER_RINGING_CALL:
+ answerRingingCallInternal();
+ break;
+
+ case CMD_SILENCE_RINGER:
+ silenceRingerInternal();
+ break;
+
+ case CMD_END_CALL:
+ request = (MainThreadRequest) msg.obj;
+ boolean hungUp = false;
+ int phoneType = mPhone.getPhoneType();
+ if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+ // CDMA: If the user presses the Power button we treat it as
+ // ending the complete call session
+ hungUp = PhoneUtils.hangupRingingAndActive(mPhone);
+ } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+ // GSM: End the call as per the Phone state
+ hungUp = PhoneUtils.hangup(mCM);
+ } else {
+ throw new IllegalStateException("Unexpected phone type: " + phoneType);
+ }
+ if (DBG) log("CMD_END_CALL: " + (hungUp ? "hung up!" : "no call to hang up"));
+ request.result = hungUp;
+ // Wake up the requesting thread
+ synchronized (request) {
+ request.notifyAll();
+ }
+ break;
+
+ default:
+ Log.w(LOG_TAG, "MainThreadHandler: unexpected message code: " + msg.what);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Posts the specified command to be executed on the main thread,
+ * waits for the request to complete, and returns the result.
+ * @see #sendRequestAsync
+ */
+ private Object sendRequest(int command, Object argument) {
+ if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
+ throw new RuntimeException("This method will deadlock if called from the main thread.");
+ }
+
+ MainThreadRequest request = new MainThreadRequest(argument);
+ Message msg = mMainThreadHandler.obtainMessage(command, request);
+ msg.sendToTarget();
+
+ // Wait for the request to complete
+ synchronized (request) {
+ while (request.result == null) {
+ try {
+ request.wait();
+ } catch (InterruptedException e) {
+ // Do nothing, go back and wait until the request is complete
+ }
+ }
+ }
+ return request.result;
+ }
+
+ /**
+ * Asynchronous ("fire and forget") version of sendRequest():
+ * Posts the specified command to be executed on the main thread, and
+ * returns immediately.
+ * @see #sendRequest
+ */
+ private void sendRequestAsync(int command) {
+ mMainThreadHandler.sendEmptyMessage(command);
+ }
+
+ /**
+ * Initialize the singleton PhoneInterfaceManager instance.
+ * This is only done once, at startup, from PhoneApp.onCreate().
+ */
+ /* package */ static PhoneInterfaceManager init(PhoneGlobals app, Phone phone) {
+ synchronized (PhoneInterfaceManager.class) {
+ if (sInstance == null) {
+ sInstance = new PhoneInterfaceManager(app, phone);
+ } else {
+ Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance);
+ }
+ return sInstance;
+ }
+ }
+
+ /** Private constructor; @see init() */
+ private PhoneInterfaceManager(PhoneGlobals app, Phone phone) {
+ mApp = app;
+ mPhone = phone;
+ mCM = PhoneGlobals.getInstance().mCM;
+ mAppOps = (AppOpsManager)app.getSystemService(Context.APP_OPS_SERVICE);
+ mMainThreadHandler = new MainThreadHandler();
+ publish();
+ }
+
+ private void publish() {
+ if (DBG) log("publish: " + this);
+
+ ServiceManager.addService("phone", this);
+ }
+
+ //
+ // Implementation of the ITelephony interface.
+ //
+
+ public void dial(String number) {
+ if (DBG) log("dial: " + number);
+ // No permission check needed here: This is just a wrapper around the
+ // ACTION_DIAL intent, which is available to any app since it puts up
+ // the UI before it does anything.
+
+ String url = createTelUrl(number);
+ if (url == null) {
+ return;
+ }
+
+ // PENDING: should we just silently fail if phone is offhook or ringing?
+ PhoneConstants.State state = mCM.getState();
+ if (state != PhoneConstants.State.OFFHOOK && state != PhoneConstants.State.RINGING) {
+ Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mApp.startActivity(intent);
+ }
+ }
+
+ public void call(String callingPackage, String number) {
+ if (DBG) log("call: " + number);
+
+ // This is just a wrapper around the ACTION_CALL intent, but we still
+ // need to do a permission check since we're calling startActivity()
+ // from the context of the phone app.
+ enforceCallPermission();
+
+ if (mAppOps.noteOp(AppOpsManager.OP_CALL_PHONE, Binder.getCallingUid(), callingPackage)
+ != AppOpsManager.MODE_ALLOWED) {
+ return;
+ }
+
+ String url = createTelUrl(number);
+ if (url == null) {
+ return;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(url));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mApp.startActivity(intent);
+ }
+
+ private boolean showCallScreenInternal(boolean specifyInitialDialpadState,
+ boolean initialDialpadState) {
+ if (!PhoneGlobals.sVoiceCapable) {
+ // Never allow the InCallScreen to appear on data-only devices.
+ return false;
+ }
+ if (isIdle()) {
+ return false;
+ }
+ // If the phone isn't idle then go to the in-call screen
+ long callingId = Binder.clearCallingIdentity();
+ try {
+ Intent intent;
+ if (specifyInitialDialpadState) {
+ intent = PhoneGlobals.createInCallIntent(initialDialpadState);
+ } else {
+ intent = PhoneGlobals.createInCallIntent();
+ }
+ try {
+ mApp.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ // It's possible that the in-call UI might not exist
+ // (like on non-voice-capable devices), although we
+ // shouldn't be trying to bring up the InCallScreen on
+ // devices like that in the first place!
+ Log.w(LOG_TAG, "showCallScreenInternal: "
+ + "transition to InCallScreen failed; intent = " + intent);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ return true;
+ }
+
+ // Show the in-call screen without specifying the initial dialpad state.
+ public boolean showCallScreen() {
+ return showCallScreenInternal(false, false);
+ }
+
+ // The variation of showCallScreen() that specifies the initial dialpad state.
+ // (Ideally this would be called showCallScreen() too, just with a different
+ // signature, but AIDL doesn't allow that.)
+ public boolean showCallScreenWithDialpad(boolean showDialpad) {
+ return showCallScreenInternal(true, showDialpad);
+ }
+
+ /**
+ * End a call based on call state
+ * @return true is a call was ended
+ */
+ public boolean endCall() {
+ enforceCallPermission();
+ return (Boolean) sendRequest(CMD_END_CALL, null);
+ }
+
+ public void answerRingingCall() {
+ if (DBG) log("answerRingingCall...");
+ // TODO: there should eventually be a separate "ANSWER_PHONE" permission,
+ // but that can probably wait till the big TelephonyManager API overhaul.
+ // For now, protect this call with the MODIFY_PHONE_STATE permission.
+ enforceModifyPermission();
+ sendRequestAsync(CMD_ANSWER_RINGING_CALL);
+ }
+
+ /**
+ * Make the actual telephony calls to implement answerRingingCall().
+ * This should only be called from the main thread of the Phone app.
+ * @see #answerRingingCall
+ *
+ * TODO: it would be nice to return true if we answered the call, or
+ * false if there wasn't actually a ringing incoming call, or some
+ * other error occurred. (In other words, pass back the return value
+ * from PhoneUtils.answerCall() or PhoneUtils.answerAndEndActive().)
+ * But that would require calling this method via sendRequest() rather
+ * than sendRequestAsync(), and right now we don't actually *need* that
+ * return value, so let's just return void for now.
+ */
+ private void answerRingingCallInternal() {
+ final boolean hasRingingCall = !mPhone.getRingingCall().isIdle();
+ if (hasRingingCall) {
+ final boolean hasActiveCall = !mPhone.getForegroundCall().isIdle();
+ final boolean hasHoldingCall = !mPhone.getBackgroundCall().isIdle();
+ if (hasActiveCall && hasHoldingCall) {
+ // Both lines are in use!
+ // TODO: provide a flag to let the caller specify what
+ // policy to use if both lines are in use. (The current
+ // behavior is hardwired to "answer incoming, end ongoing",
+ // which is how the CALL button is specced to behave.)
+ PhoneUtils.answerAndEndActive(mCM, mCM.getFirstActiveRingingCall());
+ return;
+ } else {
+ // answerCall() will automatically hold the current active
+ // call, if there is one.
+ PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
+ return;
+ }
+ } else {
+ // No call was ringing.
+ return;
+ }
+ }
+
+ public void silenceRinger() {
+ if (DBG) log("silenceRinger...");
+ // TODO: find a more appropriate permission to check here.
+ // (That can probably wait till the big TelephonyManager API overhaul.
+ // For now, protect this call with the MODIFY_PHONE_STATE permission.)
+ enforceModifyPermission();
+ sendRequestAsync(CMD_SILENCE_RINGER);
+ }
+
+ /**
+ * Internal implemenation of silenceRinger().
+ * This should only be called from the main thread of the Phone app.
+ * @see #silenceRinger
+ */
+ private void silenceRingerInternal() {
+ if ((mCM.getState() == PhoneConstants.State.RINGING)
+ && mApp.notifier.isRinging()) {
+ // Ringer is actually playing, so silence it.
+ if (DBG) log("silenceRingerInternal: silencing...");
+ mApp.notifier.silenceRinger();
+ }
+ }
+
+ public boolean isOffhook() {
+ return (mCM.getState() == PhoneConstants.State.OFFHOOK);
+ }
+
+ public boolean isRinging() {
+ return (mCM.getState() == PhoneConstants.State.RINGING);
+ }
+
+ public boolean isIdle() {
+ return (mCM.getState() == PhoneConstants.State.IDLE);
+ }
+
+ public boolean isSimPinEnabled() {
+ enforceReadPermission();
+ return (PhoneGlobals.getInstance().isSimPinEnabled());
+ }
+
+ public boolean supplyPin(String pin) {
+ enforceModifyPermission();
+ final UnlockSim checkSimPin = new UnlockSim(mPhone.getIccCard());
+ checkSimPin.start();
+ return checkSimPin.unlockSim(null, pin);
+ }
+
+ public boolean supplyPuk(String puk, String pin) {
+ enforceModifyPermission();
+ final UnlockSim checkSimPuk = new UnlockSim(mPhone.getIccCard());
+ checkSimPuk.start();
+ return checkSimPuk.unlockSim(puk, pin);
+ }
+
+ /**
+ * Helper thread to turn async call to {@link SimCard#supplyPin} into
+ * a synchronous one.
+ */
+ private static class UnlockSim extends Thread {
+
+ private final IccCard mSimCard;
+
+ private boolean mDone = false;
+ private boolean mResult = false;
+
+ // For replies from SimCard interface
+ private Handler mHandler;
+
+ // For async handler to identify request type
+ private static final int SUPPLY_PIN_COMPLETE = 100;
+
+ public UnlockSim(IccCard simCard) {
+ mSimCard = simCard;
+ }
+
+ @Override
+ public void run() {
+ Looper.prepare();
+ synchronized (UnlockSim.this) {
+ mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ AsyncResult ar = (AsyncResult) msg.obj;
+ switch (msg.what) {
+ case SUPPLY_PIN_COMPLETE:
+ Log.d(LOG_TAG, "SUPPLY_PIN_COMPLETE");
+ synchronized (UnlockSim.this) {
+ mResult = (ar.exception == null);
+ mDone = true;
+ UnlockSim.this.notifyAll();
+ }
+ break;
+ }
+ }
+ };
+ UnlockSim.this.notifyAll();
+ }
+ Looper.loop();
+ }
+
+ /*
+ * Use PIN or PUK to unlock SIM card
+ *
+ * If PUK is null, unlock SIM card with PIN
+ *
+ * If PUK is not null, unlock SIM card with PUK and set PIN code
+ */
+ synchronized boolean unlockSim(String puk, String pin) {
+
+ while (mHandler == null) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ Message callback = Message.obtain(mHandler, SUPPLY_PIN_COMPLETE);
+
+ if (puk == null) {
+ mSimCard.supplyPin(pin, callback);
+ } else {
+ mSimCard.supplyPuk(puk, pin, callback);
+ }
+
+ while (!mDone) {
+ try {
+ Log.d(LOG_TAG, "wait for done");
+ wait();
+ } catch (InterruptedException e) {
+ // Restore the interrupted status
+ Thread.currentThread().interrupt();
+ }
+ }
+ Log.d(LOG_TAG, "done");
+ return mResult;
+ }
+ }
+
+ public void updateServiceLocation() {
+ // No permission check needed here: this call is harmless, and it's
+ // needed for the ServiceState.requestStateUpdate() call (which is
+ // already intentionally exposed to 3rd parties.)
+ mPhone.updateServiceLocation();
+ }
+
+ public boolean isRadioOn() {
+ return mPhone.getServiceState().getVoiceRegState() != ServiceState.STATE_POWER_OFF;
+ }
+
+ public void toggleRadioOnOff() {
+ enforceModifyPermission();
+ mPhone.setRadioPower(!isRadioOn());
+ }
+ public boolean setRadio(boolean turnOn) {
+ enforceModifyPermission();
+ if ((mPhone.getServiceState().getVoiceRegState() != ServiceState.STATE_POWER_OFF) != turnOn) {
+ toggleRadioOnOff();
+ }
+ return true;
+ }
+ public boolean setRadioPower(boolean turnOn) {
+ enforceModifyPermission();
+ mPhone.setRadioPower(turnOn);
+ return true;
+ }
+
+ public boolean enableDataConnectivity() {
+ enforceModifyPermission();
+ ConnectivityManager cm =
+ (ConnectivityManager)mApp.getSystemService(Context.CONNECTIVITY_SERVICE);
+ cm.setMobileDataEnabled(true);
+ return true;
+ }
+
+ public int enableApnType(String type) {
+ enforceModifyPermission();
+ return mPhone.enableApnType(type);
+ }
+
+ public int disableApnType(String type) {
+ enforceModifyPermission();
+ return mPhone.disableApnType(type);
+ }
+
+ public boolean disableDataConnectivity() {
+ enforceModifyPermission();
+ ConnectivityManager cm =
+ (ConnectivityManager)mApp.getSystemService(Context.CONNECTIVITY_SERVICE);
+ cm.setMobileDataEnabled(false);
+ return true;
+ }
+
+ public boolean isDataConnectivityPossible() {
+ return mPhone.isDataConnectivityPossible();
+ }
+
+ public boolean handlePinMmi(String dialString) {
+ enforceModifyPermission();
+ return (Boolean) sendRequest(CMD_HANDLE_PIN_MMI, dialString);
+ }
+
+ public void cancelMissedCallsNotification() {
+ enforceModifyPermission();
+ mApp.notificationMgr.cancelMissedCallNotification();
+ }
+
+ public int getCallState() {
+ return DefaultPhoneNotifier.convertCallState(mCM.getState());
+ }
+
+ public int getDataState() {
+ return DefaultPhoneNotifier.convertDataState(mPhone.getDataConnectionState());
+ }
+
+ public int getDataActivity() {
+ return DefaultPhoneNotifier.convertDataActivityState(mPhone.getDataActivityState());
+ }
+
+ @Override
+ public Bundle getCellLocation() {
+ try {
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_FINE_LOCATION, null);
+ } catch (SecurityException e) {
+ // If we have ACCESS_FINE_LOCATION permission, skip the check for ACCESS_COARSE_LOCATION
+ // A failure should throw the SecurityException from ACCESS_COARSE_LOCATION since this
+ // is the weaker precondition
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
+ }
+
+ if (checkIfCallerIsSelfOrForegoundUser()) {
+ if (DBG_LOC) log("getCellLocation: is active user");
+ Bundle data = new Bundle();
+ mPhone.getCellLocation().fillInNotifierBundle(data);
+ return data;
+ } else {
+ if (DBG_LOC) log("getCellLocation: suppress non-active user");
+ return null;
+ }
+ }
+
+ @Override
+ public void enableLocationUpdates() {
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
+ mPhone.enableLocationUpdates();
+ }
+
+ @Override
+ public void disableLocationUpdates() {
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
+ mPhone.disableLocationUpdates();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public List<NeighboringCellInfo> getNeighboringCellInfo(String callingPackage) {
+ try {
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_FINE_LOCATION, null);
+ } catch (SecurityException e) {
+ // If we have ACCESS_FINE_LOCATION permission, skip the check
+ // for ACCESS_COARSE_LOCATION
+ // A failure should throw the SecurityException from
+ // ACCESS_COARSE_LOCATION since this is the weaker precondition
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
+ }
+
+ if (mAppOps.noteOp(AppOpsManager.OP_NEIGHBORING_CELLS, Binder.getCallingUid(),
+ callingPackage) != AppOpsManager.MODE_ALLOWED) {
+ return null;
+ }
+ if (checkIfCallerIsSelfOrForegoundUser()) {
+ if (DBG_LOC) log("getNeighboringCellInfo: is active user");
+
+ ArrayList<NeighboringCellInfo> cells = null;
+
+ try {
+ cells = (ArrayList<NeighboringCellInfo>) sendRequest(
+ CMD_HANDLE_NEIGHBORING_CELL, null);
+ } catch (RuntimeException e) {
+ Log.e(LOG_TAG, "getNeighboringCellInfo " + e);
+ }
+ return cells;
+ } else {
+ if (DBG_LOC) log("getNeighboringCellInfo: suppress non-active user");
+ return null;
+ }
+ }
+
+
+ @Override
+ public List<CellInfo> getAllCellInfo() {
+ try {
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_FINE_LOCATION, null);
+ } catch (SecurityException e) {
+ // If we have ACCESS_FINE_LOCATION permission, skip the check for ACCESS_COARSE_LOCATION
+ // A failure should throw the SecurityException from ACCESS_COARSE_LOCATION since this
+ // is the weaker precondition
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
+ }
+
+ if (checkIfCallerIsSelfOrForegoundUser()) {
+ if (DBG_LOC) log("getAllCellInfo: is active user");
+ return mPhone.getAllCellInfo();
+ } else {
+ if (DBG_LOC) log("getAllCellInfo: suppress non-active user");
+ return null;
+ }
+ }
+
+ public void setCellInfoListRate(int rateInMillis) {
+ mPhone.setCellInfoListRate(rateInMillis);
+ }
+
+ //
+ // Internal helper methods.
+ //
+
+ private boolean checkIfCallerIsSelfOrForegoundUser() {
+ boolean ok;
+
+ boolean self = Binder.getCallingUid() == Process.myUid();
+ if (!self) {
+ // Get the caller's user id then clear the calling identity
+ // which will be restored in the finally clause.
+ int callingUser = UserHandle.getCallingUserId();
+ long ident = Binder.clearCallingIdentity();
+
+ try {
+ // With calling identity cleared the current user is the foreground user.
+ int foregroundUser = ActivityManager.getCurrentUser();
+ ok = (foregroundUser == callingUser);
+ if (DBG_LOC) {
+ log("checkIfCallerIsSelfOrForegoundUser: foregroundUser=" + foregroundUser
+ + " callingUser=" + callingUser + " ok=" + ok);
+ }
+ } catch (Exception ex) {
+ if (DBG_LOC) loge("checkIfCallerIsSelfOrForegoundUser: Exception ex=" + ex);
+ ok = false;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ } else {
+ if (DBG_LOC) log("checkIfCallerIsSelfOrForegoundUser: is self");
+ ok = true;
+ }
+ if (DBG_LOC) log("checkIfCallerIsSelfOrForegoundUser: ret=" + ok);
+ return ok;
+ }
+
+ /**
+ * Make sure the caller has the READ_PHONE_STATE permission.
+ *
+ * @throws SecurityException if the caller does not have the required permission
+ */
+ private void enforceReadPermission() {
+ mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE, null);
+ }
+
+ /**
+ * Make sure the caller has the MODIFY_PHONE_STATE permission.
+ *
+ * @throws SecurityException if the caller does not have the required permission
+ */
+ private void enforceModifyPermission() {
+ mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
+ }
+
+ /**
+ * Make sure the caller has the CALL_PHONE permission.
+ *
+ * @throws SecurityException if the caller does not have the required permission
+ */
+ private void enforceCallPermission() {
+ mApp.enforceCallingOrSelfPermission(android.Manifest.permission.CALL_PHONE, null);
+ }
+
+
+ private String createTelUrl(String number) {
+ if (TextUtils.isEmpty(number)) {
+ return null;
+ }
+
+ StringBuilder buf = new StringBuilder("tel:");
+ buf.append(number);
+ return buf.toString();
+ }
+
+ private void log(String msg) {
+ Log.d(LOG_TAG, "[PhoneIntfMgr] " + msg);
+ }
+
+ private void loge(String msg) {
+ Log.e(LOG_TAG, "[PhoneIntfMgr] " + msg);
+ }
+
+ public int getActivePhoneType() {
+ return mPhone.getPhoneType();
+ }
+
+ /**
+ * Returns the CDMA ERI icon index to display
+ */
+ public int getCdmaEriIconIndex() {
+ return mPhone.getCdmaEriIconIndex();
+ }
+
+ /**
+ * Returns the CDMA ERI icon mode,
+ * 0 - ON
+ * 1 - FLASHING
+ */
+ public int getCdmaEriIconMode() {
+ return mPhone.getCdmaEriIconMode();
+ }
+
+ /**
+ * Returns the CDMA ERI text,
+ */
+ public String getCdmaEriText() {
+ return mPhone.getCdmaEriText();
+ }
+
+ /**
+ * Returns true if CDMA provisioning needs to run.
+ */
+ public boolean needsOtaServiceProvisioning() {
+ return mPhone.needsOtaServiceProvisioning();
+ }
+
+ /**
+ * Returns the unread count of voicemails
+ */
+ public int getVoiceMessageCount() {
+ return mPhone.getVoiceMessageCount();
+ }
+
+ /**
+ * Returns the data network type
+ *
+ * @Deprecated to be removed Q3 2013 use {@link #getDataNetworkType}.
+ */
+ @Override
+ public int getNetworkType() {
+ return mPhone.getServiceState().getDataNetworkType();
+ }
+
+ /**
+ * Returns the data network type
+ */
+ @Override
+ public int getDataNetworkType() {
+ return mPhone.getServiceState().getDataNetworkType();
+ }
+
+ /**
+ * Returns the data network type
+ */
+ @Override
+ public int getVoiceNetworkType() {
+ return mPhone.getServiceState().getVoiceNetworkType();
+ }
+
+ /**
+ * @return true if a ICC card is present
+ */
+ public boolean hasIccCard() {
+ return mPhone.getIccCard().hasIccCard();
+ }
+
+ /**
+ * Return if the current radio is LTE on CDMA. This
+ * is a tri-state return value as for a period of time
+ * the mode may be unknown.
+ *
+ * @return {@link Phone#LTE_ON_CDMA_UNKNOWN}, {@link Phone#LTE_ON_CDMA_FALSE}
+ * or {@link PHone#LTE_ON_CDMA_TRUE}
+ */
+ public int getLteOnCdmaMode() {
+ return mPhone.getLteOnCdmaMode();
+ }
+}