1 package com.android.providers.contacts;
2 
3 import android.app.BroadcastOptions;
4 import android.content.ComponentName;
5 import android.content.Context;
6 import android.content.Intent;
7 import android.content.pm.ActivityInfo;
8 import android.content.pm.ResolveInfo;
9 import android.net.Uri;
10 import android.os.Binder;
11 import android.provider.VoicemailContract;
12 import android.util.ArraySet;
13 import android.util.Log;
14 
15 import com.google.android.collect.Lists;
16 
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.List;
20 import java.util.Set;
21 
22 /**
23  * Aggregates voicemail broadcasts from multiple operations in to a single one. The URIs will be
24  * {@link VoicemailContract.Voicemails#DIR_TYPE} instead of {@link
25  * VoicemailContract.Voicemails#ITEM_TYPE} if multiple URIs is notified.
26  */
27 public class VoicemailNotifier {
28 
29     private final String TAG = "VoicemailNotifier";
30 
31     /**
32      * Grant recipients of new voicemail broadcasts a 10sec allowlist so they can start a background
33      * service to do VVM processing.
34      */
35     private final long VOICEMAIL_ALLOW_LIST_DURATION_MILLIS = 10000;
36 
37     private final Context mContext;
38     private final Uri mBaseUri;
39 
40     private final VoicemailPermissions mVoicemailPermissions;
41 
42     private final Set<String> mIntentActions = new ArraySet<>();
43     private final Set<String> mModifiedPackages = new ArraySet<>();
44     private final Set<Uri> mUris = new ArraySet<>();
45 
VoicemailNotifier(Context context, Uri baseUri)46     public VoicemailNotifier(Context context, Uri baseUri) {
47         mContext = context;
48         mBaseUri = baseUri;
49         mVoicemailPermissions = new VoicemailPermissions(mContext);
50     }
51 
addIntentActions(String action)52     public void addIntentActions(String action) {
53         mIntentActions.add(action);
54     }
55 
addModifiedPackages(Collection<String> packages)56     public void addModifiedPackages(Collection<String> packages) {
57         mModifiedPackages.addAll(packages);
58     }
59 
addUri(Uri uri)60     public void addUri(Uri uri) {
61         mUris.add(uri);
62     }
63 
sendNotification()64     public void sendNotification() {
65         Uri uri = mUris.size() == 1 ? mUris.iterator().next() : mBaseUri;
66         mContext.getContentResolver().notifyChange(uri, null, true);
67         Collection<String> callingPackages = getCallingPackages();
68         // Now fire individual intents.
69         for (String intentAction : mIntentActions) {
70             // self_change extra should be included only for provider_changed events.
71             boolean includeSelfChangeExtra = intentAction.equals(Intent.ACTION_PROVIDER_CHANGED);
72             Log.i(TAG, "receivers for " + intentAction + " :" + getBroadcastReceiverComponents(
73                     intentAction, uri));
74             for (ComponentName component :
75                     getBroadcastReceiverComponents(intentAction, uri)) {
76                 boolean hasFullReadAccess =
77                         mVoicemailPermissions.packageHasReadAccess(component.getPackageName());
78                 boolean hasOwnAccess =
79                         mVoicemailPermissions.packageHasOwnVoicemailAccess(
80                                 component.getPackageName());
81                 // If we don't have full access, ignore the broadcast if the package isn't affected
82                 // by the change or doesn't have access to its own messages.
83                 if (!hasFullReadAccess
84                         && (!mModifiedPackages.contains(component.getPackageName())
85                                 || !hasOwnAccess)) {
86                     continue;
87                 }
88 
89                 Intent intent = new Intent(intentAction, uri);
90                 intent.setComponent(component);
91                 if (includeSelfChangeExtra && callingPackages != null) {
92                     intent.putExtra(VoicemailContract.EXTRA_SELF_CHANGE,
93                             callingPackages.contains(component.getPackageName()));
94                 }
95                 if (intentAction.equals(VoicemailContract.ACTION_NEW_VOICEMAIL)) {
96                     BroadcastOptions bopts = BroadcastOptions.makeBasic();
97                     bopts.setTemporaryAppWhitelistDuration(VOICEMAIL_ALLOW_LIST_DURATION_MILLIS);
98                     Log.i(TAG, String.format("sendNotification: allowMillis=%d, pkg=%s",
99                             VOICEMAIL_ALLOW_LIST_DURATION_MILLIS, component.getPackageName()));
100                     mContext.sendBroadcast(intent, android.Manifest.permission.READ_VOICEMAIL,
101                             bopts.toBundle());
102                 } else {
103                     mContext.sendBroadcast(intent);
104                 }
105 
106                 Log.v(TAG, String.format("Sent intent. act:%s, url:%s, comp:%s," +
107                                 " self_change:%s", intent.getAction(), intent.getData(),
108                         component.getClassName(),
109                         intent.hasExtra(VoicemailContract.EXTRA_SELF_CHANGE) ?
110                                 intent.getBooleanExtra(VoicemailContract.EXTRA_SELF_CHANGE, false) :
111                                 null));
112             }
113         }
114         mIntentActions.clear();
115         mModifiedPackages.clear();
116         mUris.clear();
117     }
118 
119     /**
120      * Returns the package names of the calling process. If the calling process has more than
121      * one packages, this returns them all
122      */
getCallingPackages()123     private Collection<String> getCallingPackages() {
124         int caller = Binder.getCallingUid();
125         if (caller == 0) {
126             return null;
127         }
128         return Lists.newArrayList(mContext.getPackageManager().getPackagesForUid(caller));
129     }
130 
131     /**
132      * Determines the components that can possibly receive the specified intent.
133      */
getBroadcastReceiverComponents(String intentAction, Uri uri)134     private List<ComponentName> getBroadcastReceiverComponents(String intentAction, Uri uri) {
135         Intent intent = new Intent(intentAction, uri);
136         List<ComponentName> receiverComponents = new ArrayList<ComponentName>();
137         // For broadcast receivers ResolveInfo.activityInfo is the one that is populated.
138         for (ResolveInfo resolveInfo :
139                 mContext.getPackageManager().queryBroadcastReceivers(intent, 0)) {
140             ActivityInfo activityInfo = resolveInfo.activityInfo;
141             receiverComponents.add(new ComponentName(activityInfo.packageName, activityInfo.name));
142         }
143         return receiverComponents;
144     }
145 }
146