1322 lines
57 KiB
Java
1322 lines
57 KiB
Java
/*
|
|
* 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;
|
|
}
|
|
} |