summaryrefslogtreecommitdiffstats
path: root/src/com/android/providers/contacts/DbModifierWithNotification.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/providers/contacts/DbModifierWithNotification.java')
-rw-r--r--src/com/android/providers/contacts/DbModifierWithNotification.java260
1 files changed, 260 insertions, 0 deletions
diff --git a/src/com/android/providers/contacts/DbModifierWithNotification.java b/src/com/android/providers/contacts/DbModifierWithNotification.java
new file mode 100644
index 0000000..c13f4a8
--- /dev/null
+++ b/src/com/android/providers/contacts/DbModifierWithNotification.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.providers.contacts;
+
+import static android.Manifest.permission.ADD_VOICEMAIL;
+import static com.android.providers.contacts.Manifest.permission.READ_WRITE_ALL_VOICEMAIL;
+
+import com.android.common.io.MoreCloseables;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.util.DbQueryUtils;
+import com.google.android.collect.Lists;
+
+import android.content.ComponentName;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.database.DatabaseUtils.InsertHelper;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.Binder;
+import android.provider.CallLog.Calls;
+import android.provider.VoicemailContract;
+import android.provider.VoicemailContract.Status;
+import android.provider.VoicemailContract.Voicemails;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * An implementation of {@link DatabaseModifier} for voicemail related tables which additionally
+ * generates necessary notifications after the modification operation is performed.
+ * The class generates notifications for both voicemail as well as call log URI depending on which
+ * of then got affected by the change.
+ */
+public class DbModifierWithNotification implements DatabaseModifier {
+ private static final String TAG = "DbModifierWithVmNotification";
+
+ private static final String[] PROJECTION = new String[] {
+ VoicemailContract.SOURCE_PACKAGE_FIELD
+ };
+ private static final int SOURCE_PACKAGE_COLUMN_INDEX = 0;
+ private static final String NON_NULL_SOURCE_PACKAGE_SELECTION =
+ VoicemailContract.SOURCE_PACKAGE_FIELD + " IS NOT NULL";
+
+ private final String mTableName;
+ private final SQLiteDatabase mDb;
+ private final InsertHelper mInsertHelper;
+ private final Context mContext;
+ private final Uri mBaseUri;
+ private final boolean mIsCallsTable;
+ private final VoicemailPermissions mVoicemailPermissions;
+
+ public DbModifierWithNotification(String tableName, SQLiteDatabase db, Context context) {
+ this(tableName, db, null, context);
+ }
+
+ public DbModifierWithNotification(String tableName, InsertHelper insertHelper,
+ Context context) {
+ this(tableName, null, insertHelper, context);
+ }
+
+ private DbModifierWithNotification(String tableName, SQLiteDatabase db,
+ InsertHelper insertHelper, Context context) {
+ mTableName = tableName;
+ mDb = db;
+ mInsertHelper = insertHelper;
+ mContext = context;
+ mBaseUri = mTableName.equals(Tables.VOICEMAIL_STATUS) ?
+ Status.CONTENT_URI : Voicemails.CONTENT_URI;
+ mIsCallsTable = mTableName.equals(Tables.CALLS);
+ mVoicemailPermissions = new VoicemailPermissions(mContext);
+ }
+
+ @Override
+ public long insert(String table, String nullColumnHack, ContentValues values) {
+ Set<String> packagesModified = getModifiedPackages(values);
+ long rowId = mDb.insert(table, nullColumnHack, values);
+ if (rowId > 0 && packagesModified.size() != 0) {
+ notifyVoicemailChangeOnInsert(ContentUris.withAppendedId(mBaseUri, rowId),
+ packagesModified);
+ }
+ if (rowId > 0 && mIsCallsTable) {
+ notifyCallLogChange();
+ }
+ return rowId;
+ }
+
+ @Override
+ public long insert(ContentValues values) {
+ Set<String> packagesModified = getModifiedPackages(values);
+ long rowId = mInsertHelper.insert(values);
+ if (rowId > 0 && packagesModified.size() != 0) {
+ notifyVoicemailChangeOnInsert(
+ ContentUris.withAppendedId(mBaseUri, rowId), packagesModified);
+ }
+ if (rowId > 0 && mIsCallsTable) {
+ notifyCallLogChange();
+ }
+ return rowId;
+ }
+
+ private void notifyCallLogChange() {
+ mContext.getContentResolver().notifyChange(Calls.CONTENT_URI, null, false);
+ }
+
+ private void notifyVoicemailChangeOnInsert(Uri notificationUri, Set<String> packagesModified) {
+ if (mIsCallsTable) {
+ notifyVoicemailChange(notificationUri, packagesModified,
+ VoicemailContract.ACTION_NEW_VOICEMAIL, Intent.ACTION_PROVIDER_CHANGED);
+ } else {
+ notifyVoicemailChange(notificationUri, packagesModified,
+ Intent.ACTION_PROVIDER_CHANGED);
+ }
+ }
+
+ @Override
+ public int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
+ Set<String> packagesModified = getModifiedPackages(whereClause, whereArgs);
+ packagesModified.addAll(getModifiedPackages(values));
+ int count = mDb.update(table, values, whereClause, whereArgs);
+ if (count > 0 && packagesModified.size() != 0) {
+ notifyVoicemailChange(mBaseUri, packagesModified, Intent.ACTION_PROVIDER_CHANGED);
+ }
+ if (count > 0 && mIsCallsTable) {
+ notifyCallLogChange();
+ }
+ return count;
+ }
+
+ @Override
+ public int delete(String table, String whereClause, String[] whereArgs) {
+ Set<String> packagesModified = getModifiedPackages(whereClause, whereArgs);
+ int count = mDb.delete(table, whereClause, whereArgs);
+ if (count > 0 && packagesModified.size() != 0) {
+ notifyVoicemailChange(mBaseUri, packagesModified, Intent.ACTION_PROVIDER_CHANGED);
+ }
+ if (count > 0 && mIsCallsTable) {
+ notifyCallLogChange();
+ }
+ return count;
+ }
+
+ /**
+ * Returns the set of packages affected when a modify operation is run for the specified
+ * where clause. When called from an insert operation an empty set returned by this method
+ * implies (indirectly) that this does not affect any voicemail entry, as a voicemail entry is
+ * always expected to have the source package field set.
+ */
+ private Set<String> getModifiedPackages(String whereClause, String[] whereArgs) {
+ Set<String> modifiedPackages = new HashSet<String>();
+ Cursor cursor = mDb.query(mTableName, PROJECTION,
+ DbQueryUtils.concatenateClauses(NON_NULL_SOURCE_PACKAGE_SELECTION, whereClause),
+ whereArgs, null, null, null);
+ while(cursor.moveToNext()) {
+ modifiedPackages.add(cursor.getString(SOURCE_PACKAGE_COLUMN_INDEX));
+ }
+ MoreCloseables.closeQuietly(cursor);
+ return modifiedPackages;
+ }
+
+ /**
+ * Returns the source package that gets affected (in an insert/update operation) by the supplied
+ * content values. An empty set returned by this method also implies (indirectly) that this does
+ * not affect any voicemail entry, as a voicemail entry is always expected to have the source
+ * package field set.
+ */
+ private Set<String> getModifiedPackages(ContentValues values) {
+ Set<String> impactedPackages = new HashSet<String>();
+ if(values.containsKey(VoicemailContract.SOURCE_PACKAGE_FIELD)) {
+ impactedPackages.add(values.getAsString(VoicemailContract.SOURCE_PACKAGE_FIELD));
+ }
+ return impactedPackages;
+ }
+
+ private void notifyVoicemailChange(Uri notificationUri, Set<String> modifiedPackages,
+ String... intentActions) {
+ // Notify the observers.
+ // Must be done only once, even if there are multiple broadcast intents.
+ mContext.getContentResolver().notifyChange(notificationUri, null, true);
+ Collection<String> callingPackages = getCallingPackages();
+ // Now fire individual intents.
+ for (String intentAction : intentActions) {
+ // self_change extra should be included only for provider_changed events.
+ boolean includeSelfChangeExtra = intentAction.equals(Intent.ACTION_PROVIDER_CHANGED);
+ for (ComponentName component :
+ getBroadcastReceiverComponents(intentAction, notificationUri)) {
+ // Ignore any package that is not affected by the change and don't have full access
+ // either.
+ if (!modifiedPackages.contains(component.getPackageName()) &&
+ !mVoicemailPermissions.packageHasFullAccess(component.getPackageName())) {
+ continue;
+ }
+
+ Intent intent = new Intent(intentAction, notificationUri);
+ intent.setComponent(component);
+ if (includeSelfChangeExtra && callingPackages != null) {
+ intent.putExtra(VoicemailContract.EXTRA_SELF_CHANGE,
+ callingPackages.contains(component.getPackageName()));
+ }
+ String permissionNeeded = modifiedPackages.contains(component.getPackageName()) ?
+ ADD_VOICEMAIL : READ_WRITE_ALL_VOICEMAIL;
+ mContext.sendBroadcast(intent, permissionNeeded);
+ Log.v(TAG, String.format("Sent intent. act:%s, url:%s, comp:%s, perm:%s," +
+ " self_change:%s", intent.getAction(), intent.getData(),
+ component.getClassName(), permissionNeeded,
+ intent.hasExtra(VoicemailContract.EXTRA_SELF_CHANGE) ?
+ intent.getBooleanExtra(VoicemailContract.EXTRA_SELF_CHANGE, false) :
+ null));
+ }
+ }
+ }
+
+ /** 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;
+ }
+
+ /**
+ * 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));
+ }
+}