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