| /* |
| * 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.CALL_PRIVILEGED; |
| import static android.Manifest.permission.DUMP; |
| import static android.Manifest.permission.MANAGE_OWN_CALLS; |
| import static android.Manifest.permission.MODIFY_PHONE_STATE; |
| import static android.Manifest.permission.READ_PHONE_NUMBERS; |
| import static android.Manifest.permission.READ_PHONE_STATE; |
| import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE; |
| import static android.Manifest.permission.READ_SMS; |
| import static android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION; |
| import static android.Manifest.permission.WRITE_SECURE_SETTINGS; |
| import static android.telecom.CallException.CODE_ERROR_UNKNOWN; |
| import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS; |
| |
| import android.Manifest; |
| |
| import android.app.privatecompute.flags.Flags; |
| import android.annotation.SuppressLint; |
| import android.app.ActivityManager; |
| import android.app.AppOpsManager; |
| import android.app.UiModeManager; |
| import android.app.compat.CompatChanges; |
| import android.content.AttributionSource; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.content.res.Resources; |
| import android.graphics.drawable.Icon; |
| import android.net.Uri; |
| import android.os.Binder; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.OutcomeReceiver; |
| import android.os.ParcelFileDescriptor; |
| import android.os.Process; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.permission.PermissionManager; |
| import android.provider.BlockedNumbersManager; |
| import android.telecom.CallAttributes; |
| import android.telecom.CallException; |
| import android.telecom.DisconnectCause; |
| import android.telecom.Log; |
| import android.telecom.PhoneAccount; |
| import android.telecom.PhoneAccountHandle; |
| import android.telecom.StatusHints; |
| import android.telecom.TelecomAnalytics; |
| import android.telecom.TelecomManager; |
| import android.telecom.VideoProfile; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyManager; |
| import android.text.TextUtils; |
| import android.util.Base64; |
| import android.util.EventLog; |
| import android.util.IndentingPrintWriter; |
| |
| import androidx.annotation.NonNull; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telecom.ICallControl; |
| import com.android.internal.telecom.ICallEventCallback; |
| import com.android.internal.telecom.ITelecomService; |
| import com.android.modules.utils.ParceledListSlice; |
| import com.android.server.telecom.callsequencing.voip.VoipCallMonitor; |
| import com.android.server.telecom.components.UserCallIntentProcessorFactory; |
| import com.android.server.telecom.flags.FeatureFlags; |
| import com.android.server.telecom.metrics.ApiStats; |
| import com.android.server.telecom.metrics.ErrorStats; |
| import com.android.server.telecom.metrics.EventStats; |
| import com.android.server.telecom.metrics.EventStats.CriticalEvent; |
| import com.android.server.telecom.metrics.TelecomMetricsController; |
| import com.android.server.telecom.callsequencing.TransactionManager; |
| import com.android.server.telecom.callsequencing.CallTransaction; |
| import com.android.server.telecom.callsequencing.CallTransactionResult; |
| import com.android.server.telecom.util.TelecomBundleUtils; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.Collections; |
| import java.lang.reflect.Method; |
| import java.time.Duration; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.concurrent.CompletableFuture; |
| |
| /** |
| * Implementation of the ITelecom interface. |
| */ |
| /* TODO: b/478043076 - Remove SuppressLint once the API is finalized. |
| * And update the SDK check to the final version number. |
| */ |
| @SuppressLint("NewApi") |
| public class TelecomServiceImpl { |
| |
| /** |
| * Anomaly Report UUIDs and corresponding error descriptions specific to TelecomServiceImpl. |
| */ |
| public static final UUID REGISTER_PHONE_ACCOUNT_ERROR_UUID = |
| UUID.fromString("0e49f82e-6acc-48a9-b088-66c8296c1eb5"); |
| public static final String REGISTER_PHONE_ACCOUNT_ERROR_MSG = |
| "Exception thrown while registering phone account."; |
| public static final UUID SET_USER_PHONE_ACCOUNT_ERROR_UUID = |
| UUID.fromString("80866066-7818-4869-bd44-1f7f689543e2"); |
| public static final String SET_USER_PHONE_ACCOUNT_ERROR_MSG = |
| "Exception thrown while setting the user selected outgoing phone account."; |
| public static final UUID GET_CALL_CAPABLE_ACCOUNTS_ERROR_UUID = |
| UUID.fromString("4f39b865-01f2-4c1f-83a5-37ce52807e83"); |
| public static final String GET_CALL_CAPABLE_ACCOUNTS_ERROR_MSG = |
| "Exception thrown while getting the call capable phone accounts"; |
| public static final UUID GET_PHONE_ACCOUNT_ERROR_UUID = |
| UUID.fromString("b653c1f0-91b4-45c8-ad05-3ee4d1006c7f"); |
| public static final String GET_PHONE_ACCOUNT_ERROR_MSG = |
| "Exception thrown while retrieving the phone account."; |
| public static final UUID GET_SIM_MANAGER_ERROR_UUID = |
| UUID.fromString("4244cb3f-bd02-4cc5-9f90-f41ea62ce0bb"); |
| public static final String GET_SIM_MANAGER_ERROR_MSG = |
| "Exception thrown while retrieving the SIM CallManager."; |
| public static final UUID GET_SIM_MANAGER_FOR_USER_ERROR_UUID = |
| UUID.fromString("5d347ce7-7527-40d3-b98a-09b423ad031c"); |
| public static final String GET_SIM_MANAGER_FOR_USER_ERROR_MSG = |
| "Exception thrown while retrieving the SIM CallManager based on the provided user."; |
| public static final UUID PLACE_CALL_SECURITY_EXCEPTION_ERROR_UUID = |
| UUID.fromString("4edf6c8d-1e43-4c94-b0fc-a40c8d80cfe8"); |
| public static final String PLACE_CALL_SECURITY_EXCEPTION_ERROR_MSG = |
| "Security exception thrown while placing an outgoing call."; |
| public static final UUID CALL_IS_NULL_OR_ID_MISMATCH_UUID = |
| UUID.fromString("b11f3251-474c-4f90-96d6-a256aebc3c19"); |
| public static final String CALL_IS_NULL_OR_ID_MISMATCH_MSG = |
| "call is null or id mismatch"; |
| public static final UUID ADD_CALL_ON_ERROR_UUID = |
| UUID.fromString("f8e7d6c5-b4a3-9210-8765-432109abcdef"); |
| // TODO(b/469227855): This is a hidden constant in UiModeManager used by the resetCarMode(). |
| // Redefined locally to remove hidden API dependency. |
| // |
| // Original documentation from UiModeManager: |
| // Flag for use with {@link #disableCarMode(int)}: Disables car mode at ALL priority levels. |
| // Primarily intended for use from {@link com.android.internal.app.DisableCarModeActivity} to |
| // provide the user with a means to exit car mode at all priority levels. |
| private static final int DISABLE_CAR_MODE_ALL_PRIORITIES = 0x0002; |
| |
| private static final String TAG = "TelecomServiceImpl"; |
| private static final String TIME_LINE_ARG = "timeline"; |
| private static final int DEFAULT_VIDEO_STATE = -1; |
| private static final String PERMISSION_HANDLE_CALL_INTENT = |
| "android.permission.HANDLE_CALL_INTENT"; |
| private static final String ADD_CALL_ERR_MSG = "Call could not be created or found. " |
| + "Retry operation."; |
| private final PhoneAccountRegistrar mPhoneAccountRegistrar; |
| private final CallIntentProcessor.Adapter mCallIntentProcessorAdapter; |
| private final UserCallIntentProcessorFactory mUserCallIntentProcessorFactory; |
| private final DefaultDialerCache mDefaultDialerCache; |
| private final SubscriptionManagerAdapter mSubscriptionManagerAdapter; |
| private final TelecomSystem.SyncRoot mLock; |
| private final TransactionalServiceRepository mTransactionalServiceRepository; |
| private final BlockedNumbersManager mBlockedNumbersManager; |
| private final FeatureFlags mFeatureFlags; |
| private final android.telecom.flags.FeatureFlags mModuleFeatureFlags; |
| private final com.android.internal.telecom.flags.Flags mBugFixFlags; |
| private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags; |
| private final TelecomMetricsController mMetricsController; |
| private final String mSystemUiPackageName; |
| private String mInitPath = "unknown"; |
| private final String mTelecomUiPackageName; |
| private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl(); |
| private final Context mContext; |
| private Context mAllUsersContext; |
| private final AppOpsManager mAppOpsManager; |
| private final PackageManager mPackageManager; |
| private final CallsManager mCallsManager; |
| private TransactionManager mTransactionManager; |
| private final PermissionManager mPermissionManager; |
| private PackageRemovedReceiver mPackageRemovedReceiver; |
| |
| private int getAppUidIfPcc(int uid) { |
| if (mPackageManager != null && Process.isPrivateComputeCoreUid(uid)) { |
| try { |
| return mPackageManager.getAppUidForPrivateComputeCoreUid(uid); |
| } catch (Exception e) { |
| Log.w(TAG, "getAppUidIfPcc: exception for uid " + uid + " : " + e); |
| } |
| } |
| return uid; |
| } |
| |
| private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() { |
| |
| @Override |
| public boolean hasForegroundServiceDelegation( |
| PhoneAccountHandle handle, |
| String packageName) { |
| enforceCallingPackage(packageName, "hasForegroundServiceDelegation"); |
| long token = Binder.clearCallingIdentity(); |
| try { |
| VoipCallMonitor vcm = mCallsManager.getVoipCallMonitor(); |
| if (vcm != null) { |
| return vcm.hasForegroundServiceDelegation(handle); |
| } |
| return false; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| @Override |
| public void addCall(CallAttributes callAttributes, ICallEventCallback callEventCallback, |
| String callId, String callingPackage) { |
| int uid = Binder.getCallingUid(); |
| int pid = Binder.getCallingPid(); |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ADDCALL, |
| uid, ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.aC", Log.getPackageAbbreviation(callingPackage)); |
| Log.i(TAG, "addCall: id=[%s], attributes=[%s]", callId, callAttributes); |
| PhoneAccountHandle handle = callAttributes.getPhoneAccountHandle(); |
| |
| // enforce permissions and arguments |
| enforcePermission(android.Manifest.permission.MANAGE_OWN_CALLS); |
| enforceUserHandleMatchesCaller(handle); |
| enforcePhoneAccountIsNotManaged(handle);// only allow self-managed packages (temp.) |
| enforcePhoneAccountIsRegisteredEnabled(handle, handle.getUserHandle()); |
| enforceCallingPackage(callingPackage, "addCall"); |
| |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| |
| // add extras about info used for FGS delegation |
| Bundle extras = new Bundle(); |
| extras.putInt(CallAttributes.CALLER_UID_KEY, uid); |
| extras.putInt(CallAttributes.CALLER_PID_KEY, pid); |
| |
| |
| CompletableFuture<CallTransaction> transactionFuture; |
| long token = Binder.clearCallingIdentity(); |
| try { |
| transactionFuture = mCallsManager.createTransactionalCall(callId, |
| callAttributes, extras, callingPackage); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| |
| transactionFuture.thenCompose((transaction) -> { |
| if (transaction != null) { |
| mTransactionManager.addTransaction(transaction, new OutcomeReceiver<>() { |
| @Override |
| public void onResult(CallTransactionResult result) { |
| Log.d(TAG, "addCall: onResult"); |
| Call call = result.getCall(); |
| mMetricsController.getEventStats().log(new CriticalEvent( |
| EventStats.ID_ADD_CALL, uid, |
| EventStats.CAUSE_CALL_TRANSACTION_SUCCESS)); |
| |
| if (call == null || !call.getId().equals(callId)) { |
| Log.i(TAG, "addCall: onResult: call is null or id mismatch"); |
| mMetricsController.getErrorStats().log(ErrorStats.SUB_VOIP_CALL, |
| ErrorStats.ERROR_TRANSACTION_UNKNOWN); |
| onAddCallControl(callId, callEventCallback, null, |
| new CallException(ADD_CALL_ERR_MSG, |
| CODE_ERROR_UNKNOWN)); |
| mAnomalyReporter.reportAnomaly( |
| CALL_IS_NULL_OR_ID_MISMATCH_UUID, |
| CALL_IS_NULL_OR_ID_MISMATCH_MSG); |
| return; |
| } |
| |
| TransactionalServiceWrapper serviceWrapper = |
| mTransactionalServiceRepository |
| .addNewCallForTransactionalServiceWrapper(handle, |
| callEventCallback, mCallsManager, call); |
| |
| call.setTransactionServiceWrapper(serviceWrapper); |
| |
| if (mFeatureFlags.transactionalVideoState()) { |
| call.setTransactionalCallSupportsVideoCalling(callAttributes); |
| } |
| if (mFeatureFlags.integratedCallLogs()) { |
| call.setIsTransactionalLogExcluded( |
| callAttributes.isLogExcluded()); |
| } |
| if (android.telecom.flags.Flags.integratedCallLogsStage2()) { |
| call.setIsGroupCall(callAttributes.isGroupCall()); |
| call.setVoipContactLookupUri(callAttributes.getContactUri()); |
| } |
| ICallControl clientCallControl = serviceWrapper.getICallControl(); |
| |
| if (clientCallControl == null) { |
| throw new IllegalStateException("TransactionalServiceWrapper" |
| + "#ICallControl is null."); |
| } |
| |
| // finally, send objects back to the client |
| onAddCallControl(callId, callEventCallback, clientCallControl, |
| null); |
| } |
| |
| @Override |
| public void onError(@NonNull CallException exception) { |
| Log.d(TAG, "addCall: onError: e=[%s]", exception.toString()); |
| onAddCallControl(callId, callEventCallback, null, exception); |
| mAnomalyReporter.reportAnomaly( |
| ADD_CALL_ON_ERROR_UUID, |
| exception.getMessage()); |
| mMetricsController.getEventStats().log(new CriticalEvent( |
| EventStats.ID_ADD_CALL, uid, |
| EventStats.CAUSE_CALL_TRANSACTION_BASE |
| + exception.getCode())); |
| } |
| }); |
| } |
| event.setResult(ApiStats.RESULT_NORMAL); |
| return CompletableFuture.completedFuture(transaction); |
| }); |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| private void onAddCallControl(String callId, ICallEventCallback callEventCallback, |
| ICallControl callControl, CallException callException) { |
| try { |
| if (callException == null) { |
| callEventCallback.onAddCallControl(callId, TELECOM_TRANSACTION_SUCCESS, |
| callControl, null); |
| } else { |
| mMetricsController.getErrorStats().log(ErrorStats.SUB_VOIP_CALL, |
| ErrorStats.ERROR_TRANSACTION_UNKNOWN); |
| callEventCallback.onAddCallControl(callId, |
| CallException.CODE_ERROR_UNKNOWN, |
| null, callException); |
| } |
| } catch (RemoteException remoteException) { |
| throw remoteException.rethrowAsRuntimeException(); |
| } |
| } |
| |
| @Override |
| public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme, |
| String callingPackage, String callingFeatureId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_GETDEFAULTOUTGOINGPHONEACCOUNT, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.gDOPA", Log.getPackageAbbreviation(callingPackage)); |
| synchronized (mLock) { |
| PhoneAccountHandle phoneAccountHandle = null; |
| final UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| try { |
| phoneAccountHandle = mPhoneAccountRegistrar |
| .getOutgoingPhoneAccountForScheme(uriScheme, callingUserHandle); |
| } catch (Exception e) { |
| Log.e(this, e, "getDefaultOutgoingPhoneAccount"); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| |
| event.setResult(ApiStats.RESULT_NORMAL); |
| if (isCallerSimCallManager(phoneAccountHandle) |
| || canReadPhoneState( |
| callingPackage, |
| callingFeatureId, |
| "getDefaultOutgoingPhoneAccount")) { |
| return phoneAccountHandle; |
| } |
| return null; |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_GETUSERSELECTEDOUTGOINGPHONEACCOUNT, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| synchronized (mLock) { |
| try { |
| Log.startSession("TSI.gUSOPA", Log.getPackageAbbreviation(callingPackage)); |
| if (!isDialerOrPrivileged(callingPackage, "getDefaultOutgoingPhoneAccount")) { |
| throw new SecurityException("Only the default dialer, or caller with " |
| + "READ_PRIVILEGED_PHONE_STATE can call this method."); |
| } |
| event.setResult(ApiStats.RESULT_NORMAL); |
| final UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| return mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount( |
| callingUserHandle); |
| } catch (Exception e) { |
| Log.e(this, e, "getUserSelectedOutgoingPhoneAccount"); |
| throw e; |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| } |
| |
| @Override |
| public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_SETUSERSELECTEDOUTGOINGPHONEACCOUNT, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.sUSOPA"); |
| synchronized (mLock) { |
| enforceModifyPermission(); |
| UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| try { |
| mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount( |
| accountHandle, callingUserHandle); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| } catch (Exception e) { |
| Log.e(this, e, "setUserSelectedOutgoingPhoneAccount"); |
| mAnomalyReporter.reportAnomaly(SET_USER_PHONE_ACCOUNT_ERROR_UUID, |
| SET_USER_PHONE_ACCOUNT_ERROR_MSG); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public ParceledListSlice<PhoneAccountHandle> getCallCapablePhoneAccounts( |
| boolean includeDisabledAccounts, String callingPackage, |
| String callingFeatureId, boolean acrossProfiles) { |
| boolean hasUiAccess = mContext.checkCallingOrSelfPermission( |
| TelecomManager.PERMISSION_TELECOM_UI_ACCESS) |
| == PackageManager.PERMISSION_GRANTED; |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_GETCALLCAPABLEPHONEACCOUNTS, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.gCCPA", Log.getPackageAbbreviation(callingPackage)); |
| |
| if (!hasUiAccess) { |
| if (mTelephonyFeatureFlags.workProfileApiSplit()) { |
| if (acrossProfiles) { |
| enforceInAppCrossProfilePermission(); |
| } |
| |
| if (includeDisabledAccounts && !canReadPrivilegedPhoneState( |
| callingPackage, "getCallCapablePhoneAccounts")) { |
| throw new SecurityException( |
| "Requires READ_PRIVILEGED_PHONE_STATE permission."); |
| } |
| |
| if (!includeDisabledAccounts && !canReadPhoneState(callingPackage, |
| callingFeatureId, "Requires READ_PHONE_STATE permission.")) { |
| throw new SecurityException("Requires READ_PHONE_STATE permission."); |
| } |
| } |
| |
| if (includeDisabledAccounts && |
| !canReadPrivilegedPhoneState( |
| callingPackage, "getCallCapablePhoneAccounts")) { |
| return ParceledListSlice.emptyList(); |
| } |
| if (!canReadPhoneState(callingPackage, callingFeatureId, |
| "getCallCapablePhoneAccounts")) { |
| return ParceledListSlice.emptyList(); |
| } |
| } |
| event.setResult(ApiStats.RESULT_NORMAL); |
| synchronized (mLock) { |
| final UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| boolean crossUserAccess; |
| if (hasUiAccess) { |
| crossUserAccess = acrossProfiles; |
| } else { |
| crossUserAccess = (!mTelephonyFeatureFlags.workProfileApiSplit() |
| || acrossProfiles) && (mTelephonyFeatureFlags.workProfileApiSplit() |
| ? hasInAppCrossProfilePermission() |
| : hasInAppCrossUserPermission()); |
| } |
| long token = Binder.clearCallingIdentity(); |
| try { |
| return new ParceledListSlice<>( |
| mPhoneAccountRegistrar.getCallCapablePhoneAccounts(null, |
| includeDisabledAccounts, callingUserHandle, |
| crossUserAccess)); |
| } catch (Exception e) { |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.e(this, e, "getCallCapablePhoneAccounts"); |
| mAnomalyReporter.reportAnomaly(GET_CALL_CAPABLE_ACCOUNTS_ERROR_UUID, |
| GET_CALL_CAPABLE_ACCOUNTS_ERROR_MSG); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public ParceledListSlice<PhoneAccountHandle> getSelfManagedPhoneAccounts( |
| String callingPackage, String callingFeatureId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_GETSELFMANAGEDPHONEACCOUNTS, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.gSMPA", Log.getPackageAbbreviation(callingPackage)); |
| if (!canReadPhoneState(callingPackage, callingFeatureId, |
| "Requires READ_PHONE_STATE permission.")) { |
| throw new SecurityException("Requires READ_PHONE_STATE permission."); |
| } |
| synchronized (mLock) { |
| final UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return new ParceledListSlice<>(mPhoneAccountRegistrar |
| .getSelfManagedPhoneAccounts(callingUserHandle)); |
| } catch (Exception e) { |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.e(this, e, "getSelfManagedPhoneAccounts"); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public ParceledListSlice<PhoneAccountHandle> getOwnSelfManagedPhoneAccounts( |
| String callingPackage, String callingFeatureId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_GETOWNSELFMANAGEDPHONEACCOUNTS, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.gOSMPA", Log.getPackageAbbreviation(callingPackage)); |
| try { |
| enforceCallingPackage(callingPackage, "getOwnSelfManagedPhoneAccounts"); |
| } catch (SecurityException se) { |
| EventLog.writeEvent(0x534e4554, "231986341", Binder.getCallingUid(), |
| "getOwnSelfManagedPhoneAccounts: invalid calling package"); |
| throw se; |
| } |
| if (!canReadMangeOwnCalls("Requires MANAGE_OWN_CALLS permission.")) { |
| throw new SecurityException("Requires MANAGE_OWN_CALLS permission."); |
| } |
| synchronized (mLock) { |
| final UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return new ParceledListSlice<>(mPhoneAccountRegistrar |
| .getSelfManagedPhoneAccountsForPackage(callingPackage, |
| callingUserHandle)); |
| } catch (Exception e) { |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.e(this, e, |
| "getSelfManagedPhoneAccountsForPackage"); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public ParceledListSlice<PhoneAccountHandle> getPhoneAccountsSupportingScheme( |
| String uriScheme, String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_GETPHONEACCOUNTSSUPPORTINGSCHEME, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.gPASS", Log.getPackageAbbreviation(callingPackage)); |
| try { |
| enforceModifyPermission( |
| "getPhoneAccountsSupportingScheme requires MODIFY_PHONE_STATE"); |
| } catch (SecurityException e) { |
| EventLog.writeEvent(0x534e4554, "62347125", Binder.getCallingUid(), |
| "getPhoneAccountsSupportingScheme: " + callingPackage); |
| return ParceledListSlice.emptyList(); |
| } |
| |
| synchronized (mLock) { |
| final UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return new ParceledListSlice<>(mPhoneAccountRegistrar |
| .getCallCapablePhoneAccounts(uriScheme, false, |
| callingUserHandle, false)); |
| } catch (Exception e) { |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public ParceledListSlice<PhoneAccountHandle> getPhoneAccountsForPackage( |
| String packageName) { |
| //TODO: Deprecate this in S |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETPHONEACCOUNTSFORPACKAGE, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| try { |
| enforceCallingPackage(packageName, "getPhoneAccountsForPackage"); |
| } catch (SecurityException se1) { |
| EventLog.writeEvent(0x534e4554, "153995334", Binder.getCallingUid(), |
| "getPhoneAccountsForPackage: invalid calling package"); |
| throw se1; |
| } |
| |
| try { |
| enforcePermission(READ_PRIVILEGED_PHONE_STATE); |
| } catch (SecurityException se2) { |
| EventLog.writeEvent(0x534e4554, "153995334", Binder.getCallingUid(), |
| "getPhoneAccountsForPackage: no permission"); |
| throw se2; |
| } |
| |
| synchronized (mLock) { |
| final UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| Log.startSession("TSI.gPAFP"); |
| return new ParceledListSlice<>(mPhoneAccountRegistrar |
| .getAllPhoneAccountHandlesForPackage( |
| callingUserHandle, packageName)); |
| } catch (Exception e) { |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.e(this, e, "getPhoneAccountsForPackage %s", packageName); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| Log.endSession(); |
| } |
| } |
| } finally { |
| logEvent(event); |
| } |
| } |
| |
| @Override |
| public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle, |
| String callingPackage) { |
| boolean hasUiAccess = mContext.checkCallingOrSelfPermission( |
| TelecomManager.PERMISSION_TELECOM_UI_ACCESS) |
| == PackageManager.PERMISSION_GRANTED; |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETPHONEACCOUNT, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.gPA", Log.getPackageAbbreviation(callingPackage)); |
| if (!hasUiAccess) { |
| try { |
| enforceCallingPackage(callingPackage, "getPhoneAccount"); |
| } catch (SecurityException se) { |
| EventLog.writeEvent(0x534e4554, "196406138", Binder.getCallingUid(), |
| "getPhoneAccount: invalid calling package"); |
| throw se; |
| } |
| } |
| boolean isPhoneAccountPermitted = false; |
| |
| if (com.android.internal.telecom.flags.Flags.moveGetPhoneAccountOutsideLock() |
| && !hasUiAccess |
| && CompatChanges.isChangeEnabled( |
| TelecomManager.ENABLE_GET_PHONE_ACCOUNT_PERMISSION_PROTECTION, |
| callingPackage, Binder.getCallingUserHandle()) |
| && (Binder.getCallingUid() != Process.SHELL_UID)) { |
| isPhoneAccountPermitted = canGetPhoneAccount(callingPackage, accountHandle); |
| } |
| |
| synchronized (mLock) { |
| final UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| if (!hasUiAccess) { |
| if (CompatChanges.isChangeEnabled( |
| TelecomManager.ENABLE_GET_PHONE_ACCOUNT_PERMISSION_PROTECTION, |
| callingPackage, Binder.getCallingUserHandle())) { |
| |
| if (Binder.getCallingUid() != Process.SHELL_UID) { |
| |
| if (!com.android.internal.telecom.flags.Flags |
| .moveGetPhoneAccountOutsideLock()) { |
| isPhoneAccountPermitted = canGetPhoneAccount(callingPackage, |
| accountHandle); |
| } |
| |
| if(!isPhoneAccountPermitted) { |
| SecurityException e = new SecurityException( |
| "getPhoneAccount API requires" + |
| "READ_PHONE_NUMBERS"); |
| Log.e(this, e, "getPhoneAccount %s", accountHandle); |
| throw e; |
| } |
| } |
| } |
| } |
| Set<String> permissions = computePermissionsForBoundPackage( |
| Set.of(MODIFY_PHONE_STATE), null); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| // In ideal case, we should not resolve the handle across profiles. But |
| // given the fact that profile's call is handled by its parent user's |
| // in-call UI, parent user's in call UI need to be able to get phone account |
| // from the profile's phone account handle. |
| PhoneAccount account = mPhoneAccountRegistrar |
| .getPhoneAccount(accountHandle, callingUserHandle, |
| /* acrossProfiles */ true); |
| if (hasUiAccess) { |
| return account; |
| } |
| return maybeCleansePhoneAccount(account, permissions); |
| } catch (Exception e) { |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.e(this, e, "getPhoneAccount %s", accountHandle); |
| mAnomalyReporter.reportAnomaly(GET_PHONE_ACCOUNT_ERROR_UUID, |
| GET_PHONE_ACCOUNT_ERROR_MSG); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public ParceledListSlice<PhoneAccount> getRegisteredPhoneAccounts(String callingPackage, |
| String callingFeatureId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETREGISTEREDPHONEACCOUNTS, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.gRPA", Log.getPackageAbbreviation(callingPackage)); |
| try { |
| enforceCallingPackage(callingPackage, "getRegisteredPhoneAccounts"); |
| } catch (SecurityException se) { |
| EventLog.writeEvent(0x534e4554, "307609763", Binder.getCallingUid(), |
| "getRegisteredPhoneAccounts: invalid calling package"); |
| throw se; |
| } |
| |
| boolean hasCrossUserAccess = false; |
| try { |
| enforceInAppCrossUserPermission(); |
| hasCrossUserAccess = true; |
| } catch (SecurityException e) { |
| // pass through |
| } |
| |
| synchronized (mLock) { |
| final UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return new ParceledListSlice<>( |
| mPhoneAccountRegistrar.getPhoneAccounts( |
| 0 /* capabilities */, |
| 0 /* excludedCapabilities */, |
| null /* UriScheme */, |
| callingPackage, |
| true /* includeDisabledAccounts */, |
| callingUserHandle, |
| hasCrossUserAccess /* crossUserAccess */, |
| false /* includeAll */)); |
| } catch (Exception e) { |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.e(this, e, "getRegisteredPhoneAccounts"); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public int getAllPhoneAccountsCount() { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETALLPHONEACCOUNTSCOUNT, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.gAPAC"); |
| event.setCallerUid(Binder.getCallingUid()); |
| try { |
| enforceModifyPermission( |
| "getAllPhoneAccountsCount requires MODIFY_PHONE_STATE permission."); |
| } catch (SecurityException e) { |
| EventLog.writeEvent(0x534e4554, "62347125", Binder.getCallingUid(), |
| "getAllPhoneAccountsCount"); |
| throw e; |
| } |
| |
| synchronized (mLock) { |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| // This list is pre-filtered for the calling user. |
| return getAllPhoneAccounts().getList().size(); |
| } catch (Exception e) { |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.e(this, e, "getAllPhoneAccountsCount"); |
| throw e; |
| |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public ParceledListSlice<PhoneAccount> getAllPhoneAccounts() { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETALLPHONEACCOUNTS, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| synchronized (mLock) { |
| try { |
| Log.startSession("TSI.gAPA"); |
| try { |
| enforceModifyPermission( |
| "getAllPhoneAccounts requires MODIFY_PHONE_STATE permission."); |
| } catch (SecurityException e) { |
| EventLog.writeEvent(0x534e4554, "62347125", Binder.getCallingUid(), |
| "getAllPhoneAccounts"); |
| throw e; |
| } |
| |
| final UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return new ParceledListSlice<>(mPhoneAccountRegistrar |
| .getAllPhoneAccounts(callingUserHandle, false)); |
| } catch (Exception e) { |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.e(this, e, "getAllPhoneAccounts"); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| } |
| |
| @Override |
| public ParceledListSlice<PhoneAccountHandle> getAllPhoneAccountHandles() { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETALLPHONEACCOUNTHANDLES, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.gAPAH"); |
| |
| try { |
| enforceModifyPermission( |
| "getAllPhoneAccountHandles requires MODIFY_PHONE_STATE permission."); |
| } catch (SecurityException e) { |
| EventLog.writeEvent(0x534e4554, "62347125", Binder.getCallingUid(), |
| "getAllPhoneAccountHandles"); |
| throw e; |
| } |
| |
| synchronized (mLock) { |
| final UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| boolean crossUserAccess = hasInAppCrossUserPermission(); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return new ParceledListSlice<>(mPhoneAccountRegistrar |
| .getAllPhoneAccountHandles(callingUserHandle, |
| crossUserAccess)); |
| } catch (Exception e) { |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.e(this, e, "getAllPhoneAccountsHandles"); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public PhoneAccountHandle getSimCallManager(int subId, String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETSIMCALLMANAGER, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| synchronized (mLock) { |
| try { |
| Log.startSession("TSI.gSCM", Log.getPackageAbbreviation(callingPackage)); |
| final int callingUid = Binder.getCallingUid(); |
| final int callingUserId = Binder.getCallingUserHandle().getIdentifier(); |
| final UserHandle user = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| try { |
| if (callingUserId != ActivityManager.getCurrentUser()) { |
| enforceCrossUserPermission(callingUid); |
| } |
| event.setResult(ApiStats.RESULT_NORMAL); |
| return mPhoneAccountRegistrar.getSimCallManager(subId, user); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } catch (Exception e) { |
| Log.e(this, e, "getSimCallManager"); |
| mAnomalyReporter.reportAnomaly(GET_SIM_MANAGER_ERROR_UUID, |
| GET_SIM_MANAGER_ERROR_MSG); |
| throw e; |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| } |
| |
| @Override |
| public PhoneAccountHandle getSimCallManagerForUser(int user, String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETSIMCALLMANAGERFORUSER, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| synchronized (mLock) { |
| try { |
| Log.startSession("TSI.gSCMFU", Log.getPackageAbbreviation(callingPackage)); |
| final int callingUid = Binder.getCallingUid(); |
| if (user != ActivityManager.getCurrentUser()) { |
| enforceCrossUserPermission(callingUid); |
| } |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return mPhoneAccountRegistrar.getSimCallManager(UserHandle.of(user)); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } catch (Exception e) { |
| Log.e(this, e, "getSimCallManager"); |
| mAnomalyReporter.reportAnomaly(GET_SIM_MANAGER_FOR_USER_ERROR_UUID, |
| GET_SIM_MANAGER_FOR_USER_ERROR_MSG); |
| throw e; |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| } |
| |
| @Override |
| public void registerPhoneAccount(PhoneAccount account, String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_REGISTERPHONEACCOUNT, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.rPA", Log.getPackageAbbreviation(callingPackage)); |
| synchronized (mLock) { |
| try { |
| enforcePhoneAccountModificationForPackage( |
| account.getAccountHandle().getComponentName().getPackageName()); |
| if (account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED) |
| || account.hasCapabilities( |
| PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)) { |
| enforceRegisterSelfManaged(); |
| if (account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER) || |
| account.hasCapabilities( |
| PhoneAccount.CAPABILITY_CONNECTION_MANAGER) || |
| account.hasCapabilities( |
| PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { |
| throw new SecurityException("Self-managed ConnectionServices " |
| + "cannot also be call capable, connection managers, or " |
| + "SIM accounts."); |
| } |
| |
| // For self-managed CS, the phone account registrar will override the |
| // label the user has set for the phone account. This ensures the |
| // self-managed cs implementation can't spoof their app name. |
| } |
| if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { |
| enforceRegisterSimSubscriptionPermission(); |
| } |
| if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { |
| enforceRegisterMultiUser(); |
| } |
| // These capabilities are for SIM-based accounts only, so only the platform |
| // and carrier-designated SIM call manager can register accounts with these |
| // capabilities. |
| if (account.hasCapabilities( |
| PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS) |
| || account.hasCapabilities( |
| PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE)) { |
| enforceRegisterVoiceCallingIndicationCapabilities(account); |
| } |
| Bundle extras = account.getExtras(); |
| if (extras != null |
| && extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING)) { |
| // System apps should be granted the MODIFY_PHONE_STATE permission. |
| enforceModifyPermission( |
| "registerPhoneAccount requires MODIFY_PHONE_STATE permission."); |
| enforceRegisterSkipCallFiltering(); |
| } |
| final int callingUid = Binder.getCallingUid(); |
| if (callingUid != Process.SHELL_UID) { |
| enforceUserHandleMatchesCaller(account.getAccountHandle()); |
| } |
| |
| if (TextUtils.isEmpty(account.getGroupId()) |
| && mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) |
| != PackageManager.PERMISSION_GRANTED) { |
| Log.w(this, "registerPhoneAccount - attempt to set a" |
| + " group from a non-system caller."); |
| // Not permitted to set group, so null it out. |
| account = new PhoneAccount.Builder(account) |
| .setGroupId(null) |
| .build(); |
| } |
| |
| // Validate the profile boundary of the given image URI. |
| validateAccountIconUserBoundary(account.getIcon()); |
| |
| if (account.hasSimultaneousCallingRestriction()) { |
| validateSimultaneousCallingPackageNames( |
| account.getAccountHandle().getComponentName().getPackageName(), |
| account.getSimultaneousCallingRestriction()); |
| } |
| |
| final long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| Log.i(this, "registerPhoneAccount: account=%s", |
| account); |
| mPhoneAccountRegistrar.registerPhoneAccount(account); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } catch (Exception e) { |
| Log.e(this, e, "registerPhoneAccount %s", account); |
| mAnomalyReporter.reportAnomaly(REGISTER_PHONE_ACCOUNT_ERROR_UUID, |
| REGISTER_PHONE_ACCOUNT_ERROR_MSG); |
| throw e; |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void unregisterPhoneAccount(PhoneAccountHandle accountHandle, |
| String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_UNREGISTERPHONEACCOUNT, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| synchronized (mLock) { |
| try { |
| Log.startSession("TSI.uPA", Log.getPackageAbbreviation(callingPackage)); |
| enforcePhoneAccountModificationForPackage( |
| accountHandle.getComponentName().getPackageName()); |
| enforceUserHandleMatchesCaller(accountHandle); |
| final long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| mPhoneAccountRegistrar.unregisterPhoneAccount(accountHandle); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } catch (Exception e) { |
| Log.e(this, e, "unregisterPhoneAccount %s", accountHandle); |
| throw e; |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| } |
| |
| @Override |
| public void clearAccounts(String packageName) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_CLEARACCOUNTS, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| synchronized (mLock) { |
| try { |
| Log.startSession("TSI.cA"); |
| enforcePhoneAccountModificationForPackage(packageName); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| mPhoneAccountRegistrar |
| .clearAccounts(packageName, Binder.getCallingUserHandle()); |
| } catch (Exception e) { |
| Log.e(this, e, "clearAccounts %s", packageName); |
| throw e; |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#isVoiceMailNumber |
| */ |
| @Override |
| public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number, |
| String callingPackage, String callingFeatureId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISVOICEMAILNUMBER, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.iVMN", Log.getPackageAbbreviation(callingPackage)); |
| synchronized (mLock) { |
| if (!canReadPhoneState(callingPackage, callingFeatureId, "isVoiceMailNumber")) { |
| return false; |
| } |
| final UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle, |
| callingUserHandle)) { |
| Log.d(this, "%s is not visible for the calling user [iVMN]", accountHandle); |
| return false; |
| } |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return mPhoneAccountRegistrar.isVoiceMailNumber(accountHandle, number); |
| } catch (Exception e) { |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.e(this, e, "getSubscriptionIdForPhoneAccount"); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#getVoiceMailNumber |
| */ |
| @Override |
| public String getVoiceMailNumber(PhoneAccountHandle accountHandle, String callingPackage, |
| String callingFeatureId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETVOICEMAILNUMBER, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.gVMN", Log.getPackageAbbreviation(callingPackage)); |
| if (!canReadPhoneState(callingPackage, callingFeatureId, "getVoiceMailNumber")) { |
| return null; |
| } |
| try { |
| final UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle, |
| callingUserHandle)) { |
| Log.d(this, "%s is not visible for the calling user [gVMN]", |
| accountHandle); |
| return null; |
| } |
| int subId = mSubscriptionManagerAdapter.getDefaultVoiceSubId(); |
| synchronized (mLock) { |
| if (accountHandle != null) { |
| subId = mPhoneAccountRegistrar |
| .getSubscriptionIdForPhoneAccount(accountHandle); |
| } |
| } |
| event.setResult(ApiStats.RESULT_NORMAL); |
| return getTelephonyManager(subId).getVoiceMailNumber(); |
| } catch (UnsupportedOperationException ignored) { |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.w(this, "getVoiceMailNumber: no Telephony"); |
| return null; |
| } catch (Exception e) { |
| Log.e(this, e, "getSubscriptionIdForPhoneAccount"); |
| throw e; |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#getLine1Number |
| */ |
| @Override |
| public String getLine1Number(PhoneAccountHandle accountHandle, String callingPackage, |
| String callingFeatureId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETLINE1NUMBER, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("getL1N", Log.getPackageAbbreviation(callingPackage)); |
| if (!canReadPhoneNumbers(callingPackage, callingFeatureId, "getLine1Number")) { |
| return null; |
| } |
| |
| final UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle, |
| callingUserHandle)) { |
| Log.d(this, "%s is not visible for the calling user [gL1N]", accountHandle); |
| return null; |
| } |
| |
| long token = Binder.clearCallingIdentity(); |
| try { |
| int subId; |
| synchronized (mLock) { |
| subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount( |
| accountHandle); |
| } |
| if (!mPhoneAccountRegistrar.isSubscriptionIdActive(subId)) |
| return null; |
| event.setResult(ApiStats.RESULT_NORMAL); |
| return getTelephonyManager(subId).getLine1Number(); |
| } catch (UnsupportedOperationException ignored) { |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.w(this, "getLine1Number: no telephony"); |
| return null; |
| } catch (Exception e) { |
| Log.e(this, e, "getSubscriptionIdForPhoneAccount"); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#silenceRinger |
| */ |
| @Override |
| public void silenceRinger(String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_SILENCERINGER, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.sR", Log.getPackageAbbreviation(callingPackage)); |
| synchronized (mLock) { |
| enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage); |
| UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| boolean crossUserAccess = hasInAppCrossUserPermission(); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| try { |
| Log.i(this, "Silence Ringer requested by %s", callingPackage); |
| Set<UserHandle> userHandles = mCallsManager.getCallAudioManager(). |
| silenceRingers(mContext, callingUserHandle, |
| crossUserAccess); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| mCallsManager.getInCallController().silenceRinger(userHandles); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#getDefaultPhoneApp |
| * @deprecated - Use {@link android.telecom.TelecomManager#getDefaultDialerPackage()} |
| * instead. |
| */ |
| @Override |
| public ComponentName getDefaultPhoneApp() { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETDEFAULTPHONEAPP, |
| Binder.getCallingUid(), ApiStats.RESULT_NORMAL); |
| try { |
| Log.startSession("TSI.gDPA"); |
| return mDefaultDialerCache.getDialtactsSystemDialerComponent(); |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @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(String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETDEFAULTDIALERPACKAGE, |
| Binder.getCallingUid(), ApiStats.RESULT_NORMAL); |
| try { |
| Log.startSession("TSI.gDDP", Log.getPackageAbbreviation(callingPackage)); |
| UserHandle callerUser = Binder.getCallingUserHandle(); |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| return mDefaultDialerCache.getDefaultDialerApplication(callerUser); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @param userId user id to get the default dialer package for |
| * @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 getDefaultDialerPackageForUser(int userId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_GETDEFAULTDIALERPACKAGEFORUSER, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.gDDPU"); |
| mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, |
| "READ_PRIVILEGED_PHONE_STATE permission required."); |
| |
| final long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return mDefaultDialerCache |
| .getDefaultDialerApplication(UserHandle.of(userId)); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#getSystemDialerPackage |
| */ |
| @Override |
| public String getSystemDialerPackage(String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETSYSTEMDIALERPACKAGE, |
| Binder.getCallingUid(), ApiStats.RESULT_NORMAL); |
| try { |
| Log.startSession("TSI.gSDP", Log.getPackageAbbreviation(callingPackage)); |
| return mDefaultDialerCache.getSystemDialerApplication(); |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| public void setSystemDialer(ComponentName testComponentName) { |
| try { |
| Log.startSession("TSI.sSD"); |
| Log.i(this, "setSystemDialer: %s", testComponentName); |
| enforceModifyPermission(); |
| enforceShellOnly(Binder.getCallingUid(), "setSystemDialer"); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| mDefaultDialerCache.setSystemDialerComponentName(testComponentName); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#isInCall |
| */ |
| @Override |
| public boolean isInCall(String callingPackage, String callingFeatureId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISINCALL, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.iIC", Log.getPackageAbbreviation(callingPackage)); |
| if (!canReadPhoneState(callingPackage, callingFeatureId, "isInCall")) { |
| return false; |
| } |
| event.setResult(ApiStats.RESULT_NORMAL); |
| synchronized (mLock) { |
| return mCallsManager.hasOngoingCalls(Binder.getCallingUserHandle(), |
| hasInAppCrossUserPermission()); |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#isInCall |
| */ |
| @Override |
| public boolean isInExternalCall(String callingPackage, String callingFeatureId) { |
| // TODO(b/435261628): Add API stats collection for this interface |
| try { |
| Log.startSession("TSI.iIC", Log.getPackageAbbreviation(callingPackage)); |
| if (!canReadPhoneState(callingPackage, callingFeatureId, "isInExternalCall")) { |
| throw new SecurityException("Only the default dialer or caller with " + |
| "READ_PHONE_STATE permission can use this method."); |
| } |
| synchronized (mLock) { |
| return mCallsManager.hasOngoingExternalCalls(Binder.getCallingUserHandle(), |
| hasInAppCrossUserPermission()); |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#hasManageOngoingCallsPermission |
| */ |
| @Override |
| public boolean hasManageOngoingCallsPermission(String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_HASMANAGEONGOINGCALLSPERMISSION, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.hMOCP", Log.getPackageAbbreviation(callingPackage)); |
| enforceCallingPackage(callingPackage, "hasManageOngoingCallsPermission"); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| AttributionSource attributionSource; |
| // checkPermissionForPreflight is documented as useful for cases where you are |
| // seeing if a permission is held, but you're not going to deliver data to the |
| // app yet; that's what we'd expect here. |
| int result = mPermissionManager.checkPermissionForPreflight( |
| Manifest.permission.MANAGE_ONGOING_CALLS, |
| new AttributionSource.Builder(Binder.getCallingUid()) |
| .setPackageName(callingPackage).build()); |
| Log.i(this, "hasManageOngoingCallPermission: caller=%s; uid=%d, result=%d", |
| callingPackage, Binder.getCallingUid(), result); |
| return result == PermissionManager.PERMISSION_GRANTED; |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#isInManagedCall |
| */ |
| @Override |
| public boolean isInManagedCall(String callingPackage, String callingFeatureId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISINMANAGEDCALL, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.iIMC", Log.getPackageAbbreviation(callingPackage)); |
| if (!canReadPhoneState(callingPackage, callingFeatureId, "isInManagedCall")) { |
| throw new SecurityException("Only the default dialer or caller with " + |
| "READ_PHONE_STATE permission can use this method."); |
| } |
| event.setResult(ApiStats.RESULT_NORMAL); |
| synchronized (mLock) { |
| return mCallsManager.hasOngoingManagedCalls(Binder.getCallingUserHandle(), |
| hasInAppCrossUserPermission()); |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#isRinging |
| */ |
| @Override |
| public boolean isRinging(String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISRINGING, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.iR"); |
| if (!isPrivilegedDialerCalling(callingPackage)) { |
| try { |
| enforceModifyPermission( |
| "isRinging requires MODIFY_PHONE_STATE permission."); |
| } catch (SecurityException e) { |
| EventLog.writeEvent(0x534e4554, "62347125", "isRinging: " + callingPackage); |
| throw e; |
| } |
| } |
| |
| event.setResult(ApiStats.RESULT_NORMAL); |
| synchronized (mLock) { |
| // Note: We are explicitly checking the calls telecom is tracking rather than |
| // relying on mCallsManager#getCallState(). Since getCallState() relies on the |
| // current state as tracked by PhoneStateBroadcaster, any failure to properly |
| // track the current call state there could result in the wrong ringing state |
| // being reported by this API. |
| return mCallsManager.hasRingingOrSimulatedRingingCall(); |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see TelecomManager#getCallState() |
| * @deprecated this is only being kept due to an @UnsupportedAppUsage tag. Apps targeting |
| * API 31+ must use {@link #getCallStateUsingPackage(String, String)} below. |
| */ |
| @Deprecated |
| @Override |
| public int getCallState() { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETCALLSTATE, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.getCallState(DEPRECATED)"); |
| if (CompatChanges.isChangeEnabled( |
| TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION, |
| Binder.getCallingUid())) { |
| // Do not allow this API to be called on API version 31+, it should only be |
| // called on old apps using this Binder call directly. |
| throw new SecurityException("This method can only be used for applications " |
| + "targeting API version 30 or less."); |
| } |
| event.setResult(ApiStats.RESULT_NORMAL); |
| synchronized (mLock) { |
| return mCallsManager.getCallState(); |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see TelecomManager#getCallState() |
| */ |
| @Override |
| public int getCallStateUsingPackage(String callingPackage, String callingFeatureId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETCALLSTATEUSINGPACKAGE, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.getCallStateUsingPackage"); |
| |
| // ensure the callingPackage is not spoofed |
| // skip check for privileged UIDs and throw SE if package does not match records |
| if (!isPrivilegedUid() |
| && !callingUidMatchesPackageManagerRecords(callingPackage)) { |
| EventLog.writeEvent(0x534e4554, "236813210", Binder.getCallingUid(), |
| "getCallStateUsingPackage"); |
| Log.i(this, |
| "getCallStateUsingPackage: packageName does not match records for " |
| + "callingPackage=[%s], callingUid=[%d]", |
| callingPackage, Binder.getCallingUid()); |
| throw new SecurityException(String.format("getCallStateUsingPackage: " |
| + "enforceCallingPackage: callingPackage=[%s], callingUid=[%d]", |
| callingPackage, Binder.getCallingUid())); |
| } |
| |
| if (CompatChanges.isChangeEnabled( |
| TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION, callingPackage, |
| Binder.getCallingUserHandle())) { |
| // Bypass canReadPhoneState check if this is being called from SHELL UID |
| if (Binder.getCallingUid() != Process.SHELL_UID && !canReadPhoneState( |
| callingPackage, callingFeatureId, "getCallState")) { |
| throw new SecurityException("getCallState API requires READ_PHONE_STATE" |
| + " for API version 31+"); |
| } |
| } |
| event.setResult(ApiStats.RESULT_NORMAL); |
| synchronized (mLock) { |
| return mCallsManager.getCallState(); |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| private boolean isPrivilegedUid() { |
| int callingUid = Binder.getCallingUid(); |
| return isSameApp(callingUid, Process.ROOT_UID) |
| || isSameApp(callingUid, Process.SYSTEM_UID) |
| || isSameApp(callingUid, Process.SHELL_UID); |
| } |
| |
| private boolean isSameApp(int uid1, int uid2) { |
| return UserHandle.getAppId(uid1) == UserHandle.getAppId(uid2); |
| } |
| |
| private boolean isSameAppIncludingPccUid(int uid1, int uid2) { |
| if (Flags.enablePccFrameworkSupport()) { |
| uid1 = getAppUidIfPcc(uid1); |
| uid2 = getAppUidIfPcc(uid2); |
| } |
| return isSameApp(uid1, uid2); |
| } |
| |
| private boolean isSysUiUid() { |
| int callingUid = Binder.getCallingUid(); |
| int systemUiUid; |
| if (mPackageManager != null && mSystemUiPackageName != null) { |
| long whosCalling = Binder.clearCallingIdentity(); |
| try { |
| try { |
| systemUiUid = mPackageManager.getPackageUid(mSystemUiPackageName, 0); |
| Log.i(TAG, "isSysUiUid: callingUid = " + callingUid + "; systemUiUid = " |
| + systemUiUid); |
| return isSameAppIncludingPccUid(callingUid, systemUiUid); |
| } catch (PackageManager.NameNotFoundException e) { |
| Log.w(TAG, |
| "isSysUiUid: caught PackageManager NameNotFoundException = " + e); |
| return false; |
| } |
| } finally { |
| Binder.restoreCallingIdentity(whosCalling); |
| } |
| } else { |
| Log.w(TAG, "isSysUiUid: caught null check and returned false; " |
| + "mPackageManager = " + mPackageManager + "; mSystemUiPackageName = " |
| + mSystemUiPackageName); |
| } |
| return false; |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#endCall |
| */ |
| @Override |
| public boolean endCall(String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ENDCALL, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.eC", Log.getPackageAbbreviation(callingPackage)); |
| synchronized (mLock) { |
| if (!enforceAnswerCallPermission(callingPackage, Binder.getCallingUid())) { |
| throw new SecurityException("requires ANSWER_PHONE_CALLS permission"); |
| } |
| // Legacy behavior is to ignore whether the invocation is from a system app: |
| boolean isCallerPrivileged = isPrivilegedUid() || isSysUiUid(); |
| Log.i(TAG, "endCall: Binder.getCallingUid = [" + |
| Binder.getCallingUid() + "] isCallerPrivileged = " + |
| isCallerPrivileged); |
| Call callToEnd = getOngoingCall(); |
| // Ensure that the associated user of the call matches with the calling user |
| // handle. Otherwise, we should verify that the user has the cross user |
| // permission. If none of the requirements are met, we will exit immediately. |
| if (!doesAssociatedUserMatchCaller(callToEnd, Binder.getCallingUserHandle())) { |
| return false; |
| } |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return endCallInternal(callToEnd, callingPackage, isCallerPrivileged); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#acceptRingingCall |
| */ |
| @Override |
| public void acceptRingingCall(String packageName) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ACCEPTRINGINGCALL, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.aRC", Log.getPackageAbbreviation(packageName)); |
| synchronized (mLock) { |
| if (!enforceAnswerCallPermission(packageName, Binder.getCallingUid())) return; |
| // Legacy behavior is to ignore whether the invocation is from a system app: |
| boolean isCallerPrivileged = isPrivilegedUid() || isSysUiUid(); |
| Call call = mCallsManager.getFirstCallWithState(CallState.RINGING, |
| CallState.SIMULATED_RINGING); |
| // Ensure that the associated user of the call matches with the calling user |
| // handle. Otherwise, we should verify that the user has the cross user |
| // permission. If none of the requirements are met, we will exit immediately. |
| if (!doesAssociatedUserMatchCaller(call, Binder.getCallingUserHandle())) { |
| return; |
| } |
| Log.i(TAG, "acceptRingingCall: Binder.getCallingUid = [" + |
| Binder.getCallingUid() + "] isCallerPrivileged = " + |
| isCallerPrivileged); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| acceptRingingCallInternal(call, DEFAULT_VIDEO_STATE, packageName, |
| isCallerPrivileged); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#acceptRingingCall(int) |
| */ |
| @Override |
| public void acceptRingingCallWithVideoState(String packageName, int videoState) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_ACCEPTRINGINGCALLWITHVIDEOSTATE, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.aRCWVS", Log.getPackageAbbreviation(packageName)); |
| synchronized (mLock) { |
| if (!enforceAnswerCallPermission(packageName, Binder.getCallingUid())) return; |
| // Legacy behavior is to ignore whether the invocation is from a system app: |
| boolean isCallerPrivileged = isPrivilegedUid() || isSysUiUid(); |
| Call call = mCallsManager.getFirstCallWithState(CallState.RINGING, |
| CallState.SIMULATED_RINGING); |
| // Ensure that the associated user of the call matches with the calling user |
| // handle. Otherwise, we should verify that the user has the cross user |
| // permission. If none of the requirements are met, we will exit immediately. |
| if (!doesAssociatedUserMatchCaller(call, Binder.getCallingUserHandle())) { |
| return; |
| } |
| Log.i(TAG, "acceptRingingCallWithVideoState: Binder.getCallingUid = " |
| + "[" + Binder.getCallingUid() + "] isCallerPrivileged = " + |
| isCallerPrivileged); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| acceptRingingCallInternal(call, videoState, packageName, |
| isCallerPrivileged); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#showInCallScreen |
| */ |
| @Override |
| public void showInCallScreen(boolean showDialpad, String callingPackage, |
| String callingFeatureId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_SHOWINCALLSCREEN, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.sICS", Log.getPackageAbbreviation(callingPackage)); |
| if (!canReadPhoneState(callingPackage, callingFeatureId, "showInCallScreen")) { |
| return; |
| } |
| |
| synchronized (mLock) { |
| UserHandle callingUser = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| mCallsManager.getInCallController().bringToForeground( |
| showDialpad, callingUser); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#cancelMissedCallsNotification |
| */ |
| @Override |
| public void cancelMissedCallsNotification(String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_CANCELMISSEDCALLSNOTIFICATION, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.cMCN", Log.getPackageAbbreviation(callingPackage)); |
| synchronized (mLock) { |
| enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage); |
| UserHandle userHandle = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| mCallsManager.getMissedCallNotifier().clearMissedCalls(userHandle); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#handleMmi |
| */ |
| @Override |
| public boolean handlePinMmi(String dialString, String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_HANDLEPINMMI, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.hPM", Log.getPackageAbbreviation(callingPackage)); |
| enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage); |
| |
| // Switch identity so that TelephonyManager checks Telecom's permissions |
| // instead. |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| boolean retval = false; |
| try { |
| retval = getTelephonyManager( |
| SubscriptionManager.getDefaultVoiceSubscriptionId()) |
| .handlePinMmi(dialString); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| |
| return retval; |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#handleMmi |
| */ |
| @Override |
| public boolean handlePinMmiForPhoneAccount(PhoneAccountHandle accountHandle, |
| String dialString, String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_HANDLEPINMMIFORPHONEACCOUNT, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.hPMFPA", Log.getPackageAbbreviation(callingPackage)); |
| enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage); |
| UserHandle callingUserHandle = Binder.getCallingUserHandle(); |
| synchronized (mLock) { |
| if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle, |
| callingUserHandle)) { |
| 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(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| boolean retval = false; |
| int subId; |
| try { |
| synchronized (mLock) { |
| subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount( |
| accountHandle); |
| } |
| try { |
| retval = getTelephonyManager(subId) |
| .handlePinMmiForSubscriber(subId, dialString); |
| } catch (UnsupportedOperationException uoe) { |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.w(this, "handlePinMmiForPhoneAccount: no telephony"); |
| retval = false; |
| } |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| return retval; |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#getAdnUriForPhoneAccount |
| */ |
| @Override |
| public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle, |
| String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETADNURIFORPHONEACCOUNT, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.aAUFPA", Log.getPackageAbbreviation(callingPackage)); |
| enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage); |
| synchronized (mLock) { |
| if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle, |
| Binder.getCallingUserHandle())) { |
| 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(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| String retval = "content://icc/adn/"; |
| try { |
| long subId = mPhoneAccountRegistrar |
| .getSubscriptionIdForPhoneAccount(accountHandle); |
| retval = retval + "subId/" + subId; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| |
| return Uri.parse(retval); |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#isTtySupported |
| */ |
| @Override |
| public boolean isTtySupported(String callingPackage, String callingFeatureId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISTTYSUPPORTED, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.iTS", Log.getPackageAbbreviation(callingPackage)); |
| if (!canReadPhoneState(callingPackage, callingFeatureId, "isTtySupported")) { |
| throw new SecurityException("Only default dialer or an app with" + |
| "READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE can call this api"); |
| } |
| |
| event.setResult(ApiStats.RESULT_NORMAL); |
| synchronized (mLock) { |
| return mCallsManager.isTtySupported(); |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#getCurrentTtyMode |
| */ |
| @Override |
| public int getCurrentTtyMode(String callingPackage, String callingFeatureId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_GETCURRENTTTYMODE, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.gCTM", Log.getPackageAbbreviation(callingPackage)); |
| if (!canReadPhoneState(callingPackage, callingFeatureId, "getCurrentTtyMode")) { |
| return TelecomManager.TTY_MODE_OFF; |
| } |
| |
| event.setResult(ApiStats.RESULT_NORMAL); |
| synchronized (mLock) { |
| return mCallsManager.getCurrentTtyMode(); |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#addNewIncomingCall |
| */ |
| @Override |
| public void addNewIncomingCall(PhoneAccountHandle phoneAccountHandle, Bundle extras, |
| String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ADDNEWINCOMINGCALL, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.aNIC", Log.getPackageAbbreviation(callingPackage)); |
| synchronized (mLock) { |
| Log.i(this, "Adding new incoming call with phoneAccountHandle %s", |
| phoneAccountHandle); |
| if (phoneAccountHandle != null && |
| phoneAccountHandle.getComponentName() != null) { |
| if (isCallerSimCallManager(phoneAccountHandle) |
| && 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, |
| phoneAccountHandle.getUserHandle()); |
| if (isSelfManagedConnectionService(phoneAccountHandle)) { |
| // Self-managed phone account, ensure it has MANAGE_OWN_CALLS. |
| mContext.enforceCallingOrSelfPermission( |
| android.Manifest.permission.MANAGE_OWN_CALLS, |
| "Self-managed phone accounts must have MANAGE_OWN_CALLS " + |
| "permission."); |
| |
| // Self-managed ConnectionServices can ONLY add new incoming calls |
| // using their own PhoneAccounts. The checkPackage(..) app opps |
| // check above ensures this. |
| } |
| } |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| 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) { |
| extras = TelecomBundleUtils.defuse(extras); |
| intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras); |
| } |
| mCallIntentProcessorAdapter.processIncomingCallIntent( |
| mCallsManager, intent); |
| PhoneAccount account = |
| mPhoneAccountRegistrar.getPhoneAccountUnchecked( |
| phoneAccountHandle); |
| Bundle accountExtra = |
| account == null ? new Bundle() : account.getExtras(); |
| PackageManager packageManager = mContext.getPackageManager(); |
| // Start binding to InCallServices for wearable calls that do not |
| // require call filtering. This is to wake up default dialer earlier |
| // to mitigate InCallService binding latency. |
| if (packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH) |
| && accountExtra != null && accountExtra.getBoolean( |
| PhoneAccount.EXTRA_SKIP_CALL_FILTERING, |
| false)) { |
| mCallsManager.getInCallController().bindToBTService( |
| null, null); |
| // Should be able to run this as is even if above flag is |
| // enabled (BT binding should be skipped automatically). |
| mCallsManager.getInCallController().bindToServices(null); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } else { |
| // Invalid parameters are considered as an exception |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.w(this, "Null phoneAccountHandle. Ignoring request to add new" + |
| " incoming call"); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#addNewIncomingConference |
| */ |
| @Override |
| public void addNewIncomingConference(PhoneAccountHandle phoneAccountHandle, Bundle extras, |
| String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ADDNEWINCOMINGCONFERENCE, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.aNIC", Log.getPackageAbbreviation(callingPackage)); |
| synchronized (mLock) { |
| Log.i(this, "Adding new incoming conference with phoneAccountHandle %s", |
| phoneAccountHandle); |
| if (phoneAccountHandle != null && |
| phoneAccountHandle.getComponentName() != null) { |
| if (isCallerSimCallManager(phoneAccountHandle) |
| && TelephonyUtil.isPstnComponentName( |
| phoneAccountHandle.getComponentName())) { |
| Log.v(this, "Allowing call manager to add incoming conference" + |
| " with PSTN handle"); |
| } else { |
| mAppOpsManager.checkPackage( |
| Binder.getCallingUid(), |
| phoneAccountHandle.getComponentName().getPackageName()); |
| // Make sure it doesn't cross the UserHandle boundary |
| enforceUserHandleMatchesCaller(phoneAccountHandle); |
| enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle, |
| Binder.getCallingUserHandle()); |
| if (isSelfManagedConnectionService(phoneAccountHandle)) { |
| throw new SecurityException( |
| "Self-Managed ConnectionServices cannot add " |
| + "adhoc conference calls"); |
| } |
| } |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| mCallsManager.processIncomingConference( |
| phoneAccountHandle, extras); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } else { |
| // Invalid parameters are considered as an exception |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.w(this, "Null phoneAccountHandle. Ignoring request to add new" + |
| " incoming conference"); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#acceptHandover |
| */ |
| @Override |
| public void acceptHandover(Uri srcAddr, int videoState, PhoneAccountHandle destAcct, |
| String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ACCEPTHANDOVER, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.aHO", Log.getPackageAbbreviation(callingPackage)); |
| synchronized (mLock) { |
| Log.i(this, "acceptHandover; srcAddr=%s, videoState=%s, dest=%s", |
| Log.pii(srcAddr), VideoProfile.videoStateToString(videoState), |
| destAcct); |
| |
| if (destAcct != null && destAcct.getComponentName() != null) { |
| mAppOpsManager.checkPackage( |
| Binder.getCallingUid(), |
| destAcct.getComponentName().getPackageName()); |
| enforceUserHandleMatchesCaller(destAcct); |
| enforcePhoneAccountIsRegisteredEnabled(destAcct, |
| Binder.getCallingUserHandle()); |
| if (isSelfManagedConnectionService(destAcct)) { |
| // Self-managed phone account, ensure it has MANAGE_OWN_CALLS. |
| mContext.enforceCallingOrSelfPermission( |
| android.Manifest.permission.MANAGE_OWN_CALLS, |
| "Self-managed phone accounts must have MANAGE_OWN_CALLS " + |
| "permission."); |
| } |
| if (!enforceAcceptHandoverPermission( |
| destAcct.getComponentName().getPackageName(), |
| Binder.getCallingUid())) { |
| throw new SecurityException("App must be granted runtime " |
| + "ACCEPT_HANDOVER permission."); |
| } |
| |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| try { |
| mCallsManager.acceptHandover(srcAddr, videoState, destAcct); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } else { |
| // Invalid parameters are considered as an exception |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.w(this, "Null phoneAccountHandle. Ignoring request " + |
| "to handover the call"); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#addNewUnknownCall |
| */ |
| @Override |
| public void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ADDNEWUNKNOWNCALL, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.aNUC"); |
| try { |
| enforceModifyPermission( |
| "addNewUnknownCall requires MODIFY_PHONE_STATE permission."); |
| } catch (SecurityException e) { |
| EventLog.writeEvent(0x534e4554, "62347125", Binder.getCallingUid(), |
| "addNewUnknownCall"); |
| throw e; |
| } |
| |
| 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, |
| Binder.getCallingUserHandle()); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL); |
| if (extras != null) { |
| extras = TelecomBundleUtils.defuse(extras); |
| intent.putExtras(extras); |
| } |
| intent.putExtra(CallIntentProcessor.KEY_IS_UNKNOWN_CALL, true); |
| intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, |
| phoneAccountHandle); |
| mCallIntentProcessorAdapter.processUnknownCallIntent(mCallsManager, |
| intent); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } else { |
| // Invalid parameters are considered as an exception |
| event.setResult(ApiStats.RESULT_EXCEPTION); |
| Log.i(this, |
| "Null phoneAccountHandle or not initiated by Telephony. " + |
| "Ignoring request to add new unknown call."); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#startConference. |
| */ |
| @Override |
| public void startConference(List<Uri> participants, Bundle extras, |
| String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_STARTCONFERENCE, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.sC", Log.getPackageAbbreviation(callingPackage)); |
| if (!canCallPhone(callingPackage, "startConference")) { |
| throw new SecurityException("Package " + callingPackage + " is not allowed" |
| + " to start conference call"); |
| } |
| // Binder is clearing the identity, so we need to keep the store the handle |
| UserHandle currentUserHandle = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| mCallsManager.startConference(participants, extras, callingPackage, |
| currentUserHandle); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#placeCall |
| */ |
| @Override |
| public void placeCall(Uri handle, Bundle extras, String callingPackage, |
| String callingFeatureId) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_PLACECALL, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.pC", Log.getPackageAbbreviation(callingPackage)); |
| enforceCallingPackage(callingPackage, "placeCall"); |
| |
| PhoneAccountHandle phoneAccountHandle = null; |
| if (extras != null) { |
| phoneAccountHandle = extras.getParcelable( |
| TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); |
| if (extras.containsKey(TelecomManager.EXTRA_IS_HANDOVER)) { |
| // This extra is for Telecom use only so should never be passed in. |
| extras.remove(TelecomManager.EXTRA_IS_HANDOVER); |
| } |
| } |
| ComponentName phoneAccountComponentName = phoneAccountHandle != null |
| ? phoneAccountHandle.getComponentName() : null; |
| String phoneAccountPackageName = phoneAccountComponentName != null |
| ? phoneAccountComponentName.getPackageName() : null; |
| boolean isCallerOwnerOfPhoneAccount = |
| callingPackage.equals(phoneAccountPackageName); |
| boolean isSelfManagedPhoneAccount = |
| isSelfManagedConnectionService(phoneAccountHandle); |
| // Ensure the app's calling package matches the PhoneAccount package name before |
| // checking self-managed status so that we do not leak installed package |
| // information. |
| boolean isSelfManagedRequest = isCallerOwnerOfPhoneAccount && |
| isSelfManagedPhoneAccount; |
| if (isSelfManagedRequest) { |
| // The package name of the caller matches the package name of the |
| // PhoneAccountHandle, so ensure the app has MANAGE_OWN_CALLS permission if |
| // self-managed. |
| mContext.enforceCallingOrSelfPermission( |
| Manifest.permission.MANAGE_OWN_CALLS, |
| "Self-managed ConnectionServices require MANAGE_OWN_CALLS permission."); |
| } else if (!canCallPhone(callingPackage, callingFeatureId, |
| "CALL_PHONE permission required to place calls.")) { |
| // not self-managed, so CALL_PHONE is required. |
| mAnomalyReporter.reportAnomaly(PLACE_CALL_SECURITY_EXCEPTION_ERROR_UUID, |
| PLACE_CALL_SECURITY_EXCEPTION_ERROR_MSG); |
| throw new SecurityException( |
| "CALL_PHONE permission required to place calls."); |
| } |
| |
| // An application can not place a call with a self-managed PhoneAccount that |
| // they do not own. If this is the case (and the app has CALL_PHONE permission), |
| // remove the PhoneAccount from the request and place the call as if it was a |
| // managed call request with no PhoneAccount specified. |
| if (!isCallerOwnerOfPhoneAccount && isSelfManagedPhoneAccount) { |
| // extras can not be null if isSelfManagedPhoneAccount is true |
| extras.remove(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); |
| } |
| |
| // 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.OPSTR_CALL_PHONE, |
| Binder.getCallingUid(), callingPackage, callingFeatureId, null) |
| == AppOpsManager.MODE_ALLOWED; |
| |
| final boolean hasCallPermission = mContext.checkCallingOrSelfPermission(CALL_PHONE) |
| == PackageManager.PERMISSION_GRANTED; |
| // The Emergency Dialer has call privileged permission and uses this to place |
| // emergency calls. We ensure permission checks in |
| // NewOutgoingCallIntentBroadcaster#process pass by sending this to |
| // Telecom as an ACTION_CALL_PRIVILEGED intent (which makes sense since the |
| // com.android.phone process has that permission). |
| final boolean hasCallPrivilegedPermission = mContext.checkCallingOrSelfPermission( |
| CALL_PRIVILEGED) == PackageManager.PERMISSION_GRANTED; |
| |
| synchronized (mLock) { |
| final UserHandle userHandle = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| final Intent intent = new Intent(hasCallPrivilegedPermission ? |
| Intent.ACTION_CALL_PRIVILEGED : Intent.ACTION_CALL, handle); |
| if (extras != null) { |
| extras = TelecomBundleUtils.defuse(extras); |
| intent.putExtras(extras); |
| } |
| mUserCallIntentProcessorFactory.create(mContext, userHandle) |
| .processIntent(intent, callingPackage, isSelfManagedRequest, |
| (hasCallAppOp && hasCallPermission) |
| || hasCallPrivilegedPermission, |
| true /* isLocalInvocation */); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#enablePhoneAccount |
| */ |
| @Override |
| public boolean enablePhoneAccount(PhoneAccountHandle accountHandle, boolean isEnabled) { |
| boolean hasUiAccess = mContext.checkCallingOrSelfPermission( |
| TelecomManager.PERMISSION_TELECOM_UI_ACCESS) |
| == PackageManager.PERMISSION_GRANTED; |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ENABLEPHONEACCOUNT, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.ePA"); |
| if (!hasUiAccess) { |
| enforceModifyPermission(); |
| } |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| // enable/disable phone account |
| return mPhoneAccountRegistrar.enablePhoneAccount(accountHandle, isEnabled); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public boolean setDefaultDialer(String packageName) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_SETDEFAULTDIALER, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.sDD"); |
| enforcePermission(MODIFY_PHONE_STATE); |
| enforcePermission(WRITE_SECURE_SETTINGS); |
| synchronized (mLock) { |
| UserHandle callerUser = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return mDefaultDialerCache.setDefaultDialer(packageName, callerUser); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void stopBlockSuppression() { |
| try { |
| Log.startSession("TSI.sBS"); |
| enforceModifyPermission(); |
| if (Binder.getCallingUid() != Process.SHELL_UID |
| && Binder.getCallingUid() != Process.ROOT_UID) { |
| throw new SecurityException("Shell-only API."); |
| } |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| if (mBlockedNumbersManager != null) { |
| mBlockedNumbersManager.endBlockSuppression(); |
| } else { |
| SystemBlockedNumberContract.endBlockSuppression(mContext); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public TelecomAnalytics dumpCallAnalytics() { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_DUMPCALLANALYTICS, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.dCA"); |
| enforcePermission(DUMP); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| // Don't return null since the API contract doesn't specifically say it is |
| // possible. |
| // Note: TelecomManager#dumpAnalytics is an unused system API. |
| return new TelecomAnalytics(Collections.emptyList(), Collections.emptyList()); |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * 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) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_DUMP, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| 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; |
| } |
| |
| event.setResult(ApiStats.RESULT_NORMAL); |
| logEvent(event); |
| if (args != null && args.length > 0 && "analytics".equals(args[0])) { |
| // Return nothing so that we don't confuse a legacy analytics reader which is going |
| // to expect either nothing or a base64 encoded proto. Returning the regular |
| // dumpsys will confuse it. |
| writer.print(Base64.encodeToString(new byte[]{' '}, Base64.DEFAULT)); |
| return; |
| } |
| |
| long token = Binder.clearCallingIdentity(); |
| try { |
| boolean isTimeLineView = (args != null && args.length > 0 |
| && TIME_LINE_ARG.equalsIgnoreCase(args[0])); |
| |
| final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); |
| pw.println("Init Path: On " + mInitPath); |
| pw.println("TelecomUI package: " + mTelecomUiPackageName); |
| if (mCallsManager != null) { |
| pw.println("CallsManager: "); |
| pw.increaseIndent(); |
| mCallsManager.dump(pw, args); |
| pw.decreaseIndent(); |
| |
| pw.println("PhoneAccountRegistrar: "); |
| pw.increaseIndent(); |
| mPhoneAccountRegistrar.dump(pw); |
| pw.decreaseIndent(); |
| |
| pw.println("Flag Configurations (framework - com.android.server.telecom): "); |
| pw.increaseIndent(); |
| reflectAndPrintFlagConfigs(FeatureFlags.class.getMethods(), mFeatureFlags, pw); |
| pw.decreaseIndent(); |
| |
| pw.println("Flag Configurations (module API - android.telecom): "); |
| pw.increaseIndent(); |
| reflectAndPrintFlagConfigs( |
| android.telecom.flags.FeatureFlags.class.getMethods(), |
| mModuleFeatureFlags, pw); |
| pw.decreaseIndent(); |
| |
| pw.println("Flag Configurations (module bugfix - com.android.internal.telecom):" |
| + " "); |
| pw.increaseIndent(); |
| reflectAndPrintFlagConfigs( |
| com.android.internal.telecom.flags.Flags.class.getMethods(), |
| mBugFixFlags, pw); |
| pw.decreaseIndent(); |
| |
| pw.println("TransactionManager: "); |
| pw.increaseIndent(); |
| TransactionManager.getInstance().dump(pw); |
| pw.decreaseIndent(); |
| } |
| if (isTimeLineView) { |
| Log.dumpEventsTimeline(pw); |
| } else { |
| Log.dumpEvents(pw); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| @Override |
| public int handleShellCommand(@NonNull ParcelFileDescriptor in, |
| @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, |
| @NonNull String[] args) { |
| return new TelecomShellCommand(this, mContext).exec(this, |
| in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); |
| } |
| |
| /** |
| * Print all feature flag configurations that Telecom is using for debugging purposes. |
| */ |
| private void reflectAndPrintFlagConfigs(Method[] methods, Object target, |
| IndentingPrintWriter pw) { |
| try { |
| int maxLength = Arrays.stream(methods) |
| .map(Method::getName) |
| .map(String::length) |
| .max(Integer::compare) |
| .orElse(0); // Default to 0 if methods is empty. |
| String format = "\t%s: %-" + maxLength + "s %s"; |
| |
| if (methods.length == 0) { |
| pw.println("NONE"); |
| return; |
| } |
| |
| // Look away, a forbidden technique (reflection) is being used to allow us to get |
| // all flag configs without having to add them manually to this method. |
| for (Method m : methods) { |
| if (m == null || target == null) continue; |
| String flagEnabled = (Boolean) m.invoke(target) ? "[✅]" : "[❌]"; |
| String methodName = m.getName(); |
| String camelCaseName = methodName.replaceAll("([a-z])([A-Z]+)", "$1_$2") |
| .toLowerCase(Locale.US); |
| pw.println(String.format(format, flagEnabled, methodName, camelCaseName)); |
| } |
| } catch (IllegalArgumentException e) { |
| // Do nothing |
| } catch (Exception e) { |
| pw.println("[ERROR]"); |
| } |
| } |
| |
| @Override |
| public String getPackageForCreateLaunchEmergencyDialerIntent() { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_CREATELAUNCHEMERGENCYDIALERINTENT, |
| Binder.getCallingUid(), ApiStats.RESULT_NORMAL); |
| // Get the package name of the emergency dialer |
| int resourceId = Resources.getSystem().getIdentifier("config_emergency_dialer_package", |
| "string", "android"); |
| String packageName = ""; |
| if (resourceId != 0) { |
| packageName = mContext.getApplicationContext().getString(resourceId); |
| } |
| // Test to see if the package exists on the device |
| Intent intent = new Intent(Intent.ACTION_DIAL_EMERGENCY).setPackage(packageName); |
| long token = Binder.clearCallingIdentity(); |
| try { |
| ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0 /* flags*/); |
| if (resolveInfo == null) { |
| // No matching activity from config, fallback to default platform implementation |
| return null; |
| } |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| logEvent(event); |
| return packageName; |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#isIncomingCallPermitted(PhoneAccountHandle) |
| */ |
| @Override |
| public boolean isIncomingCallPermitted(PhoneAccountHandle phoneAccountHandle, |
| String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISINCOMINGCALLPERMITTED, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| Log.startSession("TSI.iICP", Log.getPackageAbbreviation(callingPackage)); |
| try { |
| enforceCallingPackage(callingPackage, "isIncomingCallPermitted"); |
| enforcePhoneAccountHandleMatchesCaller(phoneAccountHandle, callingPackage); |
| enforcePermission(android.Manifest.permission.MANAGE_OWN_CALLS); |
| enforceUserHandleMatchesCaller(phoneAccountHandle); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return mCallsManager.isIncomingCallPermitted(phoneAccountHandle); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle) |
| */ |
| @Override |
| public boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle, |
| String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISOUTGOINGCALLPERMITTED, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| Log.startSession("TSI.iOCP", Log.getPackageAbbreviation(callingPackage)); |
| try { |
| enforceCallingPackage(callingPackage, "isOutgoingCallPermitted"); |
| enforcePhoneAccountHandleMatchesCaller(phoneAccountHandle, callingPackage); |
| enforcePermission(android.Manifest.permission.MANAGE_OWN_CALLS); |
| enforceUserHandleMatchesCaller(phoneAccountHandle); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return mCallsManager.isOutgoingCallPermitted(phoneAccountHandle); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * Blocks until all Telecom handlers have completed their current work. |
| * |
| * See {@link com.android.commands.telecom.Telecom}. |
| */ |
| @Override |
| public void waitOnHandlers() { |
| try { |
| Log.startSession("TSI.wOH"); |
| enforceModifyPermission(); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| Log.i(this, "waitOnHandlers"); |
| mCallsManager.waitOnHandlers(); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void setTestEmergencyPhoneAccountPackageNameFilter(String packageName) { |
| try { |
| Log.startSession("TSI.sTPAPNF"); |
| enforceModifyPermission(); |
| enforceShellOnly(Binder.getCallingUid(), |
| "setTestEmergencyPhoneAccountPackageNameFilter"); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| mPhoneAccountRegistrar.setTestPhoneAccountPackageNameFilter(packageName); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void setTestLocalVoicemailService(String packageName) { |
| try { |
| Log.startSession("TSI.sTLVS"); |
| enforceModifyPermission(); |
| enforceShellOnly(Binder.getCallingUid(), |
| "setTestLocalVoicemailService"); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| LocalVoicemailController lvc = mCallsManager.getLocalVoicemailController(); |
| if (lvc == null) { |
| return; |
| } |
| lvc.setTestLocalVoicemailService(packageName); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * Determines if local voicemail is supported on this device; available if there is an |
| * active local voicemail service configured. |
| * @return {@code true} is local VM is supported on the device, {@code false} otherwise. |
| * @throws RemoteException |
| */ |
| @Override |
| public boolean isLocalVoicemailSupported(String callingPackage) { |
| try { |
| Log.startSession("TSI.iLVS", Log.getPackageAbbreviation(callingPackage)); |
| mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, |
| "READ_PRIVILEGED_PHONE_STATE required."); |
| // NOTE: This DOES NOT sync on `mLock` since we are just getting a single |
| // value from `LocalVoicemailController`. |
| long token = Binder.clearCallingIdentity(); |
| try { |
| return getLocalVoicemailSupported(); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#enableLocalVoicemail |
| * @param callingPackage the calling package. |
| * @param phoneAccountHandle the phone account handle. |
| * @param timeout the timeout. |
| */ |
| @Override |
| public void enableLocalVoicemail(String callingPackage, |
| PhoneAccountHandle phoneAccountHandle, long timeout) { |
| |
| Log.startSession("TSI.iLVS", Log.getPackageAbbreviation(callingPackage)); |
| try { |
| enforceModifyPermission(); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| if (!getLocalVoicemailSupported()) { |
| throw new IllegalArgumentException("Local voicemail is disabled."); |
| } |
| |
| mPhoneAccountRegistrar.setLocalVoicemailTimeout(phoneAccountHandle, |
| Duration.ofMillis(timeout)); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#disableLocalVoicemail |
| * @param callingPackage the calling package. |
| * @param phoneAccountHandle the phone account handle. |
| */ |
| @Override |
| public void disableLocalVoicemail(String callingPackage, |
| PhoneAccountHandle phoneAccountHandle) { |
| try { |
| Log.startSession("TSI.dLV", Log.getPackageAbbreviation(callingPackage)); |
| enforceModifyPermission(); |
| synchronized (mLock) { |
| enforceModifyPermission(); |
| long token = Binder.clearCallingIdentity(); |
| try { |
| if (!getLocalVoicemailSupported()) { |
| throw new IllegalArgumentException("Local voicemail is disabled."); |
| } |
| |
| mPhoneAccountRegistrar.setLocalVoicemailTimeout(phoneAccountHandle, |
| null); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| /*** |
| * @see android.telecom.TelecomManager#getLocalVoicemailTimeout |
| * @param callingPackage the calling package |
| * @param phoneAccountHandle the phone account handle |
| * @return the timeout duration. |
| */ |
| @Override |
| public long getLocalVoicemailTimeout(String callingPackage, |
| PhoneAccountHandle phoneAccountHandle) { |
| try { |
| Log.startSession("TSI.gLVT", Log.getPackageAbbreviation(callingPackage)); |
| mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, |
| "READ_PRIVILEGED_PHONE_STATE required."); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| if (!getLocalVoicemailSupported()) { |
| throw new IllegalArgumentException("Local voicemail is disabled."); |
| } |
| |
| Duration duration = mPhoneAccountRegistrar.getLocalVoicemailTimeout( |
| phoneAccountHandle); |
| if (duration == null) { |
| throw new IllegalArgumentException("Local voicemail not enabled."); |
| } |
| return duration.toMillis(); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#isLocalVoicemailEnabled |
| * @param callingPackage the calling packager. |
| * @param phoneAccountHandle the phone account handle |
| * @return {@code true} if local vm is enabled, {@code false} otherwise. |
| */ |
| @Override |
| public boolean isLocalVoicemailEnabled(String callingPackage, |
| PhoneAccountHandle phoneAccountHandle) { |
| try { |
| Log.startSession("TSI.iLVE", Log.getPackageAbbreviation(callingPackage)); |
| |
| synchronized (mLock) { |
| if (!getLocalVoicemailSupported()) { |
| throw new IllegalArgumentException("Local voicemail is disabled."); |
| } |
| |
| mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, |
| "READ_PRIVILEGED_PHONE_STATE required."); |
| long token = Binder.clearCallingIdentity(); |
| try { |
| return mPhoneAccountRegistrar.getLocalVoicemailTimeout(phoneAccountHandle) |
| != null; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * @return the package name associated with telecomui app. |
| */ |
| @Override |
| public String getTelecomUiPackageName() { |
| return mTelecomUiPackageName; |
| } |
| |
| /** |
| * See {@link TelecomManager#isInEmergencyCall()} |
| */ |
| @Override |
| public boolean isInEmergencyCall() { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISINEMERGENCYCALL, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.iIEC"); |
| enforceModifyPermission(); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| boolean isInEmergencyCall = mCallsManager.isInEmergencyCall(); |
| Log.i(this, "isInEmergencyCall: %b", isInEmergencyCall); |
| return isInEmergencyCall; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * See {@link TelecomManager#handleCallIntent(Intent, String)} |
| */ |
| @Override |
| public void handleCallIntent(Intent intent, String callingPackage) { |
| try { |
| Log.startSession("TSI.hCI"); |
| synchronized (mLock) { |
| mContext.enforceCallingOrSelfPermission(PERMISSION_HANDLE_CALL_INTENT, |
| "handleCallIntent is for internal use only."); |
| |
| long token = Binder.clearCallingIdentity(); |
| try { |
| Log.i(this, "handleCallIntent: handling call intent"); |
| mCallIntentProcessorAdapter.processOutgoingCallIntent(mContext, |
| mCallsManager, intent, callingPackage, mFeatureFlags); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * A method intended for use in testing to clean up any calls are ongoing. Stuck |
| * calls during CTS cause cascading failures, so if the CTS test detects such a state, it |
| * should call this method via a shell command to clean up before moving on to the next |
| * test. Also cleans up any pending futures related to |
| * {@link android.telecom.CallDiagnosticService}s. |
| */ |
| @Override |
| public void cleanupStuckCalls() { |
| Log.startSession("TCI.cSC"); |
| try { |
| synchronized (mLock) { |
| enforceShellOnly(Binder.getCallingUid(), "cleanupStuckCalls"); |
| long token = Binder.clearCallingIdentity(); |
| try { |
| Set<UserHandle> userHandles = new HashSet<>(); |
| for (Call call : mCallsManager.getCalls()) { |
| // Any call that is not in a disconnect* state should be moved to the |
| // disconnected state |
| if (!isDisconnectingOrDisconnected(call)) { |
| mCallsManager.markCallAsDisconnected( |
| call, |
| new DisconnectCause(DisconnectCause.OTHER, |
| "cleaning up stuck calls")); |
| } |
| // ensure the call is immediately removed from CallsManager instead of |
| // using a Future to do the work. |
| call.cleanup(); |
| // finally, officially remove the call from CallsManager tracking |
| mCallsManager.markCallAsRemoved(call); |
| userHandles.add(call.getAssociatedUser()); |
| } |
| for (UserHandle userHandle : userHandles) { |
| mCallsManager.getInCallController().unbindFromServices(userHandle); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| private boolean isDisconnectingOrDisconnected(Call call) { |
| return call.getState() == CallState.DISCONNECTED |
| || call.getState() == CallState.DISCONNECTING; |
| } |
| |
| /** |
| * A method intended for test to clean up orphan {@link PhoneAccount}. An orphan |
| * {@link PhoneAccount} is a phone account belongs to an invalid {@link UserHandle} |
| * or a |
| * deleted package. |
| * |
| * @return the number of orphan {@code PhoneAccount} deleted. |
| */ |
| @Override |
| public int cleanupOrphanPhoneAccounts() { |
| Log.startSession("TCI.cOPA"); |
| try { |
| synchronized (mLock) { |
| enforceShellOnly(Binder.getCallingUid(), "cleanupOrphanPhoneAccounts"); |
| long token = Binder.clearCallingIdentity(); |
| try { |
| return mPhoneAccountRegistrar.cleanupOrphanedPhoneAccounts(); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * A method intended for use in testing to query whether a particular non-ui inCallService |
| * is bound in a call. |
| * @param packageName of the service to query. |
| * @return whether it is bound or not. |
| */ |
| @Override |
| public boolean isNonUiInCallServiceBound(String packageName) { |
| Log.startSession("TCI.iNUICSB"); |
| try { |
| synchronized (mLock) { |
| enforceShellOnly(Binder.getCallingUid(), "isNonUiInCallServiceBound"); |
| if (!(mContext.checkCallingOrSelfPermission(READ_PHONE_STATE) |
| == PackageManager.PERMISSION_GRANTED) || |
| !(mContext.checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE) |
| == PackageManager.PERMISSION_GRANTED)) { |
| throw new SecurityException("isNonUiInCallServiceBound requires the" |
| + " READ_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE permission"); |
| } |
| long token = Binder.clearCallingIdentity(); |
| try { |
| return mCallsManager |
| .getInCallController() |
| .isNonUiInCallServiceBound(packageName); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| /** |
| * A method intended for use in testing to reset car mode at all priorities. |
| * |
| * Runs during setup to avoid cascading failures from failing car mode CTS. |
| */ |
| @Override |
| public void resetCarMode() { |
| Log.startSession("TCI.rCM"); |
| try { |
| synchronized (mLock) { |
| enforceShellOnly(Binder.getCallingUid(), "resetCarMode"); |
| long token = Binder.clearCallingIdentity(); |
| try { |
| UiModeManager uiModeManager = |
| mContext.getSystemService(UiModeManager.class); |
| uiModeManager.disableCarMode(DISABLE_CAR_MODE_ALL_PRIORITIES); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void setTestDefaultCallRedirectionApp(String packageName) { |
| try { |
| Log.startSession("TSI.sTDCRA"); |
| enforceModifyPermission(); |
| enforceShellOnly(Binder.getCallingUid(), "Test-only API"); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| mCallsManager.getRoleManagerAdapter().setTestDefaultCallRedirectionApp( |
| packageName); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void setTestDefaultCallScreeningApp(String packageName) { |
| try { |
| Log.startSession("TSI.sTDCSA"); |
| enforceModifyPermission(); |
| enforceShellOnly(Binder.getCallingUid(), "Test-only API"); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| mCallsManager.getRoleManagerAdapter().setTestDefaultCallScreeningApp( |
| packageName); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded) { |
| try { |
| Log.startSession("TSI.aORTCCA"); |
| enforceModifyPermission(); |
| enforceShellOnly(Binder.getCallingUid(), "addOrRemoveTestCallCompanionApp"); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| mCallsManager.getRoleManagerAdapter().addOrRemoveTestCallCompanionApp( |
| packageName, isAdded); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void requestLogMark(String message) { |
| try { |
| Log.startSession("TSI.rLM"); |
| enforceShellOnly(Binder.getCallingUid(), "requestLogMark is for shell only"); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| mCallsManager.requestLogMark(message); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void setTestPhoneAcctSuggestionComponent(String flattenedComponentName, |
| UserHandle userHandle) { |
| try { |
| Log.startSession("TSI.sPASA"); |
| enforceModifyPermission(); |
| if (Binder.getCallingUid() != Process.SHELL_UID |
| && Binder.getCallingUid() != Process.ROOT_UID) { |
| throw new SecurityException("Shell-only API."); |
| } |
| synchronized (mLock) { |
| PhoneAccountSuggestionHelper.setOverrideServiceName(flattenedComponentName); |
| PhoneAccountSuggestionHelper.setOverrideUserHandle(userHandle); |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void setTestDefaultDialer(String packageName) { |
| try { |
| Log.startSession("TSI.sTDD"); |
| enforceModifyPermission(); |
| if (Binder.getCallingUid() != Process.SHELL_UID |
| && Binder.getCallingUid() != Process.ROOT_UID) { |
| throw new SecurityException("Shell-only API."); |
| } |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| mCallsManager.getRoleManagerAdapter().setTestDefaultDialer(packageName); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void setTestCallDiagnosticService(String packageName) { |
| try { |
| Log.startSession("TSI.sTCDS"); |
| enforceModifyPermission(); |
| enforceShellOnly(Binder.getCallingUid(), "setTestCallDiagnosticService is for " |
| + "shell use only."); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| CallDiagnosticServiceController controller = |
| mCallsManager.getCallDiagnosticServiceController(); |
| if (controller != null) { |
| controller.setTestCallDiagnosticService(packageName); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void setMetricsTestMode(boolean enabled) { |
| mMetricsController.setTestMode(enabled); |
| } |
| |
| @Override |
| public void waitForAudioToUpdate(boolean expectActive) { |
| mCallsManager.waitForAudioToUpdate(expectActive); |
| } |
| /** |
| * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED} |
| * calls for a given {@code packageName} and {@code userHandle}. |
| * |
| * @param packageName the package name of the app to check calls for. |
| * @param userHandle the user handle on which to check for calls. |
| * @param callingPackage The caller's package name. |
| * @return {@code true} if there are ongoing calls, {@code false} otherwise. |
| */ |
| @Override |
| public boolean isInSelfManagedCall(String packageName, UserHandle userHandle, |
| String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent(ApiStats.API_ISINSELFMANAGEDCALL, |
| Binder.getCallingUid(), ApiStats.RESULT_PERMISSION); |
| try { |
| mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, |
| "READ_PRIVILEGED_PHONE_STATE required."); |
| // Ensure that the caller has the INTERACT_ACROSS_USERS permission if it's trying |
| // to access calls that don't belong to it. |
| if (!Binder.getCallingUserHandle().equals(userHandle)) { |
| enforceInAppCrossUserPermission(); |
| } |
| |
| Log.startSession("TSI.iISMC", Log.getPackageAbbreviation(callingPackage)); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return mCallsManager.isInSelfManagedCall( |
| packageName, userHandle); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public int getCallConnectedIndicatorPreference(String callingPackage) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_GETCALLCONNECTEDINDICATORPREF, Binder.getCallingUid(), |
| ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.gCCIPB", Log.getPackageAbbreviation(callingPackage)); |
| enforcePermission(READ_PRIVILEGED_PHONE_STATE); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| return mCallsManager.getCallConnectedIndicatorPreference(); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void setCallConnectedIndicatorPreference(String callingPackage, int preference) { |
| ApiStats.ApiEvent event = new ApiStats.ApiEvent( |
| ApiStats.API_SETCALLCONNECTEDINDICATORPREF, Binder.getCallingUid(), |
| ApiStats.RESULT_PERMISSION); |
| try { |
| Log.startSession("TSI.sCCIPB", Log.getPackageAbbreviation(callingPackage)); |
| mContext.enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, |
| "MODIFY_PHONE_STATE required."); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| event.setResult(ApiStats.RESULT_NORMAL); |
| try { |
| mCallsManager.setCallConnectedIndicatorPreference(preference); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| logEvent(event); |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public @android.annotation.NonNull Map<String, Boolean> getVoipCallLogIntegrationStatus( |
| String callingPackage) { |
| try { |
| mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, |
| "READ_PRIVILEGED_PHONE_STATE required."); |
| |
| Log.startSession("TSI.gVCLIS", Log.getPackageAbbreviation(callingPackage)); |
| synchronized (mLock) { |
| UserHandle userHandle = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| try { |
| return mCallsManager.getVoipPackageNamesCallLogIntegration(userHandle); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| |
| @Override |
| public void setVoipCallLogIntegrationEnabled(String callingPackage, |
| @android.annotation.NonNull String packageName, boolean enabled) { |
| try { |
| mContext.enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, |
| "MODIFY_PHONE_STATE required."); |
| // Verify that the package supports call log integration by checking that it has |
| // registered the callback intent. |
| UserHandle userHandle = Binder.getCallingUserHandle(); |
| if (!doesPackageSupportCallback(packageName, userHandle)) { |
| throw new IllegalArgumentException("Package " + packageName + " does not" |
| + " register the TelecomManager.ACTION_CALL_BACK intent."); |
| } |
| |
| Log.startSession("TSI.sVCLIE", Log.getPackageAbbreviation(callingPackage)); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| mCallsManager.setVoipCallLogIntegrationEnabled(userHandle, packageName, |
| enabled); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } finally { |
| Log.endSession(); |
| } |
| } |
| }; |
| |
| /** |
| * Determines whether the local voicemail service is supported on this device. |
| * @return {@code true} if local voicemail is supported, {@code false} otherwise. |
| */ |
| private boolean getLocalVoicemailSupported() { |
| |
| LocalVoicemailController localVoicemailController = |
| mCallsManager.getLocalVoicemailController(); |
| if (localVoicemailController == null) { |
| return false; |
| } |
| return localVoicemailController.getActiveLocalVoicemailService() != null; |
| } |
| |
| public TelecomServiceImpl( |
| Context context, |
| CallsManager callsManager, |
| PhoneAccountRegistrar phoneAccountRegistrar, |
| CallIntentProcessor.Adapter callIntentProcessorAdapter, |
| UserCallIntentProcessorFactory userCallIntentProcessorFactory, |
| DefaultDialerCache defaultDialerCache, |
| SubscriptionManagerAdapter subscriptionManagerAdapter, |
| FeatureFlags featureFlags, |
| android.telecom.flags.FeatureFlags moduleFeatureFlags, |
| com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags, |
| com.android.internal.telecom.flags.Flags bugFixFlags, |
| TelecomSystem.SyncRoot lock, TelecomMetricsController metricsController, |
| String sysUiPackageName, |
| String telecomUiPackageName) { |
| mContext = context; |
| mAppOpsManager = mContext.getSystemService(AppOpsManager.class); |
| |
| mPackageManager = mContext.getPackageManager(); |
| mPermissionManager = mContext.getSystemService(PermissionManager.class); |
| |
| mCallsManager = callsManager; |
| mFeatureFlags = featureFlags; |
| mModuleFeatureFlags = moduleFeatureFlags; |
| mBugFixFlags = bugFixFlags; |
| if (telephonyFeatureFlags != null) { |
| mTelephonyFeatureFlags = telephonyFeatureFlags; |
| } else { |
| mTelephonyFeatureFlags = |
| new com.android.internal.telephony.flags.FeatureFlagsImpl(); |
| } |
| mLock = lock; |
| mPhoneAccountRegistrar = phoneAccountRegistrar; |
| mUserCallIntentProcessorFactory = userCallIntentProcessorFactory; |
| mDefaultDialerCache = defaultDialerCache; |
| mCallIntentProcessorAdapter = callIntentProcessorAdapter; |
| mSubscriptionManagerAdapter = subscriptionManagerAdapter; |
| mMetricsController = metricsController; |
| mSystemUiPackageName = sysUiPackageName; |
| mTelecomUiPackageName = telecomUiPackageName; |
| |
| setupPackageRemovedReceiver(phoneAccountRegistrar); |
| |
| mDefaultDialerCache.observeDefaultDialerApplication(mContext.getMainExecutor(), userId -> { |
| String defaultDialer = mDefaultDialerCache.getDefaultDialerApplication( |
| UserHandle.of(userId)); |
| if (defaultDialer == null) { |
| // We are replacing the dialer, just wait for the upcoming callback. |
| return; |
| } |
| final Intent intent = new Intent(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED) |
| .putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, |
| defaultDialer); |
| mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); |
| }); |
| |
| mTransactionManager = TransactionManager.getInstance(); |
| mTransactionManager.setFeatureFlag(mFeatureFlags); |
| mTransactionManager.setAnomalyReporter(mAnomalyReporter); |
| mTransactionalServiceRepository = new TransactionalServiceRepository(mFeatureFlags, |
| mAnomalyReporter); |
| mBlockedNumbersManager = mFeatureFlags.telecomMainlineBlockedNumbersManager() |
| ? mContext.getSystemService(BlockedNumbersManager.class) |
| : null; |
| } |
| |
| /** |
| * Helper function to initialize and register a BroadcastReceiver for package removal events. |
| * This setup runs on a background thread if the specified feature flag is enabled. |
| * Assumes this method is called only once or is protected against multiple thread creations. |
| */ |
| private void setupPackageRemovedReceiver(PhoneAccountRegistrar phoneAccountRegistrar) { |
| if (mPackageRemovedReceiver != null) { |
| Log.w(TAG, "PackageRemovedReceiver appears to be already initialized. Skipping setup."); |
| return; |
| } |
| |
| // IMPORTANT: This thread, once started, will run for the lifetime of the process |
| // unless the process is killed, as we won't have a reference to 'quit()' it later. |
| HandlerThread localHandlerThread = new HandlerThread("TelRemoveAcctsBckgrndThread"); |
| localHandlerThread.start(); // The thread starts and will keep running. |
| |
| // Get a Handler associated with the local background thread's Looper. |
| Handler backgroundHandler = new Handler(localHandlerThread.getLooper()); |
| |
| // IntentFilter for package removal events. |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); |
| filter.addDataScheme("package"); |
| |
| mPackageRemovedReceiver = new PackageRemovedReceiver( |
| phoneAccountRegistrar, |
| backgroundHandler, |
| new UserHandleWrapper()); |
| |
| try { |
| Log.v(TAG, "Registering PackageRemovedReceiver (local thread) for all users" + |
| " with RECEIVER_NOT_EXPORTED flag."); |
| mAllUsersContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); |
| mAllUsersContext.registerReceiver( |
| mPackageRemovedReceiver, |
| filter, |
| null, |
| backgroundHandler, // Handler uses the local thread's Looper |
| Context.RECEIVER_NOT_EXPORTED); |
| Log.v(TAG, "PackageRemovedReceiver (local thread) registered successfully."); |
| } catch (Exception e) { |
| if (localHandlerThread.isAlive()) { |
| localHandlerThread.quitSafely(); // Attempt to clean up the just-started thread |
| Log.w(TAG, "Attempted to quit localHandlerThread due to registration failure."); |
| } |
| mPackageRemovedReceiver = null; |
| } |
| } |
| |
| public String getTelecomUiPackageName() { |
| return mTelecomUiPackageName; |
| } |
| |
| @VisibleForTesting |
| public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter) { |
| mAnomalyReporter = mAnomalyReporterAdapter; |
| } |
| |
| private boolean enforceCallStreamingPermission(String packageName, PhoneAccountHandle handle, |
| int uid) { |
| // TODO: implement this permission check (make sure the calling package is the d2di package) |
| PhoneAccount account = mPhoneAccountRegistrar.getPhoneAccount(handle, |
| UserHandle.getUserHandleForUid(uid)); |
| if (account == null |
| || !account.hasCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_CALL_STREAMING)) { |
| throw new SecurityException( |
| "The phone account handle in requesting can't support call streaming: " |
| + handle); |
| } |
| return true; |
| } |
| |
| /** |
| * @return whether to return early without doing the action/throwing |
| * @throws SecurityException same as {@link Context#enforceCallingOrSelfPermission} |
| */ |
| private boolean enforceAnswerCallPermission(String packageName, int uid) { |
| try { |
| enforceModifyPermission(); |
| } catch (SecurityException e) { |
| final String permission = Manifest.permission.ANSWER_PHONE_CALLS; |
| enforcePermission(permission); |
| |
| return mAppOpsManager.checkOp(AppOpsManager.OPSTR_ANSWER_PHONE_CALLS, uid, |
| packageName) == AppOpsManager.MODE_ALLOWED; |
| } |
| return true; |
| } |
| |
| /** |
| * @return {@code true} if the app has the handover permission and has received runtime |
| * permission to perform that operation, {@code false}. |
| * @throws SecurityException same as {@link Context#enforceCallingOrSelfPermission} |
| */ |
| private boolean enforceAcceptHandoverPermission(String packageName, int uid) { |
| mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCEPT_HANDOVER, |
| "App requires ACCEPT_HANDOVER permission to accept handovers."); |
| |
| return mAppOpsManager.checkOp(AppOpsManager.OPSTR_ACCEPT_HANDOVER, uid, packageName) |
| == AppOpsManager.MODE_ALLOWED; |
| } |
| |
| @VisibleForTesting |
| public void setTransactionManager(TransactionManager transactionManager) { |
| mTransactionManager = transactionManager; |
| } |
| |
| public void setInitPath(String initPath) { |
| mInitPath = initPath; |
| } |
| |
| public ITelecomService.Stub getBinder() { |
| return mBinderImpl; |
| } |
| |
| private boolean isPhoneAccountHandleVisibleToCallingUser( |
| PhoneAccountHandle phoneAccountUserHandle, UserHandle callingUser) { |
| synchronized (mLock) { |
| return mPhoneAccountRegistrar.getPhoneAccount(phoneAccountUserHandle, callingUser) |
| != null; |
| } |
| } |
| |
| 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, int videoState, String packageName, |
| boolean isCallerPrivileged) { |
| if (call != null) { |
| if (call.isSelfManaged() && !isCallerPrivileged) { |
| Log.addEvent(call, LogUtils.Events.REQUEST_ACCEPT, |
| "self-mgd accept ignored from non-privileged app " + packageName); |
| return; |
| } |
| |
| if (videoState == DEFAULT_VIDEO_STATE || !isValidAcceptVideoState(videoState)) { |
| videoState = call.getVideoState(); |
| } |
| mCallsManager.answerCall(call, videoState); |
| } |
| } |
| |
| // |
| // Supporting methods for the ITelecomService interface implementation. |
| // |
| |
| private Call getOngoingCall() { |
| // 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.PULLING, |
| CallState.RINGING, |
| CallState.SIMULATED_RINGING, |
| CallState.ON_HOLD); |
| } |
| return call; |
| } |
| |
| private boolean endCallInternal(Call call, String callingPackage, boolean isCallerPrivileged) { |
| if (call != null) { |
| if (call.isEmergencyCall()) { |
| android.util.EventLog.writeEvent(0x534e4554, "132438333", -1, ""); |
| return false; |
| } |
| |
| if (call.isSelfManaged() && !isCallerPrivileged) { |
| Log.addEvent(call, LogUtils.Events.REQUEST_DISCONNECT, |
| "self-mgd disconnect ignored from non-privileged app " + |
| callingPackage); |
| return false; |
| } |
| |
| if (call.getState() == CallState.RINGING |
| || call.getState() == CallState.SIMULATED_RINGING) { |
| mCallsManager.rejectCall(call, false /* rejectWithMessage */, null); |
| } else { |
| mCallsManager.disconnectCall(call); |
| } |
| 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, |
| UserHandle callingUserHandle) { |
| PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, |
| callingUserHandle); |
| 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!"); |
| } |
| } |
| |
| // Enforce that the PhoneAccountHandle is tied to a self-managed package and not managed (aka |
| // sim calling, etc.) |
| private void enforcePhoneAccountIsNotManaged(PhoneAccountHandle phoneAccountHandle) { |
| PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, |
| phoneAccountHandle.getUserHandle()); |
| if (phoneAccount == null) { |
| throw new IllegalArgumentException("enforcePhoneAccountIsNotManaged:" |
| + " phoneAccount is null"); |
| } |
| if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { |
| throw new IllegalArgumentException("enforcePhoneAccountIsNotManaged:" |
| + " CAPABILITY_SIM_SUBSCRIPTION is not allowed"); |
| } |
| if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)) { |
| throw new IllegalArgumentException("enforcePhoneAccountIsNotManaged:" |
| + " CAPABILITY_CALL_PROVIDER is not allowed"); |
| } |
| if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) { |
| throw new IllegalArgumentException("enforcePhoneAccountIsNotManaged:" |
| + " CAPABILITY_CONNECTION_MANAGER is not allowed"); |
| } |
| } |
| |
| 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 ... |
| enforceTelecomFeature(); |
| // ... and the PhoneAccounts they refer to are for their own package. |
| enforceCallingPackage(packageName, "enforcePhoneAccountModificationForPackage"); |
| } |
| } |
| |
| 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, String message) { |
| int callingUid = Binder.getCallingUid(); |
| |
| if (callingUid != Process.ROOT_UID && |
| !callingUidMatchesPackageManagerRecords(packageName)) { |
| throw new SecurityException(message + ": Package " + packageName |
| + " does not belong to " + callingUid); |
| } |
| } |
| |
| /** |
| * helper method that compares the binder_uid to what the packageManager_uid reports for the |
| * passed in packageName. |
| * <p> |
| * returns true if the binder_uid matches the packageManager_uid records |
| */ |
| private boolean callingUidMatchesPackageManagerRecords(String packageName) { |
| int packageUid = -1; |
| int callingUid = Binder.getCallingUid(); |
| PackageManager pm; |
| long token = Binder.clearCallingIdentity(); |
| try { |
| pm = mContext.createContextAsUser( |
| UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager(); |
| |
| // This has to happen inside the scope of the `clearCallingIdentity` block |
| // otherwise the caller may fail to call `TelecomManager#endCall`. |
| if (pm != null) { |
| try { |
| packageUid = pm.getPackageUid(packageName, 0); |
| } catch (PackageManager.NameNotFoundException e) { |
| // packageUid is -1. |
| } |
| } |
| } catch (Exception e) { |
| Log.i(this, "callingUidMatchesPackageManagerRecords:" |
| + " createContextAsUser hit exception=[%s]", e.toString()); |
| return false; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| |
| int definingAppUid = Flags.enablePccFrameworkSupport() ? |
| getAppUidIfPcc(callingUid) : callingUid; |
| |
| if (packageUid != definingAppUid) { |
| Log.i(this, "callingUidMatchesPackageManagerRecords: uid mismatch found for" |
| + "packageName=[%s]. packageManager reports packageUid=[%d] but " |
| + "binder reports callingUid=[%d]", packageName, packageUid, callingUid); |
| } |
| |
| return packageUid == definingAppUid; |
| } |
| |
| /** |
| * Note: This method should be called BEFORE clearing the binder identity. |
| * |
| * @param permissionsToValidate set of permissions that should be checked |
| * @param alreadyComputedPermissions a list of permissions that were already checked |
| * @return all the permissions that |
| */ |
| private Set<String> computePermissionsForBoundPackage( |
| Set<String> permissionsToValidate, |
| Set<String> alreadyComputedPermissions) { |
| Set<String> permissions = Objects.requireNonNullElseGet(alreadyComputedPermissions, |
| HashSet::new); |
| for (String permission : permissionsToValidate) { |
| if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) { |
| permissions.add(permission); |
| } |
| } |
| return permissions; |
| } |
| |
| /** |
| * This method should be used to clear {@link PhoneAccount} properties based on a |
| * callingPackages permissions. |
| * |
| * @param account to clear properties from |
| * @param permissions the list of permissions the callingPackge has |
| * @return the account that callingPackage will receive |
| */ |
| private PhoneAccount maybeCleansePhoneAccount(PhoneAccount account, |
| Set<String> permissions) { |
| if (account == null) { |
| return null; |
| } |
| PhoneAccount.Builder accountBuilder = new PhoneAccount.Builder(account); |
| if (!permissions.contains(MODIFY_PHONE_STATE)) { |
| accountBuilder.setGroupId("***"); |
| } |
| return accountBuilder.build(); |
| } |
| |
| private void enforceTelecomFeature() { |
| PackageManager pm = mContext.getPackageManager(); |
| if (!pm.hasSystemFeature(PackageManager.FEATURE_TELECOM) |
| && !pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) { |
| throw new UnsupportedOperationException( |
| "System does not support feature " + PackageManager.FEATURE_TELECOM); |
| } |
| } |
| |
| private void enforceRegisterSimSubscriptionPermission() { |
| enforcePermission(REGISTER_SIM_SUBSCRIPTION); |
| } |
| |
| private void enforceModifyPermission() { |
| enforcePermission(MODIFY_PHONE_STATE); |
| } |
| |
| private void enforceModifyPermission(String message) { |
| mContext.enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, message); |
| } |
| |
| private void enforcePermission(String permission) { |
| mContext.enforceCallingOrSelfPermission(permission, null); |
| } |
| |
| private void enforceRegisterSelfManaged() { |
| mContext.enforceCallingPermission(android.Manifest.permission.MANAGE_OWN_CALLS, null); |
| } |
| |
| private void enforceRegisterMultiUser() { |
| if (!isCallerSystemApp()) { |
| throw new SecurityException("CAPABILITY_MULTI_USER is only available to system apps."); |
| } |
| } |
| |
| private void enforceRegisterVoiceCallingIndicationCapabilities(PhoneAccount account) { |
| // Caller must be able to register a SIM PhoneAccount or be the SIM call manager (as named |
| // in carrier config) to declare the two voice indication capabilities. |
| boolean prerequisiteCapabilitiesOk = |
| account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) |
| || account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER); |
| boolean permissionsOk = |
| isCallerSimCallManagerForAnySim(account.getAccountHandle()) |
| || mContext.checkCallingOrSelfPermission(REGISTER_SIM_SUBSCRIPTION) |
| == PackageManager.PERMISSION_GRANTED; |
| if (!prerequisiteCapabilitiesOk || !permissionsOk) { |
| throw new SecurityException( |
| "Only SIM subscriptions and connection managers are allowed to declare " |
| + "CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS and " |
| + "CAPABILITY_VOICE_CALLING_AVAILABLE"); |
| } |
| } |
| |
| private void enforceRegisterSkipCallFiltering() { |
| if (!isCallerSystemApp()) { |
| throw new SecurityException( |
| "EXTRA_SKIP_CALL_FILTERING is only available to system apps."); |
| } |
| } |
| |
| private void enforceUserHandleMatchesCaller(PhoneAccountHandle accountHandle) { |
| if (!Binder.getCallingUserHandle().equals(accountHandle.getUserHandle())) { |
| // Enforce INTERACT_ACROSS_USERS if the calling user handle does not match |
| // phone account's user handle |
| enforceInAppCrossUserPermission(); |
| } |
| } |
| |
| private boolean doesAssociatedUserMatchCaller(Call call, UserHandle callingUser) { |
| if (call != null && !Objects.equals(callingUser, call.getAssociatedUser())) { |
| return hasInAppCrossUserPermission(); |
| } |
| return true; |
| } |
| |
| private void enforcePhoneAccountHandleMatchesCaller(PhoneAccountHandle phoneAccountHandle, |
| String callingPackage) { |
| if (!callingPackage.equals(phoneAccountHandle.getComponentName().getPackageName())) { |
| throw new SecurityException("Caller does not own the PhoneAccountHandle"); |
| } |
| } |
| |
| private void enforceCrossUserPermission(int callingUid) { |
| if (callingUid != Process.SYSTEM_UID && callingUid != 0) { |
| mContext.enforceCallingOrSelfPermission( |
| android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, "Must be system or have" |
| + " INTERACT_ACROSS_USERS_FULL permission"); |
| } |
| } |
| |
| private void enforceInAppCrossUserPermission() { |
| mContext.enforceCallingOrSelfPermission( |
| android.Manifest.permission.INTERACT_ACROSS_USERS, "Must be system or have" |
| + " INTERACT_ACROSS_USERS permission"); |
| } |
| |
| private void enforceInAppCrossProfilePermission() { |
| mContext.enforceCallingOrSelfPermission( |
| android.Manifest.permission.INTERACT_ACROSS_PROFILES, "Must be system or have" |
| + " INTERACT_ACROSS_PROFILES permission"); |
| } |
| |
| private boolean hasInAppCrossUserPermission() { |
| return mContext.checkCallingOrSelfPermission( |
| Manifest.permission.INTERACT_ACROSS_USERS) |
| == PackageManager.PERMISSION_GRANTED; |
| } |
| |
| private boolean hasInAppCrossProfilePermission() { |
| return mContext.checkCallingOrSelfPermission( |
| Manifest.permission.INTERACT_ACROSS_PROFILES) |
| == PackageManager.PERMISSION_GRANTED; |
| } |
| |
| // to be used for TestApi methods that can only be called with SHELL UID. |
| private void enforceShellOnly(int callingUid, String message) { |
| if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { |
| return; // okay |
| } |
| |
| throw new SecurityException(message + ": Only shell user can call it"); |
| } |
| |
| private boolean canReadPhoneState(String callingPackage, String callingFeatureId, |
| 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.OPSTR_READ_PHONE_STATE, |
| Binder.getCallingUid(), |
| callingPackage, callingFeatureId, message) == AppOpsManager.MODE_ALLOWED; |
| } |
| } |
| |
| private boolean canReadMangeOwnCalls(String message) { |
| try { |
| mContext.enforceCallingOrSelfPermission(MANAGE_OWN_CALLS, message); |
| return true; |
| } catch (SecurityException e) { |
| return false; |
| } |
| } |
| |
| private boolean canReadPhoneNumbers(String callingPackage, String callingFeatureId, |
| String message) { |
| boolean targetSdkPreR = false; |
| int uid = Binder.getCallingUid(); |
| try { |
| ApplicationInfo applicationInfo = mPackageManager.getApplicationInfoAsUser( |
| callingPackage, 0, UserHandle.getUserHandleForUid(Binder.getCallingUid())); |
| targetSdkPreR = applicationInfo != null |
| && applicationInfo.targetSdkVersion < Build.VERSION_CODES.R; |
| } catch (PackageManager.NameNotFoundException e) { |
| // In the case that the PackageManager cannot find the specified calling package apply |
| // the more restrictive target R+ requirements. |
| } |
| // Apps targeting pre-R can access phone numbers via READ_PHONE_STATE |
| if (targetSdkPreR) { |
| try { |
| return canReadPhoneState(callingPackage, callingFeatureId, message); |
| } catch (SecurityException e) { |
| // Apps targeting pre-R can still access phone numbers via the additional checks |
| // below. |
| } |
| } else { |
| // The system/default dialer can always read phone state - so that emergency calls will |
| // still work. |
| if (isPrivilegedDialerCalling(callingPackage)) { |
| return true; |
| } |
| if (mContext.checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE) |
| == PackageManager.PERMISSION_GRANTED) { |
| return true; |
| } |
| } |
| if (mContext.checkCallingOrSelfPermission(READ_PHONE_NUMBERS) |
| == PackageManager.PERMISSION_GRANTED && mAppOpsManager.noteOpNoThrow( |
| AppOpsManager.OPSTR_READ_PHONE_NUMBERS, uid, callingPackage, callingFeatureId, |
| message) == AppOpsManager.MODE_ALLOWED) { |
| return true; |
| } |
| if (mContext.checkCallingOrSelfPermission(READ_SMS) == PackageManager.PERMISSION_GRANTED |
| && mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_READ_SMS, uid, callingPackage, |
| callingFeatureId, message) == AppOpsManager.MODE_ALLOWED) { |
| return true; |
| } |
| // The default SMS app with the WRITE_SMS appop granted can access phone numbers. |
| if (mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_WRITE_SMS, uid, callingPackage, |
| callingFeatureId, message) == AppOpsManager.MODE_ALLOWED) { |
| return true; |
| } |
| throw new SecurityException("Package " + callingPackage |
| + " does not meet the requirements to access the phone number"); |
| } |
| |
| private boolean canReadPrivilegedPhoneState(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; |
| } |
| |
| mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, message); |
| return true; |
| } |
| |
| private boolean isDialerOrPrivileged(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; |
| } |
| |
| mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, message); |
| // SKIP checking run-time OP_READ_PHONE_STATE since caller or self has PRIVILEGED |
| // permission |
| return true; |
| } |
| |
| private boolean isSelfManagedConnectionService(PhoneAccountHandle phoneAccountHandle) { |
| if (phoneAccountHandle != null) { |
| PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked( |
| phoneAccountHandle); |
| return phoneAccount != null && phoneAccount.isSelfManaged(); |
| } |
| return false; |
| } |
| |
| private boolean canCallPhone(String callingPackage, String message) { |
| return canCallPhone(callingPackage, null /* featureId */, message); |
| } |
| |
| private boolean canCallPhone(String callingPackage, String callingFeatureId, String message) { |
| // The system/default dialer can always read phone state - so that emergency calls will |
| // still work. |
| if (isPrivilegedDialerCalling(callingPackage)) { |
| return true; |
| } |
| |
| if (mContext.checkCallingOrSelfPermission(CALL_PRIVILEGED) |
| == PackageManager.PERMISSION_GRANTED) { |
| 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.OPSTR_CALL_PHONE, Binder.getCallingUid(), |
| callingPackage, callingFeatureId, message) |
| == AppOpsManager.MODE_ALLOWED; |
| } |
| |
| private boolean canGetPhoneAccount(String callingPackage, PhoneAccountHandle accountHandle) { |
| // Allow default dialer, system dialer and sim call manager to be able to do this without |
| // extra permission |
| try { |
| if (isPrivilegedDialerCalling(callingPackage) || isCallerSimCallManager( |
| accountHandle)) { |
| return true; |
| } |
| } catch (SecurityException e) { |
| // ignore |
| } |
| |
| try { |
| mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, null); |
| return true; |
| } catch (SecurityException e) { |
| // Accessing phone state is gated by a special permission. |
| mContext.enforceCallingOrSelfPermission(READ_PHONE_NUMBERS, null); |
| return true; |
| } |
| } |
| |
| private boolean isCallerSimCallManager(PhoneAccountHandle targetPhoneAccount) { |
| long token = Binder.clearCallingIdentity(); |
| PhoneAccountHandle accountHandle = null; |
| try { |
| accountHandle = mPhoneAccountRegistrar.getSimCallManagerFromHandle(targetPhoneAccount, |
| mCallsManager.getCurrentUserHandle()); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| |
| if (accountHandle != null) { |
| try { |
| mAppOpsManager.checkPackage( |
| Binder.getCallingUid(), accountHandle.getComponentName().getPackageName()); |
| return true; |
| } catch (SecurityException e) { |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Similar to {@link #isCallerSimCallManager}, but works for all SIMs and does not require |
| * {@code accountHandle} to be registered yet. |
| */ |
| private boolean isCallerSimCallManagerForAnySim(PhoneAccountHandle accountHandle) { |
| if (isCallerSimCallManager(accountHandle)) { |
| // The caller has already registered a CONNECTION_MANAGER PhoneAccount, so let them pass |
| // (this allows the SIM call manager through in case of SIM switches, where carrier |
| // config may be in a transient state) |
| return true; |
| } |
| // If the caller isn't already registered, then we have to look at the active PSTN |
| // PhoneAccounts and check their carrier configs to see if any point to this one's component |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| return !mPhoneAccountRegistrar |
| .getSimPhoneAccountsFromSimCallManager(accountHandle) |
| .isEmpty(); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| private boolean isPrivilegedDialerCalling(String callingPackage) { |
| mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage); |
| |
| // Note: Important to clear the calling identity since the code below calls into RoleManager |
| // to check who holds the dialer role, and that requires MANAGE_ROLE_HOLDERS permission |
| // which is a system permission. |
| int callingUserId = Binder.getCallingUserHandle().getIdentifier(); |
| long token = Binder.clearCallingIdentity(); |
| try { |
| return mDefaultDialerCache.isDefaultOrSystemDialer( |
| callingPackage, callingUserId); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| private TelephonyManager getTelephonyManager(int subId) { |
| return ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)) |
| .createForSubscriptionId(subId); |
| } |
| |
| /** |
| * Determines if a video state is valid for accepting an incoming call. |
| * For the purpose of accepting a call, states {@link VideoProfile#STATE_AUDIO_ONLY}, and |
| * any combination of {@link VideoProfile#STATE_RX_ENABLED} and |
| * {@link VideoProfile#STATE_TX_ENABLED} are considered valid. |
| * |
| * @param videoState The video state. |
| * @return {@code true} if the video state is valid, {@code false} otherwise. |
| */ |
| private boolean isValidAcceptVideoState(int videoState) { |
| // Given a video state input, turn off TX and RX so that we can determine if those were the |
| // only bits set. |
| int remainingState = videoState & ~VideoProfile.STATE_TX_ENABLED; |
| remainingState = remainingState & ~VideoProfile.STATE_RX_ENABLED; |
| |
| // If only TX or RX were set (or neither), the video state is valid. |
| return remainingState == 0; |
| } |
| |
| private void broadcastCallScreeningAppChangedIntent(String componentName, |
| boolean isDefault) { |
| if (TextUtils.isEmpty(componentName)) { |
| return; |
| } |
| |
| ComponentName broadcastComponentName = ComponentName.unflattenFromString(componentName); |
| |
| if (broadcastComponentName != null) { |
| Intent intent = new Intent(TelecomManager |
| .ACTION_DEFAULT_CALL_SCREENING_APP_CHANGED); |
| intent.putExtra(TelecomManager |
| .EXTRA_IS_DEFAULT_CALL_SCREENING_APP, isDefault); |
| intent.putExtra(TelecomManager |
| .EXTRA_DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME, componentName); |
| intent.setPackage(broadcastComponentName.getPackageName()); |
| mContext.sendBroadcast(intent); |
| } |
| } |
| |
| private void validateAccountIconUserBoundary(Icon icon) { |
| // Refer to Icon#getUriString for context. The URI string is invalid for icons of |
| // incompatible types. |
| if (icon != null && (icon.getType() == Icon.TYPE_URI |
| || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { |
| int callingUserId = Binder.getCallingUserHandle().getIdentifier(); |
| int requestingUserId = StatusHints.getUserIdFromAuthority( |
| icon.getUri().getAuthority(), callingUserId); |
| if(callingUserId != requestingUserId) { |
| // If we are transcending the profile boundary, throw an error. |
| throw new IllegalArgumentException("Attempting to register a phone account with" |
| + " an image icon belonging to another user."); |
| } |
| } |
| } |
| |
| private void validateSimultaneousCallingPackageNames(String appPackageName, |
| Set<PhoneAccountHandle> handles) { |
| for (PhoneAccountHandle handle : handles) { |
| ComponentName name = handle.getComponentName(); |
| if (name == null) { |
| throw new IllegalArgumentException("ComponentName is null"); |
| } |
| String restrictionPackageName = name.getPackageName(); |
| if (!appPackageName.equals(restrictionPackageName)) { |
| throw new SecurityException("The package name of the PhoneAccount does not " |
| + "match one or more of the package names set in the simultaneous " |
| + "calling restriction."); |
| } |
| } |
| } |
| |
| private void logEvent(ApiStats.ApiEvent event) { |
| mMetricsController.getApiStats().log(event); |
| } |
| |
| /** |
| * Checks if a given package has registered a broadcast receiver for |
| * TelecomManager.ACTION_CALL_BACK for a specific user. |
| * |
| * @param packageName The package to check. |
| * @param userHandle The user for which to check. |
| * @return {@code true} if the package is relevant, {@code false} otherwise. |
| * @throws IllegalStateException if unable to create the context for the user. |
| */ |
| private boolean doesPackageSupportCallback(String packageName, UserHandle userHandle) { |
| Context userContext = mContext.createContextAsUser(userHandle, 0 /* flags */); |
| PackageManager packageManager = userContext.getPackageManager(); |
| Intent checkIntent = new Intent(TelecomManager.ACTION_CALL_BACK); |
| checkIntent.setPackage(packageName); |
| // Check if the package supports the callback |
| List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(checkIntent, |
| PackageManager.MATCH_ALL); |
| return !resolveInfoList.isEmpty(); |
| } |
| |
| public interface SubscriptionManagerAdapter { |
| int getDefaultVoiceSubId(); |
| } |
| |
| static class SubscriptionManagerAdapterImpl implements SubscriptionManagerAdapter { |
| @Override |
| public int getDefaultVoiceSubId() { |
| return SubscriptionManager.getDefaultVoiceSubscriptionId(); |
| } |
| } |
| |
| } |