SmsApplication.java revision 041cfe21bd66c8cb002435e7dc54177db2e927aa
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.internal.telephony; 18 19import android.Manifest.permission; 20import android.app.AppOpsManager; 21import android.content.ComponentName; 22import android.content.Context; 23import android.content.Intent; 24import android.content.pm.ActivityInfo; 25import android.content.pm.PackageManager; 26import android.content.pm.ResolveInfo; 27import android.content.pm.ServiceInfo; 28import android.content.res.Resources; 29import android.net.Uri; 30import android.provider.Settings; 31import android.provider.Telephony.Sms.Intents; 32import android.telephony.TelephonyManager; 33 34import java.util.Collection; 35import java.util.HashMap; 36import java.util.List; 37 38/** 39 * Class for managing the primary application that we will deliver SMS/MMS messages to 40 * 41 * {@hide} 42 */ 43public final class SmsApplication { 44 public static class SmsApplicationData { 45 /** 46 * Name of this SMS app for display. 47 */ 48 public String mApplicationName; 49 50 /** 51 * Package name for this SMS app. 52 */ 53 public String mPackageName; 54 55 /** 56 * The class name of the SMS_DELIVER_ACTION receiver in this app. 57 */ 58 public String mSmsReceiverClass; 59 60 /** 61 * The class name of the WAP_PUSH_DELIVER_ACTION receiver in this app. 62 */ 63 public String mMmsReceiverClass; 64 65 /** 66 * The class name of the ACTION_RESPOND_VIA_MESSAGE intent in this app. 67 */ 68 public String mRespondViaMessageClass; 69 70 /** 71 * The class name of the ACTION_SENDTO intent in this app. 72 */ 73 public String mSendToClass; 74 75 /** 76 * The user-id for this application 77 */ 78 public int mUid; 79 80 /** 81 * Returns true if this SmsApplicationData is complete (all intents handled). 82 * @return 83 */ 84 public boolean isComplete() { 85 return (mSmsReceiverClass != null && mMmsReceiverClass != null 86 && mRespondViaMessageClass != null && mSendToClass != null); 87 } 88 89 public SmsApplicationData(String applicationName, String packageName, int uid) { 90 mApplicationName = applicationName; 91 mPackageName = packageName; 92 mUid = uid; 93 } 94 } 95 96 /** 97 * Returns the list of available SMS apps defined as apps that are registered for both the 98 * SMS_RECEIVED_ACTION (SMS) and WAP_PUSH_RECEIVED_ACTION (MMS) broadcasts (and their broadcast 99 * receivers are enabled) 100 * 101 * Requirements to be an SMS application: 102 * Implement SMS_DELIVER_ACTION broadcast receiver. 103 * Require BROADCAST_SMS permission. 104 * 105 * Implement WAP_PUSH_DELIVER_ACTION broadcast receiver. 106 * Require BROADCAST_WAP_PUSH permission. 107 * 108 * Implement RESPOND_VIA_MESSAGE intent. 109 * Support smsto Uri scheme. 110 * Require SEND_RESPOND_VIA_MESSAGE permission. 111 * 112 * Implement ACTION_SENDTO intent. 113 * Support smsto Uri scheme. 114 */ 115 public static Collection<SmsApplicationData> getApplicationCollection(Context context) { 116 PackageManager packageManager = context.getPackageManager(); 117 118 // Get the list of apps registered for SMS 119 Intent intent = new Intent(Intents.SMS_DELIVER_ACTION); 120 List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceivers(intent, 0); 121 122 HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>(); 123 124 // Add one entry to the map for every sms receiver (ignoring duplicate sms receivers) 125 for (ResolveInfo resolveInfo : smsReceivers) { 126 final ActivityInfo activityInfo = resolveInfo.activityInfo; 127 if (activityInfo == null) { 128 continue; 129 } 130 if (!permission.BROADCAST_SMS.equals(activityInfo.permission)) { 131 continue; 132 } 133 final String packageName = activityInfo.packageName; 134 if (!receivers.containsKey(packageName)) { 135 final String applicationName = resolveInfo.loadLabel(packageManager).toString(); 136 final SmsApplicationData smsApplicationData = new SmsApplicationData( 137 applicationName, packageName, activityInfo.applicationInfo.uid); 138 smsApplicationData.mSmsReceiverClass = activityInfo.name; 139 receivers.put(packageName, smsApplicationData); 140 } 141 } 142 143 // Update any existing entries with mms receiver class 144 intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION); 145 intent.setDataAndType(null, "application/vnd.wap.mms-message"); 146 List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceivers(intent, 0); 147 for (ResolveInfo resolveInfo : mmsReceivers) { 148 final ActivityInfo activityInfo = resolveInfo.activityInfo; 149 if (activityInfo == null) { 150 continue; 151 } 152 if (!permission.BROADCAST_WAP_PUSH.equals(activityInfo.permission)) { 153 continue; 154 } 155 final String packageName = activityInfo.packageName; 156 final SmsApplicationData smsApplicationData = receivers.get(packageName); 157 if (smsApplicationData != null) { 158 smsApplicationData.mMmsReceiverClass = activityInfo.name; 159 } 160 } 161 162 // Update any existing entries with respond via message intent class. 163 intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE, 164 Uri.fromParts("smsto", "", null)); 165 List<ResolveInfo> respondServices = packageManager.queryIntentServices(intent, 0); 166 for (ResolveInfo resolveInfo : respondServices) { 167 final ServiceInfo serviceInfo = resolveInfo.serviceInfo; 168 if (serviceInfo == null) { 169 continue; 170 } 171 if (!permission.SEND_RESPOND_VIA_MESSAGE.equals(serviceInfo.permission)) { 172 continue; 173 } 174 final String packageName = serviceInfo.packageName; 175 final SmsApplicationData smsApplicationData = receivers.get(packageName); 176 if (smsApplicationData != null) { 177 smsApplicationData.mRespondViaMessageClass = serviceInfo.name; 178 } 179 } 180 181 // Update any existing entries with supports send to. 182 intent = new Intent(Intent.ACTION_SENDTO, 183 Uri.fromParts("smsto", "", null)); 184 List<ResolveInfo> sendToActivities = packageManager.queryIntentActivities(intent, 0); 185 for (ResolveInfo resolveInfo : sendToActivities) { 186 final ActivityInfo activityInfo = resolveInfo.activityInfo; 187 if (activityInfo == null) { 188 continue; 189 } 190 final String packageName = activityInfo.packageName; 191 final SmsApplicationData smsApplicationData = receivers.get(packageName); 192 if (smsApplicationData != null) { 193 smsApplicationData.mSendToClass = activityInfo.name; 194 } 195 } 196 197 // Remove any entries for which we did not find all required intents. 198 for (ResolveInfo r : smsReceivers) { 199 String packageName = r.activityInfo.packageName; 200 SmsApplicationData smsApplicationData = receivers.get(packageName); 201 if (smsApplicationData != null) { 202 if (!smsApplicationData.isComplete()) { 203 receivers.remove(packageName); 204 } 205 } 206 } 207 return receivers.values(); 208 } 209 210 /** 211 * Checks to see if we have a valid installed SMS application for the specified package name 212 * @return Data for the specified package name or null if there isn't one 213 */ 214 private static SmsApplicationData getApplicationForPackage( 215 Collection<SmsApplicationData> applications, String packageName) { 216 if (packageName == null) { 217 return null; 218 } 219 // Is there an entry in the application list for the specified package? 220 for (SmsApplicationData application : applications) { 221 if (application.mPackageName.contentEquals(packageName)) { 222 return application; 223 } 224 } 225 return null; 226 } 227 228 /** 229 * Get the application we will use for delivering SMS/MMS messages. 230 * 231 * We return the preferred sms application with the following order of preference: 232 * (1) User selected SMS app (if selected, and if still valid) 233 * (2) Android Messaging (if installed) 234 * (3) The currently configured highest priority broadcast receiver 235 * (4) Null 236 */ 237 private static SmsApplicationData getApplication(Context context, boolean updateIfNeeded) { 238 Collection<SmsApplicationData> applications = getApplicationCollection(context); 239 240 // Determine which application receives the broadcast 241 String defaultApplication = Settings.Secure.getString(context.getContentResolver(), 242 Settings.Secure.SMS_DEFAULT_APPLICATION); 243 244 SmsApplicationData applicationData = null; 245 if (defaultApplication != null) { 246 applicationData = getApplicationForPackage(applications, defaultApplication); 247 } 248 // Picking a new SMS app requires AppOps and Settings.Secure permissions, so we only do 249 // this if the caller asked us to. 250 if (updateIfNeeded) { 251 if (applicationData == null) { 252 // Try to find the default SMS package for this device 253 Resources r = context.getResources(); 254 String defaultPackage = 255 r.getString(com.android.internal.R.string.default_sms_application); 256 applicationData = getApplicationForPackage(applications, defaultPackage); 257 } 258 if (applicationData == null) { 259 // Are there any applications? 260 if (applications.size() != 0) { 261 applicationData = (SmsApplicationData)applications.toArray()[0]; 262 } 263 } 264 265 // If we found a new default app, update the setting 266 if (applicationData != null) { 267 setDefaultApplication(applicationData.mPackageName, context); 268 } 269 } 270 return applicationData; 271 } 272 273 /** 274 * Sets the specified package as the default SMS/MMS application. The caller of this method 275 * needs to have permission to set AppOps and write to secure settings. 276 */ 277 public static void setDefaultApplication(String packageName, Context context) { 278 Collection<SmsApplicationData> applications = getApplicationCollection(context); 279 String oldPackageName = Settings.Secure.getString(context.getContentResolver(), 280 Settings.Secure.SMS_DEFAULT_APPLICATION); 281 SmsApplicationData oldSmsApplicationData = getApplicationForPackage(applications, 282 oldPackageName); 283 SmsApplicationData smsApplicationData = getApplicationForPackage(applications, 284 packageName); 285 286 if (smsApplicationData != null && smsApplicationData != oldSmsApplicationData) { 287 // Ignore OP_WRITE_SMS for the previously configured default SMS app. 288 AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); 289 if (oldSmsApplicationData != null) { 290 appOps.setMode(AppOpsManager.OP_WRITE_SMS, oldSmsApplicationData.mUid, 291 oldSmsApplicationData.mPackageName, AppOpsManager.MODE_IGNORED); 292 } 293 294 // Update the secure setting. 295 Settings.Secure.putString(context.getContentResolver(), 296 Settings.Secure.SMS_DEFAULT_APPLICATION, smsApplicationData.mPackageName); 297 298 // Allow OP_WRITE_SMS for the newly configured default SMS app. 299 appOps.setMode(AppOpsManager.OP_WRITE_SMS, smsApplicationData.mUid, 300 smsApplicationData.mPackageName, AppOpsManager.MODE_ALLOWED); 301 } 302 } 303 304 /** 305 * Returns SmsApplicationData for this package if this package is capable of being set as the 306 * default SMS application. 307 */ 308 public static SmsApplicationData getSmsApplicationData(String packageName, Context context) { 309 Collection<SmsApplicationData> applications = getApplicationCollection(context); 310 return getApplicationForPackage(applications, packageName); 311 } 312 313 /** 314 * Gets the default SMS application 315 * @param context context from the calling app 316 * @param updateIfNeeded update the default app if there is no valid default app configured. 317 * @return component name of the app and class to deliver SMS messages to 318 */ 319 public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) { 320 ComponentName component = null; 321 SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded); 322 if (smsApplicationData != null) { 323 component = new ComponentName(smsApplicationData.mPackageName, 324 smsApplicationData.mSmsReceiverClass); 325 } 326 return component; 327 } 328 329 /** 330 * Gets the default MMS application 331 * @param context context from the calling app 332 * @param updateIfNeeded update the default app if there is no valid default app configured. 333 * @return component name of the app and class to deliver MMS messages to 334 */ 335 public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) { 336 ComponentName component = null; 337 SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded); 338 if (smsApplicationData != null) { 339 component = new ComponentName(smsApplicationData.mPackageName, 340 smsApplicationData.mMmsReceiverClass); 341 } 342 return component; 343 } 344 345 /** 346 * Gets the default Respond Via Message application 347 * @param context context from the calling app 348 * @param updateIfNeeded update the default app if there is no valid default app configured. 349 * @return component name of the app and class to direct Respond Via Message intent to 350 */ 351 public static ComponentName getDefaultRespondViaMessageApplication(Context context, 352 boolean updateIfNeeded) { 353 ComponentName component = null; 354 SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded); 355 if (smsApplicationData != null) { 356 component = new ComponentName(smsApplicationData.mPackageName, 357 smsApplicationData.mRespondViaMessageClass); 358 } 359 return component; 360 } 361 362 /** 363 * Gets the default Send To (smsto) application 364 * @param context context from the calling app 365 * @param updateIfNeeded update the default app if there is no valid default app configured. 366 * @return component name of the app and class to direct SEND_TO (smsto) intent to 367 */ 368 public static ComponentName getDefaultSendToApplication(Context context, 369 boolean updateIfNeeded) { 370 ComponentName component = null; 371 SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded); 372 if (smsApplicationData != null) { 373 component = new ComponentName(smsApplicationData.mPackageName, 374 smsApplicationData.mSendToClass); 375 } 376 return component; 377 } 378} 379