diff options
author | Jorge Ruesga <jorge@ruesga.com> | 2015-03-01 22:02:55 +0100 |
---|---|---|
committer | Steve Kondik <steve@cyngn.com> | 2015-10-18 14:12:04 -0700 |
commit | 05ee01bff41081aea1d0a69925dbff74f26d6dca (patch) | |
tree | 051923571492c1f6b90ac586ddf454130ea184f8 /src | |
parent | 35eafb5ea9e965e0741dc0c2f37d1d42bade5fc7 (diff) | |
download | android_packages_apps_UnifiedEmail-05ee01bff41081aea1d0a69925dbff74f26d6dca.tar.gz android_packages_apps_UnifiedEmail-05ee01bff41081aea1d0a69925dbff74f26d6dca.tar.bz2 android_packages_apps_UnifiedEmail-05ee01bff41081aea1d0a69925dbff74f26d6dca.zip |
unified email: suggested contacts
This change adds support for suggested contacts (email addresses not in the contact
provider and received via email).
Change-Id: I4010d0b6cf8bbb98308c7fc13f63e9d859a87d43
Signed-off-by: Jorge Ruesga <jorge@ruesga.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/com/android/mail/compose/RecipientAdapter.java | 229 | ||||
-rw-r--r-- | src/com/android/mail/preferences/MailPrefs.java | 18 | ||||
-rw-r--r-- | src/com/android/mail/ui/settings/GeneralPrefsFragment.java | 77 |
3 files changed, 323 insertions, 1 deletions
diff --git a/src/com/android/mail/compose/RecipientAdapter.java b/src/com/android/mail/compose/RecipientAdapter.java index 2f84eea65..d11e88440 100644 --- a/src/com/android/mail/compose/RecipientAdapter.java +++ b/src/com/android/mail/compose/RecipientAdapter.java @@ -1,5 +1,6 @@ /** * Copyright (c) 2007, Google Inc. + * Copyright (c) 2015, The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +16,240 @@ */ package com.android.mail.compose; +import com.android.emailcommon.provider.EmailContent; +import com.android.emailcommon.provider.SuggestedContact; import com.android.ex.chips.BaseRecipientAdapter; +import com.android.ex.chips.RecipientEntry; +import com.android.mail.preferences.MailPrefs; import com.android.mail.providers.Account; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import android.content.ContentProviderOperation; +import android.content.ContentResolver; +import android.content.ContentUris; import android.content.Context; +import android.content.OperationApplicationException; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.database.Cursor; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.RemoteException; +import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.provider.ContactsContract.Contacts.Data; +import android.provider.ContactsContract.RawContacts; +import android.util.Log; + +public class RecipientAdapter extends BaseRecipientAdapter +implements OnSharedPreferenceChangeListener { + + private static final boolean DEBUG = false; + private static final String TAG = "RecipientAdapter"; + + private static final int MAX_SUGGESTED_CONTACTS_ENTRIES = 3; + + private static final int MAX_DAYS_FOR_RECENTS_SUGGESTED_CONTACTS = -7; + + private String mSuggestedContactsMode; + private long mAccountId; -public class RecipientAdapter extends BaseRecipientAdapter { public RecipientAdapter(Context context, Account account) { super(context); setAccount(account.getAccountManagerAccount()); + mAccountId = -1; + + // Load the account id because we needed to access the suggested contacts data + // (in async mode because will do i/o writes) + loadAccountKey(account); + + mSuggestedContactsMode = MailPrefs.get(getContext()).getSuggestedContactMode(); + MailPrefs.get(context).registerOnSharedPreferenceChangeListener(this); + } + + @Override + protected void finalize() throws Throwable { + MailPrefs.get(getContext()).unregisterOnSharedPreferenceChangeListener(this); + super.finalize(); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equals(MailPrefs.PreferenceKeys.SUGGESTED_CONTACTS_MODE)) { + mSuggestedContactsMode = MailPrefs.get(getContext()).getSuggestedContactMode(); + } + } + + @Override + protected Set<SuggestionEntry> loadSuggestedEntries(CharSequence constraint, int maxResults) { + Set<SuggestionEntry> entries = new HashSet<>(); + if (!mSuggestedContactsMode.equals(MailPrefs.SuggestedContactsMode.NONE)) { + boolean recentsMode = mSuggestedContactsMode.equals( + MailPrefs.SuggestedContactsMode.RECENTS); + + String selection = SuggestedContact.ACCOUNT_KEY + " = ?" + + " and LOWER(" + SuggestedContact.DISPLAY_NAME + ") like LOWER(?) ESCAPE '\\' "; + String[] args = new String[recentsMode ? 3 : 2]; + args[0] = String.valueOf(mAccountId); + args[1] = "%" + constraint + "%"; + String sort = SuggestedContact.LAST_SEEN + " DESC LIMIT " + + Math.min(MAX_SUGGESTED_CONTACTS_ENTRIES, maxResults); + if (recentsMode) { + Calendar c = Calendar.getInstance(Locale.getDefault()); + c.add(Calendar.DAY_OF_YEAR, MAX_DAYS_FOR_RECENTS_SUGGESTED_CONTACTS); + selection += SuggestedContact.LAST_SEEN + " >= ? "; + args[2] = String.valueOf(c.getTimeInMillis()); + } + + Cursor cursor = getContext().getContentResolver().query(SuggestedContact.CONTENT_URI, + SuggestedContact.PROJECTION, selection, args, sort); + try { + if (cursor != null) { + Set<String> cachedAddresses = new HashSet<>(); + Map<String, Integer> cachedContacts = new HashMap<>(); + int contactsIds = -100; + while (cursor.moveToNext()) { + long suggestionId = cursor.getLong( + cursor.getColumnIndexOrThrow(SuggestedContact._ID)); + String address = cursor.getString( + cursor.getColumnIndexOrThrow(SuggestedContact.ADDRESS)); + String name = cursor.getString( + cursor.getColumnIndexOrThrow(SuggestedContact.NAME)); + String displayName = cursor.getString( + cursor.getColumnIndexOrThrow(SuggestedContact.DISPLAY_NAME)); + + if (!cachedAddresses.contains(address)) { + int contactId = (cachedContacts.containsKey(name)) ? + cachedContacts.get(name) : contactsIds++; + SuggestionEntry entry = new SuggestionEntry( + suggestionId, displayName, address, name, contactId); + entries.add(entry); + cachedAddresses.add(address); + + cachedContacts.put(name, contactId); + } + } + } + } catch (IllegalArgumentException e) { + Log.w(TAG, "Failed to perform search over suggested contacts table", e); + + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + if (DEBUG) { + Log.i(TAG, "Found " + entries.size() + " entries in suggested contacts"); + } + + return entries; + } + + protected void onAddSuggestion(final RecipientEntry entry, final SuggestionAddCallback cb) { + new AsyncTask<Void, Void, Boolean>() { + @Override + protected Boolean doInBackground(Void... args) { + return createSuggestedContact(entry); + } + @Override + protected void onPostExecute(Boolean result) { + if (result) { + cb.onSucess(); + } else { + cb.onFailed(); + } + } + }.execute(); + } + + protected void onDeleteSuggestion(final RecipientEntry entry, + final SuggestionRemoveCallback cb) { + new AsyncTask<Void, Void, Boolean>() { + @Override + protected Boolean doInBackground(Void... args) { + return deleteSuggestedContact(entry); + } + @Override + protected void onPostExecute(Boolean result) { + if (result) { + cb.onSucess(); + } else { + cb.onFailed(); + } + } + }.execute(); + } + + private void loadAccountKey(final Account account) { + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + Cursor c = getContext().getContentResolver().query( + com.android.emailcommon.provider.Account.CONTENT_URI, + EmailContent.ID_PROJECTION, + com.android.emailcommon.provider.Account.AccountColumns.EMAIL_ADDRESS + + " = ?", + new String[]{account.getAccountId()}, null); + try { + if (c != null && c.moveToFirst()) { + mAccountId = c.getLong(0); + } + } finally { + if (c != null) { + c.close(); + } + } + return null; + } + }.execute(); + } + + private boolean createSuggestedContact(RecipientEntry entry) { + ContentResolver cr = getContext().getContentResolver(); + ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); + int rawContactInsertIndex = ops.size(); + + ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) + .withValue(RawContacts.ACCOUNT_TYPE, null) + .withValue(RawContacts.ACCOUNT_NAME, null) + .build()); + ops.add(ContentProviderOperation + .newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) + .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) + .withValue(StructuredName.DISPLAY_NAME, entry.getDisplayName()) + .build()); + ops.add(ContentProviderOperation + .newInsert(ContactsContract.Data.CONTENT_URI) + .withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex) + .withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE) + .withValue(Email.DATA, entry.getDestination()) + .withValue(Email.TYPE, Email.TYPE_OTHER) + .build()); + + try { + cr.applyBatch(ContactsContract.AUTHORITY, ops); + return true; + + } catch (RemoteException | OperationApplicationException e) { + Log.e(TAG, "Failed to create the suggested contact.", e); + } + return false; + } + + private boolean deleteSuggestedContact(RecipientEntry entry) { + ContentResolver cr = getContext().getContentResolver(); + final Uri uri = ContentUris.withAppendedId(SuggestedContact.CONTENT_URI, entry.getDataId()); + return cr.delete(uri, null, null) == 1; } } diff --git a/src/com/android/mail/preferences/MailPrefs.java b/src/com/android/mail/preferences/MailPrefs.java index c8d83cdf2..fc34ee77c 100644 --- a/src/com/android/mail/preferences/MailPrefs.java +++ b/src/com/android/mail/preferences/MailPrefs.java @@ -139,6 +139,8 @@ public final class MailPrefs extends VersionedPrefs { // State indicating that we have migrated all accounts. public static final int MIGRATION_STATE_ALL = 2; + public static final String SUGGESTED_CONTACTS_MODE = "suggested-contacts-mode"; + public static final ImmutableSet<String> BACKUP_KEYS = new ImmutableSet.Builder<String>() .add(DEFAULT_REPLY_ALL) @@ -154,6 +156,7 @@ public final class MailPrefs extends VersionedPrefs { .add(CONFIRM_SEND) .add(CONVERSATION_OVERVIEW_MODE) .add(SNAP_HEADER_MODE) + .add(SUGGESTED_CONTACTS_MODE) .build(); } @@ -163,6 +166,12 @@ public final class MailPrefs extends VersionedPrefs { public static final String DISABLED = "disabled"; } + public static final class SuggestedContactsMode { + public static final String NONE = "none"; + public static final String RECENTS = "recents"; + public static final String ALL = "all"; + } + @Retention(RetentionPolicy.SOURCE) @StringDef({ RemovalActions.ARCHIVE, @@ -604,4 +613,13 @@ public final class MailPrefs extends VersionedPrefs { getEditor().putLong( PreferenceKeys.ANALYTICS_NB_ACCOUNT_LATEST_REPORT, timeMs); } + + public String getSuggestedContactMode() { + return getSharedPreferences().getString( + PreferenceKeys.SUGGESTED_CONTACTS_MODE, SuggestedContactsMode.NONE); + } + + public void setSuggestedContactMode(String mode) { + getEditor().putString(PreferenceKeys.SUGGESTED_CONTACTS_MODE, mode).apply(); + } } diff --git a/src/com/android/mail/ui/settings/GeneralPrefsFragment.java b/src/com/android/mail/ui/settings/GeneralPrefsFragment.java index a5e41c91e..2f1e4e131 100644 --- a/src/com/android/mail/ui/settings/GeneralPrefsFragment.java +++ b/src/com/android/mail/ui/settings/GeneralPrefsFragment.java @@ -21,17 +21,20 @@ import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; +import android.database.Cursor; import android.os.AsyncTask; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; +import android.provider.SearchRecentSuggestions; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.widget.Toast; +import com.android.emailcommon.provider.SuggestedContact; import com.android.mail.preferences.MailPrefs; import com.android.mail.preferences.MailPrefs.PreferenceKeys; import com.android.mail.providers.SuggestionsProvider; @@ -48,6 +51,7 @@ public class GeneralPrefsFragment extends MailPreferenceFragment // Keys used to reference pref widgets which don't map directly to preference entries static final String AUTO_ADVANCE_WIDGET = "auto-advance-widget"; + static final String SUGGESTED_CONTACTS_CLEAR_ALL = "suggested-contacts-clear-all"; static final String CALLED_FROM_TEST = "called-from-test"; @@ -57,8 +61,11 @@ public class GeneralPrefsFragment extends MailPreferenceFragment protected MailPrefs mMailPrefs; private AlertDialog mClearSearchHistoryDialog; + private AlertDialog mClearSuggestedContactsDialog; private ListPreference mAutoAdvance; + private Preference mClearAllSuggestedContacts; + private static final int[] AUTO_ADVANCE_VALUES = { AutoAdvance.NEWER, AutoAdvance.OLDER, @@ -82,6 +89,17 @@ public class GeneralPrefsFragment extends MailPreferenceFragment addPreferencesFromResource(R.xml.general_preferences); mAutoAdvance = (ListPreference) findPreference(AUTO_ADVANCE_WIDGET); + + mClearAllSuggestedContacts = findPreference(SUGGESTED_CONTACTS_CLEAR_ALL); + mClearAllSuggestedContacts.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + clearSuggestedContacts(); + return true; + } + }); + mClearAllSuggestedContacts.setEnabled(false); + computeSuggestedContacts(); } @Override @@ -155,6 +173,45 @@ public class GeneralPrefsFragment extends MailPreferenceFragment .show(); } + private void clearSuggestedContacts() { + mClearSuggestedContactsDialog = new AlertDialog.Builder(getActivity()) + .setMessage(R.string.clear_suggested_contacts_dialog_message) + .setTitle(R.string.clear_suggested_contacts_dialog_title) + .setIconAttribute(android.R.attr.alertDialogIcon) + .setPositiveButton(R.string.clear, this) + .setNegativeButton(R.string.cancel, this) + .show(); + } + + private void computeSuggestedContacts() { + final Context context = getActivity(); + new AsyncTask<Void, Void, Integer>() { + @Override + protected Integer doInBackground(Void... params) { + Cursor c = context.getContentResolver().query( + SuggestedContact.CONTENT_URI, + new String[] {"count(*) AS count"}, + null, + null, + null); + try { + if (c != null && c.moveToFirst()) { + return c.getInt(0); + } + } finally { + if (c != null) { + c.close(); + } + } + return 0; + } + @Override + protected void onPostExecute(Integer result) { + mClearAllSuggestedContacts.setEnabled(result > 0); + } + }.execute(); + } + @Override public void onClick(DialogInterface dialog, int which) { @@ -176,6 +233,23 @@ public class GeneralPrefsFragment extends MailPreferenceFragment Toast.makeText(getActivity(), R.string.search_history_cleared, Toast.LENGTH_SHORT) .show(); } + } else if (dialog.equals(mClearSuggestedContactsDialog)) { + if (which == DialogInterface.BUTTON_POSITIVE) { + final Context context = getActivity(); + // Clear the suggested contacts in the background, as it causes a disk + // write. + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + context.getContentResolver().delete( + SuggestedContact.CONTENT_URI, null, null); + computeSuggestedContacts(); + return null; + } + }.execute(); + Toast.makeText(getActivity(), R.string.suggested_contacts_cleared, + Toast.LENGTH_SHORT).show(); + } } } @@ -185,6 +259,9 @@ public class GeneralPrefsFragment extends MailPreferenceFragment if (mClearSearchHistoryDialog != null && mClearSearchHistoryDialog.isShowing()) { mClearSearchHistoryDialog.dismiss(); } + if (mClearSuggestedContactsDialog != null && mClearSuggestedContactsDialog.isShowing()) { + mClearSuggestedContactsDialog.dismiss(); + } } @Override |