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.PackageInfo; 26import android.content.pm.PackageManager; 27import android.content.pm.ResolveInfo; 28import android.content.pm.ServiceInfo; 29import android.content.pm.PackageManager.NameNotFoundException; 30import android.content.res.Resources; 31import android.net.Uri; 32import android.provider.Settings; 33import android.provider.Telephony.Sms.Intents; 34import android.telephony.Rlog; 35import android.telephony.TelephonyManager; 36 37import java.util.Collection; 38import java.util.HashMap; 39import java.util.List; 40 41/** 42 * Class for managing the primary application that we will deliver SMS/MMS messages to 43 * 44 * {@hide} 45 */ 46public final class SmsApplication { 47 static final String LOG_TAG = "SmsApplication"; 48 private static final String PHONE_PACKAGE_NAME = "com.android.phone"; 49 50 public static class SmsApplicationData { 51 /** 52 * Name of this SMS app for display. 53 */ 54 public String mApplicationName; 55 56 /** 57 * Package name for this SMS app. 58 */ 59 public String mPackageName; 60 61 /** 62 * The class name of the SMS_DELIVER_ACTION receiver in this app. 63 */ 64 public String mSmsReceiverClass; 65 66 /** 67 * The class name of the WAP_PUSH_DELIVER_ACTION receiver in this app. 68 */ 69 public String mMmsReceiverClass; 70 71 /** 72 * The class name of the ACTION_RESPOND_VIA_MESSAGE intent in this app. 73 */ 74 public String mRespondViaMessageClass; 75 76 /** 77 * The class name of the ACTION_SENDTO intent in this app. 78 */ 79 public String mSendToClass; 80 81 /** 82 * The user-id for this application 83 */ 84 public int mUid; 85 86 /** 87 * Returns true if this SmsApplicationData is complete (all intents handled). 88 * @return 89 */ 90 public boolean isComplete() { 91 return (mSmsReceiverClass != null && mMmsReceiverClass != null 92 && mRespondViaMessageClass != null && mSendToClass != null); 93 } 94 95 public SmsApplicationData(String applicationName, String packageName, int uid) { 96 mApplicationName = applicationName; 97 mPackageName = packageName; 98 mUid = uid; 99 } 100 } 101 102 /** 103 * Returns the list of available SMS apps defined as apps that are registered for both the 104 * SMS_RECEIVED_ACTION (SMS) and WAP_PUSH_RECEIVED_ACTION (MMS) broadcasts (and their broadcast 105 * receivers are enabled) 106 * 107 * Requirements to be an SMS application: 108 * Implement SMS_DELIVER_ACTION broadcast receiver. 109 * Require BROADCAST_SMS permission. 110 * 111 * Implement WAP_PUSH_DELIVER_ACTION broadcast receiver. 112 * Require BROADCAST_WAP_PUSH permission. 113 * 114 * Implement RESPOND_VIA_MESSAGE intent. 115 * Support smsto Uri scheme. 116 * Require SEND_RESPOND_VIA_MESSAGE permission. 117 * 118 * Implement ACTION_SENDTO intent. 119 * Support smsto Uri scheme. 120 */ 121 public static Collection<SmsApplicationData> getApplicationCollection(Context context) { 122 PackageManager packageManager = context.getPackageManager(); 123 124 // Get the list of apps registered for SMS 125 Intent intent = new Intent(Intents.SMS_DELIVER_ACTION); 126 List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceivers(intent, 0); 127 128 HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>(); 129 130 // Add one entry to the map for every sms receiver (ignoring duplicate sms receivers) 131 for (ResolveInfo resolveInfo : smsReceivers) { 132 final ActivityInfo activityInfo = resolveInfo.activityInfo; 133 if (activityInfo == null) { 134 continue; 135 } 136 if (!permission.BROADCAST_SMS.equals(activityInfo.permission)) { 137 continue; 138 } 139 final String packageName = activityInfo.packageName; 140 if (!receivers.containsKey(packageName)) { 141 final String applicationName = resolveInfo.loadLabel(packageManager).toString(); 142 final SmsApplicationData smsApplicationData = new SmsApplicationData( 143 applicationName, packageName, activityInfo.applicationInfo.uid); 144 smsApplicationData.mSmsReceiverClass = activityInfo.name; 145 receivers.put(packageName, smsApplicationData); 146 } 147 } 148 149 // Update any existing entries with mms receiver class 150 intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION); 151 intent.setDataAndType(null, "application/vnd.wap.mms-message"); 152 List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceivers(intent, 0); 153 for (ResolveInfo resolveInfo : mmsReceivers) { 154 final ActivityInfo activityInfo = resolveInfo.activityInfo; 155 if (activityInfo == null) { 156 continue; 157 } 158 if (!permission.BROADCAST_WAP_PUSH.equals(activityInfo.permission)) { 159 continue; 160 } 161 final String packageName = activityInfo.packageName; 162 final SmsApplicationData smsApplicationData = receivers.get(packageName); 163 if (smsApplicationData != null) { 164 smsApplicationData.mMmsReceiverClass = activityInfo.name; 165 } 166 } 167 168 // Update any existing entries with respond via message intent class. 169 intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE, 170 Uri.fromParts("smsto", "", null)); 171 List<ResolveInfo> respondServices = packageManager.queryIntentServices(intent, 0); 172 for (ResolveInfo resolveInfo : respondServices) { 173 final ServiceInfo serviceInfo = resolveInfo.serviceInfo; 174 if (serviceInfo == null) { 175 continue; 176 } 177 if (!permission.SEND_RESPOND_VIA_MESSAGE.equals(serviceInfo.permission)) { 178 continue; 179 } 180 final String packageName = serviceInfo.packageName; 181 final SmsApplicationData smsApplicationData = receivers.get(packageName); 182 if (smsApplicationData != null) { 183 smsApplicationData.mRespondViaMessageClass = serviceInfo.name; 184 } 185 } 186 187 // Update any existing entries with supports send to. 188 intent = new Intent(Intent.ACTION_SENDTO, 189 Uri.fromParts("smsto", "", null)); 190 List<ResolveInfo> sendToActivities = packageManager.queryIntentActivities(intent, 0); 191 for (ResolveInfo resolveInfo : sendToActivities) { 192 final ActivityInfo activityInfo = resolveInfo.activityInfo; 193 if (activityInfo == null) { 194 continue; 195 } 196 final String packageName = activityInfo.packageName; 197 final SmsApplicationData smsApplicationData = receivers.get(packageName); 198 if (smsApplicationData != null) { 199 smsApplicationData.mSendToClass = activityInfo.name; 200 } 201 } 202 203 // Remove any entries for which we did not find all required intents. 204 for (ResolveInfo resolveInfo : smsReceivers) { 205 final ActivityInfo activityInfo = resolveInfo.activityInfo; 206 if (activityInfo == null) { 207 continue; 208 } 209 final String packageName = activityInfo.packageName; 210 final SmsApplicationData smsApplicationData = receivers.get(packageName); 211 if (smsApplicationData != null) { 212 if (!smsApplicationData.isComplete()) { 213 receivers.remove(packageName); 214 } 215 } 216 } 217 return receivers.values(); 218 } 219 220 /** 221 * Checks to see if we have a valid installed SMS application for the specified package name 222 * @return Data for the specified package name or null if there isn't one 223 */ 224 private static SmsApplicationData getApplicationForPackage( 225 Collection<SmsApplicationData> applications, String packageName) { 226 if (packageName == null) { 227 return null; 228 } 229 // Is there an entry in the application list for the specified package? 230 for (SmsApplicationData application : applications) { 231 if (application.mPackageName.contentEquals(packageName)) { 232 return application; 233 } 234 } 235 return null; 236 } 237 238 /** 239 * Get the application we will use for delivering SMS/MMS messages. 240 * 241 * We return the preferred sms application with the following order of preference: 242 * (1) User selected SMS app (if selected, and if still valid) 243 * (2) Android Messaging (if installed) 244 * (3) The currently configured highest priority broadcast receiver 245 * (4) Null 246 */ 247 private static SmsApplicationData getApplication(Context context, boolean updateIfNeeded) { 248 TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); 249 if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) { 250 // No phone, no SMS 251 return null; 252 } 253 254 Collection<SmsApplicationData> applications = getApplicationCollection(context); 255 256 // Determine which application receives the broadcast 257 String defaultApplication = Settings.Secure.getString(context.getContentResolver(), 258 Settings.Secure.SMS_DEFAULT_APPLICATION); 259 260 SmsApplicationData applicationData = null; 261 if (defaultApplication != null) { 262 applicationData = getApplicationForPackage(applications, defaultApplication); 263 } 264 // Picking a new SMS app requires AppOps and Settings.Secure permissions, so we only do 265 // this if the caller asked us to. 266 if (updateIfNeeded && applicationData == null) { 267 // Try to find the default SMS package for this device 268 Resources r = context.getResources(); 269 String defaultPackage = 270 r.getString(com.android.internal.R.string.default_sms_application); 271 applicationData = getApplicationForPackage(applications, defaultPackage); 272 273 if (applicationData == null) { 274 // Are there any applications? 275 if (applications.size() != 0) { 276 applicationData = (SmsApplicationData)applications.toArray()[0]; 277 } 278 } 279 280 // If we found a new default app, update the setting 281 if (applicationData != null) { 282 setDefaultApplication(applicationData.mPackageName, context); 283 } 284 } 285 286 // If we found a package, make sure AppOps permissions are set up correctly 287 if (applicationData != null) { 288 AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); 289 290 // We can only call checkOp if we are privileged (updateIfNeeded) or if the app we 291 // are checking is for our current uid. Doing this check from the unprivileged current 292 // SMS app allows us to tell the current SMS app that it is not in a good state and 293 // needs to ask to be the current SMS app again to work properly. 294 if (updateIfNeeded || applicationData.mUid == android.os.Process.myUid()) { 295 // Verify that the SMS app has permissions 296 int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, applicationData.mUid, 297 applicationData.mPackageName); 298 if (mode != AppOpsManager.MODE_ALLOWED) { 299 Rlog.e(LOG_TAG, applicationData.mPackageName + " lost OP_WRITE_SMS: " + 300 (updateIfNeeded ? " (fixing)" : " (no permission to fix)")); 301 if (updateIfNeeded) { 302 appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid, 303 applicationData.mPackageName, AppOpsManager.MODE_ALLOWED); 304 } else { 305 // We can not return a package if permissions are not set up correctly 306 applicationData = null; 307 } 308 } 309 } 310 311 // We can only verify the phone app's permissions from a privileged caller 312 if (updateIfNeeded) { 313 // Verify that the phone app has permissions 314 PackageManager packageManager = context.getPackageManager(); 315 try { 316 PackageInfo info = packageManager.getPackageInfo(PHONE_PACKAGE_NAME, 0); 317 int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid, 318 PHONE_PACKAGE_NAME); 319 if (mode != AppOpsManager.MODE_ALLOWED) { 320 Rlog.e(LOG_TAG, PHONE_PACKAGE_NAME + " lost OP_WRITE_SMS: (fixing)"); 321 appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid, 322 PHONE_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED); 323 } 324 } catch (NameNotFoundException e) { 325 // No phone app on this device (unexpected, even for non-phone devices) 326 Rlog.e(LOG_TAG, "Phone package not found: " + PHONE_PACKAGE_NAME); 327 applicationData = null; 328 } 329 } 330 } 331 return applicationData; 332 } 333 334 /** 335 * Sets the specified package as the default SMS/MMS application. The caller of this method 336 * needs to have permission to set AppOps and write to secure settings. 337 */ 338 public static void setDefaultApplication(String packageName, Context context) { 339 TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); 340 if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) { 341 // No phone, no SMS 342 return; 343 } 344 345 // Get old package name 346 String oldPackageName = Settings.Secure.getString(context.getContentResolver(), 347 Settings.Secure.SMS_DEFAULT_APPLICATION); 348 349 if (packageName != null && oldPackageName != null && packageName.equals(oldPackageName)) { 350 // No change 351 return; 352 } 353 354 // We only make the change if the new package is valid 355 PackageManager packageManager = context.getPackageManager(); 356 Collection<SmsApplicationData> applications = getApplicationCollection(context); 357 SmsApplicationData applicationData = getApplicationForPackage(applications, packageName); 358 if (applicationData != null) { 359 // Ignore OP_WRITE_SMS for the previously configured default SMS app. 360 AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); 361 if (oldPackageName != null) { 362 try { 363 PackageInfo info = packageManager.getPackageInfo(oldPackageName, 364 PackageManager.GET_UNINSTALLED_PACKAGES); 365 appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid, 366 oldPackageName, AppOpsManager.MODE_IGNORED); 367 } catch (NameNotFoundException e) { 368 Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName); 369 } 370 } 371 372 // Update the secure setting. 373 Settings.Secure.putString(context.getContentResolver(), 374 Settings.Secure.SMS_DEFAULT_APPLICATION, applicationData.mPackageName); 375 376 // Allow OP_WRITE_SMS for the newly configured default SMS app. 377 appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid, 378 applicationData.mPackageName, AppOpsManager.MODE_ALLOWED); 379 380 // Phone needs to always have this permission to write to the sms database 381 try { 382 PackageInfo info = packageManager.getPackageInfo(PHONE_PACKAGE_NAME, 0); 383 appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid, 384 PHONE_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED); 385 } catch (NameNotFoundException e) { 386 // No phone app on this device (unexpected, even for non-phone devices) 387 Rlog.e(LOG_TAG, "Phone package not found: " + PHONE_PACKAGE_NAME); 388 } 389 } 390 } 391 392 /** 393 * Returns SmsApplicationData for this package if this package is capable of being set as the 394 * default SMS application. 395 */ 396 public static SmsApplicationData getSmsApplicationData(String packageName, Context context) { 397 Collection<SmsApplicationData> applications = getApplicationCollection(context); 398 return getApplicationForPackage(applications, packageName); 399 } 400 401 /** 402 * Gets the default SMS application 403 * @param context context from the calling app 404 * @param updateIfNeeded update the default app if there is no valid default app configured. 405 * @return component name of the app and class to deliver SMS messages to 406 */ 407 public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) { 408 ComponentName component = null; 409 SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded); 410 if (smsApplicationData != null) { 411 component = new ComponentName(smsApplicationData.mPackageName, 412 smsApplicationData.mSmsReceiverClass); 413 } 414 return component; 415 } 416 417 /** 418 * Gets the default MMS application 419 * @param context context from the calling app 420 * @param updateIfNeeded update the default app if there is no valid default app configured. 421 * @return component name of the app and class to deliver MMS messages to 422 */ 423 public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) { 424 ComponentName component = null; 425 SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded); 426 if (smsApplicationData != null) { 427 component = new ComponentName(smsApplicationData.mPackageName, 428 smsApplicationData.mMmsReceiverClass); 429 } 430 return component; 431 } 432 433 /** 434 * Gets the default Respond Via Message application 435 * @param context context from the calling app 436 * @param updateIfNeeded update the default app if there is no valid default app configured. 437 * @return component name of the app and class to direct Respond Via Message intent to 438 */ 439 public static ComponentName getDefaultRespondViaMessageApplication(Context context, 440 boolean updateIfNeeded) { 441 ComponentName component = null; 442 SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded); 443 if (smsApplicationData != null) { 444 component = new ComponentName(smsApplicationData.mPackageName, 445 smsApplicationData.mRespondViaMessageClass); 446 } 447 return component; 448 } 449 450 /** 451 * Gets the default Send To (smsto) application 452 * @param context context from the calling app 453 * @param updateIfNeeded update the default app if there is no valid default app configured. 454 * @return component name of the app and class to direct SEND_TO (smsto) intent to 455 */ 456 public static ComponentName getDefaultSendToApplication(Context context, 457 boolean updateIfNeeded) { 458 ComponentName component = null; 459 SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded); 460 if (smsApplicationData != null) { 461 component = new ComponentName(smsApplicationData.mPackageName, 462 smsApplicationData.mSendToClass); 463 } 464 return component; 465 } 466} 467