c01c9f02cfd3f2fe1a57cd1f6ba5c8068357134f
[android/platform/frameworks/opt/telephony.git] / src / java / com / android / internal / telephony / SmsApplication.java
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
17 package com.android.internal.telephony;
18
19 import android.Manifest.permission;
20 import android.app.AppOpsManager;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.ActivityInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.content.pm.ServiceInfo;
28 import android.content.res.Resources;
29 import android.net.Uri;
30 import android.provider.Settings;
31 import android.provider.Telephony.Sms.Intents;
32 import android.telephony.TelephonyManager;
33
34 import java.util.Collection;
35 import java.util.HashMap;
36 import java.util.List;
37
38 /**
39  * Class for managing the primary application that we will deliver SMS/MMS messages to
40  *
41  * {@hide}
42  */
43 public 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 resolveInfo : smsReceivers) {
199             final ActivityInfo activityInfo = resolveInfo.activityInfo;
200             if (activityInfo == null) {
201                 continue;
202             }
203             final String packageName = activityInfo.packageName;
204             final SmsApplicationData smsApplicationData = receivers.get(packageName);
205             if (smsApplicationData != null) {
206                 if (!smsApplicationData.isComplete()) {
207                     receivers.remove(packageName);
208                 }
209             }
210         }
211         return receivers.values();
212     }
213
214     /**
215      * Checks to see if we have a valid installed SMS application for the specified package name
216      * @return Data for the specified package name or null if there isn't one
217      */
218     private static SmsApplicationData getApplicationForPackage(
219             Collection<SmsApplicationData> applications, String packageName) {
220         if (packageName == null) {
221             return null;
222         }
223         // Is there an entry in the application list for the specified package?
224         for (SmsApplicationData application : applications) {
225             if (application.mPackageName.contentEquals(packageName)) {
226                 return application;
227             }
228         }
229         return null;
230     }
231
232     /**
233      * Get the application we will use for delivering SMS/MMS messages.
234      *
235      * We return the preferred sms application with the following order of preference:
236      * (1) User selected SMS app (if selected, and if still valid)
237      * (2) Android Messaging (if installed)
238      * (3) The currently configured highest priority broadcast receiver
239      * (4) Null
240      */
241     private static SmsApplicationData getApplication(Context context, boolean updateIfNeeded) {
242         TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
243         if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
244             // No phone, no SMS
245             return null;
246         }
247
248         Collection<SmsApplicationData> applications = getApplicationCollection(context);
249
250         // Determine which application receives the broadcast
251         String defaultApplication = Settings.Secure.getString(context.getContentResolver(),
252                 Settings.Secure.SMS_DEFAULT_APPLICATION);
253
254         SmsApplicationData applicationData = null;
255         if (defaultApplication != null) {
256             applicationData = getApplicationForPackage(applications, defaultApplication);
257         }
258         // Picking a new SMS app requires AppOps and Settings.Secure permissions, so we only do
259         // this if the caller asked us to.
260         if (updateIfNeeded) {
261             if (applicationData == null) {
262                 // Try to find the default SMS package for this device
263                 Resources r = context.getResources();
264                 String defaultPackage =
265                         r.getString(com.android.internal.R.string.default_sms_application);
266                 applicationData = getApplicationForPackage(applications, defaultPackage);
267             }
268             if (applicationData == null) {
269                 // Are there any applications?
270                 if (applications.size() != 0) {
271                     applicationData = (SmsApplicationData)applications.toArray()[0];
272                 }
273             }
274
275             // If we found a new default app, update the setting
276             if (applicationData != null) {
277                 setDefaultApplication(applicationData.mPackageName, context);
278             }
279         }
280         return applicationData;
281     }
282
283     /**
284      * Sets the specified package as the default SMS/MMS application. The caller of this method
285      * needs to have permission to set AppOps and write to secure settings.
286      */
287     public static void setDefaultApplication(String packageName, Context context) {
288         TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
289         if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
290             // No phone, no SMS
291             return;
292         }
293
294         Collection<SmsApplicationData> applications = getApplicationCollection(context);
295         String oldPackageName = Settings.Secure.getString(context.getContentResolver(),
296                 Settings.Secure.SMS_DEFAULT_APPLICATION);
297         SmsApplicationData oldSmsApplicationData = getApplicationForPackage(applications,
298                 oldPackageName);
299         SmsApplicationData smsApplicationData = getApplicationForPackage(applications,
300                 packageName);
301
302         if (smsApplicationData != null && smsApplicationData != oldSmsApplicationData) {
303             // Ignore OP_WRITE_SMS for the previously configured default SMS app.
304             AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
305             if (oldSmsApplicationData != null) {
306                 appOps.setMode(AppOpsManager.OP_WRITE_SMS, oldSmsApplicationData.mUid,
307                         oldSmsApplicationData.mPackageName, AppOpsManager.MODE_IGNORED);
308             }
309
310             // Update the secure setting.
311             Settings.Secure.putString(context.getContentResolver(),
312                     Settings.Secure.SMS_DEFAULT_APPLICATION, smsApplicationData.mPackageName);
313
314             // Allow OP_WRITE_SMS for the newly configured default SMS app.
315             appOps.setMode(AppOpsManager.OP_WRITE_SMS, smsApplicationData.mUid,
316                     smsApplicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
317         }
318     }
319
320     /**
321      * Returns SmsApplicationData for this package if this package is capable of being set as the
322      * default SMS application.
323      */
324     public static SmsApplicationData getSmsApplicationData(String packageName, Context context) {
325         Collection<SmsApplicationData> applications = getApplicationCollection(context);
326         return getApplicationForPackage(applications, packageName);
327     }
328
329     /**
330      * Gets the default SMS 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 SMS messages to
334      */
335     public static ComponentName getDefaultSmsApplication(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.mSmsReceiverClass);
341         }
342         return component;
343     }
344
345     /**
346      * Gets the default MMS 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 deliver MMS messages to
350      */
351     public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) {
352         ComponentName component = null;
353         SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded);
354         if (smsApplicationData != null) {
355             component = new ComponentName(smsApplicationData.mPackageName,
356                     smsApplicationData.mMmsReceiverClass);
357         }
358         return component;
359     }
360
361     /**
362      * Gets the default Respond Via Message application
363      * @param context context from the calling app
364      * @param updateIfNeeded update the default app if there is no valid default app configured.
365      * @return component name of the app and class to direct Respond Via Message intent to
366      */
367     public static ComponentName getDefaultRespondViaMessageApplication(Context context,
368             boolean updateIfNeeded) {
369         ComponentName component = null;
370         SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded);
371         if (smsApplicationData != null) {
372             component = new ComponentName(smsApplicationData.mPackageName,
373                     smsApplicationData.mRespondViaMessageClass);
374         }
375         return component;
376     }
377
378     /**
379      * Gets the default Send To (smsto) application
380      * @param context context from the calling app
381      * @param updateIfNeeded update the default app if there is no valid default app configured.
382      * @return component name of the app and class to direct SEND_TO (smsto) intent to
383      */
384     public static ComponentName getDefaultSendToApplication(Context context,
385             boolean updateIfNeeded) {
386         ComponentName component = null;
387         SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded);
388         if (smsApplicationData != null) {
389             component = new ComponentName(smsApplicationData.mPackageName,
390                     smsApplicationData.mSendToClass);
391         }
392         return component;
393     }
394 }