SmsApplication.java revision a2ce002a0327f0deb079cc6acad1c493e6ded442
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.AppGlobals;
21import android.app.AppOpsManager;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.pm.ActivityInfo;
27import android.content.pm.IPackageManager;
28import android.content.pm.PackageInfo;
29import android.content.pm.PackageManager;
30import android.content.pm.ResolveInfo;
31import android.content.pm.ServiceInfo;
32import android.content.pm.PackageManager.NameNotFoundException;
33import android.content.res.Resources;
34import android.net.Uri;
35import android.os.Binder;
36import android.os.Debug;
37import android.os.UserHandle;
38import android.provider.Settings;
39import android.provider.Telephony.Sms.Intents;
40import android.telephony.Rlog;
41import android.telephony.SmsManager;
42import android.telephony.TelephonyManager;
43import android.util.Log;
44
45import com.android.internal.content.PackageMonitor;
46
47import java.util.Collection;
48import java.util.HashMap;
49import java.util.List;
50
51/**
52 * Class for managing the primary application that we will deliver SMS/MMS messages to
53 *
54 * {@hide}
55 */
56public final class SmsApplication {
57    static final String LOG_TAG = "SmsApplication";
58    private static final String PHONE_PACKAGE_NAME = "com.android.phone";
59    private static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth";
60    private static final String MMS_SERVICE_PACKAGE_NAME = "com.android.mms.service";
61
62    private static final String SCHEME_SMS = "sms";
63    private static final String SCHEME_SMSTO = "smsto";
64    private static final String SCHEME_MMS = "mms";
65    private static final String SCHEME_MMSTO = "mmsto";
66    private static final boolean DEBUG_MULTIUSER = false;
67
68    private static SmsPackageMonitor sSmsPackageMonitor = null;
69
70    public static class SmsApplicationData {
71        /**
72         * Name of this SMS app for display.
73         */
74        public String mApplicationName;
75
76        /**
77         * Package name for this SMS app.
78         */
79        public String mPackageName;
80
81        /**
82         * The class name of the SMS_DELIVER_ACTION receiver in this app.
83         */
84        public String mSmsReceiverClass;
85
86        /**
87         * The class name of the WAP_PUSH_DELIVER_ACTION receiver in this app.
88         */
89        public String mMmsReceiverClass;
90
91        /**
92         * The class name of the ACTION_RESPOND_VIA_MESSAGE intent in this app.
93         */
94        public String mRespondViaMessageClass;
95
96        /**
97         * The class name of the ACTION_SENDTO intent in this app.
98         */
99        public String mSendToClass;
100
101        /**
102         * The user-id for this application
103         */
104        public int mUid;
105
106        /**
107         * Returns true if this SmsApplicationData is complete (all intents handled).
108         * @return
109         */
110        public boolean isComplete() {
111            return (mSmsReceiverClass != null && mMmsReceiverClass != null
112                    && mRespondViaMessageClass != null && mSendToClass != null);
113        }
114
115        public SmsApplicationData(String applicationName, String packageName, int uid) {
116            mApplicationName = applicationName;
117            mPackageName = packageName;
118            mUid = uid;
119        }
120    }
121
122    /**
123     * Returns the userId of the Context object, if called from a system app,
124     * otherwise it returns the caller's userId
125     * @param context The context object passed in by the caller.
126     * @return
127     */
128    private static int getIncomingUserId(Context context) {
129        int contextUserId = context.getUserId();
130        final int callingUid = Binder.getCallingUid();
131        if (DEBUG_MULTIUSER) {
132            Log.i(LOG_TAG, "getIncomingUserHandle caller=" + callingUid + ", myuid="
133                    + android.os.Process.myUid() + "\n\t" + Debug.getCallers(4));
134        }
135        if (UserHandle.getAppId(callingUid)
136                < android.os.Process.FIRST_APPLICATION_UID) {
137            return contextUserId;
138        } else {
139            return UserHandle.getUserId(callingUid);
140        }
141    }
142
143    /**
144     * Returns the list of available SMS apps defined as apps that are registered for both the
145     * SMS_RECEIVED_ACTION (SMS) and WAP_PUSH_RECEIVED_ACTION (MMS) broadcasts (and their broadcast
146     * receivers are enabled)
147     *
148     * Requirements to be an SMS application:
149     * Implement SMS_DELIVER_ACTION broadcast receiver.
150     * Require BROADCAST_SMS permission.
151     *
152     * Implement WAP_PUSH_DELIVER_ACTION broadcast receiver.
153     * Require BROADCAST_WAP_PUSH permission.
154     *
155     * Implement RESPOND_VIA_MESSAGE intent.
156     * Support smsto Uri scheme.
157     * Require SEND_RESPOND_VIA_MESSAGE permission.
158     *
159     * Implement ACTION_SENDTO intent.
160     * Support smsto Uri scheme.
161     */
162    public static Collection<SmsApplicationData> getApplicationCollection(Context context) {
163        int userId = getIncomingUserId(context);
164        final long token = Binder.clearCallingIdentity();
165        try {
166            return getApplicationCollectionInternal(context, userId);
167        } finally {
168            Binder.restoreCallingIdentity(token);
169        }
170    }
171
172    private static Collection<SmsApplicationData> getApplicationCollectionInternal(
173            Context context, int userId) {
174        PackageManager packageManager = context.getPackageManager();
175
176        // Get the list of apps registered for SMS
177        Intent intent = new Intent(Intents.SMS_DELIVER_ACTION);
178        List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceivers(intent, 0,
179                userId);
180
181        HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>();
182
183        // Add one entry to the map for every sms receiver (ignoring duplicate sms receivers)
184        for (ResolveInfo resolveInfo : smsReceivers) {
185            final ActivityInfo activityInfo = resolveInfo.activityInfo;
186            if (activityInfo == null) {
187                continue;
188            }
189            if (!permission.BROADCAST_SMS.equals(activityInfo.permission)) {
190                continue;
191            }
192            final String packageName = activityInfo.packageName;
193            if (!receivers.containsKey(packageName)) {
194                final String applicationName = resolveInfo.loadLabel(packageManager).toString();
195                final SmsApplicationData smsApplicationData = new SmsApplicationData(
196                        applicationName, packageName, activityInfo.applicationInfo.uid);
197                smsApplicationData.mSmsReceiverClass = activityInfo.name;
198                receivers.put(packageName, smsApplicationData);
199            }
200        }
201
202        // Update any existing entries with mms receiver class
203        intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
204        intent.setDataAndType(null, "application/vnd.wap.mms-message");
205        List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceivers(intent, 0,
206                userId);
207        for (ResolveInfo resolveInfo : mmsReceivers) {
208            final ActivityInfo activityInfo = resolveInfo.activityInfo;
209            if (activityInfo == null) {
210                continue;
211            }
212            if (!permission.BROADCAST_WAP_PUSH.equals(activityInfo.permission)) {
213                continue;
214            }
215            final String packageName = activityInfo.packageName;
216            final SmsApplicationData smsApplicationData = receivers.get(packageName);
217            if (smsApplicationData != null) {
218                smsApplicationData.mMmsReceiverClass = activityInfo.name;
219            }
220        }
221
222        // Update any existing entries with respond via message intent class.
223        intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE,
224                Uri.fromParts(SCHEME_SMSTO, "", null));
225        List<ResolveInfo> respondServices = packageManager.queryIntentServicesAsUser(intent, 0,
226                userId);
227        for (ResolveInfo resolveInfo : respondServices) {
228            final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
229            if (serviceInfo == null) {
230                continue;
231            }
232            if (!permission.SEND_RESPOND_VIA_MESSAGE.equals(serviceInfo.permission)) {
233                continue;
234            }
235            final String packageName = serviceInfo.packageName;
236            final SmsApplicationData smsApplicationData = receivers.get(packageName);
237            if (smsApplicationData != null) {
238                smsApplicationData.mRespondViaMessageClass = serviceInfo.name;
239            }
240        }
241
242        // Update any existing entries with supports send to.
243        intent = new Intent(Intent.ACTION_SENDTO,
244                Uri.fromParts(SCHEME_SMSTO, "", null));
245        List<ResolveInfo> sendToActivities = packageManager.queryIntentActivitiesAsUser(intent, 0,
246                userId);
247        for (ResolveInfo resolveInfo : sendToActivities) {
248            final ActivityInfo activityInfo = resolveInfo.activityInfo;
249            if (activityInfo == null) {
250                continue;
251            }
252            final String packageName = activityInfo.packageName;
253            final SmsApplicationData smsApplicationData = receivers.get(packageName);
254            if (smsApplicationData != null) {
255                smsApplicationData.mSendToClass = activityInfo.name;
256            }
257        }
258
259        // Remove any entries for which we did not find all required intents.
260        for (ResolveInfo resolveInfo : smsReceivers) {
261            final ActivityInfo activityInfo = resolveInfo.activityInfo;
262            if (activityInfo == null) {
263                continue;
264            }
265            final String packageName = activityInfo.packageName;
266            final SmsApplicationData smsApplicationData = receivers.get(packageName);
267            if (smsApplicationData != null) {
268                if (!smsApplicationData.isComplete()) {
269                    receivers.remove(packageName);
270                }
271            }
272        }
273        return receivers.values();
274    }
275
276    /**
277     * Checks to see if we have a valid installed SMS application for the specified package name
278     * @return Data for the specified package name or null if there isn't one
279     */
280    private static SmsApplicationData getApplicationForPackage(
281            Collection<SmsApplicationData> applications, String packageName) {
282        if (packageName == null) {
283            return null;
284        }
285        // Is there an entry in the application list for the specified package?
286        for (SmsApplicationData application : applications) {
287            if (application.mPackageName.contentEquals(packageName)) {
288                return application;
289            }
290        }
291        return null;
292    }
293
294    /**
295     * Get the application we will use for delivering SMS/MMS messages.
296     *
297     * We return the preferred sms application with the following order of preference:
298     * (1) User selected SMS app (if selected, and if still valid)
299     * (2) Android Messaging (if installed)
300     * (3) The currently configured highest priority broadcast receiver
301     * (4) Null
302     */
303    private static SmsApplicationData getApplication(Context context, boolean updateIfNeeded,
304            int userId) {
305        TelephonyManager tm = (TelephonyManager)
306                context.getSystemService(Context.TELEPHONY_SERVICE);
307        if (!tm.isSmsCapable()) {
308            // No phone, no SMS
309            return null;
310        }
311
312        Collection<SmsApplicationData> applications = getApplicationCollectionInternal(context,
313                userId);
314        if (DEBUG_MULTIUSER) {
315            Log.i(LOG_TAG, "getApplication userId=" + userId);
316        }
317        // Determine which application receives the broadcast
318        String defaultApplication = Settings.Secure.getStringForUser(context.getContentResolver(),
319                Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
320        if (DEBUG_MULTIUSER) {
321            Log.i(LOG_TAG, "getApplication defaultApp=" + defaultApplication);
322        }
323
324        SmsApplicationData applicationData = null;
325        if (defaultApplication != null) {
326            applicationData = getApplicationForPackage(applications, defaultApplication);
327        }
328        if (DEBUG_MULTIUSER) {
329            Log.i(LOG_TAG, "getApplication appData=" + applicationData);
330        }
331        // Picking a new SMS app requires AppOps and Settings.Secure permissions, so we only do
332        // this if the caller asked us to.
333        if (updateIfNeeded && applicationData == null) {
334            // Try to find the default SMS package for this device
335            Resources r = context.getResources();
336            String defaultPackage =
337                    r.getString(com.android.internal.R.string.default_sms_application);
338            applicationData = getApplicationForPackage(applications, defaultPackage);
339
340            if (applicationData == null) {
341                // Are there any applications?
342                if (applications.size() != 0) {
343                    applicationData = (SmsApplicationData)applications.toArray()[0];
344                }
345            }
346
347            // If we found a new default app, update the setting
348            if (applicationData != null) {
349                setDefaultApplicationInternal(applicationData.mPackageName, context, userId);
350            }
351        }
352
353        // If we found a package, make sure AppOps permissions are set up correctly
354        if (applicationData != null) {
355            AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
356
357            // We can only call checkOp if we are privileged (updateIfNeeded) or if the app we
358            // are checking is for our current uid. Doing this check from the unprivileged current
359            // SMS app allows us to tell the current SMS app that it is not in a good state and
360            // needs to ask to be the current SMS app again to work properly.
361            if (updateIfNeeded || applicationData.mUid == android.os.Process.myUid()) {
362                // Verify that the SMS app has permissions
363                int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
364                        applicationData.mPackageName);
365                if (mode != AppOpsManager.MODE_ALLOWED) {
366                    Rlog.e(LOG_TAG, applicationData.mPackageName + " lost OP_WRITE_SMS: " +
367                            (updateIfNeeded ? " (fixing)" : " (no permission to fix)"));
368                    if (updateIfNeeded) {
369                        appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
370                                applicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
371                    } else {
372                        // We can not return a package if permissions are not set up correctly
373                        applicationData = null;
374                    }
375                }
376            }
377
378            // We can only verify the phone and BT app's permissions from a privileged caller
379            if (updateIfNeeded) {
380                // Ensure this component is still configured as the preferred activity. Usually the
381                // current SMS app will already be the preferred activity - but checking whether or
382                // not this is true is just as expensive as reconfiguring the preferred activity so
383                // we just reconfigure every time.
384                PackageManager packageManager = context.getPackageManager();
385                configurePreferredActivity(packageManager, new ComponentName(
386                        applicationData.mPackageName, applicationData.mSendToClass),
387                        userId);
388                // Verify that the phone, BT app and MmsService have permissions
389                try {
390                    PackageInfo info = packageManager.getPackageInfo(PHONE_PACKAGE_NAME, 0);
391                    int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
392                            PHONE_PACKAGE_NAME);
393                    if (mode != AppOpsManager.MODE_ALLOWED) {
394                        Rlog.e(LOG_TAG, PHONE_PACKAGE_NAME + " lost OP_WRITE_SMS:  (fixing)");
395                        appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
396                                PHONE_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED);
397                    }
398                } catch (NameNotFoundException e) {
399                    // No phone app on this device (unexpected, even for non-phone devices)
400                    Rlog.e(LOG_TAG, "Phone package not found: " + PHONE_PACKAGE_NAME);
401                    applicationData = null;
402                }
403
404                try {
405                    PackageInfo info = packageManager.getPackageInfo(BLUETOOTH_PACKAGE_NAME, 0);
406                    int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
407                            BLUETOOTH_PACKAGE_NAME);
408                    if (mode != AppOpsManager.MODE_ALLOWED) {
409                        Rlog.e(LOG_TAG, BLUETOOTH_PACKAGE_NAME + " lost OP_WRITE_SMS:  (fixing)");
410                        appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
411                                BLUETOOTH_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED);
412                    }
413                } catch (NameNotFoundException e) {
414                    // No BT app on this device
415                    Rlog.e(LOG_TAG, "Bluetooth package not found: " + BLUETOOTH_PACKAGE_NAME);
416                }
417
418                try {
419                    PackageInfo info = packageManager.getPackageInfo(MMS_SERVICE_PACKAGE_NAME, 0);
420                    int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
421                            MMS_SERVICE_PACKAGE_NAME);
422                    if (mode != AppOpsManager.MODE_ALLOWED) {
423                        Rlog.e(LOG_TAG, MMS_SERVICE_PACKAGE_NAME + " lost OP_WRITE_SMS:  (fixing)");
424                        appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
425                                MMS_SERVICE_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED);
426                    }
427                } catch (NameNotFoundException e) {
428                    // No phone app on this device (unexpected, even for non-phone devices)
429                    Rlog.e(LOG_TAG, "MmsService package not found: " + MMS_SERVICE_PACKAGE_NAME);
430                    applicationData = null;
431                }
432
433            }
434        }
435        if (DEBUG_MULTIUSER) {
436            Log.i(LOG_TAG, "getApplication returning appData=" + applicationData);
437        }
438        return applicationData;
439    }
440
441    /**
442     * Sets the specified package as the default SMS/MMS application. The caller of this method
443     * needs to have permission to set AppOps and write to secure settings.
444     */
445    public static void setDefaultApplication(String packageName, Context context) {
446        TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
447        if (!tm.isSmsCapable()) {
448            // No phone, no SMS
449            return;
450        }
451
452        final int userId = getIncomingUserId(context);
453        final long token = Binder.clearCallingIdentity();
454        try {
455            setDefaultApplicationInternal(packageName, context, userId);
456        } finally {
457            Binder.restoreCallingIdentity(token);
458        }
459    }
460
461    private static void setDefaultApplicationInternal(String packageName, Context context,
462            int userId) {
463        // Get old package name
464        String oldPackageName = Settings.Secure.getStringForUser(context.getContentResolver(),
465                Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
466
467        if (packageName != null && oldPackageName != null && packageName.equals(oldPackageName)) {
468            // No change
469            return;
470        }
471
472        // We only make the change if the new package is valid
473        PackageManager packageManager = context.getPackageManager();
474        Collection<SmsApplicationData> applications = getApplicationCollection(context);
475        SmsApplicationData applicationData = getApplicationForPackage(applications, packageName);
476        if (applicationData != null) {
477            // Ignore OP_WRITE_SMS for the previously configured default SMS app.
478            AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
479            if (oldPackageName != null) {
480                try {
481                    PackageInfo info = packageManager.getPackageInfo(oldPackageName,
482                            PackageManager.GET_UNINSTALLED_PACKAGES);
483                    appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
484                            oldPackageName, AppOpsManager.MODE_IGNORED);
485                } catch (NameNotFoundException e) {
486                    Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
487                }
488            }
489
490            // Update the secure setting.
491            Settings.Secure.putStringForUser(context.getContentResolver(),
492                    Settings.Secure.SMS_DEFAULT_APPLICATION, applicationData.mPackageName,
493                    userId);
494
495            // Configure this as the preferred activity for SENDTO sms/mms intents
496            configurePreferredActivity(packageManager, new ComponentName(
497                    applicationData.mPackageName, applicationData.mSendToClass), userId);
498
499            // Allow OP_WRITE_SMS for the newly configured default SMS app.
500            appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
501                    applicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
502
503            // Phone needs to always have this permission to write to the sms database
504            try {
505                PackageInfo info = packageManager.getPackageInfo(PHONE_PACKAGE_NAME, 0);
506                appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
507                        PHONE_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED);
508            } catch (NameNotFoundException e) {
509                // No phone app on this device (unexpected, even for non-phone devices)
510                Rlog.e(LOG_TAG, "Phone package not found: " + PHONE_PACKAGE_NAME);
511            }
512
513            // BT needs to always have this permission to write to the sms database
514            try {
515                PackageInfo info = packageManager.getPackageInfo(BLUETOOTH_PACKAGE_NAME, 0);
516                appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
517                        BLUETOOTH_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED);
518            } catch (NameNotFoundException e) {
519                // No BT app on this device
520                Rlog.e(LOG_TAG, "Bluetooth package not found: " + BLUETOOTH_PACKAGE_NAME);
521            }
522
523            // MmsService needs to always have this permission to write to the sms database
524            try {
525                PackageInfo info = packageManager.getPackageInfo(MMS_SERVICE_PACKAGE_NAME, 0);
526                appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
527                        MMS_SERVICE_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED);
528            } catch (NameNotFoundException e) {
529                // No phone app on this device (unexpected, even for non-phone devices)
530                Rlog.e(LOG_TAG, "MmsService package not found: " + MMS_SERVICE_PACKAGE_NAME);
531            }
532        }
533    }
534
535    /**
536     * Tracks package changes and ensures that the default SMS app is always configured to be the
537     * preferred activity for SENDTO sms/mms intents.
538     */
539    private static final class SmsPackageMonitor extends PackageMonitor {
540        final Context mContext;
541
542        public SmsPackageMonitor(Context context) {
543            super();
544            mContext = context;
545        }
546
547        @Override
548        public void onPackageDisappeared(String packageName, int reason) {
549            onPackageChanged(packageName);
550        }
551
552        @Override
553        public void onPackageAppeared(String packageName, int reason) {
554            onPackageChanged(packageName);
555        }
556
557        @Override
558        public void onPackageModified(String packageName) {
559            onPackageChanged(packageName);
560        }
561
562        private void onPackageChanged(String packageName) {
563            PackageManager packageManager = mContext.getPackageManager();
564            Context userContext = mContext;
565            final int userId = getSendingUserId();
566            if (userId != UserHandle.USER_OWNER) {
567                try {
568                    userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0,
569                            new UserHandle(userId));
570                } catch (NameNotFoundException nnfe) {
571                    if (DEBUG_MULTIUSER) {
572                        Log.w(LOG_TAG, "Unable to create package context for user " + userId);
573                    }
574                }
575            }
576            // Ensure this component is still configured as the preferred activity
577            ComponentName componentName = getDefaultSendToApplication(userContext, true);
578            if (componentName != null) {
579                configurePreferredActivity(packageManager, componentName, userId);
580            }
581        }
582    }
583
584    public static void initSmsPackageMonitor(Context context) {
585        sSmsPackageMonitor = new SmsPackageMonitor(context);
586        sSmsPackageMonitor.register(context, context.getMainLooper(), UserHandle.ALL, false);
587    }
588
589    private static void configurePreferredActivity(PackageManager packageManager,
590            ComponentName componentName, int userId) {
591        // Add the four activity preferences we want to direct to this app.
592        replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMS);
593        replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMSTO);
594        replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMS);
595        replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMSTO);
596    }
597
598    /**
599     * Updates the ACTION_SENDTO activity to the specified component for the specified scheme.
600     */
601    private static void replacePreferredActivity(PackageManager packageManager,
602            ComponentName componentName, int userId, String scheme) {
603        // Build the set of existing activities that handle this scheme
604        Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(scheme, "", null));
605        List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivitiesAsUser(
606                intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER,
607                userId);
608
609        // Build the set of ComponentNames for these activities
610        final int n = resolveInfoList.size();
611        ComponentName[] set = new ComponentName[n];
612        for (int i = 0; i < n; i++) {
613            ResolveInfo info = resolveInfoList.get(i);
614            set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
615        }
616
617        // Update the preferred SENDTO activity for the specified scheme
618        IntentFilter intentFilter = new IntentFilter();
619        intentFilter.addAction(Intent.ACTION_SENDTO);
620        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
621        intentFilter.addDataScheme(scheme);
622        packageManager.replacePreferredActivityAsUser(intentFilter,
623                IntentFilter.MATCH_CATEGORY_SCHEME + IntentFilter.MATCH_ADJUSTMENT_NORMAL,
624                set, componentName, userId);
625    }
626
627    /**
628     * Returns SmsApplicationData for this package if this package is capable of being set as the
629     * default SMS application.
630     */
631    public static SmsApplicationData getSmsApplicationData(String packageName, Context context) {
632        Collection<SmsApplicationData> applications = getApplicationCollection(context);
633        return getApplicationForPackage(applications, packageName);
634    }
635
636    /**
637     * Gets the default SMS application
638     * @param context context from the calling app
639     * @param updateIfNeeded update the default app if there is no valid default app configured.
640     * @return component name of the app and class to deliver SMS messages to
641     */
642    public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) {
643        int userId = getIncomingUserId(context);
644        final long token = Binder.clearCallingIdentity();
645        try {
646            ComponentName component = null;
647            SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
648                    userId);
649            if (smsApplicationData != null) {
650                component = new ComponentName(smsApplicationData.mPackageName,
651                        smsApplicationData.mSmsReceiverClass);
652            }
653            return component;
654        } finally {
655            Binder.restoreCallingIdentity(token);
656        }
657    }
658
659    /**
660     * Gets the default MMS application
661     * @param context context from the calling app
662     * @param updateIfNeeded update the default app if there is no valid default app configured.
663     * @return component name of the app and class to deliver MMS messages to
664     */
665    public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) {
666        int userId = getIncomingUserId(context);
667        final long token = Binder.clearCallingIdentity();
668        try {
669            ComponentName component = null;
670            SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
671                    userId);
672            if (smsApplicationData != null) {
673                component = new ComponentName(smsApplicationData.mPackageName,
674                        smsApplicationData.mMmsReceiverClass);
675            }
676            return component;
677        } finally {
678            Binder.restoreCallingIdentity(token);
679        }
680    }
681
682    /**
683     * Gets the default Respond Via Message application
684     * @param context context from the calling app
685     * @param updateIfNeeded update the default app if there is no valid default app configured.
686     * @return component name of the app and class to direct Respond Via Message intent to
687     */
688    public static ComponentName getDefaultRespondViaMessageApplication(Context context,
689            boolean updateIfNeeded) {
690        int userId = getIncomingUserId(context);
691        final long token = Binder.clearCallingIdentity();
692        try {
693            ComponentName component = null;
694            SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
695                    userId);
696            if (smsApplicationData != null) {
697                component = new ComponentName(smsApplicationData.mPackageName,
698                        smsApplicationData.mRespondViaMessageClass);
699            }
700            return component;
701        } finally {
702            Binder.restoreCallingIdentity(token);
703        }
704    }
705
706    /**
707     * Gets the default Send To (smsto) application.
708     * <p>
709     * Caller must pass in the correct user context if calling from a singleton service.
710     * @param context context from the calling app
711     * @param updateIfNeeded update the default app if there is no valid default app configured.
712     * @return component name of the app and class to direct SEND_TO (smsto) intent to
713     */
714    public static ComponentName getDefaultSendToApplication(Context context,
715            boolean updateIfNeeded) {
716        int userId = getIncomingUserId(context);
717        final long token = Binder.clearCallingIdentity();
718        try {
719            ComponentName component = null;
720            SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
721                    userId);
722            if (smsApplicationData != null) {
723                component = new ComponentName(smsApplicationData.mPackageName,
724                        smsApplicationData.mSendToClass);
725            }
726            return component;
727        } finally {
728            Binder.restoreCallingIdentity(token);
729        }
730    }
731
732    /**
733     * Returns whether need to write the SMS message to SMS database for this package.
734     * <p>
735     * Caller must pass in the correct user context if calling from a singleton service.
736     */
737    public static boolean shouldWriteMessageForPackage(String packageName, Context context) {
738        if (packageName == null) return true;
739
740        if (SmsManager.getDefault().getAutoPersisting()) {
741            return true;
742        }
743
744        String defaultSmsPackage = null;
745        ComponentName component = getDefaultSmsApplication(context, false);
746        if (component != null) {
747            defaultSmsPackage = component.getPackageName();
748        }
749
750        if ((defaultSmsPackage == null || !defaultSmsPackage.equals(packageName)) &&
751                !packageName.equals(BLUETOOTH_PACKAGE_NAME)) {
752            // To write the message for someone other than the default SMS and BT app
753            return true;
754        }
755
756        return false;
757    }
758}
759