Update the requirements for being an SMS app in KLP
[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 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 }