| package com.android.providers.contacts; |
| |
| import android.app.BroadcastOptions; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ActivityInfo; |
| import android.content.pm.ResolveInfo; |
| import android.net.Uri; |
| import android.os.Binder; |
| import android.provider.VoicemailContract; |
| import android.util.ArraySet; |
| import android.util.Log; |
| |
| import com.google.android.collect.Lists; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Aggregates voicemail broadcasts from multiple operations in to a single one. The URIs will be |
| * {@link VoicemailContract.Voicemails#DIR_TYPE} instead of {@link |
| * VoicemailContract.Voicemails#ITEM_TYPE} if multiple URIs is notified. |
| */ |
| public class VoicemailNotifier { |
| |
| private final String TAG = "VoicemailNotifier"; |
| |
| /** |
| * Grant recipients of new voicemail broadcasts a 10sec allowlist so they can start a background |
| * service to do VVM processing. |
| */ |
| private final long VOICEMAIL_ALLOW_LIST_DURATION_MILLIS = 10000; |
| |
| private final Context mContext; |
| private final Uri mBaseUri; |
| |
| private final VoicemailPermissions mVoicemailPermissions; |
| |
| private final Set<String> mIntentActions = new ArraySet<>(); |
| private final Set<String> mModifiedPackages = new ArraySet<>(); |
| private final Set<Uri> mUris = new ArraySet<>(); |
| |
| public VoicemailNotifier(Context context, Uri baseUri) { |
| mContext = context; |
| mBaseUri = baseUri; |
| mVoicemailPermissions = new VoicemailPermissions(mContext); |
| } |
| |
| public void addIntentActions(String action) { |
| mIntentActions.add(action); |
| } |
| |
| public void addModifiedPackages(Collection<String> packages) { |
| mModifiedPackages.addAll(packages); |
| } |
| |
| public void addUri(Uri uri) { |
| mUris.add(uri); |
| } |
| |
| public void sendNotification() { |
| Uri uri = mUris.size() == 1 ? mUris.iterator().next() : mBaseUri; |
| mContext.getContentResolver().notifyChange(uri, null, true); |
| Collection<String> callingPackages = getCallingPackages(); |
| // Now fire individual intents. |
| for (String intentAction : mIntentActions) { |
| // self_change extra should be included only for provider_changed events. |
| boolean includeSelfChangeExtra = intentAction.equals(Intent.ACTION_PROVIDER_CHANGED); |
| Log.i(TAG, "receivers for " + intentAction + " :" + getBroadcastReceiverComponents( |
| intentAction, uri)); |
| for (ComponentName component : |
| getBroadcastReceiverComponents(intentAction, uri)) { |
| boolean hasFullReadAccess = |
| mVoicemailPermissions.packageHasReadAccess(component.getPackageName()); |
| boolean hasOwnAccess = |
| mVoicemailPermissions.packageHasOwnVoicemailAccess( |
| component.getPackageName()); |
| // If we don't have full access, ignore the broadcast if the package isn't affected |
| // by the change or doesn't have access to its own messages. |
| if (!hasFullReadAccess |
| && (!mModifiedPackages.contains(component.getPackageName()) |
| || !hasOwnAccess)) { |
| continue; |
| } |
| |
| Intent intent = new Intent(intentAction, uri); |
| intent.setComponent(component); |
| if (includeSelfChangeExtra && callingPackages != null) { |
| intent.putExtra(VoicemailContract.EXTRA_SELF_CHANGE, |
| callingPackages.contains(component.getPackageName())); |
| } |
| if (intentAction.equals(VoicemailContract.ACTION_NEW_VOICEMAIL)) { |
| BroadcastOptions bopts = BroadcastOptions.makeBasic(); |
| bopts.setTemporaryAppWhitelistDuration(VOICEMAIL_ALLOW_LIST_DURATION_MILLIS); |
| Log.i(TAG, String.format("sendNotification: allowMillis=%d, pkg=%s", |
| VOICEMAIL_ALLOW_LIST_DURATION_MILLIS, component.getPackageName())); |
| mContext.sendBroadcast(intent, android.Manifest.permission.READ_VOICEMAIL, |
| bopts.toBundle()); |
| } else { |
| mContext.sendBroadcast(intent); |
| } |
| |
| Log.v(TAG, String.format("Sent intent. act:%s, url:%s, comp:%s," + |
| " self_change:%s", intent.getAction(), intent.getData(), |
| component.getClassName(), |
| intent.hasExtra(VoicemailContract.EXTRA_SELF_CHANGE) ? |
| intent.getBooleanExtra(VoicemailContract.EXTRA_SELF_CHANGE, false) : |
| null)); |
| } |
| } |
| mIntentActions.clear(); |
| mModifiedPackages.clear(); |
| mUris.clear(); |
| } |
| |
| /** |
| * Returns the package names of the calling process. If the calling process has more than |
| * one packages, this returns them all |
| */ |
| private Collection<String> getCallingPackages() { |
| int caller = Binder.getCallingUid(); |
| if (caller == 0) { |
| return null; |
| } |
| return Lists.newArrayList(mContext.getPackageManager().getPackagesForUid(caller)); |
| } |
| |
| /** |
| * Determines the components that can possibly receive the specified intent. |
| */ |
| private List<ComponentName> getBroadcastReceiverComponents(String intentAction, Uri uri) { |
| Intent intent = new Intent(intentAction, uri); |
| List<ComponentName> receiverComponents = new ArrayList<ComponentName>(); |
| // For broadcast receivers ResolveInfo.activityInfo is the one that is populated. |
| for (ResolveInfo resolveInfo : |
| mContext.getPackageManager().queryBroadcastReceivers(intent, 0)) { |
| ActivityInfo activityInfo = resolveInfo.activityInfo; |
| receiverComponents.add(new ComponentName(activityInfo.packageName, activityInfo.name)); |
| } |
| return receiverComponents; |
| } |
| } |