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