blob: 0f0d2219fb7ea9a3551e524c99540bfcfcb1b9f5 [file] [log] [blame]
/*
* 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 static android.Manifest.permission.CALL_PHONE;
import static android.Manifest.permission.MODIFY_PHONE_STATE;
import static android.Manifest.permission.READ_PHONE_STATE;
import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
import static android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import android.app.ActivityManager;
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.IBinder;
import android.os.UserHandle;
import android.os.UserManager;
import android.telecom.DefaultDialerManager;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.EventLog;
// 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 com.android.server.telecom.components.UserCallIntentProcessor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Implementation of the ITelecom interface.
*/
public class TelecomServiceImpl {
private static final String PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION =
"android.permission.PROCESS_PHONE_ACCOUNT_REGISTRATION";
private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
@Override
public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme,
String callingPackage) {
synchronized (mLock) {
if (!canReadPhoneState(callingPackage, "getDefaultOutgoingPhoneAccount")) {
return null;
}
long token = Binder.clearCallingIdentity();
try {
PhoneAccountHandle defaultOutgoingPhoneAccount =
mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(uriScheme);
// Make sure that the calling user can see this phone account.
// TODO: Does this isVisible check actually work considering we are clearing
// the calling identity?
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;
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
@Override
public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
synchronized (mLock) {
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) {
synchronized (mLock) {
enforceModifyPermission();
long token = Binder.clearCallingIdentity();
try {
mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(accountHandle);
} catch (Exception e) {
Log.e(this, e, "setUserSelectedOutgoingPhoneAccount");
throw e;
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
@Override
public List<PhoneAccountHandle> getCallCapablePhoneAccounts(
boolean includeDisabledAccounts, String callingPackage) {
if (!canReadPhoneState(callingPackage, "getDefaultOutgoingPhoneAccount")) {
return Collections.emptyList();
}
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
try {
// TODO: Does this isVisible check actually work considering we are clearing
// the calling identity?
return filterForAccountsVisibleToCaller(
mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
null, includeDisabledAccounts));
} catch (Exception e) {
Log.e(this, e, "getCallCapablePhoneAccounts");
throw e;
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
@Override
public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme,
String callingPackage) {
synchronized (mLock) {
if (!canReadPhoneState(callingPackage, "getPhoneAccountsSupportingScheme")) {
return Collections.emptyList();
}
long token = Binder.clearCallingIdentity();
try {
// TODO: Does this isVisible check actually work considering we are clearing
// the calling identity?
return filterForAccountsVisibleToCaller(
mPhoneAccountRegistrar.getCallCapablePhoneAccounts(uriScheme, false));
} catch (Exception e) {
Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme);
throw e;
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
@Override
public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName) {
synchronized (mLock) {
try {
return filterForAccountsVisibleToCaller(
mPhoneAccountRegistrar.getPhoneAccountsForPackage(packageName));
} catch (Exception e) {
Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
throw e;
}
}
}
@Override
public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) {
synchronized (mLock) {
try {
if (!isVisibleToCaller(accountHandle)) {
Log.d(this, "%s is not visible for the calling user [gPA]", accountHandle);
return null;
}
// TODO: Do we really want to return for *any* user?
return mPhoneAccountRegistrar.getPhoneAccount(accountHandle);
} catch (Exception e) {
Log.e(this, e, "getPhoneAccount %s", accountHandle);
throw e;
}
}
}
@Override
public int getAllPhoneAccountsCount() {
synchronized (mLock) {
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() {
synchronized (mLock) {
try {
List<PhoneAccount> allPhoneAccounts = mPhoneAccountRegistrar
.getAllPhoneAccounts();
List<PhoneAccount> profilePhoneAccounts = new ArrayList<>(
allPhoneAccounts.size());
for (PhoneAccount phoneAccount : allPhoneAccounts) {
if (isVisibleToCaller(phoneAccount)) {
profilePhoneAccounts.add(phoneAccount);
}
}
return profilePhoneAccounts;
} catch (Exception e) {
Log.e(this, e, "getAllPhoneAccounts");
throw e;
}
}
}
@Override
public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
synchronized (mLock) {
try {
return filterForAccountsVisibleToCaller(
mPhoneAccountRegistrar.getAllPhoneAccountHandles());
} catch (Exception e) {
Log.e(this, e, "getAllPhoneAccounts");
throw e;
}
}
}
@Override
public PhoneAccountHandle getSimCallManager() {
long token = Binder.clearCallingIdentity();
int user;
try {
user = ActivityManager.getCurrentUser();
} finally {
Binder.restoreCallingIdentity(token);
}
return getSimCallManagerForUser(user);
}
@Override
public PhoneAccountHandle getSimCallManagerForUser(int user) {
synchronized (mLock) {
try {
PhoneAccountHandle accountHandle = null;
long token = Binder.clearCallingIdentity();
try {
accountHandle = mPhoneAccountRegistrar.getSimCallManager(user);
} finally {
// We restore early so that isVisibleToCaller invocation below uses the
// right user context.
Binder.restoreCallingIdentity(token);
}
if (!isVisibleToCaller(accountHandle)) {
Log.d(this, "%s is not visible for the calling user [gsCM]", accountHandle);
return null;
}
return accountHandle;
} catch (Exception e) {
Log.e(this, e, "getSimCallManager");
throw e;
}
}
}
@Override
public void registerPhoneAccount(PhoneAccount account) {
synchronized (mLock) {
if (!mContext.getApplicationContext().getResources().getBoolean(
com.android.internal.R.bool.config_voice_capable)) {
Log.w(this, "registerPhoneAccount not allowed on non-voice capable device.");
return;
}
try {
enforcePhoneAccountModificationForPackage(
account.getAccountHandle().getComponentName().getPackageName());
if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
enforceRegisterSimSubscriptionPermission();
}
if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
enforceRegisterMultiUser();
}
enforceUserHandleMatchesCaller(account.getAccountHandle());
mPhoneAccountRegistrar.registerPhoneAccount(account);
// Broadcast an intent indicating the phone account which was registered.
long token = Binder.clearCallingIdentity();
try {
Intent intent = new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED);
intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
account.getAccountHandle());
Log.i(this, "Sending phone-account registered intent as user");
mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION);
} finally {
Binder.restoreCallingIdentity(token);
}
} catch (Exception e) {
Log.e(this, e, "registerPhoneAccount %s", account);
throw e;
}
}
}
@Override
public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
synchronized (mLock) {
try {
enforcePhoneAccountModificationForPackage(
accountHandle.getComponentName().getPackageName());
enforceUserHandleMatchesCaller(accountHandle);
mPhoneAccountRegistrar.unregisterPhoneAccount(accountHandle);
// Broadcast an intent indicating the phone account which was unregistered.
long token = Binder.clearCallingIdentity();
try {
Intent intent =
new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_UNREGISTERED);
intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle);
Log.i(this, "Sending phone-account unregistered intent as user");
mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION);
} finally {
Binder.restoreCallingIdentity(token);
}
} catch (Exception e) {
Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
throw e;
}
}
}
@Override
public void clearAccounts(String packageName) {
synchronized (mLock) {
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,
String callingPackage) {
synchronized (mLock) {
if (!canReadPhoneState(callingPackage, "isVoiceMailNumber")) {
return false;
}
if (!isVisibleToCaller(accountHandle)) {
Log.d(this, "%s is not visible for the calling user [iVMN]", accountHandle);
return false;
}
long token = Binder.clearCallingIdentity();
try {
return mPhoneAccountRegistrar.isVoiceMailNumber(accountHandle, number);
} catch (Exception e) {
Log.e(this, e, "getSubscriptionIdForPhoneAccount");
throw e;
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
/**
* @see android.telecom.TelecomManager#getVoiceMailNumber
*/
@Override
public String getVoiceMailNumber(PhoneAccountHandle accountHandle, String callingPackage) {
synchronized (mLock) {
if (!canReadPhoneState(callingPackage, "getVoiceMailNumber")) {
return null;
}
try {
if (!isVisibleToCaller(accountHandle)) {
Log.d(this, "%s is not visible for the calling user [gVMN]", accountHandle);
return null;
}
int subId = SubscriptionManager.getDefaultVoiceSubId();
if (accountHandle != null) {
subId = mPhoneAccountRegistrar
.getSubscriptionIdForPhoneAccount(accountHandle);
}
return getTelephonyManager().getVoiceMailNumber(subId);
} catch (Exception e) {
Log.e(this, e, "getSubscriptionIdForPhoneAccount");
throw e;
}
}
}
/**
* @see android.telecom.TelecomManager#getLine1Number
*/
@Override
public String getLine1Number(PhoneAccountHandle accountHandle, String callingPackage) {
if (!canReadPhoneState(callingPackage, "getLine1Number")) {
return null;
}
synchronized (mLock) {
if (!isVisibleToCaller(accountHandle)) {
Log.d(this, "%s is not visible for the calling user [gL1N]", accountHandle);
return null;
}
long token = Binder.clearCallingIdentity();
try {
int subId =
mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
return getTelephonyManager().getLine1NumberForSubscriber(subId);
} catch (Exception e) {
Log.e(this, e, "getSubscriptionIdForPhoneAccount");
throw e;
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
/**
* @see android.telecom.TelecomManager#silenceRinger
*/
@Override
public void silenceRinger(String callingPackage) {
synchronized (mLock) {
enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
long token = Binder.clearCallingIdentity();
try {
Log.i(this, "Silence Ringer requested by %s", callingPackage);
mCallsManager.getRinger().silence();
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
/**
* @see android.telecom.TelecomManager#getDefaultPhoneApp
* @deprecated - Use {@link android.telecom.TelecomManager#getDefaultDialerPackage()}
* instead.
*/
@Override
public ComponentName getDefaultPhoneApp() {
// No need to synchronize
Resources resources = mContext.getResources();
return new ComponentName(
resources.getString(R.string.ui_default_package),
resources.getString(R.string.dialer_default_class));
}
/**
* @return the package name of the current user-selected default dialer. If no default
* has been selected, the package name of the system dialer is returned. If
* neither exists, then {@code null} is returned.
* @see android.telecom.TelecomManager#getDefaultDialerPackage
*/
@Override
public String getDefaultDialerPackage() {
final long token = Binder.clearCallingIdentity();
try {
return DefaultDialerManager.getDefaultDialerApplication(mContext);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* @see android.telecom.TelecomManager#getSystemDialerPackage
*/
@Override
public String getSystemDialerPackage() {
return mContext.getResources().getString(R.string.ui_default_package);
}
/**
* @see android.telecom.TelecomManager#isInCall
*/
@Override
public boolean isInCall(String callingPackage) {
if (!canReadPhoneState(callingPackage, "isInCall")) {
return false;
}
synchronized (mLock) {
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(String callingPackage) {
if (!canReadPhoneState(callingPackage, "isRinging")) {
return false;
}
synchronized (mLock) {
return mCallsManager.getCallState() == TelephonyManager.CALL_STATE_RINGING;
}
}
/**
* @see TelecomManager#getCallState
*/
@Override
public int getCallState() {
synchronized (mLock) {
return mCallsManager.getCallState();
}
}
/**
* @see android.telecom.TelecomManager#endCall
*/
@Override
public boolean endCall() {
synchronized (mLock) {
enforceModifyPermission();
long token = Binder.clearCallingIdentity();
try {
return endCallInternal();
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
/**
* @see android.telecom.TelecomManager#acceptRingingCall
*/
@Override
public void acceptRingingCall() {
synchronized (mLock) {
enforceModifyPermission();
long token = Binder.clearCallingIdentity();
try {
acceptRingingCallInternal();
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
/**
* @see android.telecom.TelecomManager#showInCallScreen
*/
@Override
public void showInCallScreen(boolean showDialpad, String callingPackage) {
if (!canReadPhoneState(callingPackage, "showInCallScreen")) {
return;
}
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
try {
mCallsManager.getInCallController().bringToForeground(showDialpad);
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
/**
* @see android.telecom.TelecomManager#cancelMissedCallsNotification
*/
@Override
public void cancelMissedCallsNotification(String callingPackage) {
synchronized (mLock) {
enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
long token = Binder.clearCallingIdentity();
try {
mCallsManager.getMissedCallNotifier().clearMissedCalls();
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
/**
* @see android.telecom.TelecomManager#handleMmi
*/
@Override
public boolean handlePinMmi(String dialString, String callingPackage) {
synchronized (mLock) {
enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
// 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,
String callingPackage) {
synchronized (mLock) {
enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
if (!isVisibleToCaller(accountHandle)) {
Log.d(this, "%s is not visible for the calling user [hMMI]", 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,
String callingPackage) {
synchronized (mLock) {
enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
if (!isVisibleToCaller(accountHandle)) {
Log.d(this, "%s is not visible for the calling user [gA4PA]", 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(String callingPackage) {
if (!canReadPhoneState(callingPackage, "hasVoiceMailNumber")) {
return false;
}
synchronized (mLock) {
return mCallsManager.isTtySupported();
}
}
/**
* @see android.telecom.TelecomManager#getCurrentTtyMode
*/
@Override
public int getCurrentTtyMode(String callingPackage) {
if (!canReadPhoneState(callingPackage, "getCurrentTtyMode")) {
return TelecomManager.TTY_MODE_OFF;
}
synchronized (mLock) {
return mCallsManager.getCurrentTtyMode();
}
}
/**
* @see android.telecom.TelecomManager#addNewIncomingCall
*/
@Override
public void addNewIncomingCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
synchronized (mLock) {
Log.i(this, "Adding new incoming call with phoneAccountHandle %s",
phoneAccountHandle);
if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null) {
// TODO(sail): Add unit tests for adding incoming calls from a SIM call manager.
if (isCallerSimCallManager() && TelephonyUtil.isPstnComponentName(
phoneAccountHandle.getComponentName())) {
Log.v(this, "Allowing call manager to add incoming call with PSTN handle");
} else {
mAppOpsManager.checkPackage(
Binder.getCallingUid(),
phoneAccountHandle.getComponentName().getPackageName());
// Make sure it doesn't cross the UserHandle boundary
enforceUserHandleMatchesCaller(phoneAccountHandle);
enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle);
}
long token = Binder.clearCallingIdentity();
try {
Intent intent = new Intent(TelecomManager.ACTION_INCOMING_CALL);
intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
phoneAccountHandle);
intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, true);
if (extras != null) {
intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras);
}
CallIntentProcessor.processIncomingCallIntent(mCallsManager, intent);
} finally {
Binder.restoreCallingIdentity(token);
}
} 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) {
synchronized (mLock) {
if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null) {
mAppOpsManager.checkPackage(
Binder.getCallingUid(),
phoneAccountHandle.getComponentName().getPackageName());
// Make sure it doesn't cross the UserHandle boundary
enforceUserHandleMatchesCaller(phoneAccountHandle);
enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle);
long token = Binder.clearCallingIdentity();
try {
Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
intent.putExtras(extras);
intent.putExtra(CallIntentProcessor.KEY_IS_UNKNOWN_CALL, true);
intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
phoneAccountHandle);
CallIntentProcessor.processUnknownCallIntent(mCallsManager, intent);
} finally {
Binder.restoreCallingIdentity(token);
}
} else {
Log.i(this,
"Null phoneAccountHandle or not initiated by Telephony. " +
"Ignoring request to add new unknown call.");
}
}
}
/**
* @see android.telecom.TelecomManager#placeCall
*/
@Override
public void placeCall(Uri handle, Bundle extras, String callingPackage) {
enforceCallingPackage(callingPackage);
if (!canCallPhone(callingPackage, "placeCall")) {
throw new SecurityException("Package " + callingPackage
+ " is not allowed to place phone calls");
}
// Note: we can still get here for the default/system dialer, even if the Phone
// permission is turned off. This is because the default/system dialer is always
// allowed to attempt to place a call (regardless of permission state), in case
// it turns out to be an emergency call. If the permission is denied and the
// call is being made to a non-emergency number, the call will be denied later on
// by {@link UserCallIntentProcessor}.
final boolean hasCallAppOp = mAppOpsManager.noteOp(AppOpsManager.OP_CALL_PHONE,
Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED;
final boolean hasCallPermission = mContext.checkCallingPermission(CALL_PHONE) ==
PackageManager.PERMISSION_GRANTED;
synchronized (mLock) {
final UserHandle userHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
try {
final Intent intent = new Intent(Intent.ACTION_CALL, handle);
intent.putExtras(extras);
new UserCallIntentProcessor(mContext, userHandle).processIntent(intent,
callingPackage, hasCallAppOp && hasCallPermission);
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
/**
* @see android.telecom.TelecomManager#enablePhoneAccount
*/
@Override
public boolean enablePhoneAccount(PhoneAccountHandle accountHandle, boolean isEnabled) {
enforceModifyPermission();
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
try {
// enable/disable phone account
return mPhoneAccountRegistrar.enablePhoneAccount(accountHandle, isEnabled);
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
@Override
public boolean setDefaultDialer(String packageName) {
enforcePermission(MODIFY_PHONE_STATE);
enforcePermission(WRITE_SECURE_SETTINGS);
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
try {
final boolean result =
DefaultDialerManager.setDefaultDialerApplication(mContext, packageName);
if (result) {
final Intent intent =
new Intent(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED);
intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME,
packageName);
mContext.sendBroadcastAsUser(intent,
new UserHandle(ActivityManager.getCurrentUser()));
}
return result;
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
/**
* 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) {
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
writer.println("Permission Denial: can't dump TelecomService " +
"from from pid=" + Binder.getCallingPid() + ", uid=" +
Binder.getCallingUid());
return;
}
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
if (mCallsManager != null) {
pw.println("CallsManager: ");
pw.increaseIndent();
mCallsManager.dump(pw);
pw.decreaseIndent();
pw.println("PhoneAccountRegistrar: ");
pw.increaseIndent();
mPhoneAccountRegistrar.dump(pw);
pw.decreaseIndent();
}
Log.dumpCallEvents(pw);
}
};
private Context mContext;
private AppOpsManager mAppOpsManager;
private UserManager mUserManager;
private PackageManager mPackageManager;
private CallsManager mCallsManager;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final TelecomSystem.SyncRoot mLock;
public TelecomServiceImpl(
Context context,
CallsManager callsManager,
PhoneAccountRegistrar phoneAccountRegistrar,
TelecomSystem.SyncRoot lock) {
mContext = context;
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mPackageManager = mContext.getPackageManager();
mCallsManager = callsManager;
mLock = lock;
mPhoneAccountRegistrar = phoneAccountRegistrar;
}
public ITelecomService.Stub getBinder() {
return mBinderImpl;
}
//
// Supporting methods for the ITelecomService interface implementation.
//
private boolean isVisibleToCaller(PhoneAccountHandle accountHandle) {
if (accountHandle == null) {
return false;
}
return isVisibleToCaller(mPhoneAccountRegistrar.getPhoneAccount(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;
}
if (phoneAccountUserHandle.equals(Binder.getCallingUserHandle())) {
return true;
}
List<UserHandle> profileUserHandles;
if (UserHandle.getCallingUserId() == UserHandle.USER_OWNER) {
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;
}
// Enforce that the PhoneAccountHandle being passed in is both registered to the current user
// and enabled.
private void enforcePhoneAccountIsRegisteredEnabled(PhoneAccountHandle phoneAccountHandle) {
PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountCheckCallingUser(
phoneAccountHandle);
if (phoneAccount == null) {
EventLog.writeEvent(0x534e4554, "26864502", Binder.getCallingUid(), "R");
throw new SecurityException("This PhoneAccountHandle is not registered for this user!");
}
if (!phoneAccount.isEnabled()) {
EventLog.writeEvent(0x534e4554, "26864502", Binder.getCallingUid(), "E");
throw new SecurityException("This PhoneAccountHandle is not enabled for this user!");
}
}
private void enforcePhoneAccountModificationForPackage(String packageName) {
// TODO: Use a new telecomm permission for this instead of reusing modify.
int result = mContext.checkCallingOrSelfPermission(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 enforcePermissionOrPrivilegedDialer(String permission, String packageName) {
if (!isPrivilegedDialerCalling(packageName)) {
try {
enforcePermission(permission);
} catch (SecurityException e) {
Log.e(this, e, "Caller must be the default or system dialer, or have the permission"
+ " %s to perform this operation.", permission);
throw e;
}
}
}
private void enforceCallingPackage(String packageName) {
mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
}
private void enforceConnectionServiceFeature() {
enforceFeature(PackageManager.FEATURE_CONNECTION_SERVICE);
}
private void enforceRegisterSimSubscriptionPermission() {
enforcePermission(REGISTER_SIM_SUBSCRIPTION);
}
private void enforceModifyPermission() {
enforcePermission(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 canReadPhoneState(String callingPackage, String message) {
// The system/default dialer can always read phone state - so that emergency calls will
// still work.
if (isPrivilegedDialerCalling(callingPackage)) {
return true;
}
try {
mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, message);
// SKIP checking run-time OP_READ_PHONE_STATE since caller or self has PRIVILEGED
// permission
return true;
} catch (SecurityException e) {
// Accessing phone state is gated by a special permission.
mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, message);
// Some apps that have the permission can be restricted via app ops.
return mAppOpsManager.noteOp(AppOpsManager.OP_READ_PHONE_STATE,
Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED;
}
}
private boolean canCallPhone(String callingPackage, String message) {
// The system/default dialer can always read phone state - so that emergency calls will
// still work.
if (isPrivilegedDialerCalling(callingPackage)) {
return true;
}
// Accessing phone state is gated by a special permission.
mContext.enforceCallingOrSelfPermission(CALL_PHONE, message);
// Some apps that have the permission can be restricted via app ops.
return mAppOpsManager.noteOp(AppOpsManager.OP_CALL_PHONE,
Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED;
}
private boolean isCallerSimCallManager() {
PhoneAccountHandle accountHandle = null;
long token = Binder.clearCallingIdentity();
try {
accountHandle = mPhoneAccountRegistrar.getSimCallManager();
} finally {
Binder.restoreCallingIdentity(token);
}
if (accountHandle != null) {
try {
mAppOpsManager.checkPackage(
Binder.getCallingUid(), accountHandle.getComponentName().getPackageName());
return true;
} catch (SecurityException e) {
}
}
return false;
}
private boolean isPrivilegedDialerCalling(String callingPackage) {
mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
return DefaultDialerManager.isDefaultOrSystemDialer(mContext, callingPackage);
}
private TelephonyManager getTelephonyManager() {
return (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
}
}