am 4625edd0: am 9d29ded2: Merge "(Telecom-system part 2) Move telecom intialization from application to service." into lmp-mr1-dev
* commit '4625edd007e7f6270469e127698c6eb892024696':
(Telecom-system part 2) Move telecom intialization from application to service.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d0ae09c..4021d07 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -216,5 +216,12 @@
</intent-filter>
</service>
+ <service android:name=".TelecomService"
+ android:singleUser="true">
+ <intent-filter>
+ <android android:name="android.telecom.ITelecomService" />
+ </intent-filter>
+ </service>
+
</application>
</manifest>
diff --git a/src/com/android/server/telecom/TelecomApp.java b/src/com/android/server/telecom/TelecomApp.java
index f9df53c..8f46b49 100644
--- a/src/com/android/server/telecom/TelecomApp.java
+++ b/src/com/android/server/telecom/TelecomApp.java
@@ -17,19 +17,67 @@
package com.android.server.telecom;
import android.app.Application;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.ServiceManager;
import android.os.UserHandle;
/**
* Top-level Application class for Telecom.
*/
public final class TelecomApp extends Application {
+
+ /**
+ * Used to bind to the telecom service. Once created, the telecom service will start the telecom
+ * global state.
+ */
+ private class TelecomServiceConnection implements ServiceConnection {
+ /** {@inheritDoc} */
+ @Override public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.i(this, "onServiceConnected: %s", name);
+ ServiceManager.addService(Context.TELECOM_SERVICE, service);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onServiceDisconnected(ComponentName name) {
+ Log.i(this, "onDisconnected: %s", name);
+ bindToService();
+ }
+ }
+
+ private ServiceConnection mServiceConnection;
+
/** {@inheritDoc} */
@Override
public void onCreate() {
super.onCreate();
if (UserHandle.myUserId() == UserHandle.USER_OWNER) {
- TelecomGlobals.getInstance().initialize(this);
+ bindToService();
+ }
+ }
+
+ private void bindToService() {
+ if (mServiceConnection != null) {
+ unbindService(mServiceConnection);
+ mServiceConnection = null;
+ }
+
+ ComponentName componentName = new ComponentName(this, TelecomService.class);
+ Intent intent = new Intent(TelecomService.SERVICE_INTERFACE);
+ intent.setComponent(componentName);
+ int bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT;
+
+ Log.i(this, "binding to TelecomService.");
+ ServiceConnection serviceConnection = new TelecomServiceConnection();
+ if (bindServiceAsUser(intent, serviceConnection, bindFlags, UserHandle.OWNER)) {
+ mServiceConnection = serviceConnection;
+ Log.i(this, "TelecomService binding successful");
+ } else {
+ Log.e(this, null, "Failed to bind to TelecomService.");
}
}
}
diff --git a/src/com/android/server/telecom/TelecomGlobals.java b/src/com/android/server/telecom/TelecomGlobals.java
index e30fee1..a11b470 100644
--- a/src/com/android/server/telecom/TelecomGlobals.java
+++ b/src/com/android/server/telecom/TelecomGlobals.java
@@ -21,7 +21,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.ServiceManager;
import android.os.UserHandle;
/**
@@ -38,7 +37,7 @@
/**
* The Telecom service implementation.
*/
- private TelecomServiceImpl mTelecomService;
+ private TelecomService mTelecomService;
/**
* Missed call notifier. Exists here so that the instance can be shared with
@@ -76,12 +75,12 @@
void initialize(Context context) {
if (mContext != null) {
- Log.e(TAG, null, "Attempting to intialize TelecomGlobals a second time.");
+ Log.e(TAG, new Exception(), "Attempting to intialize TelecomGlobals a second time.");
return;
} else {
Log.i(TAG, "TelecomGlobals initializing");
}
- mContext = context;
+ mContext = context.getApplicationContext();
mMissedCallNotifier = new MissedCallNotifier(mContext);
mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext);
@@ -89,10 +88,6 @@
mCallsManager = new CallsManager(mContext, mMissedCallNotifier, mPhoneAccountRegistrar);
CallsManager.initialize(mCallsManager);
- mTelecomService = new TelecomServiceImpl(mMissedCallNotifier, mPhoneAccountRegistrar,
- mCallsManager, mContext);
- ServiceManager.addService(Context.TELECOM_SERVICE, mTelecomService);
-
// Start the BluetoothPhoneService
BluetoothPhoneService.start(mContext);
@@ -106,4 +101,8 @@
PhoneAccountRegistrar getPhoneAccountRegistrar() {
return mPhoneAccountRegistrar;
}
+
+ CallsManager getCallsManager() {
+ return mCallsManager;
+ }
}
diff --git a/src/com/android/server/telecom/TelecomService.java b/src/com/android/server/telecom/TelecomService.java
new file mode 100644
index 0000000..eab7947
--- /dev/null
+++ b/src/com/android/server/telecom/TelecomService.java
@@ -0,0 +1,956 @@
+/*
+ * Copyright (C) 2014 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.server.telecom;
+
+import android.Manifest;
+import android.annotation.SdkConstant;
+import android.app.AppOpsManager;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telecom.CallState;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+// TODO: Needed for move to system service: import com.android.internal.R;
+import com.android.internal.telecom.ITelecomService;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of the ITelecom interface.
+ */
+public class TelecomService extends Service {
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.telecom.ITelecomService";
+
+ /** The context. */
+ private Context mContext;
+
+ /**
+ * 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 result of the request that is run on the main thread */
+ public Object result;
+ /** Object that can be used to store non-integer arguments */
+ public Object arg;
+ }
+
+ /**
+ * A handler that processes messages on the main thread. Since many of the method calls are not
+ * thread safe this is needed to shuttle the requests from the inbound binder threads to the
+ * main thread.
+ */
+ private final class MainThreadHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.obj instanceof MainThreadRequest) {
+ MainThreadRequest request = (MainThreadRequest) msg.obj;
+ Object result = null;
+ switch (msg.what) {
+ case MSG_SILENCE_RINGER:
+ mCallsManager.getRinger().silence();
+ break;
+ case MSG_SHOW_CALL_SCREEN:
+ mCallsManager.getInCallController().bringToForeground(msg.arg1 == 1);
+ break;
+ case MSG_END_CALL:
+ result = endCallInternal();
+ break;
+ case MSG_ACCEPT_RINGING_CALL:
+ acceptRingingCallInternal();
+ break;
+ case MSG_CANCEL_MISSED_CALLS_NOTIFICATION:
+ mMissedCallNotifier.clearMissedCalls();
+ break;
+ case MSG_IS_TTY_SUPPORTED:
+ result = mCallsManager.isTtySupported();
+ break;
+ case MSG_GET_CURRENT_TTY_MODE:
+ result = mCallsManager.getCurrentTtyMode();
+ break;
+ case MSG_NEW_INCOMING_CALL:
+ if (request.arg == null || !(request.arg instanceof Intent)) {
+ Log.w(this, "Invalid new incoming call request");
+ break;
+ }
+ CallReceiver.processIncomingCallIntent((Intent) request.arg);
+ break;
+ }
+
+ if (result != null) {
+ request.result = result;
+ synchronized(request) {
+ request.notifyAll();
+ }
+ }
+ }
+ }
+ }
+
+ private static final String TAG = TelecomService.class.getSimpleName();
+
+ private static final String SERVICE_NAME = "telecom";
+
+ private static final int MSG_SILENCE_RINGER = 1;
+ private static final int MSG_SHOW_CALL_SCREEN = 2;
+ private static final int MSG_END_CALL = 3;
+ private static final int MSG_ACCEPT_RINGING_CALL = 4;
+ private static final int MSG_CANCEL_MISSED_CALLS_NOTIFICATION = 5;
+ private static final int MSG_IS_TTY_SUPPORTED = 6;
+ private static final int MSG_GET_CURRENT_TTY_MODE = 7;
+ private static final int MSG_NEW_INCOMING_CALL = 8;
+
+ private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
+
+ private CallsManager mCallsManager;
+ private MissedCallNotifier mMissedCallNotifier;
+ private PhoneAccountRegistrar mPhoneAccountRegistrar;
+ private AppOpsManager mAppOpsManager;
+ private UserManager mUserManager;
+ private PackageManager mPackageManager;
+ private TelecomServiceImpl mServiceImpl;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ Log.d(this, "onCreate");
+ mContext = this;
+ mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+ mServiceImpl = new TelecomServiceImpl();
+
+ TelecomGlobals globals = TelecomGlobals.getInstance();
+ globals.initialize(this);
+
+ mMissedCallNotifier = globals.getMissedCallNotifier();
+ mPhoneAccountRegistrar = globals.getPhoneAccountRegistrar();
+ mCallsManager = globals.getCallsManager();
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mPackageManager = mContext.getPackageManager();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ Log.d(this, "onBind");
+ return mServiceImpl;
+ }
+
+ /**
+ * Implementation of the ITelecomService interface.
+ * TODO: Reorganize this inner class to top of file.
+ */
+ class TelecomServiceImpl extends ITelecomService.Stub {
+ @Override
+ public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) {
+ try {
+ PhoneAccountHandle defaultOutgoingPhoneAccount =
+ mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount(uriScheme);
+ // Make sure that the calling user can see this phone account.
+ if (defaultOutgoingPhoneAccount != null
+ && !isVisibleToCaller(defaultOutgoingPhoneAccount)) {
+ Log.w(this, "No account found for the calling user");
+ return null;
+ }
+ return defaultOutgoingPhoneAccount;
+ } catch (Exception e) {
+ Log.e(this, e, "getDefaultOutgoingPhoneAccount");
+ throw e;
+ }
+ }
+
+ @Override
+ public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
+ try {
+ PhoneAccountHandle userSelectedOutgoingPhoneAccount =
+ mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount();
+ // Make sure that the calling user can see this phone account.
+ if (!isVisibleToCaller(userSelectedOutgoingPhoneAccount)) {
+ Log.w(this, "No account found for the calling user");
+ return null;
+ }
+ return userSelectedOutgoingPhoneAccount;
+ } catch (Exception e) {
+ Log.e(this, e, "getUserSelectedOutgoingPhoneAccount");
+ throw e;
+ }
+ }
+
+ @Override
+ public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
+ enforceModifyPermission();
+
+ try {
+ mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(accountHandle);
+ } catch (Exception e) {
+ Log.e(this, e, "setUserSelectedOutgoingPhoneAccount");
+ throw e;
+ }
+ }
+
+ @Override
+ public List<PhoneAccountHandle> getCallCapablePhoneAccounts() {
+ try {
+ return filterForAccountsVisibleToCaller(
+ mPhoneAccountRegistrar.getCallCapablePhoneAccounts());
+ } catch (Exception e) {
+ Log.e(this, e, "getCallCapablePhoneAccounts");
+ throw e;
+ }
+ }
+
+ @Override
+ public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme) {
+ try {
+ return filterForAccountsVisibleToCaller(
+ mPhoneAccountRegistrar.getCallCapablePhoneAccounts(uriScheme));
+ } catch (Exception e) {
+ Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme);
+ throw e;
+ }
+ }
+
+ @Override
+ public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName) {
+ try {
+ return filterForAccountsVisibleToCaller(
+ mPhoneAccountRegistrar.getPhoneAccountsForPackage(packageName));
+ } catch (Exception e) {
+ Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
+ throw e;
+ }
+ }
+
+ @Override
+ public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) {
+ try {
+ if (!isVisibleToCaller(accountHandle)) {
+ Log.w(this, "%s is not visible for the calling user", accountHandle);
+ return null;
+ }
+ return mPhoneAccountRegistrar.getPhoneAccountInternal(accountHandle);
+ } catch (Exception e) {
+ Log.e(this, e, "getPhoneAccount %s", accountHandle);
+ throw e;
+ }
+ }
+
+ @Override
+ public int getAllPhoneAccountsCount() {
+ try {
+ // This list is pre-filtered for the calling user.
+ return getAllPhoneAccounts().size();
+ } catch (Exception e) {
+ Log.e(this, e, "getAllPhoneAccountsCount");
+ throw e;
+ }
+ }
+
+ @Override
+ public List<PhoneAccount> getAllPhoneAccounts() {
+ try {
+ List<PhoneAccount> allPhoneAccounts = mPhoneAccountRegistrar.getAllPhoneAccounts();
+ List<PhoneAccount> profilePhoneAccounts = new ArrayList<>(allPhoneAccounts.size());
+ for (PhoneAccount phoneAccount : profilePhoneAccounts) {
+ if (isVisibleToCaller(phoneAccount)) {
+ profilePhoneAccounts.add(phoneAccount);
+ }
+ }
+ return profilePhoneAccounts;
+ } catch (Exception e) {
+ Log.e(this, e, "getAllPhoneAccounts");
+ throw e;
+ }
+ }
+
+ @Override
+ public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
+ try {
+ return filterForAccountsVisibleToCaller(
+ mPhoneAccountRegistrar.getAllPhoneAccountHandles());
+ } catch (Exception e) {
+ Log.e(this, e, "getAllPhoneAccounts");
+ throw e;
+ }
+ }
+
+ @Override
+ public PhoneAccountHandle getSimCallManager() {
+ try {
+ PhoneAccountHandle accountHandle = mPhoneAccountRegistrar.getSimCallManager();
+ if (!isVisibleToCaller(accountHandle)) {
+ Log.w(this, "%s is not visible for the calling user", accountHandle);
+ return null;
+ }
+ return accountHandle;
+ } catch (Exception e) {
+ Log.e(this, e, "getSimCallManager");
+ throw e;
+ }
+ }
+
+ @Override
+ public void setSimCallManager(PhoneAccountHandle accountHandle) {
+ enforceModifyPermission();
+
+ try {
+ mPhoneAccountRegistrar.setSimCallManager(accountHandle);
+ } catch (Exception e) {
+ Log.e(this, e, "setSimCallManager");
+ throw e;
+ }
+ }
+
+ @Override
+ public List<PhoneAccountHandle> getSimCallManagers() {
+ try {
+ return filterForAccountsVisibleToCaller(
+ mPhoneAccountRegistrar.getConnectionManagerPhoneAccounts());
+ } catch (Exception e) {
+ Log.e(this, e, "getSimCallManagers");
+ throw e;
+ }
+ }
+
+ @Override
+ public void registerPhoneAccount(PhoneAccount account) {
+ try {
+ enforcePhoneAccountModificationForPackage(
+ account.getAccountHandle().getComponentName().getPackageName());
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
+ enforceRegisterCallProviderPermission();
+ }
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+ enforceRegisterSimSubscriptionPermission();
+ }
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
+ enforceRegisterConnectionManagerPermission();
+ }
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+ enforceRegisterMultiUser();
+ }
+ enforceUserHandleMatchesCaller(account.getAccountHandle());
+
+ mPhoneAccountRegistrar.registerPhoneAccount(account);
+ } catch (Exception e) {
+ Log.e(this, e, "registerPhoneAccount %s", account);
+ throw e;
+ }
+ }
+
+ @Override
+ public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
+ try {
+ enforcePhoneAccountModificationForPackage(
+ accountHandle.getComponentName().getPackageName());
+ enforceUserHandleMatchesCaller(accountHandle);
+ mPhoneAccountRegistrar.unregisterPhoneAccount(accountHandle);
+ } catch (Exception e) {
+ Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
+ throw e;
+ }
+ }
+
+ @Override
+ public void clearAccounts(String packageName) {
+ try {
+ enforcePhoneAccountModificationForPackage(packageName);
+ mPhoneAccountRegistrar.clearAccounts(packageName, Binder.getCallingUserHandle());
+ } catch (Exception e) {
+ Log.e(this, e, "clearAccounts %s", packageName);
+ throw e;
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#isVoiceMailNumber
+ */
+ @Override
+ public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number) {
+ enforceReadPermissionOrDefaultDialer();
+ try {
+ if (!isVisibleToCaller(accountHandle)) {
+ Log.w(this, "%s is not visible for the calling user", accountHandle);
+ return false;
+ }
+ return mPhoneAccountRegistrar.isVoiceMailNumber(accountHandle, number);
+ } catch (Exception e) {
+ Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+ throw e;
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#hasVoiceMailNumber
+ */
+ @Override
+ public boolean hasVoiceMailNumber(PhoneAccountHandle accountHandle) {
+ enforceReadPermissionOrDefaultDialer();
+ try {
+ if (!isVisibleToCaller(accountHandle)) {
+ Log.w(this, "%s is not visible for the calling user", accountHandle);
+ return false;
+ }
+
+ int subId = SubscriptionManager.getDefaultVoiceSubId();
+ if (accountHandle != null) {
+ subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
+ }
+ return !TextUtils.isEmpty(getTelephonyManager().getVoiceMailNumber(subId));
+ } catch (Exception e) {
+ Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+ throw e;
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#silenceRinger
+ */
+ @Override
+ public void silenceRinger() {
+ Log.d(this, "silenceRinger");
+ enforceModifyPermission();
+ sendRequestAsync(MSG_SILENCE_RINGER, 0);
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#getDefaultPhoneApp
+ */
+ @Override
+ public ComponentName getDefaultPhoneApp() {
+ Resources resources = mContext.getResources();
+ return new ComponentName(
+ resources.getString(R.string.ui_default_package),
+ resources.getString(R.string.dialer_default_class));
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#isInCall
+ */
+ @Override
+ public boolean isInCall() {
+ enforceReadPermission();
+ // Do not use sendRequest() with this method since it could cause a deadlock with
+ // audio service, which we call into from the main thread: AudioManager.setMode().
+ final int callState = mCallsManager.getCallState();
+ return callState == TelephonyManager.CALL_STATE_OFFHOOK
+ || callState == TelephonyManager.CALL_STATE_RINGING;
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#isRinging
+ */
+ @Override
+ public boolean isRinging() {
+ enforceReadPermission();
+ return mCallsManager.getCallState() == TelephonyManager.CALL_STATE_RINGING;
+ }
+
+ /**
+ * @see TelecomManager#getCallState
+ */
+ @Override
+ public int getCallState() {
+ return mCallsManager.getCallState();
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#endCall
+ */
+ @Override
+ public boolean endCall() {
+ enforceModifyPermission();
+ return (boolean) sendRequest(MSG_END_CALL);
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#acceptRingingCall
+ */
+ @Override
+ public void acceptRingingCall() {
+ enforceModifyPermission();
+ sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#showInCallScreen
+ */
+ @Override
+ public void showInCallScreen(boolean showDialpad) {
+ enforceReadPermissionOrDefaultDialer();
+ sendRequestAsync(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0);
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#cancelMissedCallsNotification
+ */
+ @Override
+ public void cancelMissedCallsNotification() {
+ enforceModifyPermissionOrDefaultDialer();
+ sendRequestAsync(MSG_CANCEL_MISSED_CALLS_NOTIFICATION, 0);
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#handleMmi
+ */
+ @Override
+ public boolean handlePinMmi(String dialString) {
+ enforceModifyPermissionOrDefaultDialer();
+
+ // Switch identity so that TelephonyManager checks Telecom's permissions instead.
+ long token = Binder.clearCallingIdentity();
+ boolean retval = false;
+ try {
+ retval = getTelephonyManager().handlePinMmi(dialString);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return retval;
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#handleMmi
+ */
+ @Override
+ public boolean handlePinMmiForPhoneAccount(PhoneAccountHandle accountHandle,
+ String dialString) {
+ enforceModifyPermissionOrDefaultDialer();
+
+ if (!isVisibleToCaller(accountHandle)) {
+ Log.w(this, "%s is not visible for the calling user", accountHandle);
+ return false;
+ }
+
+ // Switch identity so that TelephonyManager checks Telecom's permissions instead.
+ long token = Binder.clearCallingIdentity();
+ boolean retval = false;
+ try {
+ int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
+ retval = getTelephonyManager().handlePinMmiForSubscriber(subId, dialString);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return retval;
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#getAdnUriForPhoneAccount
+ */
+ @Override
+ public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle) {
+ enforceModifyPermissionOrDefaultDialer();
+
+ if (!isVisibleToCaller(accountHandle)) {
+ Log.w(this, "%s is not visible for the calling user", accountHandle);
+ return null;
+ }
+
+ // Switch identity so that TelephonyManager checks Telecom's permissions instead.
+ long token = Binder.clearCallingIdentity();
+ String retval = "content://icc/adn/";
+ try {
+ long subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
+ retval = retval + "subId/" + subId;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return Uri.parse(retval);
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#isTtySupported
+ */
+ @Override
+ public boolean isTtySupported() {
+ enforceReadPermission();
+ return (boolean) sendRequest(MSG_IS_TTY_SUPPORTED);
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#getCurrentTtyMode
+ */
+ @Override
+ public int getCurrentTtyMode() {
+ enforceReadPermission();
+ return (int) sendRequest(MSG_GET_CURRENT_TTY_MODE);
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#addNewIncomingCall
+ */
+ @Override
+ public void addNewIncomingCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+ Log.i(this, "Adding new incoming call with phoneAccountHandle %s", phoneAccountHandle);
+ if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null) {
+ mAppOpsManager.checkPackage(
+ Binder.getCallingUid(), phoneAccountHandle.getComponentName().getPackageName());
+
+ // Make sure it doesn't cross the UserHandle boundary
+ enforceUserHandleMatchesCaller(phoneAccountHandle);
+
+ Intent intent = new Intent(TelecomManager.ACTION_INCOMING_CALL);
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+ intent.putExtra(CallReceiver.KEY_IS_INCOMING_CALL, true);
+ if (extras != null) {
+ intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras);
+ }
+ sendRequestAsync(MSG_NEW_INCOMING_CALL, 0, intent);
+ } else {
+ Log.w(this, "Null phoneAccountHandle. Ignoring request to add new incoming call");
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#addNewUnknownCall
+ */
+ @Override
+ public void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+ if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null &&
+ TelephonyUtil.isPstnComponentName(phoneAccountHandle.getComponentName())) {
+ mAppOpsManager.checkPackage(
+ Binder.getCallingUid(), phoneAccountHandle.getComponentName().getPackageName());
+
+ // Make sure it doesn't cross the UserHandle boundary
+ enforceUserHandleMatchesCaller(phoneAccountHandle);
+
+ Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
+ intent.setClass(mContext, CallReceiver.class);
+ intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.putExtras(extras);
+ intent.putExtra(CallReceiver.KEY_IS_UNKNOWN_CALL, true);
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+ mContext.sendBroadcastAsUser(intent, phoneAccountHandle.getUserHandle());
+ } else {
+ Log.i(this, "Null phoneAccountHandle or not initiated by Telephony. Ignoring request"
+ + " to add new unknown call.");
+ }
+ }
+ }
+
+ //
+ // Supporting methods for the ITelecomService interface implementation.
+ //
+
+ private boolean isVisibleToCaller(PhoneAccountHandle accountHandle) {
+ if (accountHandle == null) {
+ return false;
+ }
+
+ return isVisibleToCaller(mPhoneAccountRegistrar.getPhoneAccountInternal(accountHandle));
+ }
+
+ private boolean isVisibleToCaller(PhoneAccount account) {
+ if (account == null) {
+ return false;
+ }
+
+ // If this PhoneAccount has CAPABILITY_MULTI_USER, it should be visible to all users and
+ // all profiles. Only Telephony and SIP accounts should have this capability.
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+ return true;
+ }
+
+ UserHandle phoneAccountUserHandle = account.getAccountHandle().getUserHandle();
+ if (phoneAccountUserHandle == null) {
+ return false;
+ }
+
+ List<UserHandle> profileUserHandles;
+ if (isCallerSystemApp()) {
+ // If the caller lives in /system/priv-app, it can see PhoneAccounts for all of the
+ // *profiles* that the calling user owns, but not for any other *users*.
+ profileUserHandles = mUserManager.getUserProfiles();
+ } else {
+ // Otherwise, it has to be owned by the current caller's profile.
+ profileUserHandles = new ArrayList<>(1);
+ profileUserHandles.add(Binder.getCallingUserHandle());
+ }
+
+ return profileUserHandles.contains(phoneAccountUserHandle);
+ }
+
+ /**
+ * Given a list of {@link PhoneAccountHandle}s, filter them to the ones that the calling
+ * user can see.
+ *
+ * @param phoneAccountHandles Unfiltered list of account handles.
+ *
+ * @return {@link PhoneAccountHandle}s visible to the calling user and its profiles.
+ */
+ private List<PhoneAccountHandle> filterForAccountsVisibleToCaller(
+ List<PhoneAccountHandle> phoneAccountHandles) {
+ List<PhoneAccountHandle> profilePhoneAccountHandles =
+ new ArrayList<>(phoneAccountHandles.size());
+ for (PhoneAccountHandle phoneAccountHandle : phoneAccountHandles) {
+ if (isVisibleToCaller(phoneAccountHandle)) {
+ profilePhoneAccountHandles.add(phoneAccountHandle);
+ }
+ }
+ return profilePhoneAccountHandles;
+ }
+
+ private boolean isCallerSystemApp() {
+ int uid = Binder.getCallingUid();
+ String[] packages = mPackageManager.getPackagesForUid(uid);
+ for (String packageName : packages) {
+ if (isPackageSystemApp(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isPackageSystemApp(String packageName) {
+ try {
+ ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(packageName,
+ PackageManager.GET_META_DATA);
+ if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ return false;
+ }
+
+ private void acceptRingingCallInternal() {
+ Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
+ if (call != null) {
+ call.answer(call.getVideoState());
+ }
+ }
+
+ private boolean endCallInternal() {
+ // Always operate on the foreground call if one exists, otherwise get the first call in
+ // priority order by call-state.
+ Call call = mCallsManager.getForegroundCall();
+ if (call == null) {
+ call = mCallsManager.getFirstCallWithState(
+ CallState.ACTIVE,
+ CallState.DIALING,
+ CallState.RINGING,
+ CallState.ON_HOLD);
+ }
+
+ if (call != null) {
+ if (call.getState() == CallState.RINGING) {
+ call.reject(false /* rejectWithMessage */, null);
+ } else {
+ call.disconnect();
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private void enforcePhoneAccountModificationForPackage(String packageName) {
+ // TODO: Use a new telecomm permission for this instead of reusing modify.
+
+ int result = mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+
+ // Callers with MODIFY_PHONE_STATE can use the PhoneAccount mechanism to implement
+ // built-in behavior even when PhoneAccounts are not exposed as a third-part API. They
+ // may also modify PhoneAccounts on behalf of any 'packageName'.
+
+ if (result != PackageManager.PERMISSION_GRANTED) {
+ // Other callers are only allowed to modify PhoneAccounts if the relevant system
+ // feature is enabled ...
+ enforceConnectionServiceFeature();
+ // ... and the PhoneAccounts they refer to are for their own package.
+ enforceCallingPackage(packageName);
+ }
+ }
+
+ private void enforceReadPermissionOrDefaultDialer() {
+ if (!isDefaultDialerCalling()) {
+ enforceReadPermission();
+ }
+ }
+
+ private void enforceModifyPermissionOrDefaultDialer() {
+ if (!isDefaultDialerCalling()) {
+ enforceModifyPermission();
+ }
+ }
+
+ private void enforceCallingPackage(String packageName) {
+ mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
+ }
+
+ private void enforceConnectionServiceFeature() {
+ enforceFeature(PackageManager.FEATURE_CONNECTION_SERVICE);
+ }
+
+ private void enforceRegisterCallProviderPermission() {
+ enforcePermission(android.Manifest.permission.REGISTER_CALL_PROVIDER);
+ }
+
+ private void enforceRegisterSimSubscriptionPermission() {
+ enforcePermission(android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION);
+ }
+
+ private void enforceRegisterConnectionManagerPermission() {
+ enforcePermission(android.Manifest.permission.REGISTER_CONNECTION_MANAGER);
+ }
+
+ private void enforceReadPermission() {
+ enforcePermission(Manifest.permission.READ_PHONE_STATE);
+ }
+
+ private void enforceModifyPermission() {
+ enforcePermission(Manifest.permission.MODIFY_PHONE_STATE);
+ }
+
+ private void enforcePermission(String permission) {
+ mContext.enforceCallingOrSelfPermission(permission, null);
+ }
+
+ private void enforceRegisterMultiUser() {
+ if (!isCallerSystemApp()) {
+ throw new SecurityException("CAPABILITY_MULTI_USER is only available to system apps.");
+ }
+ }
+
+ private void enforceUserHandleMatchesCaller(PhoneAccountHandle accountHandle) {
+ if (!Binder.getCallingUserHandle().equals(accountHandle.getUserHandle())) {
+ throw new SecurityException("Calling UserHandle does not match PhoneAccountHandle's");
+ }
+ }
+
+ private void enforceFeature(String feature) {
+ PackageManager pm = mContext.getPackageManager();
+ if (!pm.hasSystemFeature(feature)) {
+ throw new UnsupportedOperationException(
+ "System does not support feature " + feature);
+ }
+ }
+
+ private boolean isDefaultDialerCalling() {
+ ComponentName defaultDialerComponent = getDefaultPhoneAppInternal();
+ if (defaultDialerComponent != null) {
+ try {
+ mAppOpsManager.checkPackage(
+ Binder.getCallingUid(), defaultDialerComponent.getPackageName());
+ return true;
+ } catch (SecurityException e) {
+ Log.e(TAG, e, "Could not get default dialer.");
+ }
+ }
+ return false;
+ }
+
+ private ComponentName getDefaultPhoneAppInternal() {
+ Resources resources = mContext.getResources();
+ return new ComponentName(
+ resources.getString(R.string.ui_default_package),
+ resources.getString(R.string.dialer_default_class));
+ }
+
+ private TelephonyManager getTelephonyManager() {
+ return (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ }
+
+ private MainThreadRequest sendRequestAsync(int command, int arg1) {
+ return sendRequestAsync(command, arg1, null);
+ }
+
+ private MainThreadRequest sendRequestAsync(int command, int arg1, Object arg) {
+ MainThreadRequest request = new MainThreadRequest();
+ request.arg = arg;
+ mMainThreadHandler.obtainMessage(command, arg1, 0, request).sendToTarget();
+ return request;
+ }
+
+ /**
+ * Posts the specified command to be executed on the main thread, waits for the request to
+ * complete, and returns the result.
+ */
+ private Object sendRequest(int command) {
+ if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
+ MainThreadRequest request = new MainThreadRequest();
+ mMainThreadHandler.handleMessage(mMainThreadHandler.obtainMessage(command, request));
+ return request.result;
+ } else {
+ MainThreadRequest request = sendRequestAsync(command, 0);
+
+ // 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;
+ }
+ }
+
+ /**
+ * Dumps the current state of the TelecomService. Used when generating problem reports.
+ *
+ * @param fd The file descriptor.
+ * @param writer The print writer to dump the state to.
+ * @param args Optional dump arguments.
+ */
+ @Override
+ protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ if (mCallsManager != null) {
+ pw.println("mCallsManager: ");
+ pw.increaseIndent();
+ mCallsManager.dump(pw);
+ pw.decreaseIndent();
+
+ pw.println("mPhoneAccountRegistrar: ");
+ pw.increaseIndent();
+ mPhoneAccountRegistrar.dump(pw);
+ pw.decreaseIndent();
+ }
+ }
+}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
deleted file mode 100644
index a7fbb6a..0000000
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ /dev/null
@@ -1,936 +0,0 @@
-/*
- * Copyright (C) 2014 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.server.telecom;
-
-import android.Manifest;
-import android.app.AppOpsManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.telecom.CallState;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-
-
-// TODO: Needed for move to system service: import com.android.internal.R;
-import com.android.internal.telecom.ITelecomService;
-import com.android.internal.util.IndentingPrintWriter;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Implementation of the ITelecom interface.
- */
-public class TelecomServiceImpl extends ITelecomService.Stub {
- /** The context. */
- private Context mContext;
-
- /** ${inheritDoc} */
- @Override
- public IBinder asBinder() {
- return super.asBinder();
- }
-
- /**
- * 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 result of the request that is run on the main thread */
- public Object result;
- /** Object that can be used to store non-integer arguments */
- public Object arg;
- }
-
- /**
- * 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.
- */
- private final class MainThreadHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- if (msg.obj instanceof MainThreadRequest) {
- MainThreadRequest request = (MainThreadRequest) msg.obj;
- Object result = null;
- switch (msg.what) {
- case MSG_SILENCE_RINGER:
- mCallsManager.getRinger().silence();
- break;
- case MSG_SHOW_CALL_SCREEN:
- mCallsManager.getInCallController().bringToForeground(msg.arg1 == 1);
- break;
- case MSG_END_CALL:
- result = endCallInternal();
- break;
- case MSG_ACCEPT_RINGING_CALL:
- acceptRingingCallInternal();
- break;
- case MSG_CANCEL_MISSED_CALLS_NOTIFICATION:
- mMissedCallNotifier.clearMissedCalls();
- break;
- case MSG_IS_TTY_SUPPORTED:
- result = mCallsManager.isTtySupported();
- break;
- case MSG_GET_CURRENT_TTY_MODE:
- result = mCallsManager.getCurrentTtyMode();
- break;
- case MSG_NEW_INCOMING_CALL:
- if (request.arg == null || !(request.arg instanceof Intent)) {
- Log.w(this, "Invalid new incoming call request");
- break;
- }
- CallReceiver.processIncomingCallIntent((Intent) request.arg);
- break;
- }
-
- if (result != null) {
- request.result = result;
- synchronized(request) {
- request.notifyAll();
- }
- }
- }
- }
- }
-
- /** Private constructor; @see init() */
- private static final String TAG = TelecomServiceImpl.class.getSimpleName();
-
- private static final String SERVICE_NAME = "telecom";
-
- private static final int MSG_SILENCE_RINGER = 1;
- private static final int MSG_SHOW_CALL_SCREEN = 2;
- private static final int MSG_END_CALL = 3;
- private static final int MSG_ACCEPT_RINGING_CALL = 4;
- private static final int MSG_CANCEL_MISSED_CALLS_NOTIFICATION = 5;
- private static final int MSG_IS_TTY_SUPPORTED = 6;
- private static final int MSG_GET_CURRENT_TTY_MODE = 7;
- private static final int MSG_NEW_INCOMING_CALL = 8;
-
- /** The singleton instance. */
- private static TelecomServiceImpl sInstance;
-
- private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
- private final CallsManager mCallsManager;
- private final MissedCallNotifier mMissedCallNotifier;
- private final PhoneAccountRegistrar mPhoneAccountRegistrar;
- private final AppOpsManager mAppOpsManager;
- private final UserManager mUserManager;
- private final PackageManager mPackageManager;
-
- public TelecomServiceImpl(
- MissedCallNotifier missedCallNotifier, PhoneAccountRegistrar phoneAccountRegistrar,
- CallsManager callsManager, Context context) {
- mMissedCallNotifier = missedCallNotifier;
- mPhoneAccountRegistrar = phoneAccountRegistrar;
- mCallsManager = callsManager;
- mContext = context;
- mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mPackageManager = mContext.getPackageManager();
- }
-
- //
- // Implementation of the ITelecomService interface.
- //
-
- @Override
- public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) {
- try {
- PhoneAccountHandle defaultOutgoingPhoneAccount =
- mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount(uriScheme);
- // Make sure that the calling user can see this phone account.
- if (defaultOutgoingPhoneAccount != null
- && !isVisibleToCaller(defaultOutgoingPhoneAccount)) {
- Log.w(this, "No account found for the calling user");
- return null;
- }
- return defaultOutgoingPhoneAccount;
- } catch (Exception e) {
- Log.e(this, e, "getDefaultOutgoingPhoneAccount");
- throw e;
- }
- }
-
- @Override
- public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
- try {
- PhoneAccountHandle userSelectedOutgoingPhoneAccount =
- mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount();
- // Make sure that the calling user can see this phone account.
- if (!isVisibleToCaller(userSelectedOutgoingPhoneAccount)) {
- Log.w(this, "No account found for the calling user");
- return null;
- }
- return userSelectedOutgoingPhoneAccount;
- } catch (Exception e) {
- Log.e(this, e, "getUserSelectedOutgoingPhoneAccount");
- throw e;
- }
- }
-
- @Override
- public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
- enforceModifyPermission();
-
- try {
- mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(accountHandle);
- } catch (Exception e) {
- Log.e(this, e, "setUserSelectedOutgoingPhoneAccount");
- throw e;
- }
- }
-
- @Override
- public List<PhoneAccountHandle> getCallCapablePhoneAccounts() {
- try {
- return filterForAccountsVisibleToCaller(
- mPhoneAccountRegistrar.getCallCapablePhoneAccounts());
- } catch (Exception e) {
- Log.e(this, e, "getCallCapablePhoneAccounts");
- throw e;
- }
- }
-
- @Override
- public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme) {
- try {
- return filterForAccountsVisibleToCaller(
- mPhoneAccountRegistrar.getCallCapablePhoneAccounts(uriScheme));
- } catch (Exception e) {
- Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme);
- throw e;
- }
- }
-
- @Override
- public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName) {
- try {
- return filterForAccountsVisibleToCaller(
- mPhoneAccountRegistrar.getPhoneAccountsForPackage(packageName));
- } catch (Exception e) {
- Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
- throw e;
- }
- }
-
- @Override
- public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) {
- try {
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return null;
- }
- return mPhoneAccountRegistrar.getPhoneAccountInternal(accountHandle);
- } catch (Exception e) {
- Log.e(this, e, "getPhoneAccount %s", accountHandle);
- throw e;
- }
- }
-
- @Override
- public int getAllPhoneAccountsCount() {
- try {
- // This list is pre-filtered for the calling user.
- return getAllPhoneAccounts().size();
- } catch (Exception e) {
- Log.e(this, e, "getAllPhoneAccountsCount");
- throw e;
- }
- }
-
- @Override
- public List<PhoneAccount> getAllPhoneAccounts() {
- try {
- List<PhoneAccount> allPhoneAccounts = mPhoneAccountRegistrar.getAllPhoneAccounts();
- List<PhoneAccount> profilePhoneAccounts = new ArrayList<>(allPhoneAccounts.size());
- for (PhoneAccount phoneAccount : profilePhoneAccounts) {
- if (isVisibleToCaller(phoneAccount)) {
- profilePhoneAccounts.add(phoneAccount);
- }
- }
- return profilePhoneAccounts;
- } catch (Exception e) {
- Log.e(this, e, "getAllPhoneAccounts");
- throw e;
- }
- }
-
- @Override
- public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
- try {
- return filterForAccountsVisibleToCaller(
- mPhoneAccountRegistrar.getAllPhoneAccountHandles());
- } catch (Exception e) {
- Log.e(this, e, "getAllPhoneAccounts");
- throw e;
- }
- }
-
- @Override
- public PhoneAccountHandle getSimCallManager() {
- try {
- PhoneAccountHandle accountHandle = mPhoneAccountRegistrar.getSimCallManager();
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return null;
- }
- return accountHandle;
- } catch (Exception e) {
- Log.e(this, e, "getSimCallManager");
- throw e;
- }
- }
-
- @Override
- public void setSimCallManager(PhoneAccountHandle accountHandle) {
- enforceModifyPermission();
-
- try {
- mPhoneAccountRegistrar.setSimCallManager(accountHandle);
- } catch (Exception e) {
- Log.e(this, e, "setSimCallManager");
- throw e;
- }
- }
-
- @Override
- public List<PhoneAccountHandle> getSimCallManagers() {
- try {
- return filterForAccountsVisibleToCaller(
- mPhoneAccountRegistrar.getConnectionManagerPhoneAccounts());
- } catch (Exception e) {
- Log.e(this, e, "getSimCallManagers");
- throw e;
- }
- }
-
- @Override
- public void registerPhoneAccount(PhoneAccount account) {
- try {
- enforcePhoneAccountModificationForPackage(
- account.getAccountHandle().getComponentName().getPackageName());
- if (account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
- enforceRegisterCallProviderPermission();
- }
- if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
- enforceRegisterSimSubscriptionPermission();
- }
- if (account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
- enforceRegisterConnectionManagerPermission();
- }
- if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
- enforceRegisterMultiUser();
- }
- enforceUserHandleMatchesCaller(account.getAccountHandle());
-
- mPhoneAccountRegistrar.registerPhoneAccount(account);
- } catch (Exception e) {
- Log.e(this, e, "registerPhoneAccount %s", account);
- throw e;
- }
- }
-
- @Override
- public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
- try {
- enforcePhoneAccountModificationForPackage(
- accountHandle.getComponentName().getPackageName());
- enforceUserHandleMatchesCaller(accountHandle);
- mPhoneAccountRegistrar.unregisterPhoneAccount(accountHandle);
- } catch (Exception e) {
- Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
- throw e;
- }
- }
-
- @Override
- public void clearAccounts(String packageName) {
- try {
- enforcePhoneAccountModificationForPackage(packageName);
- mPhoneAccountRegistrar.clearAccounts(packageName, Binder.getCallingUserHandle());
- } catch (Exception e) {
- Log.e(this, e, "clearAccounts %s", packageName);
- throw e;
- }
- }
-
- /**
- * @see android.telecom.TelecomManager#isVoiceMailNumber
- */
- @Override
- public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number) {
- enforceReadPermissionOrDefaultDialer();
- try {
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return false;
- }
- return mPhoneAccountRegistrar.isVoiceMailNumber(accountHandle, number);
- } catch (Exception e) {
- Log.e(this, e, "getSubscriptionIdForPhoneAccount");
- throw e;
- }
- }
-
- /**
- * @see android.telecom.TelecomManager#hasVoiceMailNumber
- */
- @Override
- public boolean hasVoiceMailNumber(PhoneAccountHandle accountHandle) {
- enforceReadPermissionOrDefaultDialer();
- try {
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return false;
- }
-
- int subId = SubscriptionManager.getDefaultVoiceSubId();
- if (accountHandle != null) {
- subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
- }
- return !TextUtils.isEmpty(getTelephonyManager().getVoiceMailNumber(subId));
- } catch (Exception e) {
- Log.e(this, e, "getSubscriptionIdForPhoneAccount");
- throw e;
- }
- }
-
- /**
- * @see android.telecom.TelecomManager#silenceRinger
- */
- @Override
- public void silenceRinger() {
- Log.d(this, "silenceRinger");
- enforceModifyPermission();
- sendRequestAsync(MSG_SILENCE_RINGER, 0);
- }
-
- /**
- * @see android.telecom.TelecomManager#getDefaultPhoneApp
- */
- @Override
- public ComponentName getDefaultPhoneApp() {
- Resources resources = mContext.getResources();
- return new ComponentName(
- resources.getString(R.string.ui_default_package),
- resources.getString(R.string.dialer_default_class));
- }
-
- /**
- * @see android.telecom.TelecomManager#isInCall
- */
- @Override
- public boolean isInCall() {
- enforceReadPermission();
- // Do not use sendRequest() with this method since it could cause a deadlock with
- // audio service, which we call into from the main thread: AudioManager.setMode().
- final int callState = mCallsManager.getCallState();
- return callState == TelephonyManager.CALL_STATE_OFFHOOK
- || callState == TelephonyManager.CALL_STATE_RINGING;
- }
-
- /**
- * @see android.telecom.TelecomManager#isRinging
- */
- @Override
- public boolean isRinging() {
- enforceReadPermission();
- return mCallsManager.getCallState() == TelephonyManager.CALL_STATE_RINGING;
- }
-
- /**
- * @see TelecomManager#getCallState
- */
- @Override
- public int getCallState() {
- return mCallsManager.getCallState();
- }
-
- /**
- * @see android.telecom.TelecomManager#endCall
- */
- @Override
- public boolean endCall() {
- enforceModifyPermission();
- return (boolean) sendRequest(MSG_END_CALL);
- }
-
- /**
- * @see android.telecom.TelecomManager#acceptRingingCall
- */
- @Override
- public void acceptRingingCall() {
- enforceModifyPermission();
- sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
- }
-
- /**
- * @see android.telecom.TelecomManager#showInCallScreen
- */
- @Override
- public void showInCallScreen(boolean showDialpad) {
- enforceReadPermissionOrDefaultDialer();
- sendRequestAsync(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0);
- }
-
- /**
- * @see android.telecom.TelecomManager#cancelMissedCallsNotification
- */
- @Override
- public void cancelMissedCallsNotification() {
- enforceModifyPermissionOrDefaultDialer();
- sendRequestAsync(MSG_CANCEL_MISSED_CALLS_NOTIFICATION, 0);
- }
-
- /**
- * @see android.telecom.TelecomManager#handleMmi
- */
- @Override
- public boolean handlePinMmi(String dialString) {
- enforceModifyPermissionOrDefaultDialer();
-
- // Switch identity so that TelephonyManager checks Telecom's permissions instead.
- long token = Binder.clearCallingIdentity();
- boolean retval = false;
- try {
- retval = getTelephonyManager().handlePinMmi(dialString);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- return retval;
- }
-
- /**
- * @see android.telecom.TelecomManager#handleMmi
- */
- @Override
- public boolean handlePinMmiForPhoneAccount(PhoneAccountHandle accountHandle,
- String dialString) {
- enforceModifyPermissionOrDefaultDialer();
-
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return false;
- }
-
- // Switch identity so that TelephonyManager checks Telecom's permissions instead.
- long token = Binder.clearCallingIdentity();
- boolean retval = false;
- try {
- int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
- retval = getTelephonyManager().handlePinMmiForSubscriber(subId, dialString);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- return retval;
- }
-
- /**
- * @see android.telecom.TelecomManager#getAdnUriForPhoneAccount
- */
- @Override
- public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle) {
- enforceModifyPermissionOrDefaultDialer();
-
- if (!isVisibleToCaller(accountHandle)) {
- Log.w(this, "%s is not visible for the calling user", accountHandle);
- return null;
- }
-
- // Switch identity so that TelephonyManager checks Telecom's permissions instead.
- long token = Binder.clearCallingIdentity();
- String retval = "content://icc/adn/";
- try {
- long subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
- retval = retval + "subId/" + subId;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- return Uri.parse(retval);
- }
-
- /**
- * @see android.telecom.TelecomManager#isTtySupported
- */
- @Override
- public boolean isTtySupported() {
- enforceReadPermission();
- return (boolean) sendRequest(MSG_IS_TTY_SUPPORTED);
- }
-
- /**
- * @see android.telecom.TelecomManager#getCurrentTtyMode
- */
- @Override
- public int getCurrentTtyMode() {
- enforceReadPermission();
- return (int) sendRequest(MSG_GET_CURRENT_TTY_MODE);
- }
-
- /**
- * @see android.telecom.TelecomManager#addNewIncomingCall
- */
- @Override
- public void addNewIncomingCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
- Log.i(this, "Adding new incoming call with phoneAccountHandle %s", phoneAccountHandle);
- if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null) {
- mAppOpsManager.checkPackage(
- Binder.getCallingUid(), phoneAccountHandle.getComponentName().getPackageName());
-
- // Make sure it doesn't cross the UserHandle boundary
- enforceUserHandleMatchesCaller(phoneAccountHandle);
-
- Intent intent = new Intent(TelecomManager.ACTION_INCOMING_CALL);
- intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
- intent.putExtra(CallReceiver.KEY_IS_INCOMING_CALL, true);
- if (extras != null) {
- intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras);
- }
- sendRequestAsync(MSG_NEW_INCOMING_CALL, 0, intent);
- } else {
- Log.w(this, "Null phoneAccountHandle. Ignoring request to add new incoming call");
- }
- }
-
- /**
- * @see android.telecom.TelecomManager#addNewUnknownCall
- */
- @Override
- public void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
- if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null &&
- TelephonyUtil.isPstnComponentName(phoneAccountHandle.getComponentName())) {
- mAppOpsManager.checkPackage(
- Binder.getCallingUid(), phoneAccountHandle.getComponentName().getPackageName());
-
- // Make sure it doesn't cross the UserHandle boundary
- enforceUserHandleMatchesCaller(phoneAccountHandle);
-
- Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
- intent.setClass(mContext, CallReceiver.class);
- intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtras(extras);
- intent.putExtra(CallReceiver.KEY_IS_UNKNOWN_CALL, true);
- intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
- mContext.sendBroadcastAsUser(intent, phoneAccountHandle.getUserHandle());
- } else {
- Log.i(this, "Null phoneAccountHandle or not initiated by Telephony. Ignoring request"
- + " to add new unknown call.");
- }
- }
-
- //
- // Supporting methods for the ITelecomService interface implementation.
- //
-
- private boolean isVisibleToCaller(PhoneAccountHandle accountHandle) {
- if (accountHandle == null) {
- return false;
- }
-
- return isVisibleToCaller(mPhoneAccountRegistrar.getPhoneAccountInternal(accountHandle));
- }
-
- private boolean isVisibleToCaller(PhoneAccount account) {
- if (account == null) {
- return false;
- }
-
- // If this PhoneAccount has CAPABILITY_MULTI_USER, it should be visible to all users and
- // all profiles. Only Telephony and SIP accounts should have this capability.
- if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
- return true;
- }
-
- UserHandle phoneAccountUserHandle = account.getAccountHandle().getUserHandle();
- if (phoneAccountUserHandle == null) {
- return false;
- }
-
- List<UserHandle> profileUserHandles;
- if (isCallerSystemApp()) {
- // If the caller lives in /system/priv-app, it can see PhoneAccounts for all of the
- // *profiles* that the calling user owns, but not for any other *users*.
- profileUserHandles = mUserManager.getUserProfiles();
- } else {
- // Otherwise, it has to be owned by the current caller's profile.
- profileUserHandles = new ArrayList<>(1);
- profileUserHandles.add(Binder.getCallingUserHandle());
- }
-
- return profileUserHandles.contains(phoneAccountUserHandle);
- }
-
- /**
- * Given a list of {@link PhoneAccountHandle}s, filter them to the ones that the calling
- * user can see.
- *
- * @param phoneAccountHandles Unfiltered list of account handles.
- *
- * @return {@link PhoneAccountHandle}s visible to the calling user and its profiles.
- */
- private List<PhoneAccountHandle> filterForAccountsVisibleToCaller(
- List<PhoneAccountHandle> phoneAccountHandles) {
- List<PhoneAccountHandle> profilePhoneAccountHandles =
- new ArrayList<>(phoneAccountHandles.size());
- for (PhoneAccountHandle phoneAccountHandle : phoneAccountHandles) {
- if (isVisibleToCaller(phoneAccountHandle)) {
- profilePhoneAccountHandles.add(phoneAccountHandle);
- }
- }
- return profilePhoneAccountHandles;
- }
-
- private boolean isCallerSystemApp() {
- int uid = Binder.getCallingUid();
- String[] packages = mPackageManager.getPackagesForUid(uid);
- for (String packageName : packages) {
- if (isPackageSystemApp(packageName)) {
- return true;
- }
- }
- return false;
- }
-
- private boolean isPackageSystemApp(String packageName) {
- try {
- ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(packageName,
- PackageManager.GET_META_DATA);
- if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- return true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- }
- return false;
- }
-
- private void acceptRingingCallInternal() {
- Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
- if (call != null) {
- call.answer(call.getVideoState());
- }
- }
-
- private boolean endCallInternal() {
- // Always operate on the foreground call if one exists, otherwise get the first call in
- // priority order by call-state.
- Call call = mCallsManager.getForegroundCall();
- if (call == null) {
- call = mCallsManager.getFirstCallWithState(
- CallState.ACTIVE,
- CallState.DIALING,
- CallState.RINGING,
- CallState.ON_HOLD);
- }
-
- if (call != null) {
- if (call.getState() == CallState.RINGING) {
- call.reject(false /* rejectWithMessage */, null);
- } else {
- call.disconnect();
- }
- return true;
- }
-
- return false;
- }
-
- private void enforcePhoneAccountModificationForPackage(String packageName) {
- // TODO: Use a new telecomm permission for this instead of reusing modify.
-
- int result = mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
-
- // Callers with MODIFY_PHONE_STATE can use the PhoneAccount mechanism to implement
- // built-in behavior even when PhoneAccounts are not exposed as a third-part API. They
- // may also modify PhoneAccounts on behalf of any 'packageName'.
-
- if (result != PackageManager.PERMISSION_GRANTED) {
- // Other callers are only allowed to modify PhoneAccounts if the relevant system
- // feature is enabled ...
- enforceConnectionServiceFeature();
- // ... and the PhoneAccounts they refer to are for their own package.
- enforceCallingPackage(packageName);
- }
- }
-
- private void enforceReadPermissionOrDefaultDialer() {
- if (!isDefaultDialerCalling()) {
- enforceReadPermission();
- }
- }
-
- private void enforceModifyPermissionOrDefaultDialer() {
- if (!isDefaultDialerCalling()) {
- enforceModifyPermission();
- }
- }
-
- private void enforceCallingPackage(String packageName) {
- mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
- }
-
- private void enforceConnectionServiceFeature() {
- enforceFeature(PackageManager.FEATURE_CONNECTION_SERVICE);
- }
-
- private void enforceRegisterCallProviderPermission() {
- enforcePermission(android.Manifest.permission.REGISTER_CALL_PROVIDER);
- }
-
- private void enforceRegisterSimSubscriptionPermission() {
- enforcePermission(android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION);
- }
-
- private void enforceRegisterConnectionManagerPermission() {
- enforcePermission(android.Manifest.permission.REGISTER_CONNECTION_MANAGER);
- }
-
- private void enforceReadPermission() {
- enforcePermission(Manifest.permission.READ_PHONE_STATE);
- }
-
- private void enforceModifyPermission() {
- enforcePermission(Manifest.permission.MODIFY_PHONE_STATE);
- }
-
- private void enforcePermission(String permission) {
- mContext.enforceCallingOrSelfPermission(permission, null);
- }
-
- private void enforceRegisterMultiUser() {
- if (!isCallerSystemApp()) {
- throw new SecurityException("CAPABILITY_MULTI_USER is only available to system apps.");
- }
- }
-
- private void enforceUserHandleMatchesCaller(PhoneAccountHandle accountHandle) {
- if (!Binder.getCallingUserHandle().equals(accountHandle.getUserHandle())) {
- throw new SecurityException("Calling UserHandle does not match PhoneAccountHandle's");
- }
- }
-
- private void enforceFeature(String feature) {
- PackageManager pm = mContext.getPackageManager();
- if (!pm.hasSystemFeature(feature)) {
- throw new UnsupportedOperationException(
- "System does not support feature " + feature);
- }
- }
-
- private boolean isDefaultDialerCalling() {
- ComponentName defaultDialerComponent = getDefaultPhoneApp();
- if (defaultDialerComponent != null) {
- try {
- mAppOpsManager.checkPackage(
- Binder.getCallingUid(), defaultDialerComponent.getPackageName());
- return true;
- } catch (SecurityException e) {
- Log.e(TAG, e, "Could not get default dialer.");
- }
- }
- return false;
- }
-
- private TelephonyManager getTelephonyManager() {
- return (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
- }
-
- private MainThreadRequest sendRequestAsync(int command, int arg1) {
- return sendRequestAsync(command, arg1, null);
- }
-
- private MainThreadRequest sendRequestAsync(int command, int arg1, Object arg) {
- MainThreadRequest request = new MainThreadRequest();
- request.arg = arg;
- mMainThreadHandler.obtainMessage(command, arg1, 0, request).sendToTarget();
- return request;
- }
-
- /**
- * Posts the specified command to be executed on the main thread, waits for the request to
- * complete, and returns the result.
- */
- private Object sendRequest(int command) {
- if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
- MainThreadRequest request = new MainThreadRequest();
- mMainThreadHandler.handleMessage(mMainThreadHandler.obtainMessage(command, request));
- return request.result;
- } else {
- MainThreadRequest request = sendRequestAsync(command, 0);
-
- // 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;
- }
- }
-
- /**
- * Dumps the current state of the TelecomService. Used when generating problem reports.
- *
- * @param fd The file descriptor.
- * @param writer The print writer to dump the state to.
- * @param args Optional dump arguments.
- */
- @Override
- protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
- final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- if (mCallsManager != null) {
- pw.println("mCallsManager: ");
- pw.increaseIndent();
- mCallsManager.dump(pw);
- pw.decreaseIndent();
-
- pw.println("mPhoneAccountRegistrar: ");
- pw.increaseIndent();
- mPhoneAccountRegistrar.dump(pw);
- pw.decreaseIndent();
- }
- }
-}