SmsApplication.java revision c67761d00a25bd095364e3ff4e4cb8e88b2e974c
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