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