/* * Copyright (C) 2013 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.internal.telephony; import android.Manifest.permission; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.role.OnRoleHoldersChangedListener; import android.app.role.RoleManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.telephony.TelephonyManager; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.stream.Collectors; /** * Class for managing the primary application that we will deliver SMS/MMS messages to * * {@hide} */ public final class SmsApplication { static final String LOG_TAG = "SmsApplication"; public static final String PHONE_PACKAGE_NAME = "com.android.phone"; public static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth"; public static final String MMS_SERVICE_PACKAGE_NAME = "com.android.mms.service"; public static final String TELEPHONY_PROVIDER_PACKAGE_NAME = "com.android.providers.telephony"; private static final String SCHEME_SMS = "sms"; private static final String SCHEME_SMSTO = "smsto"; private static final String SCHEME_MMS = "mms"; private static final String SCHEME_MMSTO = "mmsto"; private static final boolean DEBUG = false; private static final boolean DEBUG_MULTIUSER = false; private static final String[] DEFAULT_APP_EXCLUSIVE_APPOPS = { AppOpsManager.OPSTR_READ_SMS, AppOpsManager.OPSTR_WRITE_SMS, AppOpsManager.OPSTR_RECEIVE_SMS, AppOpsManager.OPSTR_RECEIVE_WAP_PUSH, AppOpsManager.OPSTR_SEND_SMS, AppOpsManager.OPSTR_READ_CELL_BROADCASTS }; private static SmsPackageMonitor sSmsPackageMonitor = null; private static SmsRoleListener sSmsRoleListener = null; public static class SmsApplicationData { /** * Name of this SMS app for display. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private String mApplicationName; /** * Package name for this SMS app. */ public String mPackageName; /** * The class name of the SMS_DELIVER_ACTION receiver in this app. */ private String mSmsReceiverClass; /** * The class name of the WAP_PUSH_DELIVER_ACTION receiver in this app. */ private String mMmsReceiverClass; /** * The class name of the ACTION_RESPOND_VIA_MESSAGE intent in this app. */ private String mRespondViaMessageClass; /** * The class name of the ACTION_SENDTO intent in this app. */ private String mSendToClass; /** * The class name of the ACTION_DEFAULT_SMS_PACKAGE_CHANGED receiver in this app. */ private String mSmsAppChangedReceiverClass; /** * The class name of the ACTION_EXTERNAL_PROVIDER_CHANGE receiver in this app. */ private String mProviderChangedReceiverClass; /** * The class name of the SIM_FULL_ACTION receiver in this app. */ private String mSimFullReceiverClass; /** * The user-id for this application */ private int mUid; /** * Returns true if this SmsApplicationData is complete (all intents handled). * @return */ public boolean isComplete() { return (mSmsReceiverClass != null && mMmsReceiverClass != null && mRespondViaMessageClass != null && mSendToClass != null); } public SmsApplicationData(String packageName, int uid) { mPackageName = packageName; mUid = uid; } public String getApplicationName(Context context) { if (mApplicationName == null) { PackageManager pm = context.getPackageManager(); ApplicationInfo appInfo; try { appInfo = pm.getApplicationInfoAsUser(mPackageName, 0, UserHandle.getUserHandleForUid(mUid)); } catch (NameNotFoundException e) { return null; } if (appInfo != null) { CharSequence label = pm.getApplicationLabel(appInfo); mApplicationName = (label == null) ? null : label.toString(); } } return mApplicationName; } @Override public String toString() { return " mPackageName: " + mPackageName + " mSmsReceiverClass: " + mSmsReceiverClass + " mMmsReceiverClass: " + mMmsReceiverClass + " mRespondViaMessageClass: " + mRespondViaMessageClass + " mSendToClass: " + mSendToClass + " mSmsAppChangedClass: " + mSmsAppChangedReceiverClass + " mProviderChangedReceiverClass: " + mProviderChangedReceiverClass + " mSimFullReceiverClass: " + mSimFullReceiverClass + " mUid: " + mUid; } } /** * Returns the userId of the Context object, if called from a system app, * otherwise it returns the caller's userId * @param context The context object passed in by the caller. * @return */ private static int getIncomingUserId(Context context) { int contextUserId = UserHandle.myUserId(); final int callingUid = Binder.getCallingUid(); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getIncomingUserHandle caller=" + callingUid + ", myuid=" + android.os.Process.myUid()); } if (UserHandle.getAppId(callingUid) < android.os.Process.FIRST_APPLICATION_UID) { return contextUserId; } else { return UserHandle.getUserHandleForUid(callingUid).getIdentifier(); } } /** * Returns the list of available SMS apps defined as apps that are registered for both the * SMS_RECEIVED_ACTION (SMS) and WAP_PUSH_RECEIVED_ACTION (MMS) broadcasts (and their broadcast * receivers are enabled) * * Requirements to be an SMS application: * Implement SMS_DELIVER_ACTION broadcast receiver. * Require BROADCAST_SMS permission. * * Implement WAP_PUSH_DELIVER_ACTION broadcast receiver. * Require BROADCAST_WAP_PUSH permission. * * Implement RESPOND_VIA_MESSAGE intent. * Support smsto Uri scheme. * Require SEND_RESPOND_VIA_MESSAGE permission. * * Implement ACTION_SENDTO intent. * Support smsto Uri scheme. */ @UnsupportedAppUsage public static Collection getApplicationCollection(Context context) { return getApplicationCollectionAsUser(context, getIncomingUserId(context)); } /** * Same as {@link #getApplicationCollection} but it takes a target user ID. */ public static Collection getApplicationCollectionAsUser(Context context, int userId) { final long token = Binder.clearCallingIdentity(); try { return getApplicationCollectionInternal(context, userId); } finally { Binder.restoreCallingIdentity(token); } } private static Collection getApplicationCollectionInternal( Context context, int userId) { PackageManager packageManager = context.getPackageManager(); UserHandle userHandle = UserHandle.of(userId); // Get the list of apps registered for SMS Intent intent = new Intent(Intents.SMS_DELIVER_ACTION); if (DEBUG) { intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION); } List smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle); HashMap receivers = new HashMap(); // Add one entry to the map for every sms receiver (ignoring duplicate sms receivers) for (ResolveInfo resolveInfo : smsReceivers) { final ActivityInfo activityInfo = resolveInfo.activityInfo; if (activityInfo == null) { continue; } if (!permission.BROADCAST_SMS.equals(activityInfo.permission)) { continue; } final String packageName = activityInfo.packageName; if (!receivers.containsKey(packageName)) { final SmsApplicationData smsApplicationData = new SmsApplicationData(packageName, activityInfo.applicationInfo.uid); smsApplicationData.mSmsReceiverClass = activityInfo.name; receivers.put(packageName, smsApplicationData); } } // Update any existing entries with mms receiver class intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION); intent.setDataAndType(null, "application/vnd.wap.mms-message"); List mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle); for (ResolveInfo resolveInfo : mmsReceivers) { final ActivityInfo activityInfo = resolveInfo.activityInfo; if (activityInfo == null) { continue; } if (!permission.BROADCAST_WAP_PUSH.equals(activityInfo.permission)) { continue; } final String packageName = activityInfo.packageName; final SmsApplicationData smsApplicationData = receivers.get(packageName); if (smsApplicationData != null) { smsApplicationData.mMmsReceiverClass = activityInfo.name; } } // Update any existing entries with respond via message intent class. intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE, Uri.fromParts(SCHEME_SMSTO, "", null)); List respondServices = packageManager.queryIntentServicesAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, UserHandle.of(userId)); for (ResolveInfo resolveInfo : respondServices) { final ServiceInfo serviceInfo = resolveInfo.serviceInfo; if (serviceInfo == null) { continue; } if (!permission.SEND_RESPOND_VIA_MESSAGE.equals(serviceInfo.permission)) { continue; } final String packageName = serviceInfo.packageName; final SmsApplicationData smsApplicationData = receivers.get(packageName); if (smsApplicationData != null) { smsApplicationData.mRespondViaMessageClass = serviceInfo.name; } } // Update any existing entries with supports send to. intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(SCHEME_SMSTO, "", null)); List sendToActivities = packageManager.queryIntentActivitiesAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle); for (ResolveInfo resolveInfo : sendToActivities) { final ActivityInfo activityInfo = resolveInfo.activityInfo; if (activityInfo == null) { continue; } final String packageName = activityInfo.packageName; final SmsApplicationData smsApplicationData = receivers.get(packageName); if (smsApplicationData != null) { smsApplicationData.mSendToClass = activityInfo.name; } } // Update any existing entries with the default sms changed handler. intent = new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED); List smsAppChangedReceivers = packageManager.queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplicationCollectionInternal smsAppChangedActivities=" + smsAppChangedReceivers); } for (ResolveInfo resolveInfo : smsAppChangedReceivers) { final ActivityInfo activityInfo = resolveInfo.activityInfo; if (activityInfo == null) { continue; } final String packageName = activityInfo.packageName; final SmsApplicationData smsApplicationData = receivers.get(packageName); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplicationCollectionInternal packageName=" + packageName + " smsApplicationData: " + smsApplicationData + " activityInfo.name: " + activityInfo.name); } if (smsApplicationData != null) { smsApplicationData.mSmsAppChangedReceiverClass = activityInfo.name; } } // Update any existing entries with the external provider changed handler. intent = new Intent(Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE); List providerChangedReceivers = packageManager.queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplicationCollectionInternal providerChangedActivities=" + providerChangedReceivers); } for (ResolveInfo resolveInfo : providerChangedReceivers) { final ActivityInfo activityInfo = resolveInfo.activityInfo; if (activityInfo == null) { continue; } final String packageName = activityInfo.packageName; final SmsApplicationData smsApplicationData = receivers.get(packageName); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplicationCollectionInternal packageName=" + packageName + " smsApplicationData: " + smsApplicationData + " activityInfo.name: " + activityInfo.name); } if (smsApplicationData != null) { smsApplicationData.mProviderChangedReceiverClass = activityInfo.name; } } // Update any existing entries with the sim full handler. intent = new Intent(Intents.SIM_FULL_ACTION); List simFullReceivers = packageManager.queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplicationCollectionInternal simFullReceivers=" + simFullReceivers); } for (ResolveInfo resolveInfo : simFullReceivers) { final ActivityInfo activityInfo = resolveInfo.activityInfo; if (activityInfo == null) { continue; } final String packageName = activityInfo.packageName; final SmsApplicationData smsApplicationData = receivers.get(packageName); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplicationCollectionInternal packageName=" + packageName + " smsApplicationData: " + smsApplicationData + " activityInfo.name: " + activityInfo.name); } if (smsApplicationData != null) { smsApplicationData.mSimFullReceiverClass = activityInfo.name; } } // Remove any entries for which we did not find all required intents. for (ResolveInfo resolveInfo : smsReceivers) { final ActivityInfo activityInfo = resolveInfo.activityInfo; if (activityInfo == null) { continue; } final String packageName = activityInfo.packageName; final SmsApplicationData smsApplicationData = receivers.get(packageName); if (smsApplicationData != null) { if (!smsApplicationData.isComplete()) { receivers.remove(packageName); } } } return receivers.values(); } /** * Checks to see if we have a valid installed SMS application for the specified package name * @return Data for the specified package name or null if there isn't one */ public static SmsApplicationData getApplicationForPackage( Collection applications, String packageName) { if (packageName == null) { return null; } // Is there an entry in the application list for the specified package? for (SmsApplicationData application : applications) { if (application.mPackageName.contentEquals(packageName)) { return application; } } return null; } /** * Get the application we will use for delivering SMS/MMS messages. * * We return the preferred sms application with the following order of preference: * (1) User selected SMS app (if selected, and if still valid) * (2) Android Messaging (if installed) * (3) The currently configured highest priority broadcast receiver * (4) Null */ private static SmsApplicationData getApplication(Context context, boolean updateIfNeeded, int userId) { TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); RoleManager roleManager = (RoleManager) context.getSystemService(Context.ROLE_SERVICE); // (b/134400042) RoleManager might be null in unit tests running older mockito versions // that do not support mocking final classes. if (!tm.isSmsCapable() && (roleManager == null || !roleManager.isRoleAvailable( RoleManager.ROLE_SMS))) { // No phone, no SMS return null; } Collection applications = getApplicationCollectionInternal(context, userId); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplication userId=" + userId); } // Determine which application receives the broadcast String defaultApplication = getDefaultSmsPackage(context, userId); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplication defaultApp=" + defaultApplication); } SmsApplicationData applicationData = null; if (defaultApplication != null) { applicationData = getApplicationForPackage(applications, defaultApplication); } if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplication appData=" + applicationData); } // If we found a package, make sure AppOps permissions are set up correctly if (applicationData != null) { // We can only call unsafeCheckOp if we are privileged (updateIfNeeded) or if the app we // are checking is for our current uid. Doing this check from the unprivileged current // SMS app allows us to tell the current SMS app that it is not in a good state and // needs to ask to be the current SMS app again to work properly. if (updateIfNeeded || applicationData.mUid == android.os.Process.myUid()) { // Verify that the SMS app has permissions boolean appOpsFixed = tryFixExclusiveSmsAppops(context, applicationData, updateIfNeeded); if (!appOpsFixed) { // We can not return a package if permissions are not set up correctly applicationData = null; } } // We can only verify the phone and BT app's permissions from a privileged caller if (applicationData != null && updateIfNeeded) { // Ensure this component is still configured as the preferred activity. Usually the // current SMS app will already be the preferred activity - but checking whether or // not this is true is just as expensive as reconfiguring the preferred activity so // we just reconfigure every time. defaultSmsAppChanged(context); } } if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getApplication returning appData=" + applicationData); } return applicationData; } private static String getDefaultSmsPackage(Context context, int userId) { return context.getSystemService(RoleManager.class).getSmsRoleHolder(userId); } /** * Grants various permissions and appops on sms app change */ private static void defaultSmsAppChanged(Context context) { PackageManager packageManager = context.getPackageManager(); AppOpsManager appOps = context.getSystemService(AppOpsManager.class); // Assign permission to special system apps assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, PHONE_PACKAGE_NAME, true); assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, BLUETOOTH_PACKAGE_NAME, true); assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, MMS_SERVICE_PACKAGE_NAME, true); assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, TELEPHONY_PROVIDER_PACKAGE_NAME, true); // CellbroadcastReceiver is a mainline module thus skip signature match. assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, CellBroadcastUtils.getDefaultCellBroadcastReceiverPackageName(context), false); // Give AppOps permission to UID 1001 which contains multiple // apps, all of them should be able to write to telephony provider. // This is to allow the proxy package permission check in telephony provider // to pass. for (String opStr : DEFAULT_APP_EXCLUSIVE_APPOPS) { appOps.setUidMode(opStr, Process.PHONE_UID, AppOpsManager.MODE_ALLOWED); } } private static boolean tryFixExclusiveSmsAppops(Context context, SmsApplicationData applicationData, boolean updateIfNeeded) { AppOpsManager appOps = context.getSystemService(AppOpsManager.class); for (String opStr : DEFAULT_APP_EXCLUSIVE_APPOPS) { int mode = appOps.unsafeCheckOp(opStr, applicationData.mUid, applicationData.mPackageName); if (mode != AppOpsManager.MODE_ALLOWED) { Log.e(LOG_TAG, applicationData.mPackageName + " lost " + opStr + ": " + (updateIfNeeded ? " (fixing)" : " (no permission to fix)")); if (updateIfNeeded) { appOps.setUidMode(opStr, applicationData.mUid, AppOpsManager.MODE_ALLOWED); } else { return false; } } } return true; } /** * Sets the specified package as the default SMS/MMS application. The caller of this method * needs to have permission to set AppOps and write to secure settings. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static void setDefaultApplication(String packageName, Context context) { setDefaultApplicationAsUser(packageName, context, getIncomingUserId(context)); } /** * Same as {@link #setDefaultApplication} but takes a target user id. */ public static void setDefaultApplicationAsUser(String packageName, Context context, int userId) { TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); RoleManager roleManager = (RoleManager) context.getSystemService(Context.ROLE_SERVICE); // (b/134400042) RoleManager might be null in unit tests running older mockito versions // that do not support mocking final classes. if (!tm.isSmsCapable() && (roleManager == null || !roleManager.isRoleAvailable( RoleManager.ROLE_SMS))) { // No phone, no SMS return; } final long token = Binder.clearCallingIdentity(); try { setDefaultApplicationInternal(packageName, context, userId); } finally { Binder.restoreCallingIdentity(token); } } private static void setDefaultApplicationInternal(String packageName, Context context, int userId) { final UserHandle userHandle = UserHandle.of(userId); // Get old package name String oldPackageName = getDefaultSmsPackage(context, userId); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldPackageName + " new=" + packageName); } if (packageName != null && oldPackageName != null && packageName.equals(oldPackageName)) { // No change return; } // We only make the change if the new package is valid PackageManager packageManager = context.createContextAsUser(userHandle, 0).getPackageManager(); Collection applications = getApplicationCollectionInternal( context, userId); SmsApplicationData oldAppData = oldPackageName != null ? getApplicationForPackage(applications, oldPackageName) : null; SmsApplicationData applicationData = getApplicationForPackage(applications, packageName); if (applicationData != null) { // Ignore relevant appops for the previously configured default SMS app. AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); if (oldPackageName != null) { try { int uid = packageManager.getPackageInfo(oldPackageName, 0).applicationInfo.uid; setExclusiveAppops(oldPackageName, appOps, uid, AppOpsManager.MODE_DEFAULT); } catch (NameNotFoundException e) { Log.w(LOG_TAG, "Old SMS package not found: " + oldPackageName); } } // Update the setting. CompletableFuture future = new CompletableFuture<>(); Consumer callback = successful -> { if (successful) { future.complete(null); } else { future.completeExceptionally(new RuntimeException()); } }; context.getSystemService(RoleManager.class).addRoleHolderAsUser( RoleManager.ROLE_SMS, applicationData.mPackageName, 0, UserHandle.of(userId), AsyncTask.THREAD_POOL_EXECUTOR, callback); try { future.get(5, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { Log.e(LOG_TAG, "Exception while adding sms role holder " + applicationData, e); return; } defaultSmsAppChanged(context); } } /** * Broadcast action: * Same as {@link Intent#ACTION_DEFAULT_SMS_PACKAGE_CHANGED} but it's implicit (e.g. sent to * all apps) and requires * {@link #PERMISSION_MONITOR_DEFAULT_SMS_PACKAGE} to receive. */ public static final String ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL = "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL"; public static final String PERMISSION_MONITOR_DEFAULT_SMS_PACKAGE = "android.permission.MONITOR_DEFAULT_SMS_PACKAGE"; /** * Sends broadcasts on sms app change: * {@link Intent#ACTION_DEFAULT_SMS_PACKAGE_CHANGED} * {@link #ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL} */ private static void broadcastSmsAppChange(Context context, UserHandle userHandle, @Nullable String oldPackage, @Nullable String newPackage) { Collection apps = getApplicationCollection(context); broadcastSmsAppChange(context, userHandle, getApplicationForPackage(apps, oldPackage), getApplicationForPackage(apps, newPackage)); } private static void broadcastSmsAppChange(Context context, UserHandle userHandle, @Nullable SmsApplicationData oldAppData, @Nullable SmsApplicationData applicationData) { if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "setDefaultApplicationInternal oldAppData=" + oldAppData); } if (oldAppData != null && oldAppData.mSmsAppChangedReceiverClass != null) { // Notify the old sms app that it's no longer the default final Intent oldAppIntent = new Intent(Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED); final ComponentName component = new ComponentName(oldAppData.mPackageName, oldAppData.mSmsAppChangedReceiverClass); oldAppIntent.setComponent(component); oldAppIntent.putExtra(Intents.EXTRA_IS_DEFAULT_SMS_APP, false); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldAppData.mPackageName); } context.sendBroadcastAsUser(oldAppIntent, userHandle); } // Notify the new sms app that it's now the default (if the new sms app has a receiver // to handle the changed default sms intent). if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "setDefaultApplicationInternal new applicationData=" + applicationData); } if (applicationData != null && applicationData.mSmsAppChangedReceiverClass != null) { final Intent intent = new Intent(Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED); final ComponentName component = new ComponentName(applicationData.mPackageName, applicationData.mSmsAppChangedReceiverClass); intent.setComponent(component); intent.putExtra(Intents.EXTRA_IS_DEFAULT_SMS_APP, true); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "setDefaultApplicationInternal new=" + applicationData.mPackageName); } context.sendBroadcastAsUser(intent, userHandle); } // Send an implicit broadcast for the system server. // (or anyone with PERMISSION_MONITOR_DEFAULT_SMS_PACKAGE, really.) final Intent intent = new Intent(ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL); context.sendBroadcastAsUser(intent, userHandle, PERMISSION_MONITOR_DEFAULT_SMS_PACKAGE); } /** * Assign WRITE_SMS AppOps permission to some special system apps. * * @param context The context * @param packageManager The package manager instance * @param appOps The AppOps manager instance * @param packageName The package name of the system app * @param sigatureMatch whether to check signature match */ private static void assignExclusiveSmsPermissionsToSystemApp(Context context, PackageManager packageManager, AppOpsManager appOps, String packageName, boolean sigatureMatch) { if (packageName == null) return; // First check package signature matches the caller's package signature. // Since this class is only used internally by the system, this check makes sure // the package signature matches system signature. if (sigatureMatch) { final int result = packageManager.checkSignatures(context.getPackageName(), packageName); if (result != PackageManager.SIGNATURE_MATCH) { Log.e(LOG_TAG, packageName + " does not have system signature"); return; } } try { PackageInfo info = packageManager.getPackageInfo(packageName, 0); int mode = appOps.unsafeCheckOp(AppOpsManager.OPSTR_WRITE_SMS, info.applicationInfo.uid, packageName); if (mode != AppOpsManager.MODE_ALLOWED) { Log.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)"); setExclusiveAppops(packageName, appOps, info.applicationInfo.uid, AppOpsManager.MODE_ALLOWED); } } catch (NameNotFoundException e) { // No whitelisted system app on this device Log.e(LOG_TAG, "Package not found: " + packageName); } } private static void setExclusiveAppops(String pkg, AppOpsManager appOpsManager, int uid, int mode) { for (String opStr : DEFAULT_APP_EXCLUSIVE_APPOPS) { appOpsManager.setUidMode(opStr, uid, mode); } } /** * Tracks package changes and ensures that the default SMS app is always configured to be the * preferred activity for SENDTO sms/mms intents. */ private static final class SmsPackageMonitor extends PackageChangeReceiver { final Context mContext; public SmsPackageMonitor(Context context) { super(); mContext = context; } @Override public void onPackageDisappeared() { onPackageChanged(); } @Override public void onPackageAppeared() { onPackageChanged(); } @Override public void onPackageModified(String packageName) { onPackageChanged(); } private void onPackageChanged() { int userId; try { userId = getSendingUser().getIdentifier(); } catch (NullPointerException e) { // This should never happen in prod -- unit tests will put the receiver into a // unusual state where the pending result is null, which produces a NPE when calling // getSendingUserId. Just pretend like it's the system user for testing. userId = UserHandle.SYSTEM.getIdentifier(); } Context userContext = mContext; if (userId != UserHandle.SYSTEM.getIdentifier()) { try { userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0, UserHandle.of(userId)); } catch (NameNotFoundException nnfe) { if (DEBUG_MULTIUSER) { Log.w(LOG_TAG, "Unable to create package context for user " + userId); } } } PackageManager packageManager = userContext.getPackageManager(); // Ensure this component is still configured as the preferred activity ComponentName componentName = getDefaultSendToApplication(userContext, true); if (componentName != null) { configurePreferredActivity(packageManager, componentName); } } } /** * Tracks SMS role changes and sends broadcasts for default SMS app change. */ private static final class SmsRoleListener implements OnRoleHoldersChangedListener { private final Context mContext; private final RoleManager mRoleManager; private final SparseArray mSmsPackageNames = new SparseArray<>(); public SmsRoleListener(@NonNull Context context) { mContext = context; mRoleManager = context.getSystemService(RoleManager.class); final List users = context.getSystemService(UserManager.class) .getUserHandles(true); final int usersSize = users.size(); for (int i = 0; i < usersSize; i++) { final UserHandle user = users.get(i); mSmsPackageNames.put(user.getIdentifier(), getSmsPackageName(user)); } mRoleManager.addOnRoleHoldersChangedListenerAsUser(context.getMainExecutor(), this, UserHandle.ALL); } @Override public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) { if (!Objects.equals(roleName, RoleManager.ROLE_SMS)) { return; } final int userId = user.getIdentifier(); final String newSmsPackageName = getSmsPackageName(user); broadcastSmsAppChange(mContext, user, mSmsPackageNames.get(userId), newSmsPackageName); mSmsPackageNames.put(userId, newSmsPackageName); } @Nullable private String getSmsPackageName(@NonNull UserHandle user) { final List roleHolders = mRoleManager.getRoleHoldersAsUser( RoleManager.ROLE_SMS, user); return !roleHolders.isEmpty() ? roleHolders.get(0) : null; } } public static void initSmsPackageMonitor(Context context) { sSmsPackageMonitor = new SmsPackageMonitor(context); sSmsPackageMonitor.register(context, context.getMainLooper(), UserHandle.ALL); sSmsRoleListener = new SmsRoleListener(context); } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private static void configurePreferredActivity(PackageManager packageManager, ComponentName componentName) { // Add the four activity preferences we want to direct to this app. replacePreferredActivity(packageManager, componentName, SCHEME_SMS); replacePreferredActivity(packageManager, componentName, SCHEME_SMSTO); replacePreferredActivity(packageManager, componentName, SCHEME_MMS); replacePreferredActivity(packageManager, componentName, SCHEME_MMSTO); } /** * Updates the ACTION_SENDTO activity to the specified component for the specified scheme. */ private static void replacePreferredActivity(PackageManager packageManager, ComponentName componentName, String scheme) { // Build the set of existing activities that handle this scheme Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(scheme, "", null)); List resolveInfoList = packageManager.queryIntentActivities( intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER); List components = resolveInfoList.stream().map(info -> new ComponentName(info.activityInfo.packageName, info.activityInfo.name)) .collect(Collectors.toList()); // Update the preferred SENDTO activity for the specified scheme IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_SENDTO); intentFilter.addCategory(Intent.CATEGORY_DEFAULT); intentFilter.addDataScheme(scheme); packageManager.replacePreferredActivity(intentFilter, IntentFilter.MATCH_CATEGORY_SCHEME + IntentFilter.MATCH_ADJUSTMENT_NORMAL, components, componentName); } /** * Returns SmsApplicationData for this package if this package is capable of being set as the * default SMS application. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static SmsApplicationData getSmsApplicationData(String packageName, Context context) { Collection applications = getApplicationCollection(context); return getApplicationForPackage(applications, packageName); } /** * Gets the default SMS application * @param context context from the calling app * @param updateIfNeeded update the default app if there is no valid default app configured. * @return component name of the app and class to deliver SMS messages to */ @UnsupportedAppUsage public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) { return getDefaultSmsApplicationAsUser(context, updateIfNeeded, getIncomingUserId(context)); } /** * Gets the default SMS application on a given user * @param context context from the calling app * @param updateIfNeeded update the default app if there is no valid default app configured. * @param userId target user ID. * @return component name of the app and class to deliver SMS messages to */ @VisibleForTesting public static ComponentName getDefaultSmsApplicationAsUser(Context context, boolean updateIfNeeded, int userId) { final long token = Binder.clearCallingIdentity(); try { ComponentName component = null; SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, userId); if (smsApplicationData != null) { component = new ComponentName(smsApplicationData.mPackageName, smsApplicationData.mSmsReceiverClass); } return component; } finally { Binder.restoreCallingIdentity(token); } } /** * Gets the default MMS application * @param context context from the calling app * @param updateIfNeeded update the default app if there is no valid default app configured. * @return component name of the app and class to deliver MMS messages to */ @UnsupportedAppUsage public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) { int userId = getIncomingUserId(context); final long token = Binder.clearCallingIdentity(); try { ComponentName component = null; SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, userId); if (smsApplicationData != null) { component = new ComponentName(smsApplicationData.mPackageName, smsApplicationData.mMmsReceiverClass); } return component; } finally { Binder.restoreCallingIdentity(token); } } /** * Gets the default Respond Via Message application * @param context context from the calling app * @param updateIfNeeded update the default app if there is no valid default app configured. * @return component name of the app and class to direct Respond Via Message intent to */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static ComponentName getDefaultRespondViaMessageApplication(Context context, boolean updateIfNeeded) { int userId = getIncomingUserId(context); final long token = Binder.clearCallingIdentity(); try { ComponentName component = null; SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, userId); if (smsApplicationData != null) { component = new ComponentName(smsApplicationData.mPackageName, smsApplicationData.mRespondViaMessageClass); } return component; } finally { Binder.restoreCallingIdentity(token); } } /** * Gets the default Send To (smsto) application. *

* Caller must pass in the correct user context if calling from a singleton service. * @param context context from the calling app * @param updateIfNeeded update the default app if there is no valid default app configured. * @return component name of the app and class to direct SEND_TO (smsto) intent to */ public static ComponentName getDefaultSendToApplication(Context context, boolean updateIfNeeded) { int userId = getIncomingUserId(context); final long token = Binder.clearCallingIdentity(); try { ComponentName component = null; SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, userId); if (smsApplicationData != null) { component = new ComponentName(smsApplicationData.mPackageName, smsApplicationData.mSendToClass); } return component; } finally { Binder.restoreCallingIdentity(token); } } /** * Gets the default application that handles external changes to the SmsProvider and * MmsProvider. * @param context context from the calling app * @param updateIfNeeded update the default app if there is no valid default app configured. * @return component name of the app and class to deliver change intents to */ public static ComponentName getDefaultExternalTelephonyProviderChangedApplication( Context context, boolean updateIfNeeded) { int userId = getIncomingUserId(context); final long token = Binder.clearCallingIdentity(); try { ComponentName component = null; SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, userId); if (smsApplicationData != null && smsApplicationData.mProviderChangedReceiverClass != null) { component = new ComponentName(smsApplicationData.mPackageName, smsApplicationData.mProviderChangedReceiverClass); } return component; } finally { Binder.restoreCallingIdentity(token); } } /** * Gets the default application that handles sim full event. * @param context context from the calling app * @param updateIfNeeded update the default app if there is no valid default app configured. * @return component name of the app and class to deliver change intents to */ public static ComponentName getDefaultSimFullApplication( Context context, boolean updateIfNeeded) { int userId = getIncomingUserId(context); final long token = Binder.clearCallingIdentity(); try { ComponentName component = null; SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, userId); if (smsApplicationData != null && smsApplicationData.mSimFullReceiverClass != null) { component = new ComponentName(smsApplicationData.mPackageName, smsApplicationData.mSimFullReceiverClass); } return component; } finally { Binder.restoreCallingIdentity(token); } } /** * Returns whether need to write the SMS message to SMS database for this package. *

* Caller must pass in the correct user context if calling from a singleton service. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static boolean shouldWriteMessageForPackage(String packageName, Context context) { return !isDefaultSmsApplication(context, packageName); } /** * Check if a package is default sms app (or equivalent, like bluetooth) * * @param context context from the calling app * @param packageName the name of the package to be checked * @return true if the package is default sms app or bluetooth */ @UnsupportedAppUsage public static boolean isDefaultSmsApplication(Context context, String packageName) { if (packageName == null) { return false; } final String defaultSmsPackage = getDefaultSmsApplicationPackageName(context); if ((defaultSmsPackage != null && defaultSmsPackage.equals(packageName)) || BLUETOOTH_PACKAGE_NAME.equals(packageName)) { return true; } return false; } private static String getDefaultSmsApplicationPackageName(Context context) { final ComponentName component = getDefaultSmsApplication(context, false); if (component != null) { return component.getPackageName(); } return null; } }