| /* |
| * 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 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 current process, if called from a system app, |
| * otherwise it returns the caller's userId |
| * @return userId of the caller. |
| */ |
| private static int getIncomingUserId() { |
| 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 userHandle of the current process, if called from a system app, |
| * otherwise it returns the caller's userHandle |
| * @return userHandle of the caller. |
| */ |
| private static UserHandle getIncomingUserHandle() { |
| return UserHandle.of(getIncomingUserId()); |
| } |
| |
| /** |
| * 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<SmsApplicationData> getApplicationCollection(Context context) { |
| return getApplicationCollectionAsUser(context, getIncomingUserId()); |
| } |
| |
| /** |
| * Same as {@link #getApplicationCollection} but it takes a target user ID. |
| */ |
| public static Collection<SmsApplicationData> getApplicationCollectionAsUser(Context context, |
| int userId) { |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| return getApplicationCollectionInternal(context, userId); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| private static Collection<SmsApplicationData> 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<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, |
| PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, |
| userHandle); |
| |
| HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>(); |
| |
| // 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<ResolveInfo> 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<ResolveInfo> 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<ResolveInfo> 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<ResolveInfo> 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<ResolveInfo> 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<ResolveInfo> 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<SmsApplicationData> 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<SmsApplicationData> 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); |
| |
| final String bluetoothPackageName = context.getResources() |
| .getString(com.android.internal.R.string.config_systemBluetoothStack); |
| // Assign permission to special system apps |
| assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, |
| PHONE_PACKAGE_NAME, true); |
| assignExclusiveSmsPermissionsToSystemApp(context, packageManager, appOps, |
| bluetoothPackageName, false); |
| 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()); |
| } |
| |
| /** |
| * 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<SmsApplicationData> 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<Void> future = new CompletableFuture<>(); |
| Consumer<Boolean> 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<SmsApplicationData> 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 allowlisted 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<String> mSmsPackageNames = new SparseArray<>(); |
| |
| public SmsRoleListener(@NonNull Context context) { |
| mContext = context; |
| mRoleManager = context.getSystemService(RoleManager.class); |
| final List<UserHandle> 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<String> 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<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities( |
| intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER); |
| |
| List<ComponentName> 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<SmsApplicationData> 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, getIncomingUserHandle()); |
| } |
| |
| /** |
| * 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 userHandle target user handle |
| * if {@code null} is passed in then calling package uid is used to find out target user handle. |
| * @return component name of the app and class to deliver SMS messages to |
| */ |
| public static ComponentName getDefaultSmsApplicationAsUser(Context context, |
| boolean updateIfNeeded, @Nullable UserHandle userHandle) { |
| if (userHandle == null) { |
| userHandle = getIncomingUserHandle(); |
| } |
| |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| ComponentName component = null; |
| SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, |
| userHandle.getIdentifier()); |
| 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) { |
| return getDefaultMmsApplicationAsUser(context, updateIfNeeded, getIncomingUserHandle()); |
| } |
| |
| /** |
| * Gets the default MMS 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 userHandle target user handle |
| * if {@code null} is passed in then calling package uid is used to find out target user handle. |
| * @return component name of the app and class to deliver MMS messages to. |
| */ |
| public static ComponentName getDefaultMmsApplicationAsUser(Context context, |
| boolean updateIfNeeded, @Nullable UserHandle userHandle) { |
| if (userHandle == null) { |
| userHandle = getIncomingUserHandle(); |
| } |
| |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| ComponentName component = null; |
| SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, |
| userHandle.getIdentifier()); |
| 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) { |
| return getDefaultRespondViaMessageApplicationAsUser(context, updateIfNeeded, |
| getIncomingUserHandle()); |
| } |
| |
| /** |
| * Gets the default Respond Via Message 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 userHandle target user handle |
| * if {@code null} is passed in then calling package uid is used to find out target user handle. |
| * @return component name of the app and class to direct Respond Via Message intent to |
| */ |
| public static ComponentName getDefaultRespondViaMessageApplicationAsUser(Context context, |
| boolean updateIfNeeded, @Nullable UserHandle userHandle) { |
| if (userHandle == null) { |
| userHandle = getIncomingUserHandle(); |
| } |
| |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| ComponentName component = null; |
| SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, |
| userHandle.getIdentifier()); |
| if (smsApplicationData != null) { |
| component = new ComponentName(smsApplicationData.mPackageName, |
| smsApplicationData.mRespondViaMessageClass); |
| } |
| return component; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| /** |
| * Gets the default Send To (smsto) application. |
| * <p> |
| * 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(); |
| |
| 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) { |
| return getDefaultExternalTelephonyProviderChangedApplicationAsUser(context, updateIfNeeded, |
| getIncomingUserHandle()); |
| } |
| |
| /** |
| * Gets the default application that handles external changes to the SmsProvider and |
| * MmsProvider 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 userHandle target user handle |
| * if {@code null} is passed in then calling package uid is used to find out target user handle. |
| * @return component name of the app and class to deliver change intents to. |
| */ |
| public static ComponentName getDefaultExternalTelephonyProviderChangedApplicationAsUser( |
| Context context, boolean updateIfNeeded, @Nullable UserHandle userHandle) { |
| if (userHandle == null) { |
| userHandle = getIncomingUserHandle(); |
| } |
| |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| ComponentName component = null; |
| SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, |
| userHandle.getIdentifier()); |
| 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) { |
| return getDefaultSimFullApplicationAsUser(context, updateIfNeeded, getIncomingUserHandle()); |
| } |
| |
| /** |
| * Gets the default application that handles sim full event 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 userHandle target user handle |
| * if {@code null} is passed in then calling package uid is used to find out target user handle. |
| * @return component name of the app and class to deliver change intents to |
| */ |
| public static ComponentName getDefaultSimFullApplicationAsUser(Context context, |
| boolean updateIfNeeded, @Nullable UserHandle userHandle) { |
| if (userHandle == null) { |
| userHandle = getIncomingUserHandle(); |
| } |
| |
| final long token = Binder.clearCallingIdentity(); |
| try { |
| ComponentName component = null; |
| SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, |
| userHandle.getIdentifier()); |
| if (smsApplicationData != null |
| && smsApplicationData.mSimFullReceiverClass != null) { |
| component = new ComponentName(smsApplicationData.mPackageName, |
| smsApplicationData.mSimFullReceiverClass); |
| } |
| return component; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| /** |
| * Returns whether it is required to write the SMS message to SMS database for this package. |
| * |
| * @param packageName the name of the package to be checked |
| * @param context context from the calling app |
| * @return true if it is required to write SMS message to SMS database for this package. |
| * |
| * <p> |
| * 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 !shouldWriteMessageForPackageAsUser(packageName, context, getIncomingUserHandle()); |
| } |
| |
| /** |
| * Returns whether it is required to write the SMS message to SMS database for this package. |
| * |
| * @param packageName the name of the package to be checked |
| * @param context context from the calling app |
| * @param userHandle target user handle |
| * if {@code null} is passed in then calling package uid is used to find out target user handle. |
| * @return true if it is required to write SMS message to SMS database for this package. |
| * |
| * <p> |
| * Caller must pass in the correct user context if calling from a singleton service. |
| */ |
| public static boolean shouldWriteMessageForPackageAsUser(String packageName, Context context, |
| @Nullable UserHandle userHandle) { |
| return !isDefaultSmsApplicationAsUser(context, packageName, userHandle); |
| } |
| |
| /** |
| * 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) { |
| return isDefaultSmsApplicationAsUser(context, packageName, getIncomingUserHandle()); |
| } |
| |
| /** |
| * Check if a package is default sms app (or equivalent, like bluetooth) on a given user. |
| * |
| * @param context context from the calling app |
| * @param packageName the name of the package to be checked |
| * @param userHandle target user handle |
| * if {@code null} is passed in then calling package uid is used to find out target user handle. |
| * @return true if the package is default sms app or bluetooth |
| */ |
| public static boolean isDefaultSmsApplicationAsUser(Context context, String packageName, |
| @Nullable UserHandle userHandle) { |
| if (packageName == null) { |
| return false; |
| } |
| |
| if (userHandle == null) { |
| userHandle = getIncomingUserHandle(); |
| } |
| |
| ComponentName component = getDefaultSmsApplicationAsUser(context, false, |
| userHandle); |
| if (component == null) { |
| return false; |
| } |
| |
| String defaultSmsPackage = component.getPackageName(); |
| if (defaultSmsPackage == null) { |
| return false; |
| } |
| |
| String bluetoothPackageName = context.getResources() |
| .getString(com.android.internal.R.string.config_systemBluetoothStack); |
| |
| if (defaultSmsPackage.equals(packageName) || bluetoothPackageName.equals(packageName)) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Check if a package is default mms 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 mms app or bluetooth |
| */ |
| @UnsupportedAppUsage |
| public static boolean isDefaultMmsApplication(Context context, String packageName) { |
| return isDefaultMmsApplicationAsUser(context, packageName, getIncomingUserHandle()); |
| } |
| |
| /** |
| * Check if a package is default mms app (or equivalent, like bluetooth) on a given user. |
| * |
| * @param context context from the calling app |
| * @param packageName the name of the package to be checked |
| * @param userHandle target user handle |
| * if {@code null} is passed in then calling package uid is used to find out target user handle. |
| * @return true if the package is default mms app or bluetooth |
| */ |
| public static boolean isDefaultMmsApplicationAsUser(Context context, String packageName, |
| @Nullable UserHandle userHandle) { |
| if (packageName == null) { |
| return false; |
| } |
| |
| if (userHandle == null) { |
| userHandle = getIncomingUserHandle(); |
| } |
| |
| ComponentName component = getDefaultMmsApplicationAsUser(context, false, |
| userHandle); |
| if (component == null) { |
| return false; |
| } |
| |
| String defaultMmsPackage = component.getPackageName(); |
| if (defaultMmsPackage == null) { |
| return false; |
| } |
| |
| String bluetoothPackageName = context.getResources() |
| .getString(com.android.internal.R.string.config_systemBluetoothStack); |
| |
| if (defaultMmsPackage.equals(packageName)|| bluetoothPackageName.equals(packageName)) { |
| return true; |
| } |
| return false; |
| } |
| } |