SmsApplication.java revision 9c9341ecba5312a4a5be5f9fee3ffe1b582b65ce
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.app.AppOpsManager;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.content.pm.ResolveInfo;
25import android.content.res.Resources;
26import android.provider.Settings;
27import android.provider.Telephony.Sms.Intents;
28
29import java.util.Collection;
30import java.util.HashMap;
31import java.util.List;
32
33/**
34 * Class for managing the primary application that we will deliver SMS/MMS messages to
35 *
36 * {@hide}
37 */
38public final class SmsApplication {
39    public static class SmsApplicationData {
40        /**
41         * Name of this SMS app for display.
42         */
43        public String mApplicationName;
44
45        /**
46         * Package name for this SMS app.
47         */
48        public String mPackageName;
49
50        /**
51         * The class name of the SMS receiver in this app.
52         */
53        public String mSmsReceiverClass;
54
55        /**
56         * The class name of the MMS receiver in this app.
57         */
58        public String mMmsReceiverClass;
59
60        /**
61         * The user-id for this application
62         */
63        public int mUid;
64
65        public SmsApplicationData(String applicationName, String packageName,
66                String smsReceiverName, String mmsReceiverName, int uid) {
67            mApplicationName = applicationName;
68            mPackageName = packageName;
69            mSmsReceiverClass = smsReceiverName;
70            mMmsReceiverClass = mmsReceiverName;
71            mUid = uid;
72        }
73    }
74
75    /**
76     * Returns the list of available SMS apps defined as apps that are registered for both the
77     * SMS_RECEIVED_ACTION (SMS) and WAP_PUSH_RECEIVED_ACTION (MMS) broadcasts (and their broadcast
78     * receivers are enabled)
79     */
80    public static Collection<SmsApplicationData> getApplicationCollection(Context context) {
81        PackageManager packageManager = context.getPackageManager();
82
83        // Get the list of apps registered for SMS
84        Intent intent = new Intent(Intents.SMS_DELIVER_ACTION);
85        int flags = 0;
86        List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceivers(intent, flags);
87
88        intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
89        intent.setDataAndType(null, "application/vnd.wap.mms-message");
90        List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceivers(intent, flags);
91
92        HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>();
93
94        // Add one entry to the map for every sms receiver (ignoring duplicate sms receivers)
95        for (ResolveInfo r : smsReceivers) {
96            String packageName = r.activityInfo.packageName;
97            if (!receivers.containsKey(packageName)) {
98                String applicationName = r.loadLabel(packageManager).toString();
99                SmsApplicationData smsApplicationData = new SmsApplicationData(applicationName,
100                        packageName, r.activityInfo.name, null, r.activityInfo.applicationInfo.uid);
101                receivers.put(packageName, smsApplicationData);
102            }
103        }
104
105        // Update any existing entries with mms receiver class
106        for (ResolveInfo r : mmsReceivers) {
107            String packageName = r.activityInfo.packageName;
108            SmsApplicationData smsApplicationData = receivers.get(packageName);
109            if (smsApplicationData != null) {
110                smsApplicationData.mMmsReceiverClass = r.activityInfo.name;
111            }
112        }
113
114        // Remove any entries (which we added for sms receivers) for which we did not also find
115        // valid mms receivers
116        for (ResolveInfo r : smsReceivers) {
117            String packageName = r.activityInfo.packageName;
118            SmsApplicationData smsApplicationData = receivers.get(packageName);
119            if (smsApplicationData != null && smsApplicationData.mMmsReceiverClass == null) {
120                receivers.remove(packageName);
121            }
122        }
123        return receivers.values();
124    }
125
126    /**
127     * Checks to see if we have a valid installed SMS application for the specified package name
128     * @return Data for the specified package name or null if there isn't one
129     */
130    private static SmsApplicationData getApplicationForPackage(
131            Collection<SmsApplicationData> applications, String packageName) {
132        if (packageName == null) {
133            return null;
134        }
135        // Is there an entry in the application list for the specified package?
136        for (SmsApplicationData application : applications) {
137            if (application.mPackageName.contentEquals(packageName)) {
138                return application;
139            }
140        }
141        return null;
142    }
143
144    /**
145     * Get the application we will use for delivering SMS/MMS messages.
146     *
147     * We return the preferred sms application with the following order of preference:
148     * (1) User selected SMS app (if selected, and if still valid)
149     * (2) Android Messaging (if installed)
150     * (3) The currently configured highest priority broadcast receiver
151     * (4) Null
152     */
153    private static SmsApplicationData getApplication(Context context, boolean updateIfNeeded) {
154        Collection<SmsApplicationData> applications = getApplicationCollection(context);
155
156        // Determine which application receives the broadcast
157        String defaultApplication = Settings.Secure.getString(context.getContentResolver(),
158                Settings.Secure.SMS_DEFAULT_APPLICATION);
159
160        SmsApplicationData applicationData = null;
161        if (defaultApplication != null) {
162            applicationData = getApplicationForPackage(applications, defaultApplication);
163        }
164        // Picking a new SMS app requires AppOps and Settings.Secure permissions, so we only do
165        // this if the caller asked us to.
166        if (updateIfNeeded) {
167            if (applicationData == null) {
168                // Try to find the default SMS package for this device
169                Resources r = context.getResources();
170                String defaultPackage =
171                        r.getString(com.android.internal.R.string.default_sms_application);
172                applicationData = getApplicationForPackage(applications, defaultPackage);
173            }
174            if (applicationData == null) {
175                // Are there any applications?
176                if (applications.size() != 0) {
177                    applicationData = (SmsApplicationData)applications.toArray()[0];
178                }
179            }
180
181            // If we found a new default app, update the setting
182            if (applicationData != null) {
183                setDefaultApplication(applicationData.mPackageName, context);
184            }
185        }
186        return applicationData;
187    }
188
189    /**
190     * Sets the specified package as the default SMS/MMS application. The caller of this method
191     * needs to have permission to set AppOps and write to secure settings.
192     */
193    public static void setDefaultApplication(String packageName, Context context) {
194        Collection<SmsApplicationData> applications = getApplicationCollection(context);
195        String oldPackageName = Settings.Secure.getString(context.getContentResolver(),
196                Settings.Secure.SMS_DEFAULT_APPLICATION);
197        SmsApplicationData oldSmsApplicationData = getApplicationForPackage(applications,
198                oldPackageName);
199        SmsApplicationData smsApplicationData = getApplicationForPackage(applications,
200                packageName);
201
202        if (smsApplicationData != null && smsApplicationData != oldSmsApplicationData) {
203            // Ignore OP_WRITE_SMS for the previously configured default SMS app.
204            AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
205            if (oldSmsApplicationData != null) {
206                appOps.setMode(AppOpsManager.OP_WRITE_SMS, oldSmsApplicationData.mUid,
207                        oldSmsApplicationData.mPackageName, AppOpsManager.MODE_IGNORED);
208            }
209
210            // Update the secure setting.
211            Settings.Secure.putString(context.getContentResolver(),
212                    Settings.Secure.SMS_DEFAULT_APPLICATION, smsApplicationData.mPackageName);
213
214            // Allow OP_WRITE_SMS for the newly configured default SMS app.
215            appOps.setMode(AppOpsManager.OP_WRITE_SMS, smsApplicationData.mUid,
216                    smsApplicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
217        }
218    }
219
220    /**
221     * Returns SmsApplicationData for this package if this package is capable of being set as the
222     * default SMS application.
223     */
224    public static SmsApplicationData getSmsApplicationData(String packageName, Context context) {
225        Collection<SmsApplicationData> applications = getApplicationCollection(context);
226        return getApplicationForPackage(applications, packageName);
227    }
228
229    /**
230     * Gets the default SMS application
231     * @param context context from the calling app
232     * @param updateIfNeeded update the default app if there is no valid default app configured.
233     * @return component name of the app and class to deliver SMS messages to
234     */
235    public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) {
236        ComponentName component = null;
237        SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded);
238        if (smsApplicationData != null) {
239            component = new ComponentName(smsApplicationData.mPackageName,
240                    smsApplicationData.mSmsReceiverClass);
241        }
242        return component;
243    }
244
245    /**
246     * Gets the default MMS application
247     * @param context context from the calling app
248     * @param updateIfNeeded update the default app if there is no valid default app configured.
249     * @return component name of the app and class to deliver MMS messages to
250     */
251    public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) {
252        ComponentName component = null;
253        SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded);
254        if (smsApplicationData != null) {
255            component = new ComponentName(smsApplicationData.mPackageName,
256                    smsApplicationData.mMmsReceiverClass);
257        }
258        return component;
259    }
260}
261