diff options
Diffstat (limited to 'src/com/android/contacts')
31 files changed, 4049 insertions, 168 deletions
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java index 6178e9de4..f46e72f57 100644 --- a/src/com/android/contacts/ContactSaveService.java +++ b/src/com/android/contacts/ContactSaveService.java @@ -29,12 +29,14 @@ import android.content.Context; import android.content.Intent; import android.content.OperationApplicationException; import android.database.Cursor; +import android.database.sqlite.SQLiteFullException; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Parcelable; import android.os.RemoteException; +import android.os.ServiceManager; import android.provider.ContactsContract; import android.provider.ContactsContract.AggregationExceptions; import android.provider.ContactsContract.CommonDataKinds.GroupMembership; @@ -46,6 +48,10 @@ import android.provider.ContactsContract.PinnedPositions; import android.provider.ContactsContract.Profile; import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.RawContactsEntity; +import android.text.TextUtils; +import android.telephony.PhoneNumberUtils; +import android.telephony.TelephonyManager; +import android.telephony.SubscriptionManager; import android.util.Log; import android.widget.Toast; @@ -56,15 +62,22 @@ import com.android.contacts.common.model.RawContactDeltaList; import com.android.contacts.common.model.RawContactModifier; import com.android.contacts.common.model.account.AccountWithDataSet; import com.android.contacts.common.util.PermissionsUtil; +import com.android.contacts.common.SimContactsConstants; +import com.android.contacts.common.SimContactsOperation; +import com.android.contacts.common.MoreContactUtils; import com.android.contacts.editor.ContactEditorFragment; import com.android.contacts.util.ContactPhotoUtils; +import com.android.internal.telephony.uicc.AdnRecord; +import com.android.internal.telephony.uicc.IccConstants; +import com.android.internal.telephony.IIccPhoneBook; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.HashMap; import java.util.concurrent.CopyOnWriteArrayList; /** @@ -90,6 +103,7 @@ public class ContactSaveService extends IntentService { public static final String EXTRA_SAVE_IS_PROFILE = "saveIsProfile"; public static final String EXTRA_SAVE_SUCCEEDED = "saveSucceeded"; public static final String EXTRA_UPDATED_PHOTOS = "updatedPhotos"; + public static final String SAVE_CONTACT_RESULT = "saveResult"; public static final String ACTION_CREATE_GROUP = "createGroup"; public static final String ACTION_RENAME_GROUP = "renameGroup"; @@ -143,6 +157,33 @@ public class ContactSaveService extends IntentService { ); private static final int PERSIST_TRIES = 3; + private static int count = TelephonyManager.getDefault().getPhoneCount(); + private static int[] mSimMaxCount = new int[count]; + + public static final int RESULT_UNCHANGED = 0; + public static final int RESULT_SUCCESS = 1; + public static final int RESULT_FAILURE = 2; + public static final int RESULT_NO_NUMBER_AND_EMAIL = 3; + public static final int RESULT_SIM_FAILURE = 4; //only for sim operation failure + public static final int RESULT_EMAIL_FAILURE = 5; // only for sim email operation failure + // only for sim failure of number or anr is too long + public static final int RESULT_NUMBER_ANR_FAILURE = 6; + public static final int RESULT_SIM_FULL_FAILURE = 7; // only for sim card is full + public static final int RESULT_TAG_FAILURE = 8; // only for sim failure of name is too long + public static final int RESULT_NUMBER_INVALID = 9; // only for sim failure of number is valid + + public static final int RESULT_MEMORY_FULL_FAILURE = 11; //for memory full exception + public static final int RESULT_NUMBER_TYPE_FAILURE =12; //only for sim failure of number TYPE + + private final int MAX_NUM_LENGTH = 20; + private final int MAX_EMAIL_LENGTH = 40; + private final int MAX_EN_LENGTH = 14; + private final int MAX_CH_LENGTH = 6; + + // Only for request accessing SIM card + // when device is in the "AirPlane" mode. + public static final int RESULT_AIR_PLANE_MODE = 10; + public static SimContactsOperation mSimContactsOperation; private static final int MAX_CONTACTS_PROVIDER_BATCH_SIZE = 499; @@ -183,6 +224,40 @@ public class ContactSaveService extends IntentService { return getApplicationContext().getSystemService(name); } + /** + * when isMultiSimEnabled is true,get the maximum how many contacts can save to sim card + */ + private int getMSimCardMaxCount(int subscription) { + if (0 != mSimMaxCount[subscription]) { + return mSimMaxCount[subscription]; + } + int[] subId = SubscriptionManager.getSubId(subscription); + try { + IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface( + ServiceManager.getService("simphonebook")); + + if (iccIpb != null) { + if (subId != null + && TelephonyManager.getDefault().isMultiSimEnabled()) { + List<AdnRecord> list = iccIpb.getAdnRecordsInEfForSubscriber( + subId[0], IccConstants.EF_ADN); + if (null != list) { + mSimMaxCount[subscription] = list.size(); + } + } else { + List<AdnRecord> list = iccIpb + .getAdnRecordsInEf(IccConstants.EF_ADN); + if (null != list) { + mSimMaxCount[subscription] = list.size(); + } + } + } + } catch (RemoteException ex) { + Log.e(TAG, "Failed to IIccPhoneBookMSim", ex); + } + return mSimMaxCount[subscription]; + } + @Override protected void onHandleIntent(Intent intent) { if (intent == null) { @@ -378,8 +453,28 @@ public class ContactSaveService extends IntentService { long insertedRawContactId = -1; // Attempt to persist changes + Integer result = RESULT_FAILURE; + + ArrayList<Long> rawContactsList = new ArrayList<Long>(); + boolean isCardOperation = false; + for (int i=0; i < state.size(); i++) { + final RawContactDelta entity = state.get(i); + final String accountType = entity.getValues().getAsString(RawContacts.ACCOUNT_TYPE); + final String accountName = entity.getValues().getAsString(RawContacts.ACCOUNT_NAME); + rawContactsList.add(entity.getRawContactId()); + + final int subscription = MoreContactUtils.getSubscription( + accountType, accountName); + isCardOperation = (subscription != SubscriptionManager.INVALID_SUBSCRIPTION_ID) ? + true : false; + if (isCardOperation) { + result = doSaveToSimCard(entity, resolver, subscription); + Log.d(TAG, "doSaveToSimCard result is " + result); + } + } int tries = 0; while (tries++ < PERSIST_TRIES) { + if (result == RESULT_SUCCESS || result == RESULT_FAILURE) { try { // Build operations and try applying final ArrayList<ContentProviderOperation> diff = state.buildDiff(); @@ -449,7 +544,17 @@ public class ContactSaveService extends IntentService { Log.e(TAG, "Problem persisting user edits", e); showToast(R.string.contactSavedErrorToast); break; - + } catch (SQLiteFullException e) { + // Memory is full. don't do any thing + Log.e(TAG, "Memory is full", e); + Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT); + if (callbackIntent != null) { + callbackIntent.putExtra(EXTRA_SAVE_SUCCEEDED, false); + callbackIntent.setData(null); + callbackIntent.putExtra(SAVE_CONTACT_RESULT, RESULT_MEMORY_FULL_FAILURE); + deliverCallback(callbackIntent); + } + return; } catch (OperationApplicationException e) { // Version consistency failed, re-parent change and try again Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString()); @@ -484,6 +589,7 @@ public class ContactSaveService extends IntentService { if (isProfile) { for (RawContactDelta delta : state) { delta.setProfileQueryUri(); + } } } } @@ -518,6 +624,8 @@ public class ContactSaveService extends IntentService { callbackIntent.putExtra(EXTRA_SAVE_SUCCEEDED, true); } callbackIntent.setData(lookupUri); + callbackIntent.putExtra(SAVE_CONTACT_RESULT, result); + deliverCallback(callbackIntent); } } @@ -534,7 +642,124 @@ public class ContactSaveService extends IntentService { return ContactPhotoUtils.savePhotoFromUriToUri(this, photoUri, outputUri, true); } - /** + private Integer doSaveToSimCard(RawContactDelta entity, ContentResolver resolver, + int subscription) { + // Return Error code to indicate caller that device is in + // the "AirPlane" mode and application can't access SIM card. + if (MoreContactUtils.isAPMOnAndSIMPowerDown(this)) { + return RESULT_AIR_PLANE_MODE; + } + + boolean isInsert = entity.isContactInsert(); + Integer result = RESULT_SIM_FAILURE; + mSimContactsOperation = new SimContactsOperation(this); + + ContentValues values = entity.buildSimDiff(); + String tag = null; + String number = null; + String anr = null; + String email = null; + + if(entity.isContactInsert()){ + tag = values.getAsString(SimContactsConstants.STR_TAG); + number = values.getAsString(SimContactsConstants.STR_NUMBER); + anr = values.getAsString(SimContactsConstants.STR_ANRS); + email = values.getAsString(SimContactsConstants.STR_EMAILS); + } else { + tag = values.getAsString(SimContactsConstants.STR_NEW_TAG); + number = values.getAsString(SimContactsConstants.STR_NEW_NUMBER); + anr = values.getAsString(SimContactsConstants.STR_NEW_ANRS); + email = values.getAsString(SimContactsConstants.STR_NEW_EMAILS); + } + + if (TextUtils.isEmpty(number) && TextUtils.isEmpty(anr) && TextUtils.isEmpty(email)) { + return RESULT_NO_NUMBER_AND_EMAIL; + } + + if (!TextUtils.isEmpty(number)) { + if (number.length() > MAX_NUM_LENGTH) { + return RESULT_NUMBER_ANR_FAILURE; + } else if (number.contains(SimContactsConstants.STR_ANRS)) { + return RESULT_NUMBER_TYPE_FAILURE; + } + } + + if (!TextUtils.isEmpty(anr)) { + String[] anrs = anr.split(SimContactsConstants.ANR_SEP); + if (anrs != null) { + if (anrs.length > MoreContactUtils + .getOneSimAnrCount(subscription)) { + return RESULT_NUMBER_TYPE_FAILURE; + } + for (String mAnr : anrs) { + if (mAnr.length() > MAX_NUM_LENGTH) { + return RESULT_NUMBER_ANR_FAILURE; + } + } + } + } + + if (!TextUtils.isEmpty(number) && TextUtils.isEmpty(PhoneNumberUtils + .stripSeparators(number))) { + return RESULT_NUMBER_INVALID; + } + + if (!TextUtils.isEmpty(email)) { + String[] emails = email.split(SimContactsConstants.EMAIL_SEP); + for (String mEmail : emails) { + if (mEmail != null && mEmail.length() > MAX_EMAIL_LENGTH) { + return RESULT_EMAIL_FAILURE; + } + } + } + + if (!TextUtils.isEmpty(tag)) { + if (tag.getBytes().length > MAX_EN_LENGTH) { + return RESULT_TAG_FAILURE; + } + } + + if (entity.isContactInsert()) { + int count = 0; + Cursor c = null; + Uri iccUri; + int[] subId = SubscriptionManager.getSubId(subscription); + if (!TelephonyManager.getDefault().isMultiSimEnabled()) { + iccUri = Uri.parse(SimContactsConstants.SIM_URI); + } else { + iccUri = Uri.parse(SimContactsConstants.SIM_SUB_URI + subId[0]); + } + try { + c = resolver.query(iccUri, null, null, null, null); + if (c != null) { + count = c.getCount(); + } + } finally { + if (c != null) { + c.close(); + } + } + + if (count == getMSimCardMaxCount(subscription)) { + return RESULT_SIM_FULL_FAILURE; + } + } + + if (isInsert) { + Uri resultUri = mSimContactsOperation.insert(values, + subscription); + if (resultUri != null) + result = RESULT_SUCCESS; + } else { + int resultInt = mSimContactsOperation.update(values, + subscription); + if (resultInt == 1) + result = RESULT_SUCCESS; + } + return result; + } + + /** * Find the ID of an existing or newly-inserted raw-contact. If none exists, return -1. */ private long getRawContactId(RawContactDeltaList state, @@ -998,12 +1223,27 @@ public class ContactSaveService extends IntentService { private void deleteContact(Intent intent) { Uri contactUri = intent.getParcelableExtra(EXTRA_CONTACT_URI); + mSimContactsOperation = new SimContactsOperation(this); if (contactUri == null) { Log.e(TAG, "Invalid arguments for deleteContact request"); return; } - getContentResolver().delete(contactUri, null, null); + final List<String> segments = contactUri.getPathSegments(); + // Contains an Id. + final long uriContactId = Long.parseLong(segments.get(3)); + int subscription = mSimContactsOperation + .getSimSubscription(uriContactId); + if (subscription != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + ContentValues values = mSimContactsOperation + .getSimAccountValues(uriContactId); + int result = mSimContactsOperation.delete(values, subscription); + if (result == RESULT_SUCCESS) { + getContentResolver().delete(contactUri, null, null); + } + } else { + getContentResolver().delete(contactUri, null, null); + } } private void deleteMultipleContacts(Intent intent) { diff --git a/src/com/android/contacts/GroupListLoader.java b/src/com/android/contacts/GroupListLoader.java index 2589c9bb4..e7fd6428f 100644 --- a/src/com/android/contacts/GroupListLoader.java +++ b/src/com/android/contacts/GroupListLoader.java @@ -49,8 +49,6 @@ public final class GroupListLoader extends CursorLoader { public GroupListLoader(Context context) { super(context, GROUP_LIST_URI, COLUMNS, Groups.ACCOUNT_TYPE + " NOT NULL AND " + Groups.ACCOUNT_NAME + " NOT NULL AND " + Groups.AUTO_ADD + "=0 AND " + - Groups.FAVORITES + "=0 AND " + Groups.DELETED + "=0", null, - Groups.ACCOUNT_TYPE + ", " + Groups.ACCOUNT_NAME + ", " + Groups.DATA_SET + ", " + - Groups.TITLE + " COLLATE LOCALIZED ASC"); + Groups.FAVORITES + "=0 AND " + Groups.DELETED + "=0", null, "account_id"); } } diff --git a/src/com/android/contacts/GroupMetaDataLoader.java b/src/com/android/contacts/GroupMetaDataLoader.java index 834404138..ea503d017 100644 --- a/src/com/android/contacts/GroupMetaDataLoader.java +++ b/src/com/android/contacts/GroupMetaDataLoader.java @@ -50,7 +50,9 @@ public final class GroupMetaDataLoader extends CursorLoader { public GroupMetaDataLoader(Context context, Uri groupUri) { super(context, ensureIsGroupUri(groupUri), COLUMNS, Groups.ACCOUNT_TYPE + " NOT NULL AND " - + Groups.ACCOUNT_NAME + " NOT NULL", null, null); + + Groups.ACCOUNT_NAME + " NOT NULL AND " + Groups.DELETED + " != ?" + , new String[] {"1"} + , null); } /** diff --git a/src/com/android/contacts/activities/ActionBarAdapter.java b/src/com/android/contacts/activities/ActionBarAdapter.java index 5a95c90bc..6a81d066b 100644 --- a/src/com/android/contacts/activities/ActionBarAdapter.java +++ b/src/com/android/contacts/activities/ActionBarAdapter.java @@ -110,8 +110,9 @@ public class ActionBarAdapter implements OnCloseListener { public interface TabState { public static int FAVORITES = 0; public static int ALL = 1; + public static int GROUPS = 2; - public static int COUNT = 2; + public static int COUNT = 3; public static int DEFAULT = ALL; } diff --git a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java index a922ca128..500d7c177 100644 --- a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java +++ b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java @@ -84,9 +84,9 @@ public class ContactEditorAccountsChangedActivity extends Activity { throw new IllegalStateException("Cannot have a negative number of accounts"); } - if (numAccounts >= 2) { - // When the user has 2+ writable accounts, show a list of accounts so the user can pick - // which account to create a contact in. + if (numAccounts > 0) { + // When the user has writable accounts, show a list of accounts so the user can pick + // which account to create a contact in (add also the phone-local storage account). setContentView(R.layout.contact_editor_accounts_changed_activity_with_picker); final TextView textView = (TextView) findViewById(R.id.text); @@ -101,33 +101,6 @@ public class ContactEditorAccountsChangedActivity extends Activity { AccountListFilter.ACCOUNTS_CONTACT_WRITABLE); accountListView.setAdapter(mAccountListAdapter); accountListView.setOnItemClickListener(mAccountListItemClickListener); - } else if (numAccounts == 1) { - // If the user has 1 writable account we will just show the user a message with 2 - // possible action buttons. - setContentView(R.layout.contact_editor_accounts_changed_activity_with_text); - - final TextView textView = (TextView) findViewById(R.id.text); - final Button leftButton = (Button) findViewById(R.id.left_button); - final Button rightButton = (Button) findViewById(R.id.right_button); - - final AccountWithDataSet account = accounts.get(0); - textView.setText(getString(R.string.contact_editor_prompt_one_account, - account.name)); - - // This button allows the user to add a new account to the device and return to - // this app afterwards. - leftButton.setText(getString(R.string.add_new_account)); - leftButton.setOnClickListener(mAddAccountClickListener); - - // This button allows the user to continue creating the contact in the specified - // account. - rightButton.setText(getString(android.R.string.ok)); - rightButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - saveAccountAndReturnResult(account); - } - }); } else { // If the user has 0 writable accounts, we will just show the user a message with 2 // possible action buttons. diff --git a/src/com/android/contacts/activities/ContactEditorBaseActivity.java b/src/com/android/contacts/activities/ContactEditorBaseActivity.java index 12f1e961b..ac5fe67da 100644 --- a/src/com/android/contacts/activities/ContactEditorBaseActivity.java +++ b/src/com/android/contacts/activities/ContactEditorBaseActivity.java @@ -172,7 +172,7 @@ abstract public class ContactEditorBaseActivity extends ContactsActivity */ void onSaveCompleted(boolean hadChanges, int saveMode, boolean saveSucceeded, Uri contactLookupUri, Bundle updatedPhotos, boolean backPressed, long photoId, - long nameId); + long nameId, int result); /** * Invoked after the contact is joined. @@ -264,7 +264,8 @@ abstract public class ContactEditorBaseActivity extends ContactsActivity intent.getBooleanExtra(ContactEditorFragment.INTENT_EXTRA_SAVE_BACK_PRESSED, false), intent.getLongExtra(ContactEditorFragment.INTENT_EXTRA_PHOTO_ID, -1), - intent.getLongExtra(ContactEditorFragment.INTENT_EXTRA_NAME_ID, -1)); + intent.getLongExtra(ContactEditorFragment.INTENT_EXTRA_NAME_ID, -1), + intent.getIntExtra(ContactSaveService.SAVE_CONTACT_RESULT, 0)); } else if (ACTION_JOIN_COMPLETED.equals(action)) { mFragment.onJoinCompleted(intent.getData()); } diff --git a/src/com/android/contacts/activities/MemoryStatusActivity.java b/src/com/android/contacts/activities/MemoryStatusActivity.java new file mode 100644 index 000000000..2622c4018 --- /dev/null +++ b/src/com/android/contacts/activities/MemoryStatusActivity.java @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * + * Not a Contribution. + * + * 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.contacts.activities; + +import android.app.ActionBar; +import android.content.Context; +import android.content.ContentResolver; +import android.content.Intent; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Bundle; +import android.os.Message; +import android.provider.ContactsContract.RawContacts; +import android.telecom.PhoneAccount; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TextView; + +import com.android.contacts.ContactsActivity; +import com.android.contacts.common.model.account.AccountType; +import com.android.contacts.common.model.account.AccountWithDataSet; +import com.android.contacts.common.model.AccountTypeManager; +import com.android.contacts.common.MoreContactUtils; +import com.android.contacts.common.SimContactsConstants; +import com.android.contacts.R; +import com.android.contacts.common.model.account.PhoneAccountType; +import com.google.android.collect.Lists; + +import java.util.ArrayList; +import java.util.List; + +/** + * Shows a list of all available accounts, letting the user select under which + * account to view contacts. + */ +public class MemoryStatusActivity extends ContactsActivity { + private static final String TAG = "MemoryStatusActivity"; + private static final int INVALID_COUNT = 0; + private ListView mListView; + private View empty; + private List<AccountListItem> mFilters; + private AccountListAdapter mAdapter; + private Handler mHandler; + private LoaderThread mThread = null; + + private final class AccountListItem { + public final String accountType; + public final String accountName; + public final String dataSet; + public final Drawable icon; + public final int total; + public final int count; + + public AccountListItem(String accountType, String accountName, String dataSet, + Drawable icon, int total, int count) { + this.accountType = accountType; + this.accountName = accountName; + this.dataSet = dataSet; + this.icon = icon; + this.total = total; + this.count = count; + } + } + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.contact_memory_list); + + mListView = (ListView) findViewById(android.R.id.list); + empty = (View) findViewById(R.id.empty); + mListView.setEmptyView(empty); + + ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + } + mFilters = Lists.newArrayList(); + mAdapter = new AccountListAdapter(this); + mListView.setAdapter(mAdapter); + + mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + mFilters = (List<AccountListItem>) msg.obj; + mAdapter.notifyDataSetChanged(); + } + }; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + default: + break; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected void onResume() { + super.onResume(); + if (mFilters.isEmpty()) { + if (mThread == null) { + mThread = new LoaderThread(); + } + try { + mThread.start(); + } catch (Exception e) { + } + } + } + + public class LoaderThread extends Thread { + @Override + public void run() { + List<AccountListItem> list = loadAccountFilters(MemoryStatusActivity.this); + Message msg = Message.obtain(); + msg.obj = list; + mHandler.sendMessage(msg); + } + } + + private List<AccountListItem> loadAccountFilters(Context context) { + final ArrayList<AccountListItem> accountFilters = Lists.newArrayList(); + final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context); + List<AccountWithDataSet> accounts = accountTypes.getAccounts(true, + AccountTypeManager.FLAG_ALL_ACCOUNTS_WITHOUT_LOCAL); + ContentResolver cr = context.getContentResolver(); + + // Add the local account first, this is a special case. + accounts.add(0, new AccountWithDataSet(SimContactsConstants.PHONE_NAME, + PhoneAccountType.ACCOUNT_TYPE, + null)); + for (AccountWithDataSet account : accounts) { + AccountType accountType = accountTypes.getAccountType(account.type, account.dataSet); + if (accountType.isExtension() && !account.hasData(context)) { + // Hide extensions with no raw_contacts. + continue; + } + Drawable icon = accountType != null ? accountType.getDisplayIcon(context) : null; + int total = INVALID_COUNT; + int count = INVALID_COUNT; + if (!TextUtils.isEmpty(account.type)) { + if (account.type.equals(SimContactsConstants.ACCOUNT_TYPE_SIM)) { + total = MoreContactUtils.getAdnCount(MoreContactUtils + .getSubscription(account.type, account.name)); + if (total > 0) { + Cursor cursor = cr.query(RawContacts.CONTENT_URI, new String[] { + RawContacts._ID + }, RawContacts.ACCOUNT_NAME + " = '" + account.name + "' AND " + + RawContacts.DELETED + " = 0", null, null); + if (cursor != null) { + try { + count = cursor.getCount(); + } finally { + cursor.close(); + } + } + } + } else { + Cursor cursor = cr.query(RawContacts.CONTENT_URI, new String[] { + RawContacts._ID + }, RawContacts.ACCOUNT_NAME + " = '" + account.name + "' AND " + + RawContacts.DELETED + " = 0", null, null); + if (cursor != null) { + try { + count = cursor.getCount(); + } finally { + cursor.close(); + } + } + } + } + accountFilters.add(new AccountListItem( + account.type, account.name, account.dataSet, icon, total, count)); + } + + return accountFilters; + } + + private class AccountListAdapter extends BaseAdapter { + private final LayoutInflater mLayoutInflater; + private Context accountContext; + + public AccountListAdapter(Context context) { + mLayoutInflater = (LayoutInflater) context.getSystemService + (Context.LAYOUT_INFLATER_SERVICE); + accountContext = context; + } + + @Override + public int getCount() { + return mFilters.size(); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public AccountListItem getItem(int position) { + return mFilters.get(position); + } + + public View getView(int position, View convertView, ViewGroup parent) { + final View view; + AccountListItemViewCache viewCache; + if (convertView != null) { + view = convertView; + viewCache = (AccountListItemViewCache) view.getTag(); + } else { + view = mLayoutInflater.inflate(R.layout.memory_account_list_item, parent, false); + viewCache = new AccountListItemViewCache(view); + view.setTag(viewCache); + } + bindView(position, convertView, parent, viewCache); + return view; + } + + private void bindView(int position, View convertView, ViewGroup parent, + AccountListItemViewCache viewCache) { + final AccountListItem filter = mFilters.get(position); + final AccountTypeManager accountTypes = AccountTypeManager.getInstance(accountContext); + final AccountType accountType = + accountTypes.getAccountType(filter.accountType, filter.dataSet); + viewCache.accountName.setText(accountType.getDisplayLabel(accountContext) + + "<" + filter.accountName + ">"); + viewCache.totally.setVisibility((filter.total != INVALID_COUNT) ? View.VISIBLE + : View.GONE); + viewCache.count_total.setText(Integer.toString(filter.total)); + viewCache.count_cur.setText(Integer.toString(filter.count)); + } + + /** + * Cache of the children views of a contact detail entry represented by + * a {@link GroupListItem} + */ + public class AccountListItemViewCache { + public final TextView accountName; + public final TextView count_total; + public final TextView count_cur; + public final LinearLayout totally; + + public AccountListItemViewCache(View view) { + accountName = (TextView) view.findViewById(R.id.account_name); + count_total = (TextView) view.findViewById(R.id.count_max); + count_cur = (TextView) view.findViewById(R.id.count_cur); + totally = (LinearLayout) view.findViewById(R.id.totally); + } + } + } +} diff --git a/src/com/android/contacts/activities/MultiPickContactActivity.java b/src/com/android/contacts/activities/MultiPickContactActivity.java new file mode 100644 index 000000000..ee7614b88 --- /dev/null +++ b/src/com/android/contacts/activities/MultiPickContactActivity.java @@ -0,0 +1,1965 @@ +/** + * Copyright (C) 2013-2015, The Linux Foundation. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.contacts.activities; + +import android.app.ActionBar; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ListActivity; +import android.app.ProgressDialog; +import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.AsyncQueryHandler; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.ContentProviderOperation; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.net.Uri; +import android.net.Uri.Builder; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.RemoteException; +import android.provider.CallLog; +import android.provider.CallLog.Calls; +import android.provider.ContactsContract; +import android.provider.ContactsContract.ContactCounts; +import android.provider.ContactsContract.CommonDataKinds.Email; +import android.provider.ContactsContract.CommonDataKinds.GroupMembership; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.Groups; +import android.provider.ContactsContract.RawContacts; +import android.telephony.PhoneNumberUtils; +import android.telephony.TelephonyManager; +import android.telephony.SubscriptionManager; +import android.text.Editable; +import android.text.format.DateUtils; +import android.text.TextUtils; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CursorAdapter; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.SearchView; +import android.widget.SectionIndexer; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.contacts.R; +import com.android.contacts.common.ContactPhotoManager; +import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; +import com.android.contacts.common.SimContactsConstants; +import com.android.contacts.common.SimContactsOperation; +import com.android.contacts.common.list.AccountFilterActivity; +import com.android.contacts.common.list.ContactListFilter; +import com.android.contacts.common.list.ContactListItemView; +import com.android.contacts.common.list.ContactsSectionIndexer; +import com.android.contacts.common.list.DefaultContactListAdapter; +import com.android.contacts.common.MoreContactUtils; +import com.android.contacts.common.model.account.SimAccountType; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +public class MultiPickContactActivity extends ListActivity implements + TextView.OnEditorActionListener, View.OnTouchListener, + SearchView.OnQueryTextListener, SearchView.OnCloseListener, + View.OnFocusChangeListener, DialogInterface.OnClickListener, + DialogInterface.OnKeyListener { + private final static String TAG = "MultiPickContactActivity"; + private final static boolean DEBUG = true; + + private static final String SORT_ORDER = " desc"; + + static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { + Contacts._ID, // 0 + Contacts.DISPLAY_NAME_PRIMARY, // 1 + Contacts.PHOTO_ID, // 2 + Contacts.LOOKUP_KEY, // 3 + RawContacts.ACCOUNT_TYPE, // 4 + RawContacts.ACCOUNT_NAME, // 5 + Contacts.NAME_RAW_CONTACT_ID, // 6 + Contacts.PHOTO_THUMBNAIL_URI // 7 + }; + + static final String[] PHONES_PROJECTION = new String[] { + Data.CONTACT_ID, // 0 + Data.DISPLAY_NAME, // 1 + Data.PHOTO_ID, // 2 + Data.LOOKUP_KEY, // 3 + Phone._ID, // 4 + Phone.TYPE, // 5 + Phone.LABEL, // 6 + Phone.NUMBER, // 7 + }; + + static final String[] EMAILS_PROJECTION = new String[] { + Data.CONTACT_ID, // 0 + Data.DISPLAY_NAME, // 1 + Data.PHOTO_ID, // 2 + Data.LOOKUP_KEY, // 3 + Email._ID, // 4 + Email.ADDRESS // 5 + }; + + static final String[] CALL_LOG_PROJECTION = new String[] { + Calls._ID, + Calls.NUMBER, + Calls.DATE, + Calls.DURATION, + Calls.TYPE, + Calls.CACHED_NAME, + Calls.CACHED_NUMBER_TYPE, + Calls.CACHED_NUMBER_LABEL, + Calls.PHONE_ACCOUNT_ID, + Calls.GEOCODED_LOCATION, + }; + + static final String CONTACTS_SELECTION = Contacts.IN_VISIBLE_GROUP + "=1"; + + static final String PHONES_SELECTION = RawContacts.ACCOUNT_TYPE + "<>?"; + + static final String[] PHONES_SELECTION_ARGS = { + SimContactsConstants.ACCOUNT_TYPE_SIM + }; + + public static final int CONTACT_COLUMN_ID = 0; + public static final int CONTACT_COLUMN_DISPLAY_NAME = 1; + public static final int CONTACT_COLUMN_PHOTO_ID = 2; + public static final int CONTACT_COLUMN_LOOKUP_KEY = 3; + // contacts query specific columns + public static final int CONTACT_COLUMN_ACCOUNT_TYPE = 4; + public static final int CONTACT_COLUMN_ACCOUNT_NAME = 5; + public static final int CONTACT_COLUMN_RAW_CONTACT_ID = 6; + public static final int CONTACT_COLUMN_PHOTO_URI = 7; + // phone query specific columns + public static final int PHONE_COLUMN_ID = 4; + public static final int PHONE_COLUMN_TYPE = 5; + public static final int PHONE_COLUMN_LABEL = 6; + public static final int PHONE_COLUMN_NUMBER = 7; + // email query specific columns + public static final int EMAIL_COLUMN_ID = 4; + public static final int EMAIL_COLUMN_ADDRESS = 5; + + public static final int CALLLOG_COLUMN_ID = 0; + public static final int CALLLOG_COLUMN_NUMBER = 1; + public static final int CALLLOG_COLUMN_DATE = 2; + public static final int CALLLOG_COLUMN_DURATION = 3; + public static final int CALLLOG_COLUMN_CALL_TYPE = 4; + public static final int CALLLOG_COLUMN_CALLER_NAME = 5; + public static final int CALLLOG_COLUMN_CALLER_NUMBERTYPE = 6; + public static final int CALLLOG_COLUMN_CALLER_NUMBERLABEL = 7; + public static final int CALLLOG_COLUMN_PHONE_ACCOUNT = 8; + public static final int CALLLOG_COLUMN_CALLER_LOCATION = 9; + + private static final int QUERY_TOKEN = 42; + private static final int MODE_MASK_SEARCH = 0x80000000; + + private static final int MODE_DEFAULT_CONTACT = 0; + private static final int MODE_DEFAULT_PHONE = 1; + private static final int MODE_DEFAULT_EMAIL = 1 << 1; + private static final int MODE_DEFAULT_CALL = 1 << 1 << 1; + private static final int MODE_DEFAULT_SIM = 1 << 1 << 1 << 1; + private static final int MODE_SEARCH_CONTACT = MODE_DEFAULT_CONTACT | MODE_MASK_SEARCH; + private static final int MODE_SEARCH_PHONE = MODE_DEFAULT_PHONE | MODE_MASK_SEARCH; + private static final int MODE_SEARCH_EMAIL = MODE_DEFAULT_EMAIL | MODE_MASK_SEARCH; + private static final int MODE_SEARCH_CALL = MODE_DEFAULT_CALL | MODE_MASK_SEARCH; + private static final int MODE_SEARCH_SIM = MODE_DEFAULT_SIM | MODE_MASK_SEARCH; + private static final int DIALOG_DEL_CALL = 1; + + public static final String ADD_GROUP_MEMBERS = "add_group_members"; + + public static final String ACTION_MULTI_PICK = "com.android.contacts.action.MULTI_PICK"; + static final String ACTION_MULTI_PICK_EMAIL = "com.android.contacts.action.MULTI_PICK_EMAIL"; + static final String ACTION_MULTI_PICK_CALL = "com.android.contacts.action.MULTI_PICK_CALL"; + static final String ACTION_MULTI_PICK_SIM = "com.android.contacts.action.MULTI_PICK_SIM"; + + public static final String EXTRA_IS_SELECT_ALL_DISALLOWED = "is_select_all_disallowed"; + public static final String EXTRA_SELECT_CALLLOG = "selectcalllog"; + public static final String EXTRA_NOT_SHOW_SIM_FLAG = "not_sim_show"; + public static final String EXTRA_GROUP_ID = "group_id"; + public static final String EXTRA_GROUP_ACTION = "group_action"; + + public static final int GROUP_ACTION_ADD_MEMBER = 0; + public static final int GROUP_ACTION_MOVE_MEMBER = 1; + public static final int GROUP_ACTION_NONE = -1; + + private ContactItemListAdapter mAdapter; + private QueryHandler mQueryHandler; + private Bundle mChoiceSet; + + private ActionBar mActionBar; + private SearchView mSearchView; + private ViewGroup mSearchViewContainer; + + private int mMode; + private boolean mSelectCallLog; + private boolean mSearchUiVisible = false; + + private ArrayList<Long> mGroupIds = new ArrayList<Long>(); + + private ProgressDialog mProgressDialog; + private SimContactsOperation mSimContactsOperation; + private AccountManager accountManager; + private CharSequence[] mLabelArray; + private AccountManager mAccountManager; + + private static final String[] SIM_COLUMN_NAMES = new String[] { + "name", + "number", + "emails", + "anrs", + "_id" + }; + + private static final int SIM_COLUMN_DISPLAY_NAME = 0; + private static final int SIM_COLUMN_NUMBER = 1; + private static final int SIM_COLUMN_EMAILS = 2; + private static final int SIM_COLUMN_ANRS = 3; + private static final int SIM_COLUMN_ID = 4; + + private int MAX_CONTACTS_NUM_TO_SELECT_ONCE = 500; + + //registerReceiver to update content when airplane mode change. + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { + updateContent(); + + // If now is airplane mode, should cancel import sim contacts + if (isPickSim() && MoreContactUtils.isAPMOnAndSIMPowerDown(context)) { + cancelSimContactsImporting(); + } + } + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent intent = getIntent(); + String action = intent.getAction(); + boolean isContact = intent.getBooleanExtra(SimContactsConstants.IS_CONTACT, false); + + if (Intent.ACTION_DELETE.equals(action)) { + mMode = MODE_DEFAULT_CONTACT; + setTitle(R.string.menu_deleteContact); + } else if (SimContactsConstants.ACTION_MULTI_PICK.equals(action)) { + mMode = isContact ? MODE_DEFAULT_CONTACT : MODE_DEFAULT_PHONE; + } else if (SimContactsConstants.ACTION_MULTI_PICK_EMAIL.equals(action)) { + mMode = MODE_DEFAULT_EMAIL; + } else if (SimContactsConstants.ACTION_MULTI_PICK_CALL.equals(action)) { + mMode = MODE_DEFAULT_CALL; + setTitle(R.string.delete_call_title); + if (intent.getBooleanExtra(EXTRA_SELECT_CALLLOG, false)) { + mSelectCallLog = true; + setTitle(R.string.select_call_title); + } + } else if (SimContactsConstants.ACTION_MULTI_PICK_SIM.equals(action)) { + mMode = MODE_DEFAULT_SIM; + } + + mChoiceSet = new Bundle(); + mAdapter = new ContactItemListAdapter(this); + getListView().setAdapter(mAdapter); + mQueryHandler = new QueryHandler(this); + mSimContactsOperation = new SimContactsOperation(this); + mAccountManager = AccountManager.get(this); + + mActionBar = getActionBar(); + mActionBar.setHomeButtonEnabled(true); + mActionBar.setDisplayHomeAsUpEnabled(true); + mActionBar.setDisplayShowTitleEnabled(true); + inflateSearchView(); + + startQuery(); + //register receiver. + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); + registerReceiver(mBroadcastReceiver, filter); + } + + private void inflateSearchView() { + LayoutInflater inflater = LayoutInflater.from(mActionBar.getThemedContext()); + mSearchViewContainer = (ViewGroup) inflater.inflate(R.layout.custom_action_bar, null); + mSearchView = (SearchView) mSearchViewContainer.findViewById(R.id.search_view); + + if (isPickCall() || isPickSim()) { + mSearchView.setVisibility(View.GONE); + return; + } + + // In order to make the SearchView look like "shown via search menu", we need to + // manually setup its state. See also DialtactsActivity.java and ActionBarAdapter.java. + mSearchView.setIconifiedByDefault(true); + mSearchView.setQueryHint(getString(R.string.hint_findContacts)); + mSearchView.setIconified(false); + mSearchView.setFocusable(true); + + mSearchView.setOnQueryTextListener(this); + mSearchView.setOnCloseListener(this); + mSearchView.setOnQueryTextFocusChangeListener(this); + + mActionBar.setCustomView(mSearchViewContainer, new ActionBar.LayoutParams( + ActionBar.LayoutParams.MATCH_PARENT, ActionBar.LayoutParams.WRAP_CONTENT)); + mActionBar.setDisplayShowCustomEnabled(true); + + configureSearchMode(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + + inflater.inflate(R.menu.search_menu, menu); + inflater.inflate(R.menu.multi_contact_picker_options, menu); + + final MenuItem selectAllItem = menu.findItem(R.id.select_all_check); + selectAllItem.setVisible(!mSearchUiVisible); + selectAllItem.setChecked(mChoiceSet.size() == mAdapter.getCount()); + + final MenuItem doneItem = menu.findItem(R.id.done); + doneItem.setVisible(!mChoiceSet.isEmpty()); + + final MenuItem searchItem = menu.findItem(R.id.menu_search); + searchItem.setVisible(!mSearchUiVisible && !isPickCall() && !isPickSim()); + + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_search: + mSearchUiVisible = !mSearchUiVisible; + if (!mSearchUiVisible) { + exitSearchMode(); + } else { + configureSearchMode(); + } + return true; + case R.id.select_all_check: + selectAll(!item.isChecked()); + return true; + case R.id.done: + if (isSearchMode()) { + exitSearchMode(); + } + if (mMode == MODE_DEFAULT_CONTACT) { + if (SimContactsConstants.ACTION_MULTI_PICK.equals(getIntent().getAction())) { + if (mChoiceSet.size() > MAX_CONTACTS_NUM_TO_SELECT_ONCE) { + String text = getString(R.string.too_many_contacts_add_to_group, + MAX_CONTACTS_NUM_TO_SELECT_ONCE); + Toast.makeText(this, text, Toast.LENGTH_SHORT).show(); + } else { + int memberAction = getIntent().getIntExtra(EXTRA_GROUP_ACTION, + GROUP_ACTION_NONE); + switch (memberAction) { + case GROUP_ACTION_ADD_MEMBER: + setResult(RESULT_OK, new Intent().putExtras(mChoiceSet)); + finish(); + break; + case GROUP_ACTION_MOVE_MEMBER: + String account = getIntent().getStringExtra( + SimContactsConstants.ACCOUNT_TYPE); + long groupId = getIntent().getLongExtra(EXTRA_GROUP_ID, -1); + showGroupSelectionList(account, groupId); + break; + default: + setResultAndFinish(); + break; + } + } + } else if (mChoiceSet.size() > 0) { + showDialog(R.id.dialog_delete_contact_confirmation); + } + } else if (mMode == MODE_DEFAULT_PHONE) { + setResultAndFinish(); + } else if (mMode == MODE_DEFAULT_SIM) { + if (mChoiceSet.size() > 0) { + showDialog(R.id.dialog_import_sim_contact_confirmation); + } + } else if (mMode == MODE_DEFAULT_EMAIL) { + setResultAndFinish(); + } else if (mMode == MODE_DEFAULT_CALL) { + if (mChoiceSet.size() > 0) { + if (mSelectCallLog) { + setResultAndFinish(); + } else { + showDialog(DIALOG_DEL_CALL); + } + } + } + return true; + case android.R.id.home: + finish(); + return true; + } + return false; + } + + @Override + public void onBackPressed() { + if (mSearchUiVisible) { + exitSearchMode(); + } else { + super.onBackPressed(); + } + } + + @Override + public boolean onQueryTextSubmit(String query) { + updateState(query); + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + updateState(newText); + return true; + } + + @Override + public boolean onClose() { + if (!TextUtils.isEmpty(mSearchView.getQuery())) { + mSearchView.setQuery(null, true); + } + return true; + } + + @Override + public void onFocusChange(View view, boolean hasFocus) { + switch (view.getId()) { + case R.id.search_view: { + if (hasFocus) { + final InputMethodManager imm = + (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mSearchView.findFocus(), 0); + } + } + } + } + + private void updateState(String query) { + if (!TextUtils.isEmpty(query)) { + if (!isSearchMode()) { + enterSearchMode(); + } + } else if (isSearchMode()) { + exitSearchMode(); + } + doFilter(query); + } + + private void setResultAndFinish() { + Intent intent = new Intent(); + Bundle bundle = new Bundle(); + bundle.putBundle(SimContactsConstants.RESULT_KEY, mChoiceSet); + intent.putExtras(bundle); + setResult(RESULT_OK, intent); + finish(); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + hideSoftKeyboard(); + + ContactItemCache cache = (ContactItemCache) v.getTag(); + String key = String.valueOf(cache.id); + + if (!mChoiceSet.containsKey(key)) { + String[] value = null; + if (isPickContact()) { + value = new String[] { + cache.lookupKey, String.valueOf(cache.id), + String.valueOf(cache.nameRawContactId), + cache.photoUri, cache.name + }; + } else if (isPickPhone()) { + value = new String[] { + cache.name, cache.number, cache.type, + cache.label, cache.contact_id + }; + } else if (isPickEmail()) { + value = new String[] { + cache.name, cache.email + }; + } else if (isPickSim()) { + value = new String[] { + cache.name, cache.number, cache.email, cache.anrs + }; + } else if (isPickCall() && mSelectCallLog) { + value = new String[] { + cache.name, cache.number + }; + } + mChoiceSet.putStringArray(key, value); + } else { + mChoiceSet.remove(key); + } + + updateActionBar(); + mAdapter.notifyDataSetChanged(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: { + if (isSearchMode()) { + exitSearchMode(); + return true; + } + } + } + return super.onKeyDown(keyCode, event); + } + + private void updateActionBar() { + String countTitle = null; + if (!mChoiceSet.isEmpty()) { + countTitle = getResources().getQuantityString(R.plurals.contacts_selected, + mChoiceSet.size(), mChoiceSet.size()); + } + mActionBar.setSubtitle(countTitle); + invalidateOptionsMenu(); + } + + private boolean isSearchMode() { + return (mMode & MODE_MASK_SEARCH) == MODE_MASK_SEARCH; + } + + private void enterSearchMode() { + mMode |= MODE_MASK_SEARCH; + configureSearchMode(); + } + + private void exitSearchMode() { + mSearchUiVisible = false; + mMode &= ~MODE_MASK_SEARCH; + configureSearchMode(); + } + + private void configureSearchMode() { + if (mSearchUiVisible) { + mSearchViewContainer.setVisibility(View.VISIBLE); + mSearchView.requestFocus(); + } else { + mSearchViewContainer.setVisibility(View.GONE); + mSearchView.setQuery(null, true); + } + mActionBar.setDisplayShowTitleEnabled(!mSearchUiVisible); + updateActionBar(); + } + + @Override + protected Dialog onCreateDialog(int id, Bundle bundle) { + switch (id) { + case R.id.dialog_delete_contact_confirmation: + return new AlertDialog.Builder(this) + .setTitle(R.string.deleteConfirmation_title) + .setMessage(getResources().getQuantityString( + R.plurals.ContactMultiDeleteConfirmation, + mChoiceSet.size(), mChoiceSet.size())) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, this) + .create(); + case DIALOG_DEL_CALL: + return new AlertDialog.Builder(this) + .setTitle(R.string.title_del_call) + .setMessage(R.string.delete_call_alert) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, this) + .create(); + case R.id.dialog_import_sim_contact_confirmation: + return new AlertDialog.Builder(this) + .setTitle(R.string.importConfirmation_title) + .setMessage(getResources().getQuantityString( + R.plurals.ContactMultiImportConfirmation, + mChoiceSet.size(), mChoiceSet.size())) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, this) + .create(); + } + + return super.onCreateDialog(id, bundle); + } + + @Override + protected void onPrepareDialog(int id, Dialog dialog, Bundle bundle) { + switch (id) { + case R.id.dialog_delete_contact_confirmation: + ((AlertDialog) dialog).setMessage(getResources().getQuantityString( + R.plurals.ContactMultiDeleteConfirmation, + mChoiceSet.size(), mChoiceSet.size())); + break; + case R.id.dialog_import_sim_contact_confirmation: + ((AlertDialog) dialog).setMessage(getResources().getQuantityString( + R.plurals.ContactMultiImportConfirmation, + mChoiceSet.size(), mChoiceSet.size())); + break; + } + } + + private class DeleteContactsThread extends Thread implements + DialogInterface.OnCancelListener, DialogInterface.OnClickListener { + boolean mCanceled = false; + private String name = null; + private String number = null; + private final String[] PROJECTION = new String[] { + Phone.CONTACT_ID, + Phone.NUMBER, + Phone.DISPLAY_NAME + }; + private final int COLUMN_NUMBER = 1; + private final int COLUMN_NAME = 2; + + private ArrayList<ContentProviderOperation> mOpsCalls = null; + private ArrayList<ContentProviderOperation> mOpsContacts = null; + + public DeleteContactsThread() { + } + + @Override + public void run() { + final Context context = MultiPickContactActivity.this; + final ContentResolver resolver = getContentResolver(); + + // The mChoiceSet object will change when activity restart, but + // DeleteContactsThread running in background, so we need clone the + // choiceSet to avoid ConcurrentModificationException. + Bundle choiceSet = (Bundle) mChoiceSet.clone(); + Set<String> keySet = choiceSet.keySet(); + Iterator<String> it = keySet.iterator(); + + ContentProviderOperation.Builder builder = null; + ContentProviderOperation cpo = null; + + // Current contact count we can delete. + int count = 0; + + // The contacts we batch delete once. + final int BATCH_DELETE_CONTACT_NUMBER = 100; + + mOpsCalls = new ArrayList<ContentProviderOperation>(); + mOpsContacts = new ArrayList<ContentProviderOperation>(); + + while (!mCanceled && it.hasNext()) { + String id = it.next(); + Uri uri = null; + if (isPickCall()) { + uri = Uri.withAppendedPath(Calls.CONTENT_URI, id); + builder = ContentProviderOperation.newDelete(uri); + cpo = builder.build(); + mOpsCalls.add(cpo); + } else { + uri = Uri.withAppendedPath(Contacts.CONTENT_URI, id); + long longId = Long.parseLong(id); + int subscription = mSimContactsOperation.getSimSubscription(longId); + + if (subscription == SimContactsConstants.SUB_1 + || subscription == SimContactsConstants.SUB_2) { + if (MoreContactUtils.isAPMOnAndSIMPowerDown(context)) { + break; + } + ContentValues values = + mSimContactsOperation.getSimAccountValues(longId); + log("values is : " + values + "; sub is " + subscription); + if (mSimContactsOperation.delete(values, subscription) == 0) { + mProgressDialog.incrementProgressBy(1); + continue; + } + } + builder = ContentProviderOperation.newDelete(uri); + cpo = builder.build(); + mOpsContacts.add(cpo); + } + // If contacts more than 2000, delete all contacts + // one by one will cause UI nonresponse. + mProgressDialog.incrementProgressBy(1); + // We batch delete contacts every 100. + if (count % BATCH_DELETE_CONTACT_NUMBER == 0) { + batchDelete(); + } + count++; + } + + batchDelete(); + mOpsCalls = null; + mOpsContacts = null; + Log.d(TAG, "DeleteContactsThread run, progress:" + mProgressDialog.getProgress()); + mProgressDialog.dismiss(); + finish(); + } + + /** + * Batch delete contacts more efficient than one by one. + */ + private void batchDelete() { + try { + getContentResolver().applyBatch(CallLog.AUTHORITY, mOpsCalls); + getContentResolver().applyBatch(ContactsContract.AUTHORITY, mOpsContacts); + mOpsCalls.clear(); + mOpsContacts.clear(); + } catch (RemoteException e) { + e.printStackTrace(); + } catch (OperationApplicationException e) { + e.printStackTrace(); + } + } + + @Override + public void onCancel(DialogInterface dialog) { + mCanceled = true; + Log.d(TAG, "DeleteContactsThread onCancel, progress:" + mProgressDialog.getProgress()); + // Give a toast show to tell user delete termination + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_NEGATIVE) { + mCanceled = true; + mProgressDialog.dismiss(); + } + } + } + + @Override + public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_SEARCH: + case KeyEvent.KEYCODE_CALL: + return true; + default: + return false; + } + } + + @Override + public void onClick(DialogInterface dialog, int which) { + CharSequence title = null; + CharSequence message = null; + + if (isPickCall()) { + title = getString(R.string.delete_call_title); + message = getString(R.string.delete_call_message); + } else if (isPickSim()) { + title = getString(R.string.import_sim_contacts_title); + message = getString(R.string.import_sim_contacts_message); + } else { + title = getString(R.string.delete_contacts_title); + message = getString(R.string.delete_contacts_message); + } + + Thread thread = isPickSim() + ? new ImportAllSimContactsThread() : new DeleteContactsThread(); + + mProgressDialog = new ProgressDialog(MultiPickContactActivity.this); + mProgressDialog.setTitle(title); + mProgressDialog.setMessage(message); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + mProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, + getString(android.R.string.cancel), (DialogInterface.OnClickListener) thread); + mProgressDialog.setOnCancelListener((DialogInterface.OnCancelListener) thread); + mProgressDialog.setOnKeyListener(this); + mProgressDialog.setProgress(0); + mProgressDialog.setMax(mChoiceSet.size()); + + // set dialog can not be canceled by touching outside area of dialog + mProgressDialog.setCanceledOnTouchOutside(false); + mProgressDialog.show(); + + thread.start(); + } + + @Override + public void onDestroy() { + mQueryHandler.removeCallbacksAndMessages(QUERY_TOKEN); + if (mAdapter.getCursor() != null) { + mAdapter.getCursor().close(); + } + + if (mProgressDialog != null) { + mProgressDialog.cancel(); + } + + // unregister receiver. + if (mBroadcastReceiver != null) { + unregisterReceiver(mBroadcastReceiver); + } + + super.onDestroy(); + } + + /** + * Just get the uri we need to query contacts. + * + * @return uri with account info parameter if explicit request contacts fit + * current account, else just search contacts fit specified keyword. + */ + private Uri getContactsFilterUri() { + Uri filterUri = Contacts.CONTENT_FILTER_URI; + + // To confirm if the search rule must contain account limitation. + Intent intent = getIntent(); + ContactListFilter filter = (ContactListFilter) intent.getParcelableExtra( + AccountFilterActivity.KEY_EXTRA_CONTACT_LIST_FILTER); + int operation = getIntent().getIntExtra(EXTRA_GROUP_ACTION, GROUP_ACTION_NONE); + long groupId = getIntent().getLongExtra(EXTRA_GROUP_ID, -1); + String accountName = getIntent().getStringExtra(SimContactsConstants.ACCOUNT_NAME); + String accountType = getIntent().getStringExtra(SimContactsConstants.ACCOUNT_TYPE); + switch (operation) { + case GROUP_ACTION_ADD_MEMBER: + case GROUP_ACTION_MOVE_MEMBER: + Uri.Builder builder = Contacts.CONTENT_FILTER_URI.buildUpon(); + builder.appendQueryParameter(ADD_GROUP_MEMBERS, + operation == GROUP_ACTION_ADD_MEMBER ? "true" : "false"); + builder.appendQueryParameter(Groups._ID, String.valueOf(groupId)); + builder.appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName); + builder.appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType); + return builder.build(); + } + if (filter != null && + filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) { + // Need consider account info limitation, construct the uri with + // account info query parameter. + Uri.Builder builder = filterUri.buildUpon(); + filter.addAccountQueryParameterToUrl(builder); + return builder.build(); + } + + if (!isShowSIM()) { + filterUri = filterUri.buildUpon() + .appendQueryParameter(RawContacts.ACCOUNT_TYPE, SimAccountType.ACCOUNT_TYPE) + .appendQueryParameter(SimContactsConstants.WITHOUT_SIM_FLAG, "true") + .build(); + } + // No need to consider account info limitation, just return a uri + // with "filter" path. + return filterUri; + } + + private Uri getUriToQuery() { + Uri uri; + switch (mMode) { + case MODE_DEFAULT_CONTACT: { + Intent intent = getIntent(); + int operation = intent.getIntExtra(EXTRA_GROUP_ACTION, GROUP_ACTION_NONE); + long groupId = intent.getLongExtra(EXTRA_GROUP_ID, -1); + String accountName = intent.getStringExtra(SimContactsConstants.ACCOUNT_NAME); + String accountType = intent.getStringExtra(SimContactsConstants.ACCOUNT_TYPE); + switch (operation) { + case GROUP_ACTION_ADD_MEMBER: + case GROUP_ACTION_MOVE_MEMBER: + Uri.Builder builder = Contacts.CONTENT_GROUP_URI.buildUpon(); + builder.appendQueryParameter(ADD_GROUP_MEMBERS, + operation == GROUP_ACTION_ADD_MEMBER ? "true" : "false"); + builder.appendQueryParameter(Groups._ID, String.valueOf(groupId)); + builder.appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName); + builder.appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType); + uri = builder.build(); + break; + default: + uri = Contacts.CONTENT_URI; + break; + } + break; + } + case MODE_SEARCH_CONTACT: + uri = Contacts.CONTENT_URI; + break; + case MODE_DEFAULT_EMAIL: + case MODE_SEARCH_EMAIL: + uri = Email.CONTENT_URI; + break; + case MODE_DEFAULT_PHONE: + case MODE_SEARCH_PHONE: + uri = Phone.CONTENT_URI; + break; + case MODE_DEFAULT_CALL: + case MODE_SEARCH_CALL: + uri = Calls.CONTENT_URI_WITH_VOICEMAIL; + break; + case MODE_DEFAULT_SIM: + case MODE_SEARCH_SIM: { + int subscription = getIntent().getIntExtra(SimContactsConstants.SUB, 0); + uri = querySimContacts(subscription); + break; + } + default: + throw new IllegalArgumentException("getUriToQuery: Incorrect mode: " + mMode); + } + return uri.buildUpon() + .appendQueryParameter(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX, "true") + .build(); + } + + private Uri getFilterUri() { + switch (mMode) { + case MODE_SEARCH_CONTACT: + return getContactsFilterUri(); + case MODE_SEARCH_PHONE: + return Phone.CONTENT_FILTER_URI; + case MODE_SEARCH_EMAIL: + return Email.CONTENT_FILTER_URI; + default: + log("getFilterUri: Incorrect mode: " + mMode); + } + return Contacts.CONTENT_FILTER_URI; + } + + public String[] getProjectionForQuery() { + switch (mMode) { + case MODE_DEFAULT_CONTACT: + case MODE_SEARCH_CONTACT: + return CONTACTS_SUMMARY_PROJECTION; + case MODE_DEFAULT_PHONE: + case MODE_SEARCH_PHONE: + return PHONES_PROJECTION; + case MODE_DEFAULT_EMAIL: + case MODE_SEARCH_EMAIL: + return EMAILS_PROJECTION; + case MODE_DEFAULT_CALL: + case MODE_SEARCH_CALL: + return CALL_LOG_PROJECTION; + case MODE_DEFAULT_SIM: + case MODE_SEARCH_SIM: + return SIM_COLUMN_NAMES; + default: + log("getProjectionForQuery: Incorrect mode: " + mMode); + } + return CONTACTS_SUMMARY_PROJECTION; + } + + private String getSortOrder(String[] projection) { + switch (mMode) { + case MODE_DEFAULT_CALL: + case MODE_SEARCH_CALL: + return CALL_LOG_PROJECTION[2] + SORT_ORDER; + } + return RawContacts.SORT_KEY_PRIMARY; + } + + private String getSelectionForQuery() { + switch (mMode) { + case MODE_DEFAULT_EMAIL: + case MODE_SEARCH_EMAIL: + case MODE_DEFAULT_PHONE: + case MODE_SEARCH_PHONE: + if (isShowSIM()) { + return null; + } + return PHONES_SELECTION; + case MODE_DEFAULT_CONTACT: + return getSelectionForAccount(); + case MODE_DEFAULT_SIM: + case MODE_SEARCH_SIM: + return null; + case MODE_DEFAULT_CALL: + // Add a subscription judgement, if selection = -1 that means + // need query both cards. + String selection = null; + int slot = getIntent().getIntExtra(SimContactsConstants.SUB, + SimContactsConstants.SUB_INVALID); + int[] subIds = SubscriptionManager.getSubId(slot); + if (SimContactsConstants.SUB_INVALID != slot + && subIds != null && subIds.length > 0) { + selection = Calls.PHONE_ACCOUNT_ID + "=" + Long.toString(subIds[0]); + } + return selection; + default: + return null; + } + } + + private String getSelectionForAccount() { + @SuppressWarnings("deprecation") + ContactListFilter filter = (ContactListFilter) getIntent().getExtra( + AccountFilterActivity.KEY_EXTRA_CONTACT_LIST_FILTER); + if (filter == null) { + return null; + } + switch (filter.filterType) { + case ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS: + return null; + case ContactListFilter.FILTER_TYPE_CUSTOM: + return CONTACTS_SELECTION; + case ContactListFilter.FILTER_TYPE_ACCOUNT: + return null; + } + return null; + } + + private String[] getSelectionArgsForQuery() { + switch (mMode) { + case MODE_DEFAULT_EMAIL: + case MODE_SEARCH_EMAIL: + case MODE_DEFAULT_PHONE: + case MODE_SEARCH_PHONE: + if (isShowSIM()) { + return null; + } + return PHONES_SELECTION_ARGS; + case MODE_DEFAULT_SIM: + case MODE_SEARCH_SIM: + return null; + default: + return null; + } + } + + private boolean isShowSIM() { + // if airplane mode on, do not show SIM. + return !getIntent().hasExtra(EXTRA_NOT_SHOW_SIM_FLAG) + && !MoreContactUtils.isAPMOnAndSIMPowerDown(this); + } + + public void startQuery() { + Uri uri = getUriToQuery(); + ContactListFilter filter = (ContactListFilter) getIntent().getExtra( + AccountFilterActivity.KEY_EXTRA_CONTACT_LIST_FILTER); + if (filter != null) { + if (filter.filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) { + // We should exclude the invisiable contacts. + uri = uri.buildUpon() + .appendQueryParameter(RawContacts.ACCOUNT_NAME, filter.accountName) + .appendQueryParameter(RawContacts.ACCOUNT_TYPE, filter.accountType) + .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, + String.valueOf(ContactsContract.Directory.DEFAULT)) + .build(); + } else if (filter.filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS) { + // Do not query sim contacts in airplane mode. + if (!isShowSIM()) { + uri = uri.buildUpon() + .appendQueryParameter(RawContacts.ACCOUNT_TYPE, + SimAccountType.ACCOUNT_TYPE) + .appendQueryParameter(SimContactsConstants.WITHOUT_SIM_FLAG, "true") + .build(); + } + } + } + String[] projection = getProjectionForQuery(); + mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, + getSelectionForQuery(), getSelectionArgsForQuery(), getSortOrder(projection)); + } + + public void doFilter(CharSequence s) { + if (TextUtils.isEmpty(s)) { + startQuery(); + return; + } + + Uri uri = Uri.withAppendedPath(getFilterUri(), Uri.encode(s.toString())); + String[] projection = getProjectionForQuery(); + mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, + getSelectionForQuery(), getSelectionArgsForQuery(), getSortOrder(projection)); + } + + public void updateContent() { + if (isSearchMode()) { + doFilter(mSearchView.getQuery().toString()); + } else { + startQuery(); + } + } + + private CharSequence getDisplayNumber(CharSequence number) { + if (TextUtils.isEmpty(number)) { + return ""; + } + if (PhoneNumberUtils.isVoiceMailNumber(number.toString())) { + return getString(R.string.voicemail); + } + return number; + } + + private boolean isPickContact() { + return mMode == MODE_DEFAULT_CONTACT || mMode == MODE_SEARCH_CONTACT; + } + + private boolean isPickPhone() { + return mMode == MODE_DEFAULT_PHONE || mMode == MODE_SEARCH_PHONE; + } + + private boolean isPickSim() { + return mMode == MODE_DEFAULT_SIM || mMode == MODE_SEARCH_SIM; + } + + private boolean isPickEmail() { + return mMode == MODE_DEFAULT_EMAIL || mMode == MODE_SEARCH_EMAIL; + } + + private boolean isPickCall() { + return mMode == MODE_DEFAULT_CALL || mMode == MODE_SEARCH_CALL; + } + + private void selectAll(boolean isSelected) { + // update mChoiceSet. + // TODO: make it more efficient + Cursor cursor = mAdapter.getCursor(); + if (cursor == null) { + log("cursor is null."); + return; + } + + cursor.moveToPosition(-1); + while (cursor.moveToNext()) { + String id = null; + String[] value = null; + if (isPickContact()) { + id = String.valueOf(cursor.getLong(CONTACT_COLUMN_ID)); + value = new String[] { + cursor.getString(CONTACT_COLUMN_LOOKUP_KEY), id, + cursor.getString(CONTACT_COLUMN_RAW_CONTACT_ID), + cursor.getString(CONTACT_COLUMN_PHOTO_URI), + cursor.getString(CONTACT_COLUMN_DISPLAY_NAME) + }; + } else if (isPickPhone()) { + id = String.valueOf(cursor.getLong(PHONE_COLUMN_ID)); + value = new String[] { + cursor.getString(CONTACT_COLUMN_DISPLAY_NAME), + cursor.getString(PHONE_COLUMN_NUMBER), + String.valueOf(cursor.getInt(PHONE_COLUMN_TYPE)), + cursor.getString(PHONE_COLUMN_LABEL), + String.valueOf(cursor.getLong(CONTACT_COLUMN_ID)) + }; + } else if (isPickEmail()) { + id = String.valueOf(cursor.getLong(EMAIL_COLUMN_ID)); + value = new String[] { + cursor.getString(CONTACT_COLUMN_DISPLAY_NAME), + cursor.getString(EMAIL_COLUMN_ADDRESS), + id + }; + } else if (isPickCall()) { + id = String.valueOf(cursor.getLong(CALLLOG_COLUMN_ID)); + if (mSelectCallLog) { + value = new String[] { + cursor.getString(CALLLOG_COLUMN_NUMBER), + cursor.getString(CALLLOG_COLUMN_CALLER_NAME) + }; + } else { + value = new String[] { + id + }; + } + } else if (isPickSim()) { + id = String.valueOf(cursor.getLong(SIM_COLUMN_ID)); + value = new String[] { + cursor.getString(SIM_COLUMN_DISPLAY_NAME), + cursor.getString(SIM_COLUMN_NUMBER), + cursor.getString(SIM_COLUMN_EMAILS), + cursor.getString(SIM_COLUMN_ANRS) + }; + } + if (isSelected) { + mChoiceSet.putStringArray(id, value); + } else { + mChoiceSet.remove(id); + } + } + + updateActionBar(); + mAdapter.notifyDataSetChanged(); + } + + private class QueryHandler extends AsyncQueryHandler { + public QueryHandler(Context context) { + super(context.getContentResolver()); + } + + @Override + protected void onQueryComplete(int token, Object cookie, Cursor cursor) { + mAdapter.changeCursor(cursor); + if (cursor == null || cursor.getCount() == 0) { + Toast.makeText(MultiPickContactActivity.this, + R.string.listFoundAllContactsZero, Toast.LENGTH_SHORT).show(); + } + } + } + + private final class ContactItemCache { + long id; + String name; + String number; + String lookupKey; + String type; + String label; + String contact_id; + String email; + String anrs; + long nameRawContactId; + String photoUri; + } + + private final class ContactItemListAdapter extends CursorAdapter implements SectionIndexer { + Context mContext; + protected LayoutInflater mInflater; + private ContactsSectionIndexer mIndexer; + private ContactPhotoManager mContactPhotoManager; + + public ContactItemListAdapter(Context context) { + super(context, null, false); + + mContext = context; + mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mContactPhotoManager = ContactPhotoManager.getInstance(mContext); + } + + private void assignContactAndFillCache(ContactListItemView cliv, Cursor cursor, + Account account, ContactItemCache cache) { + String newLookupKey = cursor.getString(CONTACT_COLUMN_LOOKUP_KEY); + boolean rebound = !TextUtils.equals(cache.lookupKey, newLookupKey); + + cache.lookupKey = newLookupKey; + cache.name = cursor.getString(CONTACT_COLUMN_DISPLAY_NAME); + + long photoId = cursor.getLong(CONTACT_COLUMN_PHOTO_ID); + mContactPhotoManager.loadThumbnail(cliv.getPhotoView(), photoId, account, false, true, + new DefaultImageRequest(cache.name, cache.lookupKey, true)); + + CharSequence query = mSearchView != null ? mSearchView.getQuery() : null; + cliv.setHighlightedPrefix(query != null ? query.toString().toUpperCase() : null); + cliv.setDisplayName(cache.name); + cliv.setChecked(mChoiceSet.containsKey(String.valueOf(cache.id)), !rebound); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + ContactItemCache cache = (ContactItemCache) view.getTag(); + ContactListItemView cliv = (ContactListItemView) view.findViewById(R.id.contact); + + if (isPickContact()) { + cache.id = cursor.getLong(CONTACT_COLUMN_ID); + cache.nameRawContactId = cursor.getLong(CONTACT_COLUMN_RAW_CONTACT_ID); + cache.photoUri = cursor.getString(CONTACT_COLUMN_PHOTO_URI); + + Account account = null; + if (!cursor.isNull(CONTACT_COLUMN_ACCOUNT_TYPE) + && !cursor.isNull(CONTACT_COLUMN_ACCOUNT_NAME)) { + final String accountType = cursor.getString(CONTACT_COLUMN_ACCOUNT_TYPE); + final String accountName = cursor.getString(CONTACT_COLUMN_ACCOUNT_NAME); + account = new Account(accountName, accountType); + } + + assignContactAndFillCache(cliv, cursor, account, cache); + cliv.setPhoneNumber(null, null); + } else if (isPickPhone()) { + cache.id = cursor.getLong(PHONE_COLUMN_ID); + cache.number = cursor.getString(PHONE_COLUMN_NUMBER); + cache.label = cursor.getString(PHONE_COLUMN_LABEL); + cache.type = String.valueOf(cursor.getInt(PHONE_COLUMN_TYPE)); + + assignContactAndFillCache(cliv, cursor, null, cache); + cliv.setPhoneNumber(cache.number, null); + } else if (isPickSim()) { + cache.id = cursor.getLong(SIM_COLUMN_ID); + cache.name = cursor.getString(SIM_COLUMN_DISPLAY_NAME); + cache.number = cursor.getString(SIM_COLUMN_NUMBER); + cache.email = cursor.getString(SIM_COLUMN_EMAILS); + cache.anrs = cursor.getString(SIM_COLUMN_ANRS); + + cliv.setDisplayName(cache.name); + mContactPhotoManager.loadThumbnail(cliv.getPhotoView(), -1, null, false, true, + new DefaultImageRequest(cache.name, cache.lookupKey, true)); + if (!TextUtils.isEmpty(cache.number)) { + cliv.setPhoneNumber(cache.number, null); + } else if (!TextUtils.isEmpty(cache.email)) { + String[] emailArray = (cache.email).split(","); + cliv.setPhoneNumber(emailArray[0], null); + } else { + cliv.setPhoneNumber(null, null); + } + cliv.setChecked(mChoiceSet.containsKey(String.valueOf(cache.id)), true); + } else if (isPickEmail()) { + cache.id = cursor.getLong(EMAIL_COLUMN_ID); + cache.email = cursor.getString(EMAIL_COLUMN_ADDRESS); + + assignContactAndFillCache(cliv, cursor, null, cache); + cliv.setPhoneNumber(cache.email, null); + } else if (isPickCall()) { + cache.id = cursor.getLong(CALLLOG_COLUMN_ID); + cache.name = cursor.getString(CALLLOG_COLUMN_CALLER_NAME); + cache.number = cursor.getString(CALLLOG_COLUMN_NUMBER); + + String callerName = cursor.getString(CALLLOG_COLUMN_CALLER_NAME); + int callerNumberType = cursor.getInt(CALLLOG_COLUMN_CALLER_NUMBERTYPE); + String callerNumberLabel = cursor.getString(CALLLOG_COLUMN_CALLER_NUMBERLABEL); + String geocodedLocation = cursor.getString(CALLLOG_COLUMN_CALLER_LOCATION); + String accountId = cursor.getString(CALLLOG_COLUMN_PHONE_ACCOUNT); + long date = cursor.getLong(CALLLOG_COLUMN_DATE); + long duration = cursor.getLong(CALLLOG_COLUMN_DURATION); + int type = cursor.getInt(CALLLOG_COLUMN_CALL_TYPE); + + ImageView callType = (ImageView) view.findViewById(R.id.call_type_icon); + TextView dateText = (TextView) view.findViewById(R.id.call_date); + TextView durationText = (TextView) view.findViewById(R.id.duration); + TextView subSlotText = (TextView) view.findViewById(R.id.subscription); + TextView numberLabelText = (TextView) view.findViewById(R.id.label); + TextView nameText = (TextView) view.findViewById(R.id.name); + + // only for monkey test, callType can not be null in normal behaviour + if (callType == null) { + return; + } + + callType.setVisibility(View.VISIBLE); + // Set the icon + switch (type) { + case Calls.INCOMING_TYPE: + callType.setImageResource(R.drawable.ic_call_incoming_holo_dark); + break; + case Calls.OUTGOING_TYPE: + callType.setImageResource(R.drawable.ic_call_outgoing_holo_dark); + break; + case Calls.MISSED_TYPE: + callType.setImageResource(R.drawable.ic_call_missed_holo_dark); + break; + default: + callType.setVisibility(View.INVISIBLE); + break; + } + + // set the number + if (!TextUtils.isEmpty(callerName)) { + nameText.setText(callerName); + } else { + nameText.setText(getDisplayNumber(cache.number)); + } + + CharSequence numberLabel = null; + if (callerNumberType != 0 && !PhoneNumberUtils.isUriNumber(cache.number)) { + numberLabel = Phone.getDisplayLabel(context, callerNumberType, + callerNumberLabel); + } else { + numberLabel = geocodedLocation; + } + numberLabelText.setText(numberLabel); + numberLabelText.setVisibility(TextUtils.isEmpty(numberLabel) + ? View.GONE : View.VISIBLE); + + // set date + dateText.setText(DateUtils.getRelativeTimeSpanString(date, + System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, + DateUtils.FORMAT_ABBREV_RELATIVE)); + + // set duration + durationText.setText(DateUtils.formatElapsedTime(duration)); + + // set slot + if (isMultiSimEnabled()) { + int slotId = SimContactsConstants.SUB_INVALID; + if (accountId != null) { + try { + slotId = SubscriptionManager.getSlotId(Integer.valueOf(accountId)); + } catch (NumberFormatException e) { + // ignore and keep the default 'invalid' + } + } + subSlotText.setText(MoreContactUtils.getMultiSimAliasesName( + MultiPickContactActivity.this, slotId)); + } else { + subSlotText.setVisibility(View.GONE); + } + + CheckBox checkBox = (CheckBox) view.findViewById(R.id.pick_contact_check); + checkBox.setChecked(mChoiceSet.containsKey(String.valueOf(cache.id))); + } + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + int layoutResId = isPickCall() ? R.layout.pick_calls_item : R.layout.pick_contact_item; + View v = mInflater.inflate(layoutResId, parent, false); + ContactListItemView cliv = (ContactListItemView) v.findViewById(R.id.contact); + if (cliv != null) { + cliv.setUnknownNameText(getString(R.string.missing_name)); + } + ContactItemCache cache = new ContactItemCache(); + v.setTag(cache); + return v; + } + + @Override + protected void onContentChanged() { + updateContent(); + } + + @Override + public void changeCursor(Cursor cursor) { + super.changeCursor(cursor); + String[] sections = null; + int[] counts = null; + Bundle extras = cursor != null ? cursor.getExtras() : null; + if (extras != null && + extras.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES)) { + sections = extras.getStringArray( + ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES); + counts = extras.getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS); + } else { + sections = new String[0]; + counts = new int[0]; + } + mIndexer = new ContactsSectionIndexer(sections, counts); + updateActionBar(); + } + + @Override + public Object[] getSections() { + if (mIndexer != null) { + return mIndexer.getSections(); + } + return null; + } + + @Override + public int getPositionForSection(int section) { + Cursor cursor = getCursor(); + if (cursor == null) { + return 0; + } + if (mIndexer != null) { + return mIndexer.getPositionForSection(section); + } + return 0; + } + + @Override + public int getSectionForPosition(int position) { + if (mIndexer != null) { + return mIndexer.getSectionForPosition(position); + } + return -1; + } + + public int getSortIndex() { + switch (mMode) { + case MODE_DEFAULT_CONTACT: + case MODE_SEARCH_CONTACT: + case MODE_DEFAULT_PHONE: + case MODE_SEARCH_PHONE: + case MODE_DEFAULT_EMAIL: + case MODE_SEARCH_EMAIL: + return CONTACT_COLUMN_DISPLAY_NAME; + case MODE_DEFAULT_CALL: + case MODE_SEARCH_CALL: + return CALLLOG_COLUMN_CALLER_NAME; + case MODE_DEFAULT_SIM: + case MODE_SEARCH_SIM: + return SIM_COLUMN_DISPLAY_NAME; + default: + throw new IllegalArgumentException("Incorrect mode for multi pick"); + } + } + } + + /** + * Dismisses the soft keyboard when the list takes focus. + */ + public boolean onTouch(View view, MotionEvent event) { + if (view == getListView()) { + hideSoftKeyboard(); + } + return false; + } + + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + hideSoftKeyboard(); + return true; + } + return false; + } + + private void hideSoftKeyboard() { + // Hide soft keyboard, if visible + InputMethodManager inputMethodManager = + (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0); + } + + protected static void log(String msg) { + if (DEBUG) Log.d(TAG, msg); + } + + private Uri querySimContacts(int subscription) { + Uri uri = null; + if (subscription != SimContactsConstants.SUB_1 + && subscription != SimContactsConstants.SUB_2) { + return uri; + } + int[] subId = SubscriptionManager.getSubId(subscription); + if (subId != null && isMultiSimEnabled()) { + uri = Uri.parse(SimContactsConstants.SIM_SUB_URI + subId[0]); + } + else { + uri = Uri.parse(SimContactsConstants.SIM_URI); + } + + return uri; + } + + private boolean isMultiSimEnabled() { + return TelephonyManager.getDefault().isMultiSimEnabled(); + } + + protected Account[] getSimAccounts() { + return mAccountManager.getAccountsByType(SimContactsConstants.ACCOUNT_TYPE_SIM); + } + + private class ImportAllSimContactsThread extends Thread + implements DialogInterface.OnCancelListener, DialogInterface.OnClickListener { + boolean mCanceled = false; + // The total count how many to import. + private int mTotalCount = 0; + // The real count have imported. + private int mActualCount = 0; + + private Account mAccount; + + public ImportAllSimContactsThread() { + } + + @Override + public void run() { + final ContentValues emptyContentValues = new ContentValues(); + final ContentResolver resolver = getContentResolver(); + + String type = getIntent().getStringExtra(SimContactsConstants.ACCOUNT_TYPE); + String name = getIntent().getStringExtra(SimContactsConstants.ACCOUNT_NAME); + mAccount = new Account(name != null ? name : SimContactsConstants.PHONE_NAME, + type != null ? type : SimContactsConstants.ACCOUNT_TYPE_PHONE); + log("import sim contact to account: " + mAccount); + mTotalCount = mChoiceSet.size(); + + for (String key : mChoiceSet.keySet()) { + if (mCanceled) { + break; + } + String[] values = mChoiceSet.getStringArray(key); + actuallyImportOneSimContact(values, resolver, mAccount); + mActualCount++; + mProgressDialog.incrementProgressBy(1); + } + finish(); + } + + @Override + public void onCancel(DialogInterface dialog) { + final Context context = MultiPickContactActivity.this; + mCanceled = true; + // Give a toast show to tell user import termination. + if (mActualCount < mTotalCount) { + String text = getResources().getQuantityString(R.plurals.import_progress, + mActualCount, mActualCount); + Toast.makeText(context, text, Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(context, R.string.import_finish, Toast.LENGTH_SHORT).show(); + } + } + + public void onClick(DialogInterface dialog, int which) { + if (which == DialogInterface.BUTTON_NEGATIVE) { + mCanceled = true; + mProgressDialog.dismiss(); + } + } + } + + private static void actuallyImportOneSimContact( + String[] values, final ContentResolver resolver, Account account) { + + final String name = values[SIM_COLUMN_DISPLAY_NAME]; + final String phoneNumber = values[SIM_COLUMN_NUMBER]; + final String emailAddresses = values[SIM_COLUMN_EMAILS]; + final String anrs = values[SIM_COLUMN_ANRS]; + final String[] emailAddressArray; + final String[] anrArray; + if (!TextUtils.isEmpty(emailAddresses)) { + emailAddressArray = emailAddresses.split(","); + } else { + emailAddressArray = null; + } + if (!TextUtils.isEmpty(anrs)) { + anrArray = anrs.split(","); + } else { + anrArray = null; + } + log(" actuallyImportOneSimContact: name= " + name + + ", phoneNumber= " + phoneNumber + ", emails= " + emailAddresses + + ", anrs= " + anrs + ", account is " + account); + final ArrayList<ContentProviderOperation> operationList = + new ArrayList<ContentProviderOperation>(); + ContentProviderOperation.Builder builder = + ContentProviderOperation.newInsert(RawContacts.CONTENT_URI); + builder.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED); + if (account != null) { + builder.withValue(RawContacts.ACCOUNT_NAME, account.name); + builder.withValue(RawContacts.ACCOUNT_TYPE, account.type); + } + operationList.add(builder.build()); + + builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); + builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0); + builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); + builder.withValue(StructuredName.DISPLAY_NAME, name); + operationList.add(builder.build()); + + builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); + builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0); + builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE); + builder.withValue(Phone.NUMBER, phoneNumber); + builder.withValue(Data.IS_PRIMARY, 1); + operationList.add(builder.build()); + + if (anrArray != null) { + for (String anr : anrArray) { + builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); + builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0); + builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); + builder.withValue(Phone.TYPE, Phone.TYPE_HOME); + builder.withValue(Phone.NUMBER, anr); + operationList.add(builder.build()); + } + } + + if (emailAddresses != null) { + for (String emailAddress : emailAddressArray) { + builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); + builder.withValueBackReference(Email.RAW_CONTACT_ID, 0); + builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); + builder.withValue(Email.TYPE, Email.TYPE_MOBILE); + builder.withValue(Email.ADDRESS, emailAddress); + operationList.add(builder.build()); + } + } + + try { + resolver.applyBatch(ContactsContract.AUTHORITY, operationList); + } catch (RemoteException e) { + log(String.format("%s: %s", e.toString(), e.getMessage())); + } catch (OperationApplicationException e) { + log(String.format("%s: %s", e.toString(), e.getMessage())); + } + } + + /** + * After turn on airplane mode, cancel import sim contacts operation. + */ + private void cancelSimContactsImporting() { + if (mProgressDialog != null && mProgressDialog.isShowing()) { + mProgressDialog.cancel(); + } + } + + private void showGroupSelectionList(String accountType, long srcGroupId) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.label_groups)); + ContentResolver resolver = getContentResolver(); + String selection = Groups.ACCOUNT_TYPE + " =? AND " + Groups.DELETED + " != ?"; + ArrayList<String> items = new ArrayList<String>(); + + mGroupIds.clear(); + items.clear(); + Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] { + Groups._ID, Groups.TITLE + }, + selection, + new String[] { + accountType, "1" + }, + null); + if (cursor == null || cursor.getCount() == 0) { + Toast.makeText(this, R.string.message_can_not_move_members, + Toast.LENGTH_LONG).show(); + return; + } else { + try { + while (cursor.moveToNext()) { + if (!cursor.getString(0).equals(String.valueOf(srcGroupId))) { + mGroupIds.add(cursor.getLong(0)); + items.add(cursor.getString(1)); + } + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + if (mGroupIds.size() == 0) { + Toast.makeText(this, R.string.message_can_not_move_members, + Toast.LENGTH_LONG).show(); + return; + } + String[] groupItem = new String[items.size()]; + for (int i = 0; i < items.size(); i++) { + groupItem[i] = items.get(i); + } + builder.setItems(groupItem, new ChooseActionListener()); + builder.create().show(); + } + + private class ChooseActionListener implements DialogInterface.OnClickListener { + public void onClick(DialogInterface dialog, int which) { + new MoveGroupMemberTask(mChoiceSet, + getIntent().getLongExtra(EXTRA_GROUP_ID, -1), + mGroupIds.get(which)).execute(); + } + } + + class MoveGroupMemberTask extends AsyncTask<Object, Object, Object> { + + private static final String GROUP_QUERY_GROUP_MEMBER_SELECTION = + Data.MIMETYPE + "=? AND " + + GroupMembership.GROUP_ROW_ID + "=?"; + + private static final String GROUP_QUERY_RAW_CONTACTS_SELECTION = + RawContacts.CONTACT_ID + "=?"; + + private static final String GROUP_DELETE_MEMBER_SELECTION = Data.CONTACT_ID + + "=? AND " + + Data.MIMETYPE + + "=? AND " + + GroupMembership.GROUP_ROW_ID + + "=?"; + + private static final int BUFFER_LENGTH = 499; + + private Bundle mChoiceSet; + private long mDestGroupId; + private long mSrcGroupId; + private boolean mCanceled = false; + + private ArrayList<ContentProviderOperation> mAddOrMoveOperation; + private ArrayList<ContentProviderOperation> mDeleteOperation; + private ArrayList<String> mGroupMemberList = new ArrayList<String>(); + + public MoveGroupMemberTask(Bundle choiceSet, + long srcGroupId, long destGroupId) { + mChoiceSet = choiceSet; + mSrcGroupId = srcGroupId; + mDestGroupId = destGroupId; + } + + @Override + protected void onPreExecute() { + mProgressDialog = new ProgressDialog(MultiPickContactActivity.this, + com.android.internal.R.style.Theme_Holo_Light_Dialog_Alert); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + mProgressDialog.setTitle(getProgressDialogTitle()); + mProgressDialog.setMessage(getProgressDialogMessage()); + mProgressDialog.setMax(mChoiceSet != null ? mChoiceSet.keySet().size() : 100); + mProgressDialog.setProgress(0); + mProgressDialog.setCanceledOnTouchOutside(false); + mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + mCanceled = true; + } + }); + mProgressDialog.show(); + } + + @Override + protected Bundle doInBackground(Object... params) { + if (mChoiceSet == null || mSrcGroupId <= 0) { + return null; + } + ContentResolver resolver = getContentResolver(); + Cursor memberCursor = null; + + memberCursor = resolver.query(Data.CONTENT_URI, + new String[] { + Data.CONTACT_ID + }, + GROUP_QUERY_GROUP_MEMBER_SELECTION, + new String[] { + GroupMembership.CONTENT_ITEM_TYPE, + String.valueOf(mDestGroupId) + }, + null); + + if (memberCursor != null && memberCursor.getCount() > 0) { + try { + while (memberCursor.moveToNext()) { + // Mark those contacts that already exist in the dest + // group + mGroupMemberList.add(String.valueOf(memberCursor.getLong(0))); + } + } finally { + if (memberCursor != null) { + memberCursor.close(); + } + } + } + + Set<String> keySet = mChoiceSet.keySet(); + Iterator<String> it = keySet.iterator(); + + ContentProviderOperation.Builder builder; + + mAddOrMoveOperation = new ArrayList<ContentProviderOperation>(); + mDeleteOperation = new ArrayList<ContentProviderOperation>(); + String id; + int count = 0; + int maxSize = mChoiceSet.keySet().size(); + while (!mCanceled && it.hasNext()) { + id = it.next(); + ++count; + + if (mDestGroupId <= 0) { + // Invalid group id, cancel the task + return null; + } + if (mProgressDialog != null && mProgressDialog.isShowing() + && count < maxSize - (maxSize) / 100) { + mProgressDialog.incrementProgressBy(1); + } + if (mGroupMemberList.contains(id)) { + // If the contact already exists in the group, need to + // delete those + // contacts that in the previous group + builder = ContentProviderOperation.newDelete(Data.CONTENT_URI); + builder.withSelection(GROUP_DELETE_MEMBER_SELECTION, + new String[] { + id, + GroupMembership.CONTENT_ITEM_TYPE, + String.valueOf(mSrcGroupId) + }); + mDeleteOperation.add(builder.build()); + continue; + } + ContentValues values = new ContentValues(); + values.put(GroupMembership.GROUP_ROW_ID, mDestGroupId); + builder = ContentProviderOperation.newUpdate(Data.CONTENT_URI); + builder.withSelection(GROUP_DELETE_MEMBER_SELECTION, + new String[] { + id, + GroupMembership.CONTENT_ITEM_TYPE, + String.valueOf(mSrcGroupId) + }); + builder.withValues(values); + mAddOrMoveOperation.add(builder.build()); + } + + if (mDeleteOperation.size() > 0) { + if (mDeleteOperation.size() > BUFFER_LENGTH) { + addOrMoveApplyBatchByBuffer(mDeleteOperation, resolver); + } else { + try { + resolver.applyBatch(ContactsContract.AUTHORITY, mDeleteOperation); + } catch (RemoteException e) { + e.printStackTrace(); + } catch (OperationApplicationException e) { + e.printStackTrace(); + } + } + } + if (mAddOrMoveOperation.size() > BUFFER_LENGTH) { + addOrMoveApplyBatchByBuffer(mAddOrMoveOperation, resolver); + } else { + try { + resolver.applyBatch(ContactsContract.AUTHORITY, mAddOrMoveOperation); + } catch (RemoteException e) { + e.printStackTrace(); + } catch (OperationApplicationException e) { + e.printStackTrace(); + } + } + return null; + } + + @Override + protected void onPostExecute(Object result) { + if (mProgressDialog != null && mProgressDialog.isShowing()) { + mProgressDialog.dismiss(); + finish(); + } + } + + private void addOrMoveApplyBatchByBuffer(ArrayList<ContentProviderOperation> list, + ContentResolver cr) { + final ArrayList<ContentProviderOperation> temp + = new ArrayList<ContentProviderOperation>(BUFFER_LENGTH); + int bufferSize = list.size() / BUFFER_LENGTH; + for (int index = 0; index <= bufferSize; index++) { + temp.clear(); + if (index == bufferSize) { + for (int i = index * BUFFER_LENGTH; i < list.size(); i++) { + temp.add(list.get(i)); + } + } else { + for (int i = index * BUFFER_LENGTH; + i < index * BUFFER_LENGTH + BUFFER_LENGTH; i++) { + temp.add(list.get(i)); + } + } + if (!temp.isEmpty()) { + try { + cr.applyBatch(ContactsContract.AUTHORITY, temp); + } catch (Exception e) { + Log.e(TAG, "apply batch by buffer error:" + e); + } + } + } + } + + private String getProgressDialogTitle() { + return getString(R.string.title_move_members); + } + + private String getProgressDialogMessage() { + return getString(R.string.message_move_members); + } + } +}
\ No newline at end of file diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java index 97c6ea028..bad3f6996 100644 --- a/src/com/android/contacts/activities/PeopleActivity.java +++ b/src/com/android/contacts/activities/PeopleActivity.java @@ -16,12 +16,15 @@ package com.android.contacts.activities; +import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.ActivityNotFoundException; import android.content.ContentUris; import android.content.Context; +import android.content.BroadcastReceiver; +import android.content.IntentFilter; import android.content.Intent; import android.graphics.Rect; import android.net.Uri; @@ -33,6 +36,7 @@ import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.ProviderStatus; import android.provider.Settings; +import android.preference.PreferenceManager; import android.support.v13.app.FragmentPagerAdapter; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; @@ -59,8 +63,14 @@ import com.android.contacts.common.dialog.ClearFrequentsDialog; import com.android.contacts.common.util.ImplicitIntentsUtil; import com.android.contacts.common.widget.FloatingActionButtonController; import com.android.contacts.editor.EditorIntents; +import com.android.contacts.common.editor.SelectAccountDialogFragment; +import com.android.contacts.group.GroupBrowseListFragment; +import com.android.contacts.group.GroupBrowseListFragment.OnGroupBrowserActionListener; +import com.android.contacts.group.GroupDetailFragment; import com.android.contacts.interactions.ContactDeletionInteraction; import com.android.contacts.common.interactions.ImportExportDialogFragment; +import com.android.contacts.common.interactions.ImportExportDialogFragment.ExportToSimThread; +import com.android.contacts.common.list.AccountFilterActivity; import com.android.contacts.common.list.ContactEntryListFragment; import com.android.contacts.common.list.ContactListFilter; import com.android.contacts.common.list.ContactListFilterController; @@ -82,17 +92,24 @@ import com.android.contacts.list.OnContactsUnavailableActionListener; import com.android.contacts.list.ProviderStatusWatcher; import com.android.contacts.list.ProviderStatusWatcher.ProviderStatusListener; import com.android.contacts.common.list.ViewPagerTabs; +import com.android.contacts.common.model.account.AccountType; import com.android.contacts.preference.ContactsPreferenceActivity; +import com.android.contacts.common.SimContactsConstants; import com.android.contacts.common.util.AccountFilterUtil; import com.android.contacts.common.util.ViewUtil; import com.android.contacts.quickcontact.QuickContactActivity; import com.android.contacts.util.AccountPromptUtils; import com.android.contacts.common.util.Constants; +import com.android.contacts.common.vcard.ExportVCardActivity; +import com.android.contacts.common.vcard.VCardCommonArguments; import com.android.contacts.util.DialogManager; import com.android.contactsbind.HelpUtils; import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; import java.util.Locale; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** @@ -110,11 +127,13 @@ public class PeopleActivity extends ContactsActivity implements private static final String TAG = "PeopleActivity"; + public static String EDITABLE_KEY = "search_contacts"; private static final String ENABLE_DEBUG_OPTIONS_HIDDEN_CODE = "debug debug!"; // These values needs to start at 2. See {@link ContactEntryListFragment}. private static final int SUBACTIVITY_ACCOUNT_FILTER = 2; - + private static final int SUBACTIVITY_NEW_GROUP = 4; + private static final int SUBACTIVITY_EDIT_GROUP = 5; private final DialogManager mDialogManager = new DialogManager(this); private ContactsIntentResolver mIntentResolver; @@ -122,6 +141,9 @@ public class PeopleActivity extends ContactsActivity implements private ActionBarAdapter mActionBarAdapter; private FloatingActionButtonController mFloatingActionButtonController; + private GroupDetailFragment mGroupDetailFragment; + private final GroupDetailFragmentListener mGroupDetailFragmentListener = + new GroupDetailFragmentListener(); private View mFloatingActionButtonContainer; private boolean wasLastFabAnimationScaleIn = false; @@ -141,6 +163,7 @@ public class PeopleActivity extends ContactsActivity implements */ private MultiSelectContactsListFragment mAllFragment; private ContactTileListFragment mFavoritesFragment; + private GroupBrowseListFragment mGroupsFragment; /** ViewPager for swipe */ private ViewPager mTabPager; @@ -151,6 +174,7 @@ public class PeopleActivity extends ContactsActivity implements private boolean mEnableDebugMenuOptions; + private ExportToSimThread mExportThread = null; /** * True if this activity instance is a re-created one. i.e. set true after orientation change. * This is set in {@link #onCreate} for later use in {@link #onStart}. @@ -173,6 +197,11 @@ public class PeopleActivity extends ContactsActivity implements /** Sequential ID assigned to each instance; used for logging */ private final int mInstanceId; private static final AtomicInteger sNextInstanceId = new AtomicInteger(); + // TODO: we need to refactor the export code in future release. + // QRD enhancement: contacts list for multi contact pick + private ArrayList<String[]> mContactList; + + private BroadcastReceiver mExportToSimCompleteListener = null; public PeopleActivity() { mInstanceId = sNextInstanceId.getAndIncrement(); @@ -247,6 +276,7 @@ public class PeopleActivity extends ContactsActivity implements Log.d(Constants.PERFORMANCE_TAG, "PeopleActivity.onCreate finish"); } getWindow().setBackgroundDrawable(null); + registerReceiver(); } @Override @@ -266,6 +296,21 @@ public class PeopleActivity extends ContactsActivity implements invalidateOptionsMenuIfNeeded(); } + private void registerReceiver() { + mExportToSimCompleteListener = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(SimContactsConstants.INTENT_EXPORT_COMPLETE)) { + ImportExportDialogFragment.destroyExportToSimThread(); + mExportThread = null; + } + } + }; + IntentFilter exportCompleteFilter = new IntentFilter( + SimContactsConstants.INTENT_EXPORT_COMPLETE); + registerReceiver(mExportToSimCompleteListener, exportCompleteFilter); + } + /** * Resolve the intent and initialize {@link #mRequest}, and launch another activity if redirect * is needed. @@ -311,6 +356,7 @@ public class PeopleActivity extends ContactsActivity implements mTabTitles = new String[TabState.COUNT]; mTabTitles[TabState.FAVORITES] = getString(R.string.favorites_tab_label); mTabTitles[TabState.ALL] = getString(R.string.all_contacts_tab_label); + mTabTitles[TabState.GROUPS] = getString(R.string.contacts_groups_label); mTabPager = getView(R.id.tab_pager); mTabPagerAdapter = new TabPagerAdapter(); mTabPager.setAdapter(mTabPagerAdapter); @@ -333,6 +379,7 @@ public class PeopleActivity extends ContactsActivity implements final String FAVORITE_TAG = "tab-pager-favorite"; final String ALL_TAG = "tab-pager-all"; + final String GROUPS_TAG = "tab-pager-groups"; // Create the fragments and add as children of the view pager. // The pager adapter will only change the visibility; it'll never create/destroy @@ -344,13 +391,17 @@ public class PeopleActivity extends ContactsActivity implements fragmentManager.findFragmentByTag(FAVORITE_TAG); mAllFragment = (MultiSelectContactsListFragment) fragmentManager.findFragmentByTag(ALL_TAG); + mGroupsFragment = (GroupBrowseListFragment) + fragmentManager.findFragmentByTag(GROUPS_TAG); if (mFavoritesFragment == null) { mFavoritesFragment = new ContactTileListFragment(); mAllFragment = new MultiSelectContactsListFragment(); + mGroupsFragment = new GroupBrowseListFragment(); transaction.add(R.id.tab_pager, mFavoritesFragment, FAVORITE_TAG); transaction.add(R.id.tab_pager, mAllFragment, ALL_TAG); + transaction.add(R.id.tab_pager, mGroupsFragment, GROUPS_TAG); } mFavoritesFragment.setListener(mFavoritesFragmentListener); @@ -358,10 +409,13 @@ public class PeopleActivity extends ContactsActivity implements mAllFragment.setOnContactListActionListener(new ContactBrowserActionListener()); mAllFragment.setCheckBoxListListener(new CheckBoxListListener()); + mGroupsFragment.setListener(new GroupBrowserActionListener()); + // Hide all fragments for now. We adjust visibility when we get onSelectedTabChanged() // from ActionBarAdapter. transaction.hide(mFavoritesFragment); transaction.hide(mAllFragment); + transaction.hide(mGroupsFragment); transaction.commitAllowingStateLoss(); fragmentManager.executePendingTransactions(); @@ -417,6 +471,8 @@ public class PeopleActivity extends ContactsActivity implements mOptionsMenuContactsAvailable = false; mProviderStatusWatcher.stop(); super.onPause(); + dismissDialog(ImportExportDialogFragment.TAG); + dismissDialog(SelectAccountDialogFragment.TAG); } @Override @@ -451,9 +507,22 @@ public class PeopleActivity extends ContactsActivity implements mContactListFilterController.removeListener(this); } + if (mExportToSimCompleteListener != null) { + unregisterReceiver(mExportToSimCompleteListener); + } super.onDestroy(); } + private void dismissDialog(String tag) { + // when this activity lose focus,dismiss the dialog + Fragment dialogFragment = getFragmentManager().findFragmentByTag(tag); + if (dialogFragment != null) { + if (dialogFragment instanceof DialogFragment) { + ((DialogFragment) dialogFragment).dismiss(); + } + } + } + private void configureFragments(boolean fromRequest) { if (fromRequest) { ContactListFilter filter = null; @@ -480,6 +549,9 @@ public class PeopleActivity extends ContactsActivity implements case ContactsRequest.ACTION_VIEW_CONTACT: tabToOpen = TabState.ALL; break; + case ContactsRequest.ACTION_GROUP: + tabToOpen = TabState.GROUPS; + break; default: tabToOpen = -1; break; @@ -502,6 +574,7 @@ public class PeopleActivity extends ContactsActivity implements } configureContactListFragment(); + configureGroupListFragment(); invalidateOptionsMenuIfNeeded(); } @@ -621,6 +694,9 @@ public class PeopleActivity extends ContactsActivity implements } invalidateOptionsMenu(); showEmptyStateForTab(tab); + if (tab == TabState.GROUPS) { + mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable()); + } } private void showEmptyStateForTab(int tab) { @@ -630,6 +706,10 @@ public class PeopleActivity extends ContactsActivity implements mContactsUnavailableFragment.setMessageText( R.string.listTotalAllContactsZeroStarred, -1); break; + case TabState.GROUPS: + mContactsUnavailableFragment.setMessageText(R.string.noGroups, + areGroupWritableAccountsAvailable() ? -1 : R.string.noAccounts); + break; case TabState.ALL: mContactsUnavailableFragment.setMessageText(R.string.noContacts, -1); break; @@ -678,6 +758,9 @@ public class PeopleActivity extends ContactsActivity implements mActionBarAdapter.setCurrentTab(position, false); mViewPagerTabs.onPageSelected(position); showEmptyStateForTab(position); + if (position == TabState.GROUPS) { + mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable()); + } invalidateOptionsMenu(); } } @@ -736,6 +819,9 @@ public class PeopleActivity extends ContactsActivity implements if (object == mAllFragment) { return getTabPositionForTextDirection(TabState.ALL); } + if (object == mGroupsFragment) { + return TabState.GROUPS; + } } return POSITION_NONE; } @@ -759,6 +845,8 @@ public class PeopleActivity extends ContactsActivity implements return mFavoritesFragment; } else if (position == TabState.ALL) { return mAllFragment; + } else if (position == TabState.GROUPS) { + return mGroupsFragment; } } throw new IllegalArgumentException("position: " + position); @@ -866,6 +954,11 @@ public class PeopleActivity extends ContactsActivity implements return TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL; } + private void configureGroupListFragment() { + mGroupsFragment.setVerticalScrollbarPosition(getScrollBarPosition()); + mGroupsFragment.setSelectionVisible(false); + } + @Override public void onProviderStatusChange() { updateViewConfiguration(false); @@ -1040,6 +1133,67 @@ public class PeopleActivity extends ContactsActivity implements } } + private final class GroupBrowserActionListener implements OnGroupBrowserActionListener { + + GroupBrowserActionListener() {} + + @Override + public void onViewGroupAction(Uri groupUri) { + Intent intent = new Intent(PeopleActivity.this, GroupDetailActivity.class); + intent.setData(groupUri); + startActivity(intent); + } + } + + private class GroupDetailFragmentListener implements GroupDetailFragment.Listener { + + GroupDetailFragmentListener() {} + + @Override + public void onGroupSizeUpdated(String size) { + // Nothing needs to be done here because the size will be displayed in the detail + // fragment + } + + @Override + public void onGroupTitleUpdated(String title) { + // Nothing needs to be done here because the title will be displayed in the detail + // fragment + } + + @Override + public void onAccountTypeUpdated(String accountTypeString, String dataSet) { + // Nothing needs to be done here because the group source will be displayed in the + // detail fragment + } + + @Override + public void onEditRequested(Uri groupUri) { + final Intent intent = new Intent(PeopleActivity.this, GroupEditorActivity.class); + intent.setData(groupUri); + intent.setAction(Intent.ACTION_EDIT); + startActivityForResult(intent, SUBACTIVITY_EDIT_GROUP); + } + + @Override + public void onContactSelected(Uri contactUri) { + // Nothing needs to be done here because either quickcontact will be displayed + // or activity will take care of selection + } + } + + public void startActivityAndForwardResult(final Intent intent) { + intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + + // Forward extras to the new activity + Bundle extras = getIntent().getExtras(); + if (extras != null) { + intent.putExtras(extras); + } + startActivity(intent); + finish(); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { if (!areContactsAvailable()) { @@ -1069,6 +1223,10 @@ public class PeopleActivity extends ContactsActivity implements return true; } + if (mGroupDetailFragment != null && mGroupDetailFragment.isOptionsMenuChanged()) { + return true; + } + return false; } @@ -1081,25 +1239,39 @@ public class PeopleActivity extends ContactsActivity implements // Get references to individual menu items in the menu final MenuItem contactsFilterMenu = menu.findItem(R.id.menu_contacts_filter); + MenuItem addGroupMenu = menu.findItem(R.id.menu_add_group); final MenuItem clearFrequentsMenu = menu.findItem(R.id.menu_clear_frequents); final MenuItem helpMenu = menu.findItem(R.id.menu_help); final boolean isSearchOrSelectionMode = mActionBarAdapter.isSearchMode() || mActionBarAdapter.isSelectionMode(); if (isSearchOrSelectionMode) { + addGroupMenu.setVisible(false); contactsFilterMenu.setVisible(false); clearFrequentsMenu.setVisible(false); helpMenu.setVisible(false); + makeMenuItemVisible(menu, R.id.menu_delete, false); } else { switch (getTabPositionForTextDirection(mActionBarAdapter.getCurrentTab())) { case TabState.FAVORITES: + addGroupMenu.setVisible(false); contactsFilterMenu.setVisible(false); clearFrequentsMenu.setVisible(hasFrequents()); break; case TabState.ALL: + addGroupMenu.setVisible(false); contactsFilterMenu.setVisible(true); clearFrequentsMenu.setVisible(false); break; + case TabState.GROUPS: + // Do not display the "new group" button if no accounts are available + if (areGroupWritableAccountsAvailable()) { + addGroupMenu.setVisible(true); + } else { + addGroupMenu.setVisible(false); + } + contactsFilterMenu.setVisible(false); + clearFrequentsMenu.setVisible(false); } helpMenu.setVisible(HelpUtils.isHelpAndFeedbackAvailable()); } @@ -1107,13 +1279,13 @@ public class PeopleActivity extends ContactsActivity implements makeMenuItemVisible(menu, R.id.menu_search, showMiscOptions); makeMenuItemVisible(menu, R.id.menu_import_export, showMiscOptions); makeMenuItemVisible(menu, R.id.menu_accounts, showMiscOptions); + makeMenuItemVisible(menu, R.id.menu_memory_status, showMiscOptions); makeMenuItemVisible(menu, R.id.menu_settings, showMiscOptions && !ContactsPreferenceActivity.isEmpty(this)); final boolean showSelectedContactOptions = mActionBarAdapter.isSelectionMode() && mAllFragment.getSelectedContactIds().size() != 0; makeMenuItemVisible(menu, R.id.menu_share, showSelectedContactOptions); - makeMenuItemVisible(menu, R.id.menu_delete, showSelectedContactOptions); makeMenuItemVisible(menu, R.id.menu_join, showSelectedContactOptions); makeMenuItemEnabled(menu, R.id.menu_join, mAllFragment.getSelectedContactIds().size() > 1); @@ -1191,9 +1363,21 @@ public class PeopleActivity extends ContactsActivity implements case R.id.menu_join: joinSelectedContacts(); return true; - case R.id.menu_delete: - deleteSelectedContacts(); + case R.id.menu_add_group: { + createNewGroup(); + return true; + } + case R.id.menu_delete: { + final Intent intent = new Intent(Intent.ACTION_DELETE, Contacts.CONTENT_URI); + intent.putExtra(EDITABLE_KEY, mActionBarAdapter.getQueryString()); + + ContactListFilter filter = ContactListFilter.restoreDefaultPreferences( + PreferenceManager.getDefaultSharedPreferences(this)); + intent.putExtra(AccountFilterActivity.KEY_EXTRA_CONTACT_LIST_FILTER, filter); + + startActivity(intent); return true; + } case R.id.menu_import_export: { ImportExportDialogFragment.show(getFragmentManager(), areContactsAvailable(), PeopleActivity.class); @@ -1221,6 +1405,12 @@ public class PeopleActivity extends ContactsActivity implements ImplicitIntentsUtil.startActivityOutsideApp(this, intent); return true; } + + case R.id.menu_memory_status: { + final Intent intent = new Intent(this, MemoryStatusActivity.class); + startActivity(intent); + return true; + } } return false; } @@ -1271,6 +1461,12 @@ public class PeopleActivity extends ContactsActivity implements mAllFragment.getSelectedContactIds()); } + private void createNewGroup() { + final Intent intent = new Intent(this, GroupEditorActivity.class); + intent.setAction(Intent.ACTION_INSERT); + startActivityForResult(intent, SUBACTIVITY_NEW_GROUP); + } + @Override public void onDeletionFinished() { mActionBarAdapter.setSelectionMode(false); @@ -1284,6 +1480,14 @@ public class PeopleActivity extends ContactsActivity implements mContactListFilterController, resultCode, data); break; } + case SUBACTIVITY_NEW_GROUP: + case SUBACTIVITY_EDIT_GROUP: { + if (resultCode == RESULT_OK) { + mRequest.setActionCode(ContactsRequest.ACTION_GROUP); + mGroupsFragment.setSelectedUri(data.getData()); + } + break; + } // TODO: Using the new startActivityWithResultFromFragment API this should not be needed // anymore @@ -1291,6 +1495,7 @@ public class PeopleActivity extends ContactsActivity implements if (resultCode == RESULT_OK) { mAllFragment.onPickerResult(data); } + break; // TODO fix or remove multipicker code // else if (resultCode == RESULT_CANCELED && mMode == MODE_PICK_MULTIPLE_PHONES) { @@ -1299,6 +1504,91 @@ public class PeopleActivity extends ContactsActivity implements // finish(); // } // break; + case ImportExportDialogFragment.SUBACTIVITY_MULTI_PICK_CONTACT: + if (resultCode == RESULT_OK) { + mContactList = new ArrayList<String[]>(); + Bundle b = data.getExtras(); + Bundle choiceSet = b.getBundle(SimContactsConstants.RESULT_KEY); + Set<String> set = choiceSet.keySet(); + Iterator<String> i = set.iterator(); + while (i.hasNext()) { + String contactInfo[] = choiceSet.getStringArray(i.next()); + mContactList.add(contactInfo); + } + Log.d(TAG, "return " + mContactList.size() + " contacts"); + if (!mContactList.isEmpty()) { + if (!ImportExportDialogFragment.isExportingToSIM()) { + ImportExportDialogFragment.destroyExportToSimThread(); + mExportThread = + new ImportExportDialogFragment().createExportToSimThread( + ImportExportDialogFragment.mExportSub, mContactList, + PeopleActivity.this); + mExportThread.start(); + } + } + } + break; + case ImportExportDialogFragment.SUBACTIVITY_EXPORT_CONTACTS: + if (resultCode == RESULT_OK) { + Bundle result = data.getExtras().getBundle( + SimContactsConstants.RESULT_KEY); + Set<String> keySet = result.keySet(); + Iterator<String> it = keySet.iterator(); + StringBuilder selExportBuilder = new StringBuilder(); + while (it.hasNext()) { + String id = it.next(); + if (0 != selExportBuilder.length()) { + selExportBuilder.append(","); + } + selExportBuilder.append(id); + } + selExportBuilder.insert(0, "_id IN ("); + selExportBuilder.append(")"); + Intent exportIntent = new Intent(this, + ExportVCardActivity.class); + exportIntent.putExtra("SelExport", selExportBuilder.toString()); + exportIntent.putExtra( + VCardCommonArguments.ARG_CALLING_ACTIVITY, + PeopleActivity.class.getName()); + this.startActivity(exportIntent); + } + break; + case ImportExportDialogFragment.SUBACTIVITY_SHARE_VISILBLE_CONTACTS: + if (resultCode == RESULT_OK) { + Bundle result = data.getExtras().getBundle( + SimContactsConstants.RESULT_KEY); + StringBuilder uriListBuilder = new StringBuilder(); + int index = 0; + int size = result.keySet().size(); + // The premise of allowing to share contacts is that the + // amount of those contacts which have been selected to + // append and will be put into intent as extra data to + // deliver is not more that 2000, because too long arguments + // will cause TransactionTooLargeException in binder. + if (size > ImportExportDialogFragment.MAX_COUNT_ALLOW_SHARE_CONTACT) { + Toast.makeText(this, R.string.share_failed, + Toast.LENGTH_SHORT).show(); + return; + } + Iterator<String> it = result.keySet().iterator(); + String[] values = null; + while (it.hasNext()) { + if (index != 0) { + uriListBuilder.append(':'); + } + values = result.getStringArray(it.next()); + uriListBuilder.append(values[0]); + index++; + } + Uri uri = Uri.withAppendedPath( + Contacts.CONTENT_MULTI_VCARD_URI, + Uri.encode(uriListBuilder.toString())); + final Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType(Contacts.CONTENT_VCARD_TYPE); + intent.putExtra(Intent.EXTRA_STREAM, uri); + startActivity(intent); + } + break; } } diff --git a/src/com/android/contacts/editor/CompactPhotoEditorView.java b/src/com/android/contacts/editor/CompactPhotoEditorView.java index 5f3e9afad..64df22850 100644 --- a/src/com/android/contacts/editor/CompactPhotoEditorView.java +++ b/src/com/android/contacts/editor/CompactPhotoEditorView.java @@ -31,6 +31,7 @@ import com.android.contacts.util.ContactPhotoUtils; import com.android.contacts.util.SchedulingUtils; import com.android.contacts.widget.QuickContactImageView; +import android.accounts.Account; import android.app.Activity; import android.content.Context; import android.content.res.TypedArray; @@ -291,8 +292,8 @@ public class CompactPhotoEditorView extends RelativeLayout implements View.OnCli if (photoUri != null) { final DefaultImageProvider fallbackToPreviousImage = new DefaultImageProvider() { @Override - public void applyDefaultImage(ImageView view, int extent, boolean darkTheme, - DefaultImageRequest defaultImageRequest) { + public void applyDefaultImage(ImageView view, Account account, int extent, + boolean darkTheme, DefaultImageRequest defaultImageRequest) { // Before we finish setting the full sized image, don't change the current // image that is set in any way. } diff --git a/src/com/android/contacts/editor/CompactRawContactsEditorView.java b/src/com/android/contacts/editor/CompactRawContactsEditorView.java index 6b7df6768..e04343c63 100644..100755 --- a/src/com/android/contacts/editor/CompactRawContactsEditorView.java +++ b/src/com/android/contacts/editor/CompactRawContactsEditorView.java @@ -17,6 +17,8 @@ package com.android.contacts.editor; import com.android.contacts.R; +import com.android.contacts.common.model.account.PhoneAccountType; +import com.android.contacts.common.model.account.SimAccountType; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.RawContactDelta; import com.android.contacts.common.model.RawContactDeltaList; @@ -24,14 +26,21 @@ import com.android.contacts.common.model.RawContactModifier; import com.android.contacts.common.model.ValuesDelta; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.model.account.AccountType.EditField; +import com.android.contacts.common.model.account.AccountType.EditType; import com.android.contacts.common.model.account.AccountWithDataSet; +import com.android.contacts.common.model.account.PhoneAccountType; +import com.android.contacts.common.model.account.SimAccountType; import com.android.contacts.common.model.dataitem.DataKind; +import com.android.contacts.common.SimContactsConstants; +import com.android.contacts.common.MoreContactUtils; import com.android.contacts.common.util.MaterialColorMapUtils; import com.android.contacts.editor.CompactContactEditorFragment.PhotoHandler; +import com.android.internal.telephony.PhoneConstants; import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; +import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.GroupMembership; import android.provider.ContactsContract.CommonDataKinds.Nickname; @@ -365,7 +374,8 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O return; } vlog("Account info loaded"); - if (accountInfo.first == null) { + if (accountInfo.first == null || SimAccountType.ACCOUNT_TYPE.equals(accountType. + accountType)|| PhoneAccountType.ACCOUNT_TYPE.equals(accountType.accountType)) { mAccountNameView.setVisibility(View.GONE); } else { mAccountNameView.setVisibility(View.VISIBLE); @@ -640,7 +650,7 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O for (RawContactDelta rawContactDelta : rawContactDeltas) { if (!rawContactDelta.isVisible()) continue; final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager); - + final String accountName = rawContactDelta.getAccountName(); for (DataKind dataKind : accountType.getSortedDataKinds()) { if (!dataKind.editable) continue; @@ -658,7 +668,7 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O final ValuesDelta valuesDelta = rawContactDelta.getSuperPrimaryEntry( StructuredName.CONTENT_ITEM_TYPE, /* forceSelection =*/ true); if (hasNonEmptyValue(dataKind, valuesDelta)) { - mPhoneticNames.addView(inflatePhoneticNameEditorView( + mPhoneticNames.addView(inflatePhoneticNameEditorView( mPhoneticNames, accountType, valuesDelta, rawContactDelta)); } } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) { @@ -667,7 +677,7 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O rawContactDelta, Nickname.CONTENT_ITEM_TYPE, dataKind); if (valuesDeltas != null && !valuesDeltas.isEmpty()) { for (ValuesDelta valuesDelta : valuesDeltas) { - mNicknames.addView(inflateNicknameEditorView( + mNicknames.addView(inflateNicknameEditorView( mNicknames, dataKind, valuesDelta, rawContactDelta)); } } @@ -687,6 +697,31 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O updateKindEditorIcons(mPhoneNumbers); } }); + if (SimContactsConstants.ACCOUNT_TYPE_SIM + .equals(accountType.accountType)) { + int sub = PhoneConstants.SUB1; + if (SimContactsConstants.SIM_NAME_2.equals(accountName)) { + sub = PhoneConstants.SUB2; + } + EditType typeHome = new EditType(Phone.TYPE_HOME, + Phone.getTypeLabelResource(Phone.TYPE_HOME)); + if (!MoreContactUtils.canSaveAnr(sub)) { + dataKind.typeOverallMax = 1; + if (null != dataKind.typeList) { + // When the sim card is not 3g the interface should + // remove the TYPE_HOME number view. + dataKind.typeList.remove(typeHome); + } + } else { + dataKind.typeOverallMax = MoreContactUtils.getOneSimAnrCount(sub) + 1; + if (null != dataKind.typeList && !dataKind.typeList.contains( + typeHome)) { + // When the sim card is 3g the interface should + // add the TYPE_HOME number view. + dataKind.typeList.add(typeHome); + } + } + } mPhoneNumbers.addView(kindSectionView); } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) { final KindSectionView kindSectionView = @@ -704,7 +739,19 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O updateKindEditorIcons(mEmails); } }); - mEmails.addView(kindSectionView); + if (SimContactsConstants.ACCOUNT_TYPE_SIM.equals( + accountType.accountType)) { + int sub = PhoneConstants.SUB1; + if (SimContactsConstants.SIM_NAME_2.equals(accountName)) { + sub = PhoneConstants.SUB2; + } + if (MoreContactUtils.canSaveEmail(sub)) { + dataKind.typeOverallMax = MoreContactUtils.getOneSimEmailCount(sub); + mEmails.addView(kindSectionView); + } + } else { + mEmails.addView(kindSectionView); + } } else if (hasNonEmptyValuesDelta(rawContactDelta, mimeType, dataKind)) { final LinearLayout otherTypeViewGroup; if (mOtherTypesMap.containsKey(mimeType)) { @@ -842,6 +889,10 @@ public class CompactRawContactsEditorView extends LinearLayout implements View.O rawContactDelta, readOnly, mViewIdGenerator); + if (rawContactDelta.getAccountType() != null && rawContactDelta.getAccountType().equals( + SimContactsConstants.ACCOUNT_TYPE_SIM)) { + result.setExpansionViewContainerDisabled(); + } return result; } diff --git a/src/com/android/contacts/editor/ContactEditorBaseFragment.java b/src/com/android/contacts/editor/ContactEditorBaseFragment.java index 18b138190..646bf4fe2 100644 --- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java +++ b/src/com/android/contacts/editor/ContactEditorBaseFragment.java @@ -25,6 +25,7 @@ import com.android.contacts.R; import com.android.contacts.activities.ContactEditorAccountsChangedActivity; import com.android.contacts.activities.ContactEditorBaseActivity; import com.android.contacts.activities.ContactEditorBaseActivity.ContactEditor; +import com.android.contacts.common.SimContactsConstants; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.Contact; import com.android.contacts.common.model.ContactLoader; @@ -35,6 +36,7 @@ import com.android.contacts.common.model.RawContactModifier; import com.android.contacts.common.model.ValuesDelta; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.model.account.AccountWithDataSet; +import com.android.contacts.common.model.account.SimAccountType; import com.android.contacts.common.util.ImplicitIntentsUtil; import com.android.contacts.common.util.MaterialColorMapUtils; import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion; @@ -652,6 +654,14 @@ abstract public class ContactEditorBaseFragment extends Fragment implements } @Override + public void onResume() { + super.onResume(); + if (Intent.ACTION_EDIT.equals(mAction)) { + mHasNewContact = false; + } + } + + @Override public void onStop() { super.onStop(); @@ -786,10 +796,18 @@ abstract public class ContactEditorBaseFragment extends Fragment implements // even if they have never added their own information and splitting will create a // name only contact. final boolean isSingleReadOnlyContact = mHasNewContact && mState.size() == 2; + String accountType = null; + if (mState.size() > 0) { + accountType = mState.get(0).getAccountType(); + } splitMenu.setVisible(mState.size() > 1 && !isEditingUserProfile() && !isSingleReadOnlyContact); // Cannot join a user profile - joinMenu.setVisible(!isEditingUserProfile()); + if (accountType != null && SimAccountType.ACCOUNT_TYPE.equals(accountType)) { + joinMenu.setVisible(false); + } else { + joinMenu.setVisible(!isEditingUserProfile()); + } deleteMenu.setVisible(!mDisableDeleteMenuOption); } else { // something else, so don't show the help menu @@ -950,7 +968,9 @@ abstract public class ContactEditorBaseFragment extends Fragment implements } onSaveCompleted(/* hadChanges =*/ false, saveMode, /* saveSucceeded =*/ mLookupUri != null, mLookupUri, - /* updatedPhotos =*/ null, backPressed, mPhotoId, mNameId); + /* updatedPhotos =*/ null, backPressed, mPhotoId, mNameId, + getActivity().getIntent().getIntExtra( + ContactSaveService.SAVE_CONTACT_RESULT, 0)); return true; } @@ -1275,7 +1295,8 @@ abstract public class ContactEditorBaseFragment extends Fragment implements // For profile contacts, we need a different query URI rawContactDelta.setProfileQueryUri(); // Try to find a local profile contact - if (rawContactDelta.getValues().getAsString(RawContacts.ACCOUNT_TYPE) == null) { + if (SimContactsConstants.ACCOUNT_TYPE_PHONE + .equals(rawContactDelta.getAccountType())) { localProfileExists = true; } } @@ -1401,20 +1422,81 @@ abstract public class ContactEditorBaseFragment extends Fragment implements @Override public void onJoinCompleted(Uri uri) { onSaveCompleted(false, SaveMode.RELOAD, uri != null, uri, /* updatedPhotos =*/ null, - /* backPressed =*/ false, mPhotoId, mNameId); + /* backPressed =*/ false, mPhotoId, mNameId, + getActivity().getIntent().getIntExtra(ContactSaveService.SAVE_CONTACT_RESULT, + 0)); } @Override public void onSaveCompleted(boolean hadChanges, int saveMode, boolean saveSucceeded, Uri contactLookupUri, Bundle updatedPhotos, boolean backPressed, long photoId, - long nameId) { + long nameId, int result) { + Log.d(TAG, "onSaveCompleted(" + saveMode + ", " + contactLookupUri + ", saveResult:" + + result); if (hadChanges) { if (saveSucceeded) { if (saveMode != SaveMode.JOIN) { - Toast.makeText(mContext, R.string.contactSavedToast, Toast.LENGTH_SHORT).show(); + if (null != contactLookupUri) { + Toast.makeText(mContext, R.string.contactSavedToast, + Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(mContext, R.string.contacts_deleted_toast, + Toast.LENGTH_SHORT).show(); + } } } else { - Toast.makeText(mContext, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show(); + if (result == ContactSaveService.RESULT_AIR_PLANE_MODE) { + // Access SIM card in the "AirPlane" + // mode prompt a toast to alert user. + Toast.makeText(mContext, R.string.airplane_mode_on, Toast.LENGTH_LONG).show(); + } else if (result == ContactSaveService.RESULT_SIM_FAILURE) { + Toast.makeText(mContext, R.string.contactSavedToSimCardError, + Toast.LENGTH_LONG).show(); + } else if (result == ContactSaveService.RESULT_NUMBER_ANR_FAILURE) { + Toast.makeText(mContext, R.string.number_anr_too_long, Toast.LENGTH_LONG) + .show(); + mStatus = Status.EDITING; + setEnabled(true); + bindEditors(); + return; + } else if (result == ContactSaveService.RESULT_EMAIL_FAILURE) { + Toast.makeText(mContext, R.string.email_address_too_long, Toast.LENGTH_LONG) + .show(); + mStatus = Status.EDITING; + setEnabled(true); + bindEditors(); + return; + } else if (result == ContactSaveService.RESULT_SIM_FULL_FAILURE) { + Toast.makeText(mContext, R.string.sim_card_full, Toast.LENGTH_LONG).show(); + } else if (result == ContactSaveService.RESULT_TAG_FAILURE) { + Toast.makeText(mContext, R.string.tag_too_long, Toast.LENGTH_SHORT).show(); + mStatus = Status.EDITING; + setEnabled(true); + bindEditors(); + return; + } else if (result == ContactSaveService.RESULT_NO_NUMBER_AND_EMAIL) { + Toast.makeText(mContext, R.string.no_phone_number_or_email, Toast.LENGTH_SHORT) + .show(); + mStatus = Status.EDITING; + setEnabled(true); + bindEditors(); + return; + } else if (result == ContactSaveService.RESULT_NUMBER_INVALID) { + Toast.makeText(mContext, R.string.invalid_phone_number, Toast.LENGTH_SHORT) + .show(); + mStatus = Status.EDITING; + setEnabled(true); + return; + } else if (result == ContactSaveService.RESULT_MEMORY_FULL_FAILURE) { + Toast.makeText(mContext, R.string.memory_card_full, Toast.LENGTH_SHORT) + .show(); + } else if(result == ContactSaveService.RESULT_NUMBER_TYPE_FAILURE) { + Toast.makeText(mContext, R.string.invalid_number_type, Toast.LENGTH_SHORT) + .show(); + } else { + Toast.makeText(mContext, R.string.contactSavedErrorToast, Toast.LENGTH_LONG) + .show(); + } } } switch (saveMode) { diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java index 4da17faf2..31e8d5155 100644 --- a/src/com/android/contacts/editor/ContactEditorFragment.java +++ b/src/com/android/contacts/editor/ContactEditorFragment.java @@ -380,7 +380,7 @@ public class ContactEditorFragment extends ContactEditorBaseFragment implements final RawContactEditorView rawContactEditorView = (RawContactEditorView) view; final PhoneticNameEditorView phoneticNameEditorView = (PhoneticNameEditorView) rawContactEditorView.getPhoneticNameEditor(); - if (phoneticNameEditorView != null) { + if (phoneticNameEditorView != null && phoneticNameEditorView.getEntry() != null) { final String phoneticName = phoneticNameEditorView.getPhoneticName(); if (!TextUtils.isEmpty(phoneticName)) { return phoneticName; diff --git a/src/com/android/contacts/editor/ContactEditorUtils.java b/src/com/android/contacts/editor/ContactEditorUtils.java index 105b88552..2f00a2894 100644 --- a/src/com/android/contacts/editor/ContactEditorUtils.java +++ b/src/com/android/contacts/editor/ContactEditorUtils.java @@ -30,6 +30,7 @@ import com.android.contacts.common.testing.NeededForTesting; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.model.account.AccountWithDataSet; +import com.android.contacts.common.SimContactsConstants; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; @@ -242,7 +243,12 @@ public class ContactEditorUtils { String[] getWritableAccountTypeStrings() { final Set<String> types = Sets.newHashSet(); for (AccountType type : mAccountTypes.getAccountTypes(true)) { - types.add(type.accountType); + if (type.accountType.equals(SimContactsConstants.ACCOUNT_TYPE_SIM) + || type.accountType.equals(SimContactsConstants.ACCOUNT_TYPE_PHONE)) { + continue; + } else { + types.add(type.accountType); + } } return types.toArray(new String[types.size()]); } diff --git a/src/com/android/contacts/editor/EditorUiUtils.java b/src/com/android/contacts/editor/EditorUiUtils.java index 78f7a42d2..78f7a42d2 100644..100755 --- a/src/com/android/contacts/editor/EditorUiUtils.java +++ b/src/com/android/contacts/editor/EditorUiUtils.java diff --git a/src/com/android/contacts/editor/PhotoEditorView.java b/src/com/android/contacts/editor/PhotoEditorView.java index f69c93514..99647312b 100644 --- a/src/com/android/contacts/editor/PhotoEditorView.java +++ b/src/com/android/contacts/editor/PhotoEditorView.java @@ -16,6 +16,7 @@ package com.android.contacts.editor; +import android.accounts.Account; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -208,8 +209,8 @@ public class PhotoEditorView extends LinearLayout implements Editor { if (photoUri != null) { final DefaultImageProvider fallbackToPreviousImage = new DefaultImageProvider() { @Override - public void applyDefaultImage(ImageView view, int extent, boolean darkTheme, - DefaultImageRequest defaultImageRequest) { + public void applyDefaultImage(ImageView view, Account account, int extent, + boolean darkTheme, DefaultImageRequest defaultImageRequest) { // Before we finish setting the full sized image, don't change the current // image that is set in any way. } diff --git a/src/com/android/contacts/editor/RawContactEditorView.java b/src/com/android/contacts/editor/RawContactEditorView.java index eeba401de..2741f2a68 100644..100755 --- a/src/com/android/contacts/editor/RawContactEditorView.java +++ b/src/com/android/contacts/editor/RawContactEditorView.java @@ -37,11 +37,14 @@ import android.widget.TextView; import com.android.contacts.GroupMetaDataLoader; import com.android.contacts.R; import com.android.contacts.common.model.account.AccountType; +import com.android.contacts.common.model.account.PhoneAccountType; +import com.android.contacts.common.model.account.SimAccountType; import com.android.contacts.common.model.account.AccountType.EditType; import com.android.contacts.common.model.dataitem.DataKind; import com.android.contacts.common.model.RawContactDelta; import com.android.contacts.common.model.ValuesDelta; import com.android.contacts.common.model.RawContactModifier; +import com.android.contacts.common.SimContactsConstants; import com.google.common.base.Objects; @@ -196,7 +199,8 @@ public class RawContactEditorView extends BaseRawContactEditorView { // Hide this view so the other text view will be centered vertically mAccountHeaderNameTextView.setVisibility(View.GONE); } else { - if (accountInfo.first == null) { + if (accountInfo.first == null || SimAccountType.ACCOUNT_TYPE.equals(type.accountType) + || PhoneAccountType.ACCOUNT_TYPE.equals(type.accountType)) { mAccountHeaderNameTextView.setVisibility(View.GONE); } else { mAccountHeaderNameTextView.setVisibility(View.VISIBLE); @@ -249,23 +253,34 @@ public class RawContactEditorView extends BaseRawContactEditorView { mName.setValues( type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME), primary, state, false, vig); - mPhoneticName.setValues( - type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME), - primary, state, false, vig); - // It is useful to use Nickname outside of a KindSectionView so that we can treat it - // as a part of StructuredName's fake KindSectionView, even though it uses a - // different CP2 mime-type. We do a bit of extra work below to make this possible. - final DataKind nickNameKind = type.getKindForMimetype(Nickname.CONTENT_ITEM_TYPE); - if (nickNameKind != null) { - ValuesDelta primaryNickNameEntry = state.getPrimaryEntry(nickNameKind.mimeType); - if (primaryNickNameEntry == null) { - primaryNickNameEntry = RawContactModifier.insertChild(state, nickNameKind); + if (!(SimContactsConstants.ACCOUNT_TYPE_SIM).equals(type.accountType)) { + mPhoneticName.setValues( + type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME), + primary, state, false, vig); + // It is useful to use Nickname outside of a KindSectionView so that we can + // treat it as a part of StructuredName's fake KindSectionView, even though + // it uses adifferent CP2 mime-type. We do a bit of extra work below to make + // this possible. + final DataKind nickNameKind = type + .getKindForMimetype(Nickname.CONTENT_ITEM_TYPE); + if (nickNameKind != null) { + ValuesDelta primaryNickNameEntry = state + .getPrimaryEntry(nickNameKind.mimeType); + if (primaryNickNameEntry == null) { + primaryNickNameEntry = RawContactModifier + .insertChild(state, nickNameKind); + } + mNickName.setValues(nickNameKind, primaryNickNameEntry, state, false, vig); + mNickName.setDeletable(false); + } else { + mPhoneticName.setPadding(0, 0, 0, (int) getResources().getDimension( + R.dimen.editor_padding_between_editor_views)); + mNickName.setVisibility(View.GONE); } - mNickName.setValues(nickNameKind, primaryNickNameEntry, state, false, vig); - mNickName.setDeletable(false); } else { - mPhoneticName.setPadding(0, 0, 0, (int) getResources().getDimension( - R.dimen.editor_padding_between_editor_views)); + //sim card can't store expand fields,so set it disabled. + mName.setExpansionViewContainerDisabled(); + mPhoneticName.setVisibility(View.GONE); mNickName.setVisibility(View.GONE); } } else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) { diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java index fe476ed01..88a8e30ee 100644 --- a/src/com/android/contacts/editor/TextFieldsEditorView.java +++ b/src/com/android/contacts/editor/TextFieldsEditorView.java @@ -377,10 +377,12 @@ public class TextFieldsEditorView extends LabeledEditorView { mHideOptional = ss.mHideOptional; - int numChildren = Math.min(mFieldEditTexts == null ? 0 : mFieldEditTexts.length, - ss.mVisibilities == null ? 0 : ss.mVisibilities.length); - for (int i = 0; i < numChildren; i++) { - mFieldEditTexts[i].setVisibility(ss.mVisibilities[i]); + if (mFieldEditTexts != null) { + int numChildren = Math.min(mFieldEditTexts == null ? 0 : mFieldEditTexts.length, + ss.mVisibilities == null ? 0 : ss.mVisibilities.length); + for (int i = 0; i < numChildren; i++) { + mFieldEditTexts[i].setVisibility(ss.mVisibilities[i]); + } } } @@ -429,4 +431,13 @@ public class TextFieldsEditorView extends LabeledEditorView { } } } + /** + * use for account type is ACCOUNT_TYPE_SIM only because that sim card + * can not store expand fields. + */ + + public void setExpansionViewContainerDisabled() { + mExpansionViewContainer.setEnabled(false); + mExpansionView.setVisibility(View.INVISIBLE); + } } diff --git a/src/com/android/contacts/group/GroupBrowseListAdapter.java b/src/com/android/contacts/group/GroupBrowseListAdapter.java index 48751e72d..40a3b6fba 100644 --- a/src/com/android/contacts/group/GroupBrowseListAdapter.java +++ b/src/com/android/contacts/group/GroupBrowseListAdapter.java @@ -30,6 +30,7 @@ import android.widget.TextView; import com.android.contacts.GroupListLoader; import com.android.contacts.R; import com.android.contacts.common.model.account.AccountType; +import com.android.contacts.common.model.account.PhoneAccountType; import com.android.contacts.common.model.AccountTypeManager; import com.google.common.base.Objects; @@ -198,7 +199,12 @@ public class GroupBrowseListAdapter extends BaseAdapter { AccountType accountType = mAccountTypeManager.getAccountType( entry.getAccountType(), entry.getDataSet()); viewCache.accountType.setText(accountType.getDisplayLabel(mContext)); - viewCache.accountName.setText(entry.getAccountName()); + // According to the UI SPEC, we will not show the account name for Phone account + if (!PhoneAccountType.ACCOUNT_TYPE.equals(entry.getAccountType())) { + viewCache.accountName.setText(entry.getAccountName()); + } else { + viewCache.accountName.setText(""); + } } private static Uri getGroupUriFromId(long groupId) { diff --git a/src/com/android/contacts/group/GroupBrowseListFragment.java b/src/com/android/contacts/group/GroupBrowseListFragment.java index d39501a16..46dc92d79 100644 --- a/src/com/android/contacts/group/GroupBrowseListFragment.java +++ b/src/com/android/contacts/group/GroupBrowseListFragment.java @@ -160,17 +160,6 @@ public class GroupBrowseListFragment extends Fragment private void configureVerticalScrollbar() { mListView.setVerticalScrollbarPosition(mVerticalScrollbarPosition); mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY); - int leftPadding = 0; - int rightPadding = 0; - if (mVerticalScrollbarPosition == View.SCROLLBAR_POSITION_LEFT) { - leftPadding = mContext.getResources().getDimensionPixelOffset( - R.dimen.list_visible_scrollbar_padding); - } else { - rightPadding = mContext.getResources().getDimensionPixelOffset( - R.dimen.list_visible_scrollbar_padding); - } - mListView.setPadding(leftPadding, mListView.getPaddingTop(), - rightPadding, mListView.getPaddingBottom()); } @Override diff --git a/src/com/android/contacts/group/GroupDetailFragment.java b/src/com/android/contacts/group/GroupDetailFragment.java index c9cf6bd58..724f37bf8 100644 --- a/src/com/android/contacts/group/GroupDetailFragment.java +++ b/src/com/android/contacts/group/GroupDetailFragment.java @@ -31,6 +31,7 @@ import android.database.Cursor; import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; +import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Groups; import android.text.TextUtils; import android.util.Log; @@ -58,6 +59,8 @@ import com.android.contacts.common.list.ContactTileView; import com.android.contacts.list.GroupMemberTileAdapter; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.account.AccountType; +import com.android.contacts.activities.MultiPickContactActivity; +import com.android.contacts.common.SimContactsConstants; /** * Displays the details of a group and shows a list of actions possible for the group. @@ -115,6 +118,7 @@ public class GroupDetailFragment extends Fragment implements OnScrollListener { private Uri mGroupUri; private long mGroupId; private String mGroupName; + private String mAccountNameString; private String mAccountTypeString; private String mDataSet; private boolean mIsReadOnly; @@ -286,6 +290,7 @@ public class GroupDetailFragment extends Fragment implements OnScrollListener { Log.e(TAG, "Failed to load group members"); return; } + getActivity().invalidateOptionsMenu(); updateSize(data.getCount()); mAdapter.setContactCursor(data); mMemberListView.setEmptyView(mEmptyView); @@ -298,6 +303,7 @@ public class GroupDetailFragment extends Fragment implements OnScrollListener { private void bindGroupMetaData(Cursor cursor) { cursor.moveToPosition(-1); if (cursor.moveToNext()) { + mAccountNameString = cursor.getString(GroupMetaDataLoader.ACCOUNT_NAME); mAccountTypeString = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE); mDataSet = cursor.getString(GroupMetaDataLoader.DATA_SET); mGroupId = cursor.getLong(GroupMetaDataLoader.GROUP_ID); @@ -454,6 +460,9 @@ public class GroupDetailFragment extends Fragment implements OnScrollListener { final MenuItem deleteMenu = menu.findItem(R.id.menu_delete_group); deleteMenu.setVisible(mOptionsMenuGroupDeletable); + + final MenuItem moveMenu = menu.findItem(R.id.menu_move_group_members); + moveMenu.setVisible(isVisible() && mAdapter != null && mAdapter.getCount() > 0); } @Override @@ -468,6 +477,18 @@ public class GroupDetailFragment extends Fragment implements OnScrollListener { mCloseActivityAfterDelete); return true; } + case R.id.menu_move_group_members: { + Intent intent = new Intent(SimContactsConstants.ACTION_MULTI_PICK); + intent.setType(Contacts.CONTENT_TYPE); + intent.putExtra(SimContactsConstants.IS_CONTACT, true); + intent.putExtra(MultiPickContactActivity.EXTRA_GROUP_ID, getGroupId()); + intent.putExtra(SimContactsConstants.ACCOUNT_TYPE, mAccountTypeString); + intent.putExtra(SimContactsConstants.ACCOUNT_NAME, mAccountNameString); + intent.putExtra(MultiPickContactActivity.EXTRA_GROUP_ACTION, + MultiPickContactActivity.GROUP_ACTION_MOVE_MEMBER); + startActivity(intent); + return true; + } } return false; } diff --git a/src/com/android/contacts/group/GroupEditorFragment.java b/src/com/android/contacts/group/GroupEditorFragment.java index eda5d4f39..ff2ee7905 100644 --- a/src/com/android/contacts/group/GroupEditorFragment.java +++ b/src/com/android/contacts/group/GroupEditorFragment.java @@ -63,20 +63,25 @@ import com.android.contacts.GroupMemberLoader.GroupEditorQuery; import com.android.contacts.GroupMetaDataLoader; import com.android.contacts.R; import com.android.contacts.activities.GroupEditorActivity; +import com.android.contacts.activities.MultiPickContactActivity; import com.android.contacts.common.ContactPhotoManager; import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.model.account.AccountWithDataSet; +import com.android.contacts.common.model.account.PhoneAccountType; import com.android.contacts.common.editor.SelectAccountDialogFragment; import com.android.contacts.group.SuggestedMemberListAdapter.SuggestedMember; import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter; import com.android.contacts.common.util.ViewUtil; +import com.android.contacts.common.SimContactsConstants; import com.google.common.base.Objects; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.Set; public class GroupEditorFragment extends Fragment implements SelectAccountDialogFragment.Listener { private static final String TAG = "GroupEditorFragment"; @@ -98,6 +103,8 @@ public class GroupEditorFragment extends Fragment implements SelectAccountDialog private static final String CURRENT_EDITOR_TAG = "currentEditorForAccount"; + public static final int REQUEST_CODE_PICK_GROUP_MEM = 1001; + public static interface Listener { /** * Group metadata was not found, close the fragment now. @@ -183,6 +190,7 @@ public class GroupEditorFragment extends Fragment implements SelectAccountDialog private TextView mGroupNameView; private AutoCompleteTextView mAutoCompleteTextView; + private ImageView mAddGroupMemberView; private String mAccountName; private String mAccountType; @@ -311,8 +319,9 @@ public class GroupEditorFragment extends Fragment implements SelectAccountDialog } private void selectAccountAndCreateGroup() { - final List<AccountWithDataSet> accounts = - AccountTypeManager.getInstance(mContext).getAccounts(true /* writeable */); + final List<AccountWithDataSet> accounts = AccountTypeManager + .getInstance(mContext).getAccounts(true /* writeable */, + AccountTypeManager.FLAG_ALL_ACCOUNTS_WITHOUT_SIM); // No Accounts available if (accounts.isEmpty()) { Log.e(TAG, "No accounts were found."); @@ -404,6 +413,7 @@ public class GroupEditorFragment extends Fragment implements SelectAccountDialog mGroupNameView = (TextView) editorView.findViewById(R.id.group_name); mAutoCompleteTextView = (AutoCompleteTextView) editorView.findViewById( R.id.add_member_field); + mAddGroupMemberView = (ImageView) editorView.findViewById(R.id.addGroupMember); mListView = (ListView) editorView.findViewById(android.R.id.list); mListView.setAdapter(mMemberListAdapter); @@ -414,9 +424,13 @@ public class GroupEditorFragment extends Fragment implements SelectAccountDialog ImageView accountIcon = (ImageView) editorView.findViewById(R.id.account_icon); TextView accountTypeTextView = (TextView) editorView.findViewById(R.id.account_type); TextView accountNameTextView = (TextView) editorView.findViewById(R.id.account_name); - if (!TextUtils.isEmpty(mAccountName)) { + if (!TextUtils.isEmpty(mAccountName) + && !SimContactsConstants.PHONE_NAME.equals(mAccountName)) { accountNameTextView.setText( mContext.getString(R.string.from_account_format, mAccountName)); + accountNameTextView.setVisibility(View.VISIBLE); + } else { + accountNameTextView.setVisibility(View.GONE); } accountTypeTextView.setText(accountTypeDisplayLabel); accountIcon.setImageDrawable(accountType.getDisplayIcon(mContext)); @@ -428,6 +442,7 @@ public class GroupEditorFragment extends Fragment implements SelectAccountDialog if (mAutoCompleteTextView != null) { mAutoCompleteAdapter = new SuggestedMemberListAdapter(mContext, android.R.layout.simple_dropdown_item_1line); + mAutoCompleteTextView.setThreshold(1); mAutoCompleteAdapter.setContentResolver(mContentResolver); mAutoCompleteAdapter.setAccountType(mAccountType); mAutoCompleteAdapter.setAccountName(mAccountName); @@ -455,6 +470,23 @@ public class GroupEditorFragment extends Fragment implements SelectAccountDialog mAutoCompleteAdapter.updateExistingMembersList(mListToDisplay); } + if (mAddGroupMemberView != null) { + mAddGroupMemberView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(SimContactsConstants.ACTION_MULTI_PICK); + intent.setType(Contacts.CONTENT_TYPE); + intent.putExtra(SimContactsConstants.IS_CONTACT, true); + intent.putExtra(SimContactsConstants.ACCOUNT_NAME, mAccountName); + intent.putExtra(SimContactsConstants.ACCOUNT_TYPE, mAccountType); + intent.putExtra(MultiPickContactActivity.EXTRA_GROUP_ACTION, + MultiPickContactActivity.GROUP_ACTION_ADD_MEMBER); + intent.putExtra(MultiPickContactActivity.EXTRA_GROUP_ID, mGroupId); + startActivityForResult(intent, REQUEST_CODE_PICK_GROUP_MEM); + } + }); + } + // If the group name is ready only, don't let the user focus on the field. mGroupNameView.setFocusable(!mGroupNameIsReadOnly); if(isNewEditor) { @@ -463,6 +495,47 @@ public class GroupEditorFragment extends Fragment implements SelectAccountDialog mStatus = Status.EDITING; } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode != Activity.RESULT_OK) { + return; + } + if (requestCode == REQUEST_CODE_PICK_GROUP_MEM) { + Bundle mChoiceSet = data.getExtras(); + Set<String> keys = mChoiceSet.keySet(); + Iterator<String> iterator = keys.iterator(); + String key; + String[] info; + String contactId; + String nameRawContactId; + String displayName; + String lookupKey; + String photoUri; + + while (iterator.hasNext()) { + key = iterator.next(); + info = mChoiceSet.getStringArray(key); + + contactId = info[1]; + + if (!mAutoCompleteAdapter.containsMember(Long.valueOf(contactId))) { + // Retrieve the contact data fields that will be sufficient + // to update + // the adapter with a new entry for this contact + lookupKey = info[0]; + nameRawContactId = info[2]; + photoUri= info[3]; + displayName = info[4]; + + Member member = new Member(Long.valueOf(nameRawContactId), lookupKey, + Long.valueOf(contactId), displayName, photoUri); + addMember(member); + } + } + } + } + public void load(String action, Uri groupUri, Bundle intentExtras) { mAction = action; mGroupUri = groupUri; @@ -713,8 +786,13 @@ public class GroupEditorFragment extends Fragment implements SelectAccountDialog } private void addMember(Member member) { - // Update the display list - mListMembersToAdd.add(member); + // If the contact was just removed during this session, remove it from + // the list of members to remove + if (mListMembersToRemove.contains(member)) { + mListMembersToRemove.remove(member); + } else { + mListMembersToAdd.add(member); + } mListToDisplay.add(member); mMemberListAdapter.notifyDataSetChanged(); diff --git a/src/com/android/contacts/group/SuggestedMemberListAdapter.java b/src/com/android/contacts/group/SuggestedMemberListAdapter.java index 19ff61177..18ad34c7c 100644 --- a/src/com/android/contacts/group/SuggestedMemberListAdapter.java +++ b/src/com/android/contacts/group/SuggestedMemberListAdapter.java @@ -116,7 +116,13 @@ public class SuggestedMemberListAdapter extends ArrayAdapter<SuggestedMember> { } public void addNewMember(long contactId) { - mExistingMemberContactIds.add(contactId); + if (!containsMember(contactId)) { + mExistingMemberContactIds.add(contactId); + } + } + + public boolean containsMember(long contactId) { + return mExistingMemberContactIds.contains(contactId); } public void removeMember(long contactId) { @@ -186,7 +192,7 @@ public class SuggestedMemberListAdapter extends ArrayAdapter<SuggestedMember> { // and have the same account name and type as specified in this adapter String searchQuery = prefix.toString() + "%"; String accountClause = RawContacts.ACCOUNT_NAME + "=? AND " + - RawContacts.ACCOUNT_TYPE + "=?"; + RawContacts.ACCOUNT_TYPE + "=? AND " + RawContacts.DELETED + "!= 1"; String[] args; if (mDataSet == null) { accountClause += " AND " + RawContacts.DATA_SET + " IS NULL"; diff --git a/src/com/android/contacts/list/ContactPickerFragment.java b/src/com/android/contacts/list/ContactPickerFragment.java index 4e8138916..fa1d2679c 100644 --- a/src/com/android/contacts/list/ContactPickerFragment.java +++ b/src/com/android/contacts/list/ContactPickerFragment.java @@ -165,7 +165,7 @@ public class ContactPickerFragment extends ContactEntryListFragment<ContactEntry HeaderEntryContactListAdapter adapter = new HeaderEntryContactListAdapter(getActivity()); adapter.setFilter(ContactListFilter.createFilterWithType( - ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS)); + ContactListFilter.FILTER_TYPE_ALL_WITHOUT_SIM)); adapter.setSectionHeaderDisplayEnabled(true); adapter.setDisplayPhotos(true); adapter.setQuickContactEnabled(false); diff --git a/src/com/android/contacts/list/EmailAddressListAdapter.java b/src/com/android/contacts/list/EmailAddressListAdapter.java index d19f960b8..b7aeaac4e 100644 --- a/src/com/android/contacts/list/EmailAddressListAdapter.java +++ b/src/com/android/contacts/list/EmailAddressListAdapter.java @@ -15,6 +15,7 @@ */ package com.android.contacts.list; +import android.accounts.Account; import android.content.ContentUris; import android.content.Context; import android.content.CursorLoader; @@ -24,6 +25,7 @@ import android.net.Uri.Builder; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.RawContacts; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; @@ -47,6 +49,8 @@ public class EmailAddressListAdapter extends ContactEntryListAdapter { Email.PHOTO_ID, // 4 Email.LOOKUP_KEY, // 5 Email.DISPLAY_NAME_PRIMARY, // 6 + RawContacts.ACCOUNT_TYPE, // 7 + RawContacts.ACCOUNT_NAME // 8 }; private static final String[] PROJECTION_ALTERNATIVE = new String[] { @@ -57,6 +61,8 @@ public class EmailAddressListAdapter extends ContactEntryListAdapter { Email.PHOTO_ID, // 4 Email.LOOKUP_KEY, // 5 Email.DISPLAY_NAME_ALTERNATIVE, // 6 + RawContacts.ACCOUNT_TYPE, // 7 + RawContacts.ACCOUNT_NAME // 8 }; public static final int EMAIL_ID = 0; @@ -66,6 +72,8 @@ public class EmailAddressListAdapter extends ContactEntryListAdapter { public static final int EMAIL_PHOTO_ID = 4; public static final int EMAIL_LOOKUP_KEY = 5; public static final int EMAIL_DISPLAY_NAME = 6; + public static final int EMAIL_ACCOUNT_TYPE = 7; + public static final int EMAIL_ACCOUNT_NAME = 8; } private final CharSequence mUnknownNameText; @@ -173,13 +181,22 @@ public class EmailAddressListAdapter extends ContactEntryListAdapter { if (!cursor.isNull(EmailQuery.EMAIL_PHOTO_ID)) { photoId = cursor.getLong(EmailQuery.EMAIL_PHOTO_ID); } + + Account account = null; + if (!cursor.isNull(EmailQuery.EMAIL_ACCOUNT_TYPE) + && !cursor.isNull(EmailQuery.EMAIL_ACCOUNT_NAME)) { + final String accountType = cursor.getString(EmailQuery.EMAIL_ACCOUNT_TYPE); + final String accountName = cursor.getString(EmailQuery.EMAIL_ACCOUNT_NAME); + account = new Account(accountName, accountType); + } + DefaultImageRequest request = null; if (photoId == 0) { request = getDefaultImageRequestFromCursor(cursor, EmailQuery.EMAIL_DISPLAY_NAME, EmailQuery.EMAIL_LOOKUP_KEY); } - getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false, getCircularPhotos(), - request); + getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, account, + false, getCircularPhotos(), request); } // // protected void bindSearchSnippet(final ContactListItemView view, Cursor cursor) { diff --git a/src/com/android/contacts/list/JoinContactListAdapter.java b/src/com/android/contacts/list/JoinContactListAdapter.java index f08fcbbe3..553993702 100644 --- a/src/com/android/contacts/list/JoinContactListAdapter.java +++ b/src/com/android/contacts/list/JoinContactListAdapter.java @@ -24,6 +24,7 @@ import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Contacts.AggregationSuggestions; import android.provider.ContactsContract.Directory; +import android.provider.ContactsContract.RawContacts; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; @@ -34,6 +35,7 @@ import com.android.contacts.R; import com.android.contacts.common.list.ContactListAdapter; import com.android.contacts.common.list.ContactListItemView; import com.android.contacts.common.list.DirectoryListLoader; +import com.android.contacts.common.SimContactsConstants; import com.android.contacts.common.preference.ContactsPreferences; public class JoinContactListAdapter extends ContactListAdapter { @@ -81,7 +83,10 @@ public class JoinContactListAdapter extends ContactListAdapter { } builder.appendQueryParameter("limit", String.valueOf(MAX_SUGGESTIONS)); - + builder.appendQueryParameter(RawContacts.ACCOUNT_TYPE, + SimContactsConstants.ACCOUNT_TYPE_SIM); + builder.appendQueryParameter(SimContactsConstants.WITHOUT_SIM_FLAG, + "true"); loader.setSuggestionUri(builder.build()); // TODO simplify projection @@ -92,11 +97,19 @@ public class JoinContactListAdapter extends ContactListAdapter { .appendEncodedPath(Uri.encode(filter)) .appendQueryParameter( ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT)) + .appendQueryParameter(RawContacts.ACCOUNT_TYPE, + SimContactsConstants.ACCOUNT_TYPE_SIM) + .appendQueryParameter( + SimContactsConstants.WITHOUT_SIM_FLAG, "true") .build(); } else { allContactsUri = buildSectionIndexerUri(Contacts.CONTENT_URI).buildUpon() .appendQueryParameter( ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT)) + .appendQueryParameter(RawContacts.ACCOUNT_TYPE, + SimContactsConstants.ACCOUNT_TYPE_SIM) + .appendQueryParameter( + SimContactsConstants.WITHOUT_SIM_FLAG, "true") .build(); } loader.setUri(allContactsUri); diff --git a/src/com/android/contacts/list/PostalAddressListAdapter.java b/src/com/android/contacts/list/PostalAddressListAdapter.java index 951a933fa..2bff7282a 100644 --- a/src/com/android/contacts/list/PostalAddressListAdapter.java +++ b/src/com/android/contacts/list/PostalAddressListAdapter.java @@ -15,6 +15,7 @@ */ package com.android.contacts.list; +import android.accounts.Account; import android.content.ContentUris; import android.content.Context; import android.content.CursorLoader; @@ -24,6 +25,7 @@ import android.net.Uri.Builder; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.RawContacts; import android.view.View; import android.view.ViewGroup; @@ -46,6 +48,8 @@ public class PostalAddressListAdapter extends ContactEntryListAdapter { StructuredPostal.PHOTO_ID, // 4 StructuredPostal.LOOKUP_KEY, // 5 StructuredPostal.DISPLAY_NAME_PRIMARY, // 6 + RawContacts.ACCOUNT_TYPE, // 7 + RawContacts.ACCOUNT_NAME // 8 }; private static final String[] PROJECTION_ALTERNATIVE = new String[] { @@ -56,6 +60,8 @@ public class PostalAddressListAdapter extends ContactEntryListAdapter { StructuredPostal.PHOTO_ID, // 4 StructuredPostal.LOOKUP_KEY, // 5 StructuredPostal.DISPLAY_NAME_ALTERNATIVE, // 6 + RawContacts.ACCOUNT_TYPE, // 7 + RawContacts.ACCOUNT_NAME, // 8 }; public static final int POSTAL_ID = 0; @@ -65,6 +71,8 @@ public class PostalAddressListAdapter extends ContactEntryListAdapter { public static final int POSTAL_PHOTO_ID = 4; public static final int POSTAL_LOOKUP_KEY = 5; public static final int POSTAL_DISPLAY_NAME = 6; + public static final int POSTAL_ACCOUNT_TYPE = 7; + public static final int POSTAL_ACCOUNT_NAME = 8; } private final CharSequence mUnknownNameText; @@ -165,14 +173,22 @@ public class PostalAddressListAdapter extends ContactEntryListAdapter { photoId = cursor.getLong(PostalQuery.POSTAL_PHOTO_ID); } + Account account = null; + if (!cursor.isNull(PostalQuery.POSTAL_ACCOUNT_TYPE) + && !cursor.isNull(PostalQuery.POSTAL_ACCOUNT_NAME)) { + final String accountType = cursor.getString(PostalQuery.POSTAL_ACCOUNT_TYPE); + final String accountName = cursor.getString(PostalQuery.POSTAL_ACCOUNT_NAME); + account = new Account(accountName, accountType); + } + DefaultImageRequest request = null; if (photoId == 0) { request = getDefaultImageRequestFromCursor(cursor, PostalQuery.POSTAL_DISPLAY_NAME, PostalQuery.POSTAL_LOOKUP_KEY); } - getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, false, getCircularPhotos(), - request); + getPhotoLoader().loadThumbnail(view.getPhotoView(), photoId, account, + false, getCircularPhotos(), request); } // // protected void bindSearchSnippet(final ContactListItemView view, Cursor cursor) { diff --git a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java index 10887cb57..c45ed41b8 100644 --- a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java +++ b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java @@ -1056,6 +1056,7 @@ public class ExpandingEntryCardView extends CardView { private final String mMimeType; private final long mId; private final boolean mIsSuperPrimary; + private String mData; public EntryContextMenuInfo(String copyText, String copyLabel, String mimeType, long id, boolean isSuperPrimary) { @@ -1066,6 +1067,16 @@ public class ExpandingEntryCardView extends CardView { mIsSuperPrimary = isSuperPrimary; } + public EntryContextMenuInfo(String copyText, String copyLabel, String mimeType, long id, + boolean isSuperPrimary, String data) { + mCopyText = copyText; + mCopyLabel = copyLabel; + mMimeType = mimeType; + mId = id; + mIsSuperPrimary = isSuperPrimary; + mData = data; + } + public String getCopyText() { return mCopyText; } @@ -1085,6 +1096,10 @@ public class ExpandingEntryCardView extends CardView { public boolean isSuperPrimary() { return mIsSuperPrimary; } + + public String getData() { + return mData; + } } static final class EntryTag { diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java index 26f84a850..99af33c36 100644 --- a/src/com/android/contacts/quickcontact/QuickContactActivity.java +++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java @@ -20,10 +20,12 @@ import android.accounts.Account; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.app.Activity; +import android.app.DialogFragment; import android.app.Fragment; import android.app.LoaderManager.LoaderCallbacks; import android.app.SearchManager; import android.content.ActivityNotFoundException; +import android.content.ContentValues; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; @@ -32,6 +34,7 @@ import android.content.Loader; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; +import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; @@ -45,6 +48,8 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Trace; import android.provider.CalendarContract; +import android.os.Handler; +import android.os.Message; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Event; import android.provider.ContactsContract.CommonDataKinds.GroupMembership; @@ -56,6 +61,7 @@ import android.provider.ContactsContract.CommonDataKinds.Organization; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.Relation; import android.provider.ContactsContract.CommonDataKinds.SipAddress; +import android.provider.ContactsContract.CommonDataKinds.Organization; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.Website; import android.provider.ContactsContract.Contacts; @@ -66,6 +72,7 @@ import android.provider.ContactsContract.DataUsageFeedback; import android.provider.ContactsContract.Intents; import android.provider.ContactsContract.QuickContact; import android.provider.ContactsContract.RawContacts; +import android.provider.Telephony; import android.support.v7.graphics.Palette; import android.telecom.PhoneAccount; import android.telecom.TelecomManager; @@ -73,6 +80,8 @@ import android.text.BidiFormatter; import android.text.SpannableString; import android.text.TextDirectionHeuristics; import android.text.TextUtils; +import android.telephony.TelephonyManager; +import android.telephony.SubscriptionManager; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -90,14 +99,18 @@ import android.widget.Toolbar; import com.android.contacts.ContactSaveService; import com.android.contacts.ContactsActivity; import com.android.contacts.NfcHandler; +import com.android.contacts.common.MoreContactUtils; +import com.android.contacts.common.SimContactsConstants; import com.android.contacts.R; import com.android.contacts.common.CallUtil; import com.android.contacts.common.ClipboardUtils; import com.android.contacts.common.Collapser; import com.android.contacts.common.ContactsUtils; +import com.android.contacts.common.GroupMetaData; import com.android.contacts.common.activity.RequestPermissionsActivity; import com.android.contacts.common.dialog.CallSubjectDialog; import com.android.contacts.common.editor.SelectAccountDialogFragment; +import com.android.contacts.common.activity.fragment.BlockContactDialogFragment; import com.android.contacts.common.interactions.TouchPointManager; import com.android.contacts.common.lettertiles.LetterTileDrawable; import com.android.contacts.common.list.ShortcutIntentBuilder; @@ -106,12 +119,14 @@ import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.common.model.Contact; import com.android.contacts.common.model.ContactLoader; import com.android.contacts.common.model.RawContact; +import com.android.contacts.common.model.RawContactDelta; import com.android.contacts.common.model.account.AccountType; import com.android.contacts.common.model.account.AccountWithDataSet; import com.android.contacts.common.model.dataitem.DataItem; import com.android.contacts.common.model.dataitem.DataKind; import com.android.contacts.common.model.dataitem.EmailDataItem; import com.android.contacts.common.model.dataitem.EventDataItem; +import com.android.contacts.common.model.dataitem.GroupMembershipDataItem; import com.android.contacts.common.model.dataitem.ImDataItem; import com.android.contacts.common.model.dataitem.NicknameDataItem; import com.android.contacts.common.model.dataitem.NoteDataItem; @@ -122,7 +137,11 @@ import com.android.contacts.common.model.dataitem.SipAddressDataItem; import com.android.contacts.common.model.dataitem.StructuredNameDataItem; import com.android.contacts.common.model.dataitem.StructuredPostalDataItem; import com.android.contacts.common.model.dataitem.WebsiteDataItem; +import com.android.contacts.common.util.BlockContactHelper; import com.android.contacts.common.util.ImplicitIntentsUtil; +import com.android.contacts.common.MoreContactUtils; +import com.android.contacts.common.SimContactsConstants; +import com.android.contacts.common.util.BitmapUtil; import com.android.contacts.common.util.DateUtils; import com.android.contacts.common.util.MaterialColorMapUtils; import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; @@ -136,6 +155,7 @@ import com.android.contacts.interactions.CallLogInteractionsLoader; import com.android.contacts.interactions.ContactDeletionInteraction; import com.android.contacts.interactions.ContactInteraction; import com.android.contacts.interactions.SmsInteractionsLoader; +import com.android.internal.telephony.PhoneConstants; import com.android.contacts.quickcontact.ExpandingEntryCardView.Entry; import com.android.contacts.quickcontact.ExpandingEntryCardView.EntryContextMenuInfo; import com.android.contacts.quickcontact.ExpandingEntryCardView.EntryTag; @@ -150,8 +170,11 @@ import com.android.contacts.widget.MultiShrinkScroller.MultiShrinkScrollerListen import com.android.contacts.widget.QuickContactImageView; import com.android.contactsbind.HelpUtils; +import com.cyanogen.lookup.phonenumber.provider.LookupProviderImpl; import com.google.common.collect.Lists; - +import com.google.common.collect.ImmutableList; +import com.squareup.picasso.Picasso; +import com.squareup.picasso.Target; import java.lang.SecurityException; import java.util.ArrayList; import java.util.Arrays; @@ -169,7 +192,8 @@ import java.util.concurrent.ConcurrentHashMap; * data asynchronously, and then shows a popup with details centered around * {@link Intent#getSourceBounds()}. */ -public class QuickContactActivity extends ContactsActivity { +public class QuickContactActivity extends ContactsActivity implements + BlockContactDialogFragment.BlockContactCallbacks { /** * QuickContacts immediately takes up the full screen. All possible information is shown. @@ -257,6 +281,9 @@ public class QuickContactActivity extends ContactsActivity { private final ImageViewDrawableSetter mPhotoSetter = new ImageViewDrawableSetter(); + private Target mContactBitmapTarget; + private BlockContactHelper mBlockContactHelper; + /** * {@link #LEADING_MIMETYPES} is used to sort MIME-types. * @@ -311,6 +338,7 @@ public class QuickContactActivity extends ContactsActivity { private static final int CARD_ENTRY_ID_EDIT_CONTACT = -2; + private static final int MAX_NUM_LENGTH = 3; // add limit length to show IP call item private static final int[] mRecentLoaderIds = new int[]{ LOADER_SMS_ID, LOADER_CALENDAR_ID, @@ -342,31 +370,6 @@ public class QuickContactActivity extends ContactsActivity { return; } - // Pass the touch point through the intent for use in the InCallUI - if (Intent.ACTION_CALL.equals(intent.getAction())) { - if (TouchPointManager.getInstance().hasValidPoint()) { - Bundle extras = new Bundle(); - extras.putParcelable(TouchPointManager.TOUCH_POINT, - TouchPointManager.getInstance().getPoint()); - intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras); - } - } - - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - mHasIntentLaunched = true; - try { - startActivity(intent); - } catch (SecurityException ex) { - Toast.makeText(QuickContactActivity.this, R.string.missing_app, - Toast.LENGTH_SHORT).show(); - Log.e(TAG, "QuickContacts does not have permission to launch " - + intent); - } catch (ActivityNotFoundException ex) { - Toast.makeText(QuickContactActivity.this, R.string.missing_app, - Toast.LENGTH_SHORT).show(); - } - // Default to USAGE_TYPE_CALL. Usage is summed among all types for sorting each data id // so the exact usage type is not necessary in all cases String usageType = DataUsageFeedback.USAGE_TYPE_CALL; @@ -442,10 +445,23 @@ public class QuickContactActivity extends ContactsActivity { } }; + @Override + public void onBlockContact(boolean notifyLookupProvider) { + mBlockContactHelper.blockContactAsync(notifyLookupProvider); + } + + @Override + public void onUnblockContact(boolean notifyLookupProvider) { + mBlockContactHelper.unblockContactAsync(notifyLookupProvider); + } + private interface ContextMenuIds { static final int COPY_TEXT = 0; static final int CLEAR_DEFAULT = 1; static final int SET_DEFAULT = 2; + static final int EDIT_BEFORE_CALL = 3; + static final int IPCALL1 = 4; + static final int IPCALL2 = 5; // add for new feature: ip call prefix } private final OnCreateContextMenuListener mEntryContextMenuListener = @@ -485,6 +501,29 @@ public class QuickContactActivity extends ContactsActivity { menu.add(ContextMenu.NONE, ContextMenuIds.SET_DEFAULT, ContextMenu.NONE, getString(R.string.set_default)); } + if (Phone.CONTENT_ITEM_TYPE.equals(info.getMimeType())) { + menu.add(ContextMenu.NONE, ContextMenuIds.EDIT_BEFORE_CALL, + ContextMenu.NONE, getString(R.string.edit_before_call)); + // add limit length to show IP call item + if (info.getData().length() > MAX_NUM_LENGTH) { + if (MoreContactUtils.isMultiSimEnable(QuickContactActivity.this, + PhoneConstants.SUB1)) { + String sub1Name = MoreContactUtils.getMultiSimAliasesName( + getApplicationContext(), PhoneConstants.SUB1); + menu.add(ContextMenu.NONE, ContextMenuIds.IPCALL1, ContextMenu.NONE, + getApplicationContext().getString( + com.android.contacts.common.R.string.ip_call_by_slot, sub1Name)); + } + if (MoreContactUtils.isMultiSimEnable(QuickContactActivity.this, + PhoneConstants.SUB2)) { + String sub2Name = MoreContactUtils.getMultiSimAliasesName( + getApplicationContext(), PhoneConstants.SUB2); + menu.add(ContextMenu.NONE, ContextMenuIds.IPCALL2, ContextMenu.NONE, + getApplicationContext().getString( + com.android.contacts.common.R.string.ip_call_by_slot, sub2Name)); + } + } + } } }; @@ -513,11 +552,32 @@ public class QuickContactActivity extends ContactsActivity { menuInfo.getId()); this.startService(clearIntent); return true; + case ContextMenuIds.EDIT_BEFORE_CALL: + callByEdit(menuInfo.getData()); + return true; + case ContextMenuIds.IPCALL1: + ipCallBySlot(menuInfo.getData(), PhoneConstants.SUB1); + return true; + case ContextMenuIds.IPCALL2: + ipCallBySlot(menuInfo.getData(), PhoneConstants.SUB2); + return true; default: throw new IllegalArgumentException("Unknown menu option " + item.getItemId()); } } + private void ipCallBySlot(String data, int subscription) { + String ipCallPrefix = MoreContactUtils.getIPCallPrefix(this, + subscription); + if (!TextUtils.isEmpty(ipCallPrefix)) { + Intent callIntent = CallUtil.getCallIntent(ipCallPrefix + data, + MoreContactUtils.getAccount(subscription)); + startActivity(callIntent); + } else { + MoreContactUtils.showNoIPNumberDialog(this, subscription); + } + } + /** * Headless fragment used to handle account selection callbacks invoked from * {@link DirectoryContactUtil}. @@ -698,7 +758,6 @@ public class QuickContactActivity extends ContactsActivity { getWindow().setStatusBarColor(Color.TRANSPARENT); - processIntent(getIntent()); // Show QuickContact in front of soft input getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, @@ -813,7 +872,12 @@ public class QuickContactActivity extends ContactsActivity { } }); } - + processIntent(getIntent()); + mBlockContactHelper = new BlockContactHelper(this, new LookupProviderImpl(this)); + if (mContactData != null) { + mBlockContactHelper.setContactInfo(mContactData); + mBlockContactHelper.gatherDataInBackground(); + } Trace.endSection(); } @@ -863,13 +927,24 @@ public class QuickContactActivity extends ContactsActivity { mExtraPrioritizedMimeType = getIntent().getStringExtra(QuickContact.EXTRA_PRIORITIZED_MIMETYPE); final Uri oldLookupUri = mLookupUri; - if (lookupUri == null) { + mLookupUri = lookupUri; + mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES); + + if (mLookupUri == null) { finish(); return; } - mLookupUri = lookupUri; - mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES); - if (oldLookupUri == null) { + + Contact contact = null; + if (UriUtils.isEncodedContactUri(mLookupUri)) { + // try to parse it as an ENHANCED_CALLER_META_DATA uri + contact = ContactLoader.parseEncodedContactEntity(mLookupUri, + ContactLoader.EncodedContactEntitySchemaVersion.ENHANCED_CALLER_META_DATA); + } + + if (contact != null) { + bindContactData(contact); + } else if (oldLookupUri == null) { mContactLoader = (ContactLoader) getLoaderManager().initLoader( LOADER_CONTACT_ID, null, mLoaderContactCallbacks); } else if (oldLookupUri != mLookupUri) { @@ -914,6 +989,21 @@ public class QuickContactActivity extends ContactsActivity { } } + private void setAttributionText(String value) { + if (!TextUtils.isEmpty(value)) { + if (mScroller != null) { + mScroller.setAttributionText(getString(R.string.powered_by_provider, value)); + } + } + } + + private void setSpamCountText(int value) { + if (mScroller != null && value > 0) { + mScroller.setSpamCountText( + getResources().getQuantityString(R.plurals.spam_count_text, value, value)); + } + } + /** * Check if the given MIME-type appears in the list of excluded MIME-types * that the most-recent caller requested. @@ -940,7 +1030,28 @@ public class QuickContactActivity extends ContactsActivity { Trace.beginSection("Set display photo & name"); mPhotoView.setIsBusiness(mContactData.isDisplayNameFromOrganization()); - mPhotoSetter.setupContactPhoto(data, mPhotoView); + if (mContactData.getPhotoBinaryData() == null && mContactData.getPhotoUri() != null) { + mContactBitmapTarget = new Target() { + @Override + public void onPrepareLoad(Drawable d){} + @Override + public void onBitmapLoaded(Bitmap result, Picasso.LoadedFrom from) { + if (result != null) { + mContactData.setPhotoBinaryData(BitmapUtil.bitmapToByteArray(result)); + mPhotoSetter.setupContactPhoto(data, mPhotoView); + } + mContactBitmapTarget = null; + } + @Override + public void onBitmapFailed(Drawable drawable) { + mPhotoSetter.setupContactPhoto(data, mPhotoView); + mContactBitmapTarget = null; + } + }; + Picasso.with(this).load(mContactData.getPhotoUri()).into(mContactBitmapTarget); + } else { + mPhotoSetter.setupContactPhoto(data, mPhotoView); + } extractAndApplyTintFromPhotoViewAsynchronously(); String phoneticName = ContactDisplayUtils.getPhoneticName(this, data); String displayName = ContactDisplayUtils.getDisplayName(this, data).toString(); @@ -953,6 +1064,16 @@ public class QuickContactActivity extends ContactsActivity { setHeaderNameText(displayName); } + setAttributionText(data.getProviderName()); + final int spamCount = data.getSpamCount(); + if (spamCount > 0) { + mHasComputedThemeColor = true; + setThemeColor(mMaterialColorMapUtils + .calculatePrimaryAndSecondaryColor(getResources() + .getColor(R.color.letter_tile_red_color))); + setSpamCountText(spamCount); + } + Trace.endSection(); mEntriesAndActionsTask = new AsyncTask<Void, Void, Cp2DataCardModel>() { @@ -1059,7 +1180,7 @@ public class QuickContactActivity extends ContactsActivity { // the name mimetype. final List<Entry> aboutEntries = dataItemsToEntries(mimeTypeItems, /* aboutCardTitleOut = */ null); - if (aboutEntries.size() > 0) { + if (aboutEntries != null && aboutEntries.size() > 0) { aboutCardEntries.add(aboutEntries); } } @@ -1177,6 +1298,30 @@ public class QuickContactActivity extends ContactsActivity { } /** + * Maps group ID to the corresponding group name, collapses all synonymous groups. Ignores + * default groups (e.g. My Contacts) and favorites groups. + */ + private static String getGroupName(List<GroupMetaData> groupMetaData, long groupId) { + if (groupMetaData == null) { + return ""; + } + + for (GroupMetaData group : groupMetaData) { + if (group.getGroupId() == groupId) { + if (!group.isDefaultGroup() && !group.isFavorites()) { + String title = group.getTitle(); + if (!TextUtils.isEmpty(title)) { + return title; + } + } + break; + } + } + + return ""; + } + + /** * Create a card that shows "Add email" and "Add phone number" entries in grey. */ private void initializeNoContactDetailCard() { @@ -1496,7 +1641,7 @@ public class QuickContactActivity extends ContactsActivity { TextDirectionHeuristics.LTR); entryContextMenuInfo = new EntryContextMenuInfo(header, res.getString(R.string.phoneLabelsGroup), dataItem.getMimeType(), - dataItem.getId(), dataItem.isSuperPrimary()); + dataItem.getId(), dataItem.isSuperPrimary(), header); if (phone.hasKindTypeColumn(kind)) { final int kindTypeColumn = phone.getKindTypeColumn(kind); final String label = phone.getLabel(); @@ -1634,6 +1779,35 @@ public class QuickContactActivity extends ContactsActivity { aboutCardName.value = res.getString(R.string.about_card_title); } } + } else if (dataItem instanceof GroupMembershipDataItem) { + GroupMembershipDataItem groupMembership = + (GroupMembershipDataItem) dataItem; + Long groupId = groupMembership.getGroupRowId(); + if (groupId != null) { + return new Entry(/* viewId = */-1, + /* icon = */null, + res.getString(R.string.groupsLabel), + getGroupName(contactData.getGroupMetaData(), + groupId), + /* mSubHeaderIcon= */null, + /* text = */null, + /* mTextIcon= */null, + /* primaryContentDescription = */null, + /* intent = */null, + /* alternateIcon = */null, + /* alternateIntent = */null, + /* alternateContentDescription = */null, + /* shouldApplyColor = */false, + /* isEditable = */false, + /* EntryContextMenuInfo = */null, + /* thirdIcon = */null, + /* thirdIntent = */null, + /* thirdContentDescription = */null, + /* thirdAction = */ Entry.ACTION_NONE, + /* thirdExtras = */ null, + /* iconResourceId = */0); + } + return null; } else { // Custom DataItem header = dataItem.buildDataStringForDisplay(context, kind); @@ -1754,6 +1928,12 @@ public class QuickContactActivity extends ContactsActivity { if (dataItems.get(0).getMimeType().equals(MIMETYPE_GPLUS_PROFILE) || dataItems.get(0).getMimeType().equals(MIMETYPE_HANGOUTS)) { return gPlusOrHangoutsDataItemsToEntries(dataItems); + } else if (dataItems.get(0).getMimeType().equals(GroupMembership.CONTENT_ITEM_TYPE)) { + final Entry entry = groupDataItemsToEntry(dataItems); + if (entry != null) { + return Lists.newArrayList(entry); + } + return null; } else { final List<Entry> entries = new ArrayList<>(); for (DataItem dataItem : dataItems) { @@ -1767,6 +1947,48 @@ public class QuickContactActivity extends ContactsActivity { } } + private Entry groupDataItemsToEntry(List<DataItem> dataItems) { + final List<String> titles = new ArrayList<>(); + for (DataItem dataItem : dataItems) { + if (!(dataItem instanceof GroupMembershipDataItem)) { + continue; + } + + final GroupMembershipDataItem groupItem = (GroupMembershipDataItem) dataItem; + if (groupItem.isDefaultGroup() || groupItem.isFavoritesGroup()) { + continue; + } + final String title = groupItem.getGroupTitle(); + if (title != null) { + titles.add(title); + } + } + if (titles.isEmpty()) { + return null; + } + + return new Entry(/* viewId = */ -1, /* icon = */ null, + /* header */ getResources().getString(R.string.contacts_groups_label), + /* subHeader */ null, + /* subHeaderIcon = */ null, + /* text = */ TextUtils.join(", ", titles), + /* textIcon = */ null, + /* primaryContentDescription = */ null, + /* intent = */ null, + /* alternateIcon = */ null, + /* alternateIntent = */ null, + /* alternateContentDescription = */ null, + /* shouldApplyColor = */ true, + /* isEditable = */ false, + /* EntryContextMenuInfo = */ null, + /* thirdIcon = */ null, + /* thirdIntent = */ null, + /* thirdContentDescription = */ null, + /* thirdAction = */ Entry.ACTION_NONE, + /* thirdExtras = */ null, + /* iconResourceId = */ 0); + } + /** * G+ and Hangout entries are unique in that a single ExpandingEntryCardView.Entry consists * of two data items. This method attempts to build each entry using the two data items if @@ -2054,7 +2276,8 @@ public class QuickContactActivity extends ContactsActivity { finish(); return; } - + mBlockContactHelper.setContactInfo(data); + mBlockContactHelper.gatherDataInBackground(); bindContactData(data); } finally { @@ -2092,6 +2315,7 @@ public class QuickContactActivity extends ContactsActivity { // override transitions to skip the standard window animations overridePendingTransition(0, 0); + mBlockContactHelper.destroy(); } private final LoaderCallbacks<List<ContactInteraction>> mLoaderInteractionsCallbacks = @@ -2328,6 +2552,12 @@ public class QuickContactActivity extends ContactsActivity { } } + private void callByEdit(String data) { + Intent intent = new Intent(Intent.ACTION_DIAL, Uri.fromParts(PhoneAccount.SCHEME_TEL, + data, null)); + startActivity(intent); + } + /** * Creates a launcher shortcut with the current contact. */ @@ -2355,7 +2585,8 @@ public class QuickContactActivity extends ContactsActivity { private boolean isShortcutCreatable() { if (mContactData == null || mContactData.isUserProfile() || - mContactData.isDirectoryEntry()) { + mContactData.isDirectoryEntry() || + !TextUtils.isEmpty(mContactData.getProviderName())) { return false; } final Intent createShortcutIntent = new Intent(); @@ -2365,6 +2596,88 @@ public class QuickContactActivity extends ContactsActivity { return receivers != null && receivers.size() > 0; } + private void sendContactViaSMS() { + // Get name string + String name = mContactData.getDisplayName(); + String phone = null; + String email = null; + String postal = null; + String organization = null; + String sipAddress = null; + + Log.d(TAG, "Contact name: " + name); + + for (RawContact raw: mContactData.getRawContacts()) { + for (DataItem dataItem : raw.getDataItems()) { + final ContentValues entryValues = dataItem.getContentValues(); + final String mimeType = dataItem.getMimeType(); + + Log.d(TAG, " entryValues:" + entryValues); + + if (mimeType == null) continue; + + if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) { // Get phone string + if (phone == null) { + phone = entryValues.getAsString(Phone.NUMBER); + } else { + phone = phone + ", " + entryValues.getAsString(Phone.NUMBER); + } + } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) { // Get email string + if (email == null) { + email = entryValues.getAsString(Email.ADDRESS); + } else { + email = email + ", " + entryValues.getAsString(Email.ADDRESS); + } + } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType)) { + if (postal == null) { + postal = entryValues.getAsString(StructuredPostal.FORMATTED_ADDRESS); + } else { + postal = postal + ", " + entryValues.getAsString( + StructuredPostal.FORMATTED_ADDRESS); + } + } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) { + if (organization == null) { + organization = entryValues.getAsString(Organization.COMPANY); + } else { + organization = organization + ", " + entryValues + .getAsString(Organization.COMPANY); + } + } else if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType)) { + if (sipAddress == null) { + sipAddress = entryValues.getAsString(SipAddress.SIP_ADDRESS); + } else { + sipAddress = sipAddress + ", " + entryValues + .getAsString(SipAddress.SIP_ADDRESS); + } + } + } + } + + if (TextUtils.isEmpty(name)) { + name = getResources().getString(R.string.missing_name); + } + + name = getString(R.string.nameLabelsGroup) + ":" + name + "\r\n"; + phone = (phone == null) ? "" : getString(R.string.phoneLabelsGroup) + + ":" + phone + "\r\n"; + email = (email == null )? "" : getString(R.string.emailLabelsGroup) + + ":" + email + "\r\n"; + postal = (postal == null) ? "" : getString(R.string.postalLabelsGroup) + + ":" + postal + "\r\n"; + organization = (organization == null) ? "" : getString(R.string.organizationLabelsGroup) + + ":" + organization + "\r\n"; + sipAddress = (sipAddress == null) ? "" : getString(R.string.label_sip_address) + ":" + + sipAddress + "\r\n"; + String defaultSmsPackageName = Telephony.Sms.getDefaultSmsPackage(this); + Intent intent = new Intent(Intent.ACTION_SEND); + intent.putExtra("sms_body", name + phone + email + postal + organization + sipAddress); + intent.setType("text/plain"); + if (defaultSmsPackageName != null) { + intent.setPackage(defaultSmsPackageName); + } + startActivity(intent); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { final MenuInflater inflater = getMenuInflater(); @@ -2405,6 +2718,82 @@ public class QuickContactActivity extends ContactsActivity { final MenuItem helpMenu = menu.findItem(R.id.menu_help); helpMenu.setVisible(HelpUtils.isHelpAndFeedbackAvailable()); + String accoutName = null; + String accoutType = null; + + final RawContact rawContact = mContactData.getRawContacts().get(0); + accoutName = rawContact.getAccountName(); + accoutType = rawContact.getAccountTypeString(); + + final MenuItem copyToPhoneMenu = menu.findItem(R.id.menu_copy_to_phone); + if (copyToPhoneMenu != null) { + copyToPhoneMenu.setVisible(false); + } + + final MenuItem copyToSim1Menu = menu.findItem(R.id.menu_copy_to_sim1); + if (copyToSim1Menu != null) { + copyToSim1Menu.setVisible(false); + } + + final MenuItem copyToSim2Menu = menu.findItem(R.id.menu_copy_to_sim2); + if (copyToSim2Menu != null) { + copyToSim2Menu.setVisible(false); + } + + if (!TextUtils.isEmpty(accoutType)) { + if (SimContactsConstants.ACCOUNT_TYPE_SIM.equals(accoutType)) { + copyToPhoneMenu.setVisible(true); + copyToPhoneMenu.setTitle(getString(R.string.menu_copyTo, + getString(R.string.phoneLabelsGroup))); + if (TelephonyManager.getDefault().isMultiSimEnabled()) { + if (SimContactsConstants.SIM_NAME_1.equals(accoutName) + && simIsReady(PhoneConstants.SUB2)) { + copyToSim2Menu.setTitle(getString(R.string.menu_copyTo, + MoreContactUtils.getMultiSimAliasesName( + this, PhoneConstants.SUB2))); + copyToSim2Menu.setVisible(true); + } + if (SimContactsConstants.SIM_NAME_2.equals(accoutName) + && simIsReady(PhoneConstants.SUB1)) { + copyToSim1Menu.setTitle(getString(R.string.menu_copyTo, + MoreContactUtils.getMultiSimAliasesName( + this, PhoneConstants.SUB1))); + copyToSim1Menu.setVisible(true); + } + } + } else if (SimContactsConstants.ACCOUNT_TYPE_PHONE.equals(accoutType)) { + copyToPhoneMenu.setVisible(false); + boolean hasPhoneOrEmail = hasPhoneOrEmailDate(mContactData); + if (TelephonyManager.getDefault().isMultiSimEnabled()) { + if (hasPhoneOrEmail && simIsReady(PhoneConstants.SUB1)) { + copyToSim1Menu.setTitle(getString(R.string.menu_copyTo, + MoreContactUtils.getMultiSimAliasesName( + this, PhoneConstants.SUB1))); + copyToSim1Menu.setVisible(true); + } + if (hasPhoneOrEmail && simIsReady(PhoneConstants.SUB2)) { + copyToSim2Menu.setTitle(getString(R.string.menu_copyTo, + MoreContactUtils.getMultiSimAliasesName( + this, PhoneConstants.SUB2))); + copyToSim2Menu.setVisible(true); + } + } else { + if (hasPhoneOrEmail && simIsReady(PhoneConstants.SUB1)) { + copyToSim1Menu.setTitle(getString(R.string.menu_copyTo, + SimContactsConstants.SIM_NAME)); + copyToSim1Menu.setVisible(true); + } + } + } + } + + // set block or un-block menu titles accordingly + final MenuItem blockMenuItem = menu.findItem(R.id.menu_block_contact); + if (mBlockContactHelper.isContactBlacklisted()) { + blockMenuItem.setTitle(R.string.menu_unblock_contact); + } else { + blockMenuItem.setTitle(R.string.menu_block_contact); + } return true; } @@ -2495,8 +2884,363 @@ public class QuickContactActivity extends ContactsActivity { case R.id.menu_help: HelpUtils.launchHelpAndFeedbackForContactScreen(this); return true; + + case R.id.menu_send_via_sms: { + if (mContactData == null) { + return false; + } + sendContactViaSMS(); + return true; + } + case R.id.menu_copy_to_phone: { + if (mContactData == null) return false; + copyToPhone(); + return true; + } + case R.id.menu_copy_to_sim1: { + if (mContactData == null) return false; + copyToCard(PhoneConstants.SUB1); + return true; + } + case R.id.menu_copy_to_sim2: { + if (mContactData == null) return false; + copyToCard(PhoneConstants.SUB2); + return true; + } + case R.id.menu_block_contact: { + // block contact dialog fragment + DialogFragment f = mBlockContactHelper.getBlockContactDialog( + mBlockContactHelper.isContactBlacklisted() ? + BlockContactHelper.BlockOperation.UNBLOCK : + BlockContactHelper.BlockOperation.BLOCK + ); + f.show(getFragmentManager(), "block_contact"); + return true; + } default: return super.onOptionsItemSelected(item); } } + private boolean hasPhoneOrEmailDate(Contact contact){ + int phoneCount = 0; + int emailCount = 0; + ImmutableList<RawContact> rawContacts = contact.getRawContacts(); + for (RawContact rawContact : rawContacts) { + RawContactDelta rawContactDelta = RawContactDelta.fromBefore(rawContact); + phoneCount += rawContactDelta.getMimeEntriesCount( + Phone.CONTENT_ITEM_TYPE, true); + emailCount += rawContactDelta.getMimeEntriesCount( + Email.CONTENT_ITEM_TYPE, true); + } + if (phoneCount > 0 || emailCount > 0) { + return true; + } else { + return false; + } + } + + //supply phone number and email which could stored in one ADN + class UsimEntity { + private ArrayList<String> mNumberList = new ArrayList<String>(); + private ArrayList<String> mEmailList = new ArrayList<String>(); + + public ArrayList<String> getEmailList() { + return mEmailList; + } + + public ArrayList<String> getNumberList() { + return mNumberList; + } + + public void putEmailList(ArrayList<String> list) { + mEmailList = list; + } + + public void putNumberList(ArrayList<String> list) { + mNumberList = list; + } + + public boolean containsEmail() { + return !mEmailList.isEmpty(); + } + + public boolean containsNumber() { + return !mNumberList.isEmpty(); + } + } + + private void copyToPhone() { + String name = mContactData.getDisplayName(); + if (TextUtils.isEmpty(name)) { + name = ""; + } + String phoneNumber = ""; + StringBuilder anrNumber = new StringBuilder(); + StringBuilder email = new StringBuilder(); + + //get phonenumber,email,anr from SIM contacts,then insert them to phone + for (RawContact rawContact : mContactData.getRawContacts()) { + for (DataItem dataItem : rawContact.getDataItems()) { + if (dataItem.getMimeType() == null) { + continue; + } + if (dataItem instanceof PhoneDataItem) { + PhoneDataItem phoneNum = (PhoneDataItem) dataItem; + final String number = phoneNum.getNumber(); + if (!TextUtils.isEmpty(number)) { + if (Phone.TYPE_MOBILE == phoneNum.getContentValues().getAsInteger( + Phone.TYPE)) { + phoneNumber = number; + } else { + if(!TextUtils.isEmpty(anrNumber.toString())) { + anrNumber.append(","); + } + anrNumber.append(number); + } + } + } else if (dataItem instanceof EmailDataItem) { + EmailDataItem emailData = (EmailDataItem) dataItem; + final String address = emailData.getData(); + if (!TextUtils.isEmpty(address)) { + if(!TextUtils.isEmpty(email.toString())) { + email.append(","); + } + email.append(address); + } + } + } + } + + String[] value = new String[] { + name, phoneNumber, email.toString(), anrNumber.toString() + }; + boolean success = MoreContactUtils + .insertToPhone(value, getContentResolver(), + SubscriptionManager.INVALID_SUBSCRIPTION_ID); + Toast.makeText(this, success ? R.string.copy_done : R.string.copy_failure, + Toast.LENGTH_SHORT).show(); + } + + private Handler mHandler = null; + + private void copyToCard(final int sub) { + final Contact contactData = mContactData; + final int MSG_COPY_DONE = 0; + final int MSG_COPY_FAILURE = 1; + final int MSG_CARD_NO_SPACE = 2; + final int MSG_NO_EMPTY_EMAIL = 3; + if (mHandler == null) { + mHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_COPY_DONE: + Toast.makeText(QuickContactActivity.this, R.string.copy_done, + Toast.LENGTH_SHORT).show(); + break; + case MSG_COPY_FAILURE: + Toast.makeText(QuickContactActivity.this, R.string.copy_failure, + Toast.LENGTH_SHORT).show(); + break; + case MSG_CARD_NO_SPACE: + Toast.makeText(QuickContactActivity.this, R.string.card_no_space, + Toast.LENGTH_SHORT).show(); + break; + case MSG_NO_EMPTY_EMAIL: + Toast.makeText(QuickContactActivity.this, + R.string.no_empty_email_in_usim, + Toast.LENGTH_SHORT).show(); + break; + } + } + }; + } + + new Thread(new Runnable() { + public void run() { + synchronized (this) { + int adnCountInSimContact = 1; + int anrCountInSimContact = 0; + int emailCountInSimContact = 0; + + Cursor cr = null; + // call query first, otherwise the count queries will fail + try{ + int[] subId = SubscriptionManager.getSubId(sub); + if (subId != null + && TelephonyManager.getDefault().isMultiSimEnabled()) { + cr = getContentResolver().query( + Uri.parse(SimContactsConstants.SIM_SUB_URI + + subId[0]), null, null, null, null); + } else { + cr = getContentResolver().query( + Uri.parse(SimContactsConstants.SIM_URI), null, + null, null, null); + } + } catch (NullPointerException e) { + Log.e(TAG, "Exception:" + e); + } finally { + if (cr != null) { + cr.close(); + } + } + + if (MoreContactUtils.canSaveAnr(sub)) { + anrCountInSimContact = MoreContactUtils.getOneSimAnrCount(sub); + } + if (MoreContactUtils.canSaveEmail(sub)) { + emailCountInSimContact = MoreContactUtils.getOneSimEmailCount(sub); + } + int totalEmptyAdn = MoreContactUtils.getSimFreeCount( + QuickContactActivity.this, sub); + int totalEmptyAnr = MoreContactUtils.getSpareAnrCount(sub); + int totalEmptyEmail = MoreContactUtils.getSpareEmailCount(sub); + + Message msg = Message.obtain(); + if (totalEmptyAdn <= 0) { + msg.what = MSG_CARD_NO_SPACE; + mHandler.sendMessage(msg); + return; + } + + //to indiacate how many number in one ADN can saved to SIM card, + //1 means can only save one number,2,3 ... means can save anr + int numEntitySize = adnCountInSimContact + anrCountInSimContact; + + //empty number is equals to the sum of adn and anr + int emptyNumTotal = totalEmptyAdn + totalEmptyAnr; + + // Get name string + String strName = contactData.getDisplayName(); + + ArrayList<String> arrayNumber = new ArrayList<String>(); + ArrayList<String> arrayEmail = new ArrayList<String>(); + + for (RawContact rawContact : contactData.getRawContacts()) { + for (DataItem dataItem : rawContact.getDataItems()) { + if (dataItem.getMimeType() == null) { + continue; + } + if (dataItem instanceof PhoneDataItem) { + // Get phone string + PhoneDataItem phoneNum = (PhoneDataItem) dataItem; + final String number = phoneNum.getNumber(); + if (!TextUtils.isEmpty(number) && emptyNumTotal-- > 0) { + arrayNumber.add(number); + } + } else if (dataItem instanceof EmailDataItem) { + // Get email string + EmailDataItem emailData = (EmailDataItem) dataItem; + final String address = emailData.getData(); + if (!TextUtils.isEmpty(address) && totalEmptyEmail-- > 0) { + arrayEmail.add(address); + } + } + } + } + + //calculate how many ADN needed according to the number,name,phone,email, + //then uses the max of them + int nameCount = (strName != null && !strName.equals("")) ? 1 : 0; + int groupNumCount = (arrayNumber.size() % numEntitySize) != 0 ? (arrayNumber + .size() / numEntitySize + 1) : (arrayNumber.size() / numEntitySize); + int groupEmailCount = emailCountInSimContact == 0 ? 0 + : ((arrayEmail.size() % emailCountInSimContact) != 0 ? (arrayEmail + .size() / emailCountInSimContact + 1) + : (arrayEmail.size() / emailCountInSimContact)); + + int groupCount = Math.max(groupEmailCount, Math.max(nameCount, groupNumCount)); + + ArrayList<UsimEntity> results = new ArrayList<UsimEntity>(); + for (int i = 0; i < groupCount; i++) { + results.add(new UsimEntity()); + } + + UsimEntity value; + //get the phone number for each ADN from arrayNumber,put them in UsimEntity + for (int i = 0; i < groupNumCount; i++) { + value = results.get(i); + ArrayList<String> numberItem = new ArrayList<String>(); + for (int j = 0; j < numEntitySize; j++) { + if ((i * numEntitySize + j) < arrayNumber.size()) { + numberItem.add(arrayNumber.get(i * numEntitySize + j)); + } + } + value.putNumberList(numberItem); + } + + for (int i = 0; i < groupEmailCount; i++) { + value = results.get(i); + ArrayList<String> emailItem = new ArrayList<String>(); + for (int j = 0; j < emailCountInSimContact; j++) { + if ((i * emailCountInSimContact + j) < arrayEmail.size()) { + emailItem.add(arrayEmail.get(i * emailCountInSimContact + j)); + } + } + value.putEmailList(emailItem); + } + + ArrayList<String> emptyList = new ArrayList<String>(); + Uri itemUri = null; + if (totalEmptyEmail < 0 && MoreContactUtils.canSaveEmail(sub)) { + Message e_msg = Message.obtain(); + e_msg.what = MSG_NO_EMPTY_EMAIL; + mHandler.sendMessage(e_msg); + } + + //get phone number from UsimEntity,then insert to SIM card + for (int i = 0; i < groupCount; i++) { + value = results.get(i); + if (value.containsNumber()) { + arrayNumber = (ArrayList<String>) value.getNumberList(); + } else { + arrayNumber = emptyList; + } + + if (value.containsEmail()) { + arrayEmail = (ArrayList<String>) value.getEmailList(); + } else { + arrayEmail = emptyList; + } + String strNum = arrayNumber.size() > 0 ? arrayNumber.get(0) : null; + StringBuilder strAnrNum = new StringBuilder(); + for (int j = 1; j < arrayNumber.size(); j++) { + String s = arrayNumber.get(j); + if (s.length() > MoreContactUtils.MAX_LENGTH_NUMBER_IN_SIM) { + s = s.substring( + 0, MoreContactUtils.MAX_LENGTH_NUMBER_IN_SIM); + } + strAnrNum.append(s); + strAnrNum.append(SimContactsConstants.ANR_SEP); + } + StringBuilder strEmail = new StringBuilder(); + for (int j = 0; j < arrayEmail.size(); j++) { + String s = arrayEmail.get(j); + if (s.length() > MoreContactUtils.MAX_LENGTH_EMAIL_IN_SIM) { + s = s.substring( + 0, MoreContactUtils.MAX_LENGTH_EMAIL_IN_SIM); + } + strEmail.append(s); + strEmail.append(SimContactsConstants.EMAIL_SEP); + } + itemUri = MoreContactUtils.insertToCard(QuickContactActivity.this, strName, + strNum, strEmail.toString(), strAnrNum.toString(), sub); + } + if (itemUri != null) { + msg.what = MSG_COPY_DONE; + mHandler.sendMessage(msg); + } else { + msg.what = MSG_COPY_FAILURE; + mHandler.sendMessage(msg); + } + } + } + }).start(); + } + + private boolean simIsReady(int sub) { + if (TelephonyManager.getDefault().getSimState(sub) + == TelephonyManager.SIM_STATE_READY) + return true; + return false; + } } diff --git a/src/com/android/contacts/util/ImageViewDrawableSetter.java b/src/com/android/contacts/util/ImageViewDrawableSetter.java index 6147c3975..1ca82124e 100644 --- a/src/com/android/contacts/util/ImageViewDrawableSetter.java +++ b/src/com/android/contacts/util/ImageViewDrawableSetter.java @@ -16,6 +16,8 @@ package com.android.contacts.util; +import android.accounts.Account; +import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -30,6 +32,7 @@ import com.android.contacts.common.ContactPhotoManager; import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest; import com.android.contacts.common.lettertiles.LetterTileDrawable; import com.android.contacts.common.model.Contact; +import com.android.contacts.common.model.RawContact; import java.util.Arrays; @@ -53,9 +56,17 @@ public class ImageViewDrawableSetter { } public Bitmap setupContactPhoto(Contact contactData, ImageView photoView) { + Account account = null; mContact = contactData; setTarget(photoView); - return setCompressedImage(contactData.getPhotoBinaryData()); + RawContact rawContact = contactData.getRawContacts().get(0); + final String accountType = rawContact.getAccountTypeString(); + final String accountName = rawContact.getAccountName(); + if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) { + account = new Account(accountName, accountType); + } + return setCompressedImage(contactData.getPhotoBinaryData(), + photoView.getContext(), account); } public void setTransitionDuration(int durationInMillis) { @@ -83,7 +94,7 @@ public class ImageViewDrawableSetter { return mCompressed; } - protected Bitmap setCompressedImage(byte[] compressed) { + protected Bitmap setCompressedImage(byte[] compressed, Context c, Account account) { if (mPreviousDrawable == null) { // If we don't already have a drawable, skip the exit-early test // below; otherwise we might not end up setting the default image. @@ -97,10 +108,9 @@ public class ImageViewDrawableSetter { return previousBitmap(); } - Drawable newDrawable = decodedBitmapDrawable(compressed); - if (newDrawable == null) { - newDrawable = defaultDrawable(); - } + final Drawable newDrawable = (compressed == null) + ? defaultDrawable(c,account) + : decodedBitmapDrawable(compressed); // Remember this for next time, so that we can check if it changed. mCompressed = compressed; @@ -140,7 +150,7 @@ public class ImageViewDrawableSetter { * retrieve a default drawable for this contact. If not, then use the name as the contact * identifier instead. */ - private Drawable defaultDrawable() { + private Drawable defaultDrawable(Context c, Account account) { Resources resources = mTarget.getResources(); DefaultImageRequest request; int contactType = ContactPhotoManager.TYPE_DEFAULT; @@ -156,7 +166,8 @@ public class ImageViewDrawableSetter { request = new DefaultImageRequest(mContact.getDisplayName(), mContact.getLookupKey(), contactType, false /* isCircular */); } - return ContactPhotoManager.getDefaultAvatarDrawableForContact(resources, true, request); + return ContactPhotoManager.getDefaultAvatarDrawableForContact( + c, resources, true, request, account); } private BitmapDrawable decodedBitmapDrawable(byte[] compressed) { diff --git a/src/com/android/contacts/widget/MultiShrinkScroller.java b/src/com/android/contacts/widget/MultiShrinkScroller.java index 7c46a86c9..80d69d363 100644 --- a/src/com/android/contacts/widget/MultiShrinkScroller.java +++ b/src/com/android/contacts/widget/MultiShrinkScroller.java @@ -20,7 +20,9 @@ import android.graphics.ColorMatrixColorFilter; import android.graphics.drawable.GradientDrawable; import android.hardware.display.DisplayManager; import android.os.Trace; +import android.text.TextUtils; import android.util.AttributeSet; +import android.util.Log; import android.util.TypedValue; import android.view.Display; import android.view.Gravity; @@ -108,6 +110,9 @@ public class MultiShrinkScroller extends FrameLayout { private View mTransparentView; private MultiShrinkScrollerListener mListener; private TextView mLargeTextView; + private TextView mAttributionTextView; + private TextView mSpamCountTextView; + private View mContactInfo; private View mPhotoTouchInterceptOverlay; /** Contains desired size & vertical offset of the title, once the header is fully compressed */ private TextView mInvisiblePlaceholderTextView; @@ -130,6 +135,7 @@ public class MultiShrinkScroller extends FrameLayout { */ private boolean mIsOpenContactSquare; private int mMaximumHeaderTextSize; + private int mMaximumHeaderInfoSize; private int mCollapsedTitleBottomMargin; private int mCollapsedTitleStartMargin; private int mMinimumPortraitHeaderHeight; @@ -176,7 +182,7 @@ public class MultiShrinkScroller extends FrameLayout { }; private final PathInterpolator mTextSizePathInterpolator - = new PathInterpolator(0.16f, 0.4f, 0.2f, 1); + = new PathInterpolator(0.19f, 0.0f, 0.2f, 1); private final int[] mGradientColors = new int[] {0,0x88000000}; private GradientDrawable mTitleGradientDrawable = new GradientDrawable( @@ -288,7 +294,10 @@ public class MultiShrinkScroller extends FrameLayout { mToolbar = findViewById(R.id.toolbar_parent); mPhotoViewContainer = findViewById(R.id.toolbar_parent); mTransparentView = findViewById(R.id.transparent_view); + mContactInfo = findViewById(R.id.contact_info); mLargeTextView = (TextView) findViewById(R.id.large_title); + mAttributionTextView = (TextView) findViewById(R.id.contact_info_attribution); + mSpamCountTextView = (TextView) findViewById(R.id.contact_spam_count); mInvisiblePlaceholderTextView = (TextView) findViewById(R.id.placeholder_textview); mStartColumn = findViewById(R.id.empty_start_column); // Touching the empty space should close the card @@ -340,6 +349,7 @@ public class MultiShrinkScroller extends FrameLayout { : mPhotoViewContainer.getWidth(); setHeaderHeight(getMaximumScrollableHeaderHeight()); mMaximumHeaderTextSize = mLargeTextView.getHeight(); + mMaximumHeaderInfoSize = mContactInfo.getHeight(); if (mIsTwoPanel) { mMaximumHeaderHeight = getHeight(); mMinimumHeaderHeight = mMaximumHeaderHeight; @@ -353,8 +363,8 @@ public class MultiShrinkScroller extends FrameLayout { mPhotoViewContainer.setLayoutParams(photoLayoutParams); // Permanently set title width and margin. - final FrameLayout.LayoutParams largeTextLayoutParams - = (FrameLayout.LayoutParams) mLargeTextView.getLayoutParams(); + final LinearLayout.LayoutParams largeTextLayoutParams + = (LinearLayout.LayoutParams) mLargeTextView.getLayoutParams(); largeTextLayoutParams.width = photoLayoutParams.width - largeTextLayoutParams.leftMargin - largeTextLayoutParams.rightMargin; largeTextLayoutParams.gravity = Gravity.BOTTOM | Gravity.START; @@ -382,8 +392,8 @@ public class MultiShrinkScroller extends FrameLayout { = (FrameLayout.LayoutParams) mTitleGradientView.getLayoutParams(); final float TITLE_GRADIENT_SIZE_COEFFICIENT = 1.25f; final FrameLayout.LayoutParams largeTextLayoutParms - = (FrameLayout.LayoutParams) mLargeTextView.getLayoutParams(); - titleGradientLayoutParams.height = (int) ((mLargeTextView.getHeight() + = (FrameLayout.LayoutParams) mContactInfo.getLayoutParams(); + titleGradientLayoutParams.height = (int) ((mContactInfo.getHeight() + largeTextLayoutParms.bottomMargin) * TITLE_GRADIENT_SIZE_COEFFICIENT); mTitleGradientView.setLayoutParams(titleGradientLayoutParams); } @@ -393,6 +403,24 @@ public class MultiShrinkScroller extends FrameLayout { mPhotoTouchInterceptOverlay.setContentDescription(title); } + public void setAttributionText(String attribution) { + if (!TextUtils.isEmpty(attribution)) { + mAttributionTextView.setText(attribution); + mAttributionTextView.setVisibility(View.VISIBLE); + } else { + mAttributionTextView.setVisibility(View.GONE); + } + } + + public void setSpamCountText(String spamCount) { + if (!TextUtils.isEmpty(spamCount)) { + mSpamCountTextView.setText(spamCount); + mSpamCountTextView.setVisibility(View.VISIBLE); + } else { + mSpamCountTextView.setVisibility(View.GONE); + } + } + @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (mVelocityTracker == null) { @@ -997,7 +1025,7 @@ public class MultiShrinkScroller extends FrameLayout { } else { mLargeTextView.setPivotX(0); } - mLargeTextView.setPivotY(mLargeTextView.getHeight() / 2); + mLargeTextView.setPivotY(mContactInfo.getHeight() / 2); final int toolbarHeight = mToolbar.getLayoutParams().height; mPhotoTouchInterceptOverlay.setClickable(toolbarHeight != mMaximumHeaderHeight); @@ -1009,7 +1037,7 @@ public class MultiShrinkScroller extends FrameLayout { setInterpolatedTitleMargins(1); return; } - + mMaximumHeaderInfoSize = mContactInfo.getHeight(); final float ratio = (toolbarHeight - mMinimumHeaderHeight) / (float)(mMaximumHeaderHeight - mMinimumHeaderHeight); final float minimumSize = mInvisiblePlaceholderTextView.getHeight(); @@ -1019,8 +1047,8 @@ public class MultiShrinkScroller extends FrameLayout { // Clamp to reasonable/finite values before passing into framework. The values // can be wacky before the first pre-render. - bezierOutput = (float) Math.min(bezierOutput, 1.0f); - scale = (float) Math.min(scale, 1.0f); + bezierOutput = Math.min(bezierOutput, 1.0f); + scale = Math.min(scale, 1.0f); mLargeTextView.setScaleX(scale); mLargeTextView.setScaleY(scale); @@ -1051,7 +1079,7 @@ public class MultiShrinkScroller extends FrameLayout { */ private void setInterpolatedTitleMargins(float x) { final FrameLayout.LayoutParams titleLayoutParams - = (FrameLayout.LayoutParams) mLargeTextView.getLayoutParams(); + = (FrameLayout.LayoutParams) mContactInfo.getLayoutParams(); final LinearLayout.LayoutParams toolbarLayoutParams = (LinearLayout.LayoutParams) mToolbar.getLayoutParams(); @@ -1067,11 +1095,12 @@ public class MultiShrinkScroller extends FrameLayout { // calling mLargeTextView.getHeight() use the mMaximumHeaderTextSize for this calculation. // The getHeight() value acts unexpectedly when mLargeTextView is partially clipped by // its parent. - titleLayoutParams.topMargin = getTransparentViewHeight() - + toolbarLayoutParams.height - pretendBottomMargin - - mMaximumHeaderTextSize; + final int minHeaderInfoTopMargin = getTransparentViewHeight() + + toolbarLayoutParams.height - pretendBottomMargin - mMaximumHeaderInfoSize; + final int topMargin = Math.max(minHeaderInfoTopMargin, 0); + titleLayoutParams.topMargin = topMargin; titleLayoutParams.bottomMargin = 0; - mLargeTextView.setLayoutParams(titleLayoutParams); + mContactInfo.setLayoutParams(titleLayoutParams); } private void updatePhotoTintAndDropShadow() { @@ -1148,7 +1177,11 @@ public class MultiShrinkScroller extends FrameLayout { mPhotoView.setTint(mHeaderTintColor); mTitleGradientDrawable.setAlpha(gradientAlpha); mActionBarGradientDrawable.setAlpha(gradientAlpha); - + final int attributionAlpha = calculateAttributionTextAlpha(toolbarHeight); + mAttributionTextView + .setTextColor(mAttributionTextView.getTextColors().withAlpha(attributionAlpha)); + mSpamCountTextView + .setTextColor(mSpamCountTextView.getTextColors().withAlpha(attributionAlpha)); Trace.endSection(); } @@ -1167,6 +1200,16 @@ public class MultiShrinkScroller extends FrameLayout { return (intermediateHeight - height) / interpolatingHeightRange; } + private int calculateAttributionTextAlpha(int height) { + final float ratio = calculateHeightRatioToBlendingStartHeight(height); + final float alpha = 1.0f - (float) Math.min(Math.pow(ratio, 1.5f) * 2f, 1f); + final float tint = (float) Math.min(Math.pow(ratio, 1.5f) * 3f, 1f); + mColorMatrix.setSaturation(alpha); + mColorMatrix.postConcat(alphaMatrix(alpha, Color.WHITE)); + mColorMatrix.postConcat(multiplyBlendMatrix(mHeaderTintColor, tint)); + return (int) (255 * alpha); + } + /** * Simulates alpha blending an image with {@param color}. */ |