| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.telecom; |
| |
| import static android.Manifest.permission.CALL_PHONE; |
| import static android.Manifest.permission.MODIFY_PHONE_STATE; |
| import static android.Manifest.permission.READ_PHONE_STATE; |
| import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE; |
| import static android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION; |
| import static android.Manifest.permission.WRITE_SECURE_SETTINGS; |
| |
| import android.app.ActivityManager; |
| import android.app.AppOpsManager; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.res.Resources; |
| import android.net.Uri; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.telecom.DefaultDialerManager; |
| import android.telecom.PhoneAccount; |
| import android.telecom.PhoneAccountHandle; |
| import android.telecom.TelecomManager; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyManager; |
| import android.text.TextUtils; |
| import android.util.EventLog; |
| |
| // TODO: Needed for move to system service: import com.android.internal.R; |
| import com.android.internal.telecom.ITelecomService; |
| import com.android.internal.util.IndentingPrintWriter; |
| import com.android.server.telecom.components.UserCallIntentProcessor; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * Implementation of the ITelecom interface. |
| */ |
| public class TelecomServiceImpl { |
| private static final String PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION = |
| "android.permission.PROCESS_PHONE_ACCOUNT_REGISTRATION"; |
| |
| private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() { |
| @Override |
| public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme, |
| String callingPackage) { |
| synchronized (mLock) { |
| if (!canReadPhoneState(callingPackage, "getDefaultOutgoingPhoneAccount")) { |
| return null; |
| } |
| |
| long token = Binder.clearCallingIdentity(); |
| try { |
| PhoneAccountHandle defaultOutgoingPhoneAccount = |
| mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(uriScheme); |
| // Make sure that the calling user can see this phone account. |
| // TODO: Does this isVisible check actually work considering we are clearing |
| // the calling identity? |
| if (defaultOutgoingPhoneAccount != null |
| && !isVisibleToCaller(defaultOutgoingPhoneAccount)) { |
| Log.w(this, "No account found for the calling user"); |
| return null; |
| } |
| return defaultOutgoingPhoneAccount; |
| } catch (Exception e) { |
| Log.e(this, e, "getDefaultOutgoingPhoneAccount"); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| |
| @Override |
| public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() { |
| synchronized (mLock) { |
| try { |
| PhoneAccountHandle userSelectedOutgoingPhoneAccount = |
| mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount(); |
| // Make sure that the calling user can see this phone account. |
| if (!isVisibleToCaller(userSelectedOutgoingPhoneAccount)) { |
| Log.w(this, "No account found for the calling user"); |
| return null; |
| } |
| return userSelectedOutgoingPhoneAccount; |
| } catch (Exception e) { |
| Log.e(this, e, "getUserSelectedOutgoingPhoneAccount"); |
| throw e; |
| } |
| } |
| } |
| |
| @Override |
| public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) { |
| synchronized (mLock) { |
| enforceModifyPermission(); |
| |
| long token = Binder.clearCallingIdentity(); |
| try { |
| mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(accountHandle); |
| } catch (Exception e) { |
| Log.e(this, e, "setUserSelectedOutgoingPhoneAccount"); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| |
| @Override |
| public List<PhoneAccountHandle> getCallCapablePhoneAccounts( |
| boolean includeDisabledAccounts, String callingPackage) { |
| if (!canReadPhoneState(callingPackage, "getDefaultOutgoingPhoneAccount")) { |
| return Collections.emptyList(); |
| } |
| |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| // TODO: Does this isVisible check actually work considering we are clearing |
| // the calling identity? |
| return filterForAccountsVisibleToCaller( |
| mPhoneAccountRegistrar.getCallCapablePhoneAccounts( |
| null, includeDisabledAccounts)); |
| } catch (Exception e) { |
| Log.e(this, e, "getCallCapablePhoneAccounts"); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| |
| @Override |
| public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme, |
| String callingPackage) { |
| synchronized (mLock) { |
| if (!canReadPhoneState(callingPackage, "getPhoneAccountsSupportingScheme")) { |
| return Collections.emptyList(); |
| } |
| |
| long token = Binder.clearCallingIdentity(); |
| try { |
| // TODO: Does this isVisible check actually work considering we are clearing |
| // the calling identity? |
| return filterForAccountsVisibleToCaller( |
| mPhoneAccountRegistrar.getCallCapablePhoneAccounts(uriScheme, false)); |
| } catch (Exception e) { |
| Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| |
| @Override |
| public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName) { |
| synchronized (mLock) { |
| try { |
| return filterForAccountsVisibleToCaller( |
| mPhoneAccountRegistrar.getPhoneAccountsForPackage(packageName)); |
| } catch (Exception e) { |
| Log.e(this, e, "getPhoneAccountsForPackage %s", packageName); |
| throw e; |
| } |
| } |
| } |
| |
| @Override |
| public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) { |
| synchronized (mLock) { |
| try { |
| if (!isVisibleToCaller(accountHandle)) { |
| Log.d(this, "%s is not visible for the calling user [gPA]", accountHandle); |
| return null; |
| } |
| // TODO: Do we really want to return for *any* user? |
| return mPhoneAccountRegistrar.getPhoneAccount(accountHandle); |
| } catch (Exception e) { |
| Log.e(this, e, "getPhoneAccount %s", accountHandle); |
| throw e; |
| } |
| } |
| } |
| |
| @Override |
| public int getAllPhoneAccountsCount() { |
| synchronized (mLock) { |
| try { |
| // This list is pre-filtered for the calling user. |
| return getAllPhoneAccounts().size(); |
| } catch (Exception e) { |
| Log.e(this, e, "getAllPhoneAccountsCount"); |
| throw e; |
| } |
| } |
| } |
| |
| @Override |
| public List<PhoneAccount> getAllPhoneAccounts() { |
| synchronized (mLock) { |
| try { |
| List<PhoneAccount> allPhoneAccounts = mPhoneAccountRegistrar |
| .getAllPhoneAccounts(); |
| List<PhoneAccount> profilePhoneAccounts = new ArrayList<>( |
| allPhoneAccounts.size()); |
| for (PhoneAccount phoneAccount : allPhoneAccounts) { |
| if (isVisibleToCaller(phoneAccount)) { |
| profilePhoneAccounts.add(phoneAccount); |
| } |
| } |
| return profilePhoneAccounts; |
| } catch (Exception e) { |
| Log.e(this, e, "getAllPhoneAccounts"); |
| throw e; |
| } |
| } |
| } |
| |
| @Override |
| public List<PhoneAccountHandle> getAllPhoneAccountHandles() { |
| synchronized (mLock) { |
| try { |
| return filterForAccountsVisibleToCaller( |
| mPhoneAccountRegistrar.getAllPhoneAccountHandles()); |
| } catch (Exception e) { |
| Log.e(this, e, "getAllPhoneAccounts"); |
| throw e; |
| } |
| } |
| } |
| |
| @Override |
| public PhoneAccountHandle getSimCallManager() { |
| long token = Binder.clearCallingIdentity(); |
| int user; |
| try { |
| user = ActivityManager.getCurrentUser(); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| return getSimCallManagerForUser(user); |
| } |
| |
| @Override |
| public PhoneAccountHandle getSimCallManagerForUser(int user) { |
| synchronized (mLock) { |
| try { |
| PhoneAccountHandle accountHandle = null; |
| |
| long token = Binder.clearCallingIdentity(); |
| try { |
| accountHandle = mPhoneAccountRegistrar.getSimCallManager(user); |
| } finally { |
| // We restore early so that isVisibleToCaller invocation below uses the |
| // right user context. |
| Binder.restoreCallingIdentity(token); |
| } |
| |
| if (!isVisibleToCaller(accountHandle)) { |
| Log.d(this, "%s is not visible for the calling user [gsCM]", accountHandle); |
| return null; |
| } |
| return accountHandle; |
| } catch (Exception e) { |
| Log.e(this, e, "getSimCallManager"); |
| throw e; |
| } |
| } |
| } |
| |
| @Override |
| public void registerPhoneAccount(PhoneAccount account) { |
| synchronized (mLock) { |
| if (!mContext.getApplicationContext().getResources().getBoolean( |
| com.android.internal.R.bool.config_voice_capable)) { |
| Log.w(this, "registerPhoneAccount not allowed on non-voice capable device."); |
| return; |
| } |
| try { |
| enforcePhoneAccountModificationForPackage( |
| account.getAccountHandle().getComponentName().getPackageName()); |
| if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { |
| enforceRegisterSimSubscriptionPermission(); |
| } |
| if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { |
| enforceRegisterMultiUser(); |
| } |
| enforceUserHandleMatchesCaller(account.getAccountHandle()); |
| |
| mPhoneAccountRegistrar.registerPhoneAccount(account); |
| |
| // Broadcast an intent indicating the phone account which was registered. |
| long token = Binder.clearCallingIdentity(); |
| try { |
| Intent intent = new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED); |
| intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, |
| account.getAccountHandle()); |
| Log.i(this, "Sending phone-account intent as user"); |
| mContext.sendBroadcastAsUser(intent, UserHandle.ALL, |
| PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } catch (Exception e) { |
| Log.e(this, e, "registerPhoneAccount %s", account); |
| throw e; |
| } |
| } |
| } |
| |
| @Override |
| public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) { |
| synchronized (mLock) { |
| try { |
| enforcePhoneAccountModificationForPackage( |
| accountHandle.getComponentName().getPackageName()); |
| enforceUserHandleMatchesCaller(accountHandle); |
| mPhoneAccountRegistrar.unregisterPhoneAccount(accountHandle); |
| } catch (Exception e) { |
| Log.e(this, e, "unregisterPhoneAccount %s", accountHandle); |
| throw e; |
| } |
| } |
| } |
| |
| @Override |
| public void clearAccounts(String packageName) { |
| synchronized (mLock) { |
| try { |
| enforcePhoneAccountModificationForPackage(packageName); |
| mPhoneAccountRegistrar |
| .clearAccounts(packageName, Binder.getCallingUserHandle()); |
| } catch (Exception e) { |
| Log.e(this, e, "clearAccounts %s", packageName); |
| throw e; |
| } |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#isVoiceMailNumber |
| */ |
| @Override |
| public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number, |
| String callingPackage) { |
| synchronized (mLock) { |
| if (!canReadPhoneState(callingPackage, "isVoiceMailNumber")) { |
| return false; |
| } |
| |
| if (!isVisibleToCaller(accountHandle)) { |
| Log.d(this, "%s is not visible for the calling user [iVMN]", accountHandle); |
| return false; |
| } |
| |
| long token = Binder.clearCallingIdentity(); |
| try { |
| return mPhoneAccountRegistrar.isVoiceMailNumber(accountHandle, number); |
| } catch (Exception e) { |
| Log.e(this, e, "getSubscriptionIdForPhoneAccount"); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#getVoiceMailNumber |
| */ |
| @Override |
| public String getVoiceMailNumber(PhoneAccountHandle accountHandle, String callingPackage) { |
| synchronized (mLock) { |
| if (!canReadPhoneState(callingPackage, "getVoiceMailNumber")) { |
| return null; |
| } |
| |
| try { |
| if (!isVisibleToCaller(accountHandle)) { |
| Log.d(this, "%s is not visible for the calling user [gVMN]", accountHandle); |
| return null; |
| } |
| |
| int subId = SubscriptionManager.getDefaultVoiceSubId(); |
| if (accountHandle != null) { |
| subId = mPhoneAccountRegistrar |
| .getSubscriptionIdForPhoneAccount(accountHandle); |
| } |
| return getTelephonyManager().getVoiceMailNumber(subId); |
| } catch (Exception e) { |
| Log.e(this, e, "getSubscriptionIdForPhoneAccount"); |
| throw e; |
| } |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#getLine1Number |
| */ |
| @Override |
| public String getLine1Number(PhoneAccountHandle accountHandle, String callingPackage) { |
| if (!canReadPhoneState(callingPackage, "getLine1Number")) { |
| return null; |
| } |
| |
| synchronized (mLock) { |
| if (!isVisibleToCaller(accountHandle)) { |
| Log.d(this, "%s is not visible for the calling user [gL1N]", accountHandle); |
| return null; |
| } |
| |
| long token = Binder.clearCallingIdentity(); |
| try { |
| int subId = |
| mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle); |
| return getTelephonyManager().getLine1NumberForSubscriber(subId); |
| } catch (Exception e) { |
| Log.e(this, e, "getSubscriptionIdForPhoneAccount"); |
| throw e; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#silenceRinger |
| */ |
| @Override |
| public void silenceRinger(String callingPackage) { |
| synchronized (mLock) { |
| enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage); |
| |
| long token = Binder.clearCallingIdentity(); |
| try { |
| Log.i(this, "Silence Ringer requested by %s", callingPackage); |
| mCallsManager.getRinger().silence(); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#getDefaultPhoneApp |
| * @deprecated - Use {@link android.telecom.TelecomManager#getDefaultDialerPackage()} |
| * instead. |
| */ |
| @Override |
| public ComponentName getDefaultPhoneApp() { |
| // No need to synchronize |
| Resources resources = mContext.getResources(); |
| return new ComponentName( |
| resources.getString(R.string.ui_default_package), |
| resources.getString(R.string.dialer_default_class)); |
| } |
| |
| /** |
| * @return the package name of the current user-selected default dialer. If no default |
| * has been selected, the package name of the system dialer is returned. If |
| * neither exists, then {@code null} is returned. |
| * @see android.telecom.TelecomManager#getDefaultDialerPackage |
| */ |
| @Override |
| public String getDefaultDialerPackage() { |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| return DefaultDialerManager.getDefaultDialerApplication(mContext); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#getSystemDialerPackage |
| */ |
| @Override |
| public String getSystemDialerPackage() { |
| return mContext.getResources().getString(R.string.ui_default_package); |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#isInCall |
| */ |
| @Override |
| public boolean isInCall(String callingPackage) { |
| if (!canReadPhoneState(callingPackage, "isInCall")) { |
| return false; |
| } |
| |
| synchronized (mLock) { |
| final int callState = mCallsManager.getCallState(); |
| return callState == TelephonyManager.CALL_STATE_OFFHOOK |
| || callState == TelephonyManager.CALL_STATE_RINGING; |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#isRinging |
| */ |
| @Override |
| public boolean isRinging(String callingPackage) { |
| if (!canReadPhoneState(callingPackage, "isRinging")) { |
| return false; |
| } |
| |
| synchronized (mLock) { |
| return mCallsManager.getCallState() == TelephonyManager.CALL_STATE_RINGING; |
| } |
| } |
| |
| /** |
| * @see TelecomManager#getCallState |
| */ |
| @Override |
| public int getCallState() { |
| synchronized (mLock) { |
| return mCallsManager.getCallState(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#endCall |
| */ |
| @Override |
| public boolean endCall() { |
| synchronized (mLock) { |
| enforceModifyPermission(); |
| |
| long token = Binder.clearCallingIdentity(); |
| try { |
| return endCallInternal(); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#acceptRingingCall |
| */ |
| @Override |
| public void acceptRingingCall() { |
| synchronized (mLock) { |
| enforceModifyPermission(); |
| |
| long token = Binder.clearCallingIdentity(); |
| try { |
| acceptRingingCallInternal(); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#showInCallScreen |
| */ |
| @Override |
| public void showInCallScreen(boolean showDialpad, String callingPackage) { |
| if (!canReadPhoneState(callingPackage, "showInCallScreen")) { |
| return; |
| } |
| |
| synchronized (mLock) { |
| |
| long token = Binder.clearCallingIdentity(); |
| try { |
| mCallsManager.getInCallController().bringToForeground(showDialpad); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#cancelMissedCallsNotification |
| */ |
| @Override |
| public void cancelMissedCallsNotification(String callingPackage) { |
| synchronized (mLock) { |
| enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage); |
| long token = Binder.clearCallingIdentity(); |
| try { |
| mCallsManager.getMissedCallNotifier().clearMissedCalls(); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#handleMmi |
| */ |
| @Override |
| public boolean handlePinMmi(String dialString, String callingPackage) { |
| synchronized (mLock) { |
| enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage); |
| |
| // Switch identity so that TelephonyManager checks Telecom's permissions instead. |
| long token = Binder.clearCallingIdentity(); |
| boolean retval = false; |
| try { |
| retval = getTelephonyManager().handlePinMmi(dialString); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| |
| return retval; |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#handleMmi |
| */ |
| @Override |
| public boolean handlePinMmiForPhoneAccount( |
| PhoneAccountHandle accountHandle, |
| String dialString, |
| String callingPackage) { |
| synchronized (mLock) { |
| enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage); |
| |
| if (!isVisibleToCaller(accountHandle)) { |
| Log.d(this, "%s is not visible for the calling user [hMMI]", accountHandle); |
| return false; |
| } |
| |
| // Switch identity so that TelephonyManager checks Telecom's permissions instead. |
| long token = Binder.clearCallingIdentity(); |
| boolean retval = false; |
| try { |
| int subId = mPhoneAccountRegistrar |
| .getSubscriptionIdForPhoneAccount(accountHandle); |
| retval = getTelephonyManager().handlePinMmiForSubscriber(subId, dialString); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| |
| return retval; |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#getAdnUriForPhoneAccount |
| */ |
| @Override |
| public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle, |
| String callingPackage) { |
| synchronized (mLock) { |
| enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage); |
| |
| if (!isVisibleToCaller(accountHandle)) { |
| Log.d(this, "%s is not visible for the calling user [gA4PA]", accountHandle); |
| return null; |
| } |
| |
| // Switch identity so that TelephonyManager checks Telecom's permissions instead. |
| long token = Binder.clearCallingIdentity(); |
| String retval = "content://icc/adn/"; |
| try { |
| long subId = mPhoneAccountRegistrar |
| .getSubscriptionIdForPhoneAccount(accountHandle); |
| retval = retval + "subId/" + subId; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| |
| return Uri.parse(retval); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#isTtySupported |
| */ |
| @Override |
| public boolean isTtySupported(String callingPackage) { |
| if (!canReadPhoneState(callingPackage, "hasVoiceMailNumber")) { |
| return false; |
| } |
| |
| synchronized (mLock) { |
| return mCallsManager.isTtySupported(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#getCurrentTtyMode |
| */ |
| @Override |
| public int getCurrentTtyMode(String callingPackage) { |
| if (!canReadPhoneState(callingPackage, "getCurrentTtyMode")) { |
| return TelecomManager.TTY_MODE_OFF; |
| } |
| |
| synchronized (mLock) { |
| return mCallsManager.getCurrentTtyMode(); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#getActiveSubscription |
| */ |
| public int getActiveSubscription() { |
| synchronized (mLock) { |
| String activeSub = mCallsManager.getActiveSubscription(); |
| return (activeSub == null) ? SubscriptionManager.INVALID_SUBSCRIPTION_ID: |
| Integer.parseInt(activeSub); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#switchToOtherActiveSub |
| */ |
| public void switchToOtherActiveSub(int subId) { |
| enforceModifyPermission(); |
| |
| synchronized (mLock) { |
| String activeSub = (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) |
| ? null : String.valueOf(subId); |
| mCallsManager.switchToOtherActiveSub(activeSub); |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#addNewIncomingCall |
| */ |
| @Override |
| public void addNewIncomingCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) { |
| synchronized (mLock) { |
| Log.i(this, "Adding new incoming call with phoneAccountHandle %s", |
| phoneAccountHandle); |
| if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null) { |
| // TODO(sail): Add unit tests for adding incoming calls from a SIM call manager. |
| if (isCallerSimCallManager() && TelephonyUtil.isPstnComponentName( |
| phoneAccountHandle.getComponentName())) { |
| Log.v(this, "Allowing call manager to add incoming call with PSTN handle"); |
| } else { |
| mAppOpsManager.checkPackage( |
| Binder.getCallingUid(), |
| phoneAccountHandle.getComponentName().getPackageName()); |
| // Make sure it doesn't cross the UserHandle boundary |
| enforceUserHandleMatchesCaller(phoneAccountHandle); |
| enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle); |
| } |
| |
| long token = Binder.clearCallingIdentity(); |
| try { |
| Intent intent = new Intent(TelecomManager.ACTION_INCOMING_CALL); |
| intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, |
| phoneAccountHandle); |
| intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, true); |
| if (extras != null) { |
| intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras); |
| } |
| CallIntentProcessor.processIncomingCallIntent(mCallsManager, intent); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } else { |
| Log.w(this, |
| "Null phoneAccountHandle. Ignoring request to add new incoming call"); |
| } |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#addNewUnknownCall |
| */ |
| @Override |
| public void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) { |
| synchronized (mLock) { |
| if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null) { |
| mAppOpsManager.checkPackage( |
| Binder.getCallingUid(), |
| phoneAccountHandle.getComponentName().getPackageName()); |
| |
| // Make sure it doesn't cross the UserHandle boundary |
| enforceUserHandleMatchesCaller(phoneAccountHandle); |
| enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle); |
| long token = Binder.clearCallingIdentity(); |
| |
| try { |
| Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL); |
| intent.putExtras(extras); |
| intent.putExtra(CallIntentProcessor.KEY_IS_UNKNOWN_CALL, true); |
| intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, |
| phoneAccountHandle); |
| CallIntentProcessor.processUnknownCallIntent(mCallsManager, intent); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } else { |
| Log.i(this, |
| "Null phoneAccountHandle or not initiated by Telephony. " + |
| "Ignoring request to add new unknown call."); |
| } |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#placeCall |
| */ |
| @Override |
| public void placeCall(Uri handle, Bundle extras, String callingPackage) { |
| enforceCallingPackage(callingPackage); |
| if (!canCallPhone(callingPackage, "placeCall")) { |
| throw new SecurityException("Package " + callingPackage |
| + " is not allowed to place phone calls"); |
| } |
| |
| // Note: we can still get here for the default/system dialer, even if the Phone |
| // permission is turned off. This is because the default/system dialer is always |
| // allowed to attempt to place a call (regardless of permission state), in case |
| // it turns out to be an emergency call. If the permission is denied and the |
| // call is being made to a non-emergency number, the call will be denied later on |
| // by {@link UserCallIntentProcessor}. |
| |
| final boolean hasCallAppOp = mAppOpsManager.noteOp(AppOpsManager.OP_CALL_PHONE, |
| Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED; |
| |
| final boolean hasCallPermission = mContext.checkCallingPermission(CALL_PHONE) == |
| PackageManager.PERMISSION_GRANTED; |
| |
| synchronized (mLock) { |
| final UserHandle userHandle = Binder.getCallingUserHandle(); |
| long token = Binder.clearCallingIdentity(); |
| try { |
| final Intent intent = new Intent(Intent.ACTION_CALL, handle); |
| intent.putExtras(extras); |
| new UserCallIntentProcessor(mContext, userHandle).processIntent(intent, |
| callingPackage, hasCallAppOp && hasCallPermission); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| |
| /** |
| * @see android.telecom.TelecomManager#enablePhoneAccount |
| */ |
| @Override |
| public boolean enablePhoneAccount(PhoneAccountHandle accountHandle, boolean isEnabled) { |
| enforceModifyPermission(); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| // enable/disable phone account |
| return mPhoneAccountRegistrar.enablePhoneAccount(accountHandle, isEnabled); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| |
| @Override |
| public boolean setDefaultDialer(String packageName) { |
| enforcePermission(MODIFY_PHONE_STATE); |
| enforcePermission(WRITE_SECURE_SETTINGS); |
| synchronized (mLock) { |
| long token = Binder.clearCallingIdentity(); |
| try { |
| final boolean result = |
| DefaultDialerManager.setDefaultDialerApplication(mContext, packageName); |
| if (result) { |
| final Intent intent = |
| new Intent(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED); |
| intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, |
| packageName); |
| mContext.sendBroadcastAsUser(intent, |
| new UserHandle(ActivityManager.getCurrentUser())); |
| } |
| return result; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |
| |
| /** |
| * Dumps the current state of the TelecomService. Used when generating problem reports. |
| * |
| * @param fd The file descriptor. |
| * @param writer The print writer to dump the state to. |
| * @param args Optional dump arguments. |
| */ |
| @Override |
| protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { |
| if (mContext.checkCallingOrSelfPermission( |
| android.Manifest.permission.DUMP) |
| != PackageManager.PERMISSION_GRANTED) { |
| writer.println("Permission Denial: can't dump TelecomService " + |
| "from from pid=" + Binder.getCallingPid() + ", uid=" + |
| Binder.getCallingUid()); |
| return; |
| } |
| |
| final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); |
| if (mCallsManager != null) { |
| pw.println("CallsManager: "); |
| pw.increaseIndent(); |
| mCallsManager.dump(pw); |
| pw.decreaseIndent(); |
| |
| pw.println("PhoneAccountRegistrar: "); |
| pw.increaseIndent(); |
| mPhoneAccountRegistrar.dump(pw); |
| pw.decreaseIndent(); |
| } |
| |
| Log.dumpCallEvents(pw); |
| } |
| }; |
| |
| private Context mContext; |
| private AppOpsManager mAppOpsManager; |
| private UserManager mUserManager; |
| private PackageManager mPackageManager; |
| private CallsManager mCallsManager; |
| private final PhoneAccountRegistrar mPhoneAccountRegistrar; |
| private final TelecomSystem.SyncRoot mLock; |
| |
| public TelecomServiceImpl( |
| Context context, |
| CallsManager callsManager, |
| PhoneAccountRegistrar phoneAccountRegistrar, |
| TelecomSystem.SyncRoot lock) { |
| mContext = context; |
| mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); |
| |
| mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); |
| mPackageManager = mContext.getPackageManager(); |
| |
| mCallsManager = callsManager; |
| mLock = lock; |
| mPhoneAccountRegistrar = phoneAccountRegistrar; |
| } |
| |
| public ITelecomService.Stub getBinder() { |
| return mBinderImpl; |
| } |
| |
| // |
| // Supporting methods for the ITelecomService interface implementation. |
| // |
| |
| private boolean isVisibleToCaller(PhoneAccountHandle accountHandle) { |
| if (accountHandle == null) { |
| return false; |
| } |
| return isVisibleToCaller(mPhoneAccountRegistrar.getPhoneAccount(accountHandle)); |
| } |
| |
| private boolean isVisibleToCaller(PhoneAccount account) { |
| if (account == null) { |
| return false; |
| } |
| |
| // If this PhoneAccount has CAPABILITY_MULTI_USER, it should be visible to all users and |
| // all profiles. Only Telephony and SIP accounts should have this capability. |
| if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { |
| return true; |
| } |
| |
| UserHandle phoneAccountUserHandle = account.getAccountHandle().getUserHandle(); |
| if (phoneAccountUserHandle == null) { |
| return false; |
| } |
| |
| if (phoneAccountUserHandle.equals(Binder.getCallingUserHandle())) { |
| return true; |
| } |
| |
| List<UserHandle> profileUserHandles; |
| if (UserHandle.getCallingUserId() == UserHandle.USER_OWNER) { |
| profileUserHandles = mUserManager.getUserProfiles(); |
| } else { |
| // Otherwise, it has to be owned by the current caller's profile. |
| profileUserHandles = new ArrayList<>(1); |
| profileUserHandles.add(Binder.getCallingUserHandle()); |
| } |
| |
| return profileUserHandles.contains(phoneAccountUserHandle); |
| } |
| |
| /** |
| * Given a list of {@link PhoneAccountHandle}s, filter them to the ones that the calling |
| * user can see. |
| * |
| * @param phoneAccountHandles Unfiltered list of account handles. |
| * |
| * @return {@link PhoneAccountHandle}s visible to the calling user and its profiles. |
| */ |
| private List<PhoneAccountHandle> filterForAccountsVisibleToCaller( |
| List<PhoneAccountHandle> phoneAccountHandles) { |
| List<PhoneAccountHandle> profilePhoneAccountHandles = |
| new ArrayList<>(phoneAccountHandles.size()); |
| for (PhoneAccountHandle phoneAccountHandle : phoneAccountHandles) { |
| if (isVisibleToCaller(phoneAccountHandle)) { |
| profilePhoneAccountHandles.add(phoneAccountHandle); |
| } |
| } |
| return profilePhoneAccountHandles; |
| } |
| |
| private boolean isCallerSystemApp() { |
| int uid = Binder.getCallingUid(); |
| String[] packages = mPackageManager.getPackagesForUid(uid); |
| for (String packageName : packages) { |
| if (isPackageSystemApp(packageName)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean isPackageSystemApp(String packageName) { |
| try { |
| ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(packageName, |
| PackageManager.GET_META_DATA); |
| if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { |
| return true; |
| } |
| } catch (PackageManager.NameNotFoundException e) { |
| } |
| return false; |
| } |
| |
| private void acceptRingingCallInternal() { |
| Call call = mCallsManager.getFirstCallWithState(CallState.RINGING); |
| if (call != null) { |
| call.answer(call.getVideoState()); |
| } |
| } |
| |
| private boolean endCallInternal() { |
| // Always operate on the foreground call if one exists, otherwise get the first call in |
| // priority order by call-state. |
| Call call = mCallsManager.getForegroundCall(); |
| if (call == null) { |
| call = mCallsManager.getFirstCallWithState( |
| CallState.ACTIVE, |
| CallState.DIALING, |
| CallState.RINGING, |
| CallState.ON_HOLD); |
| } |
| |
| if (call != null) { |
| if (call.getState() == CallState.RINGING) { |
| call.reject(false /* rejectWithMessage */, null); |
| } else { |
| call.disconnect(); |
| } |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // Enforce that the PhoneAccountHandle being passed in is both registered to the current user |
| // and enabled. |
| private void enforcePhoneAccountIsRegisteredEnabled(PhoneAccountHandle phoneAccountHandle) { |
| PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountCheckCallingUser( |
| phoneAccountHandle); |
| if (phoneAccount == null) { |
| EventLog.writeEvent(0x534e4554, "26864502", Binder.getCallingUid(), "R"); |
| throw new SecurityException("This PhoneAccountHandle is not registered for this user!"); |
| } |
| if (!phoneAccount.isEnabled()) { |
| EventLog.writeEvent(0x534e4554, "26864502", Binder.getCallingUid(), "E"); |
| throw new SecurityException("This PhoneAccountHandle is not enabled for this user!"); |
| } |
| } |
| |
| private void enforcePhoneAccountModificationForPackage(String packageName) { |
| // TODO: Use a new telecomm permission for this instead of reusing modify. |
| |
| int result = mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE); |
| |
| // Callers with MODIFY_PHONE_STATE can use the PhoneAccount mechanism to implement |
| // built-in behavior even when PhoneAccounts are not exposed as a third-part API. They |
| // may also modify PhoneAccounts on behalf of any 'packageName'. |
| |
| if (result != PackageManager.PERMISSION_GRANTED) { |
| // Other callers are only allowed to modify PhoneAccounts if the relevant system |
| // feature is enabled ... |
| enforceConnectionServiceFeature(); |
| // ... and the PhoneAccounts they refer to are for their own package. |
| enforceCallingPackage(packageName); |
| } |
| } |
| |
| private void enforcePermissionOrPrivilegedDialer(String permission, String packageName) { |
| if (!isPrivilegedDialerCalling(packageName)) { |
| try { |
| enforcePermission(permission); |
| } catch (SecurityException e) { |
| Log.e(this, e, "Caller must be the default or system dialer, or have the permission" |
| + " %s to perform this operation.", permission); |
| throw e; |
| } |
| } |
| } |
| |
| private void enforceCallingPackage(String packageName) { |
| mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName); |
| } |
| |
| private void enforceConnectionServiceFeature() { |
| enforceFeature(PackageManager.FEATURE_CONNECTION_SERVICE); |
| } |
| |
| private void enforceRegisterSimSubscriptionPermission() { |
| enforcePermission(REGISTER_SIM_SUBSCRIPTION); |
| } |
| |
| private void enforceModifyPermission() { |
| enforcePermission(MODIFY_PHONE_STATE); |
| } |
| |
| private void enforcePermission(String permission) { |
| mContext.enforceCallingOrSelfPermission(permission, null); |
| } |
| |
| private void enforceRegisterMultiUser() { |
| if (!isCallerSystemApp()) { |
| throw new SecurityException("CAPABILITY_MULTI_USER is only available to system apps."); |
| } |
| } |
| |
| private void enforceUserHandleMatchesCaller(PhoneAccountHandle accountHandle) { |
| if (!Binder.getCallingUserHandle().equals(accountHandle.getUserHandle())) { |
| throw new SecurityException("Calling UserHandle does not match PhoneAccountHandle's"); |
| } |
| } |
| |
| private void enforceFeature(String feature) { |
| PackageManager pm = mContext.getPackageManager(); |
| if (!pm.hasSystemFeature(feature)) { |
| throw new UnsupportedOperationException( |
| "System does not support feature " + feature); |
| } |
| } |
| |
| private boolean canReadPhoneState(String callingPackage, String message) { |
| // The system/default dialer can always read phone state - so that emergency calls will |
| // still work. |
| if (isPrivilegedDialerCalling(callingPackage)) { |
| return true; |
| } |
| |
| try { |
| mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, message); |
| // SKIP checking run-time OP_READ_PHONE_STATE since caller or self has PRIVILEGED |
| // permission |
| return true; |
| } catch (SecurityException e) { |
| // Accessing phone state is gated by a special permission. |
| mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, message); |
| |
| // Some apps that have the permission can be restricted via app ops. |
| return mAppOpsManager.noteOp(AppOpsManager.OP_READ_PHONE_STATE, |
| Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED; |
| } |
| } |
| |
| private boolean canCallPhone(String callingPackage, String message) { |
| // The system/default dialer can always read phone state - so that emergency calls will |
| // still work. |
| if (isPrivilegedDialerCalling(callingPackage)) { |
| return true; |
| } |
| |
| // Accessing phone state is gated by a special permission. |
| mContext.enforceCallingOrSelfPermission(CALL_PHONE, message); |
| |
| // Some apps that have the permission can be restricted via app ops. |
| return mAppOpsManager.noteOp(AppOpsManager.OP_CALL_PHONE, |
| Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED; |
| } |
| |
| private boolean isCallerSimCallManager() { |
| PhoneAccountHandle accountHandle = null; |
| long token = Binder.clearCallingIdentity(); |
| try { |
| accountHandle = mPhoneAccountRegistrar.getSimCallManager(); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| |
| if (accountHandle != null) { |
| try { |
| mAppOpsManager.checkPackage( |
| Binder.getCallingUid(), accountHandle.getComponentName().getPackageName()); |
| return true; |
| } catch (SecurityException e) { |
| } |
| } |
| return false; |
| } |
| |
| private boolean isPrivilegedDialerCalling(String callingPackage) { |
| mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage); |
| return DefaultDialerManager.isDefaultOrSystemDialer(mContext, callingPackage); |
| } |
| |
| private TelephonyManager getTelephonyManager() { |
| return (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE); |
| } |
| } |