Merge "Fix NPE in setGroupMetaData" into klp-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0d25bb2..7cd8227 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -330,6 +330,11 @@
was found that could perform the selected action. [CHAR LIMIT=NONE] -->
<string name="quickcontact_missing_app">No app was found to handle this action.</string>
+ <!-- Shown as a toast when the user attempts an action (add contact, edit
+ contact, etc) and no application was found that could perform that
+ action. [CHAR LIMIT=NONE] -->
+ <string name="missing_app">No app was found to handle this action.</string>
+
<!-- The menu item to share the currently viewed contact [CHAR LIMIT=30] -->
<string name="menu_share">Share</string>
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 7a8f9f3..ff76844 100644
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -410,6 +410,12 @@
Log.e(TAG, "Problem persisting user edits", e);
break;
+ } catch (IllegalArgumentException e) {
+ // This is thrown by applyBatch on malformed requests
+ Log.e(TAG, "Problem persisting user edits", e);
+ showToast(R.string.contactSavedErrorToast);
+ break;
+
} catch (OperationApplicationException e) {
// Version consistency failed, re-parent change and try again
Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString());
diff --git a/src/com/android/contacts/SplitAggregateView.java b/src/com/android/contacts/SplitAggregateView.java
index 6e38549..2281ec6 100644
--- a/src/com/android/contacts/SplitAggregateView.java
+++ b/src/com/android/contacts/SplitAggregateView.java
@@ -157,6 +157,9 @@
Uri dataUri = Uri.withAppendedPath(mAggregateUri, Data.CONTENT_DIRECTORY);
Cursor cursor = getContext().getContentResolver().query(dataUri,
SplitQuery.COLUMNS, null, null, null);
+ if (cursor == null) {
+ return Collections.emptyList();
+ }
try {
while (cursor.moveToNext()) {
long rawContactId = cursor.getLong(SplitQuery.RAW_CONTACT_ID);
diff --git a/src/com/android/contacts/activities/AttachPhotoActivity.java b/src/com/android/contacts/activities/AttachPhotoActivity.java
index 2d9e9f5..6a55c30 100644
--- a/src/com/android/contacts/activities/AttachPhotoActivity.java
+++ b/src/com/android/contacts/activities/AttachPhotoActivity.java
@@ -69,6 +69,8 @@
// Height and width (in pixels) to request for the photo - queried from the provider.
private static int mPhotoDim;
+ // Default photo dimension to use if unable to query the provider.
+ private static final int mDefaultPhotoDim = 720;
private Uri mContactUri;
@@ -91,14 +93,20 @@
mContentResolver = getContentResolver();
- // Load the photo dimension to request.
- Cursor c = mContentResolver.query(DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
- new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null);
- try {
- c.moveToFirst();
- mPhotoDim = c.getInt(0);
- } finally {
- c.close();
+ // Load the photo dimension to request. mPhotoDim is a static class
+ // member varible so only need to load this if this is the first time
+ // through.
+ if (mPhotoDim == 0) {
+ Cursor c = mContentResolver.query(DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI,
+ new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null);
+ if (c != null) {
+ try {
+ c.moveToFirst();
+ mPhotoDim = c.getInt(0);
+ } finally {
+ c.close();
+ }
+ }
}
}
@@ -128,28 +136,20 @@
final Intent myIntent = getIntent();
final Uri inputUri = myIntent.getData();
- final int perm = checkUriPermission(inputUri, android.os.Process.myPid(),
- android.os.Process.myUid(), Intent.FLAG_GRANT_READ_URI_PERMISSION |
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
final Uri toCrop;
-
- if (perm == PackageManager.PERMISSION_DENIED) {
- // Work around to save a read-only URI into a temporary file provider URI so that
- // we can add the FLAG_GRANT_WRITE_URI_PERMISSION flag to the eventual
- // crop intent b/10837468
- ContactPhotoUtils.savePhotoFromUriToUri(this, inputUri, mTempPhotoUri, false);
- toCrop = mTempPhotoUri;
- } else {
- toCrop = inputUri;
- }
+ // Save the URI into a temporary file provider URI so that
+ // we can add the FLAG_GRANT_WRITE_URI_PERMISSION flag to the eventual
+ // crop intent for read-only URI's.
+ // TODO: With b/10837468 fixed should be able to avoid this copy.
+ ContactPhotoUtils.savePhotoFromUriToUri(this, inputUri, mTempPhotoUri, false);
+ toCrop = mTempPhotoUri;
final Intent intent = new Intent("com.android.camera.action.CROP", toCrop);
if (myIntent.getStringExtra("mimeType") != null) {
intent.setDataAndType(toCrop, myIntent.getStringExtra("mimeType"));
}
ContactPhotoUtils.addPhotoPickerExtras(intent, mCroppedPhotoUri);
- ContactPhotoUtils.addCropExtras(intent, mPhotoDim);
+ ContactPhotoUtils.addCropExtras(intent, mPhotoDim != 0 ? mPhotoDim : mDefaultPhotoDim);
startActivityForResult(intent, REQUEST_CROP_PHOTO);
@@ -223,6 +223,10 @@
Log.w(TAG, "Could not find bitmap");
return;
}
+ if (bitmap == null) {
+ Log.w(TAG, "Could not decode bitmap");
+ return;
+ }
final Bitmap scaled = Bitmap.createScaledBitmap(bitmap, size, size, false);
final byte[] compressed = ContactPhotoUtils.compressBitmap(scaled);
diff --git a/src/com/android/contacts/activities/ConfirmAddDetailActivity.java b/src/com/android/contacts/activities/ConfirmAddDetailActivity.java
index d60cc73..3f9116f 100644
--- a/src/com/android/contacts/activities/ConfirmAddDetailActivity.java
+++ b/src/com/android/contacts/activities/ConfirmAddDetailActivity.java
@@ -683,12 +683,15 @@
// Skip kind that are not editable
if (!kind.editable) continue;
if (mMimetype.equals(kind.mimeType)) {
- for (ValuesDelta valuesDelta : mRawContactDelta.getMimeEntries(mMimetype)) {
- // Skip entries that aren't visible
- if (!valuesDelta.isVisible()) continue;
- if (valuesDelta.isInsert()) {
- inflateEditorView(kind, valuesDelta, mRawContactDelta);
- return;
+ final ArrayList<ValuesDelta> deltas = mRawContactDelta.getMimeEntries(mMimetype);
+ if (deltas != null) {
+ for (ValuesDelta valuesDelta : deltas) {
+ // Skip entries that aren't visible
+ if (!valuesDelta.isVisible()) continue;
+ if (valuesDelta.isInsert()) {
+ inflateEditorView(kind, valuesDelta, mRawContactDelta);
+ return;
+ }
}
}
}
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index 03821a7..a4e0470 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -105,8 +105,6 @@
| ActionBar.DISPLAY_SHOW_HOME);
actionBar.setTitle("");
}
-
- Log.i(TAG, getIntent().getData().toString());
}
@Override
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
index 1eb610a..9bb7395 100644
--- a/src/com/android/contacts/activities/ContactSelectionActivity.java
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -20,6 +20,7 @@
import android.app.ActionBar.LayoutParams;
import android.app.Activity;
import android.app.Fragment;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -39,6 +40,7 @@
import android.widget.SearchView;
import android.widget.SearchView.OnCloseListener;
import android.widget.SearchView.OnQueryTextListener;
+import android.widget.Toast;
import com.android.contacts.ContactsActivity;
import com.android.contacts.R;
@@ -317,6 +319,7 @@
break;
}
+ case ContactsRequest.ACTION_DEFAULT:
case ContactsRequest.ACTION_PICK_CONTACT: {
ContactPickerFragment fragment = new ContactPickerFragment();
fragment.setIncludeProfile(mRequest.shouldIncludeProfile());
@@ -538,7 +541,13 @@
if (extras != null) {
intent.putExtras(extras);
}
- startActivity(intent);
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "startActivity() failed: " + e);
+ Toast.makeText(ContactSelectionActivity.this, R.string.missing_app,
+ Toast.LENGTH_SHORT).show();
+ }
finish();
}
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index abaa8eb..629e36b 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -2066,6 +2066,7 @@
GroupMembership.CONTENT_ITEM_TYPE);
final ValuesDelta entry = RawContactModifier.insertChild(rawContactEntityDelta,
groupMembershipKind);
+ if (entry == null) return;
entry.setGroupRowId(defaultGroupId);
// and fire off the intent. we don't need a callback, as the database listener
diff --git a/src/com/android/contacts/editor/AggregationSuggestionEngine.java b/src/com/android/contacts/editor/AggregationSuggestionEngine.java
index 2f77858..f121605 100644
--- a/src/com/android/contacts/editor/AggregationSuggestionEngine.java
+++ b/src/com/android/contacts/editor/AggregationSuggestionEngine.java
@@ -300,6 +300,9 @@
private void loadAggregationSuggestions(Uri uri) {
ContentResolver contentResolver = mContext.getContentResolver();
Cursor cursor = contentResolver.query(uri, new String[]{Contacts._ID}, null, null, null);
+ if (cursor == null) {
+ return;
+ }
try {
// If a new request is pending, chuck the result of the previous request
if (getHandler().hasMessages(MESSAGE_NAME_CHANGE)) {
@@ -324,7 +327,9 @@
Cursor dataCursor = contentResolver.query(Data.CONTENT_URI,
DataQuery.COLUMNS, sb.toString(), null, Data.CONTACT_ID);
- mMainHandler.sendMessage(mMainHandler.obtainMessage(MESSAGE_DATA_CURSOR, dataCursor));
+ if (dataCursor != null) {
+ mMainHandler.sendMessage(mMainHandler.obtainMessage(MESSAGE_DATA_CURSOR, dataCursor));
+ }
} finally {
cursor.close();
}
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 9243625..54c9d3b 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -1392,14 +1392,14 @@
String dataSet2 = two.getValues().getAsString(RawContacts.DATA_SET);
final AccountType type2 = accountTypes.getAccountType(accountType2, dataSet2);
- // Check read-only
+ // Check read-only. Sort read/write before read-only.
if (!type1.areContactsWritable() && type2.areContactsWritable()) {
return 1;
} else if (type1.areContactsWritable() && !type2.areContactsWritable()) {
return -1;
}
- // Check account type
+ // Check account type. Sort Google before non-Google.
boolean skipAccountTypeCheck = false;
boolean isGoogleAccount1 = type1 instanceof GoogleAccountType;
boolean isGoogleAccount2 = type2 instanceof GoogleAccountType;
@@ -1413,21 +1413,32 @@
int value;
if (!skipAccountTypeCheck) {
- if (type1.accountType == null) {
+ // Sort accounts with type before accounts without types.
+ if (type1.accountType != null && type2.accountType == null) {
+ return -1;
+ } else if (type1.accountType == null && type2.accountType != null) {
return 1;
}
- value = type1.accountType.compareTo(type2.accountType);
- if (value != 0) {
- return value;
- } else {
- // Fall back to data set.
- if (type1.dataSet != null) {
- value = type1.dataSet.compareTo(type2.dataSet);
- if (value != 0) {
- return value;
- }
- } else if (type2.dataSet != null) {
- return 1;
+
+ if (type1.accountType != null && type2.accountType != null) {
+ value = type1.accountType.compareTo(type2.accountType);
+ if (value != 0) {
+ return value;
+ }
+ }
+
+ // Fall back to data set. Sort accounts with data sets before
+ // those without.
+ if (type1.dataSet != null && type2.dataSet == null) {
+ return -1;
+ } else if (type1.dataSet == null && type2.dataSet != null) {
+ return 1;
+ }
+
+ if (type1.dataSet != null && type2.dataSet != null) {
+ value = type1.dataSet.compareTo(type2.dataSet);
+ if (value != 0) {
+ return value;
}
}
}
diff --git a/src/com/android/contacts/editor/GroupMembershipView.java b/src/com/android/contacts/editor/GroupMembershipView.java
index fec0aba..bcea53d 100644
--- a/src/com/android/contacts/editor/GroupMembershipView.java
+++ b/src/com/android/contacts/editor/GroupMembershipView.java
@@ -370,7 +370,9 @@
long groupId = item.getGroupId();
if (item.isChecked() && !hasMembership(groupId)) {
ValuesDelta entry = RawContactModifier.insertChild(mState, mKind);
- entry.setGroupRowId(groupId);
+ if (entry != null) {
+ entry.setGroupRowId(groupId);
+ }
}
}
diff --git a/src/com/android/contacts/editor/RawContactEditorView.java b/src/com/android/contacts/editor/RawContactEditorView.java
index c9d3c33..7fcdb7d 100644
--- a/src/com/android/contacts/editor/RawContactEditorView.java
+++ b/src/com/android/contacts/editor/RawContactEditorView.java
@@ -391,7 +391,9 @@
long defaultGroupId = getDefaultGroupId();
if (defaultGroupId != -1) {
ValuesDelta entry = RawContactModifier.insertChild(mState, mGroupMembershipKind);
- entry.setGroupRowId(defaultGroupId);
+ if (entry != null) {
+ entry.setGroupRowId(defaultGroupId);
+ }
}
}
}
diff --git a/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
index d1fa282..36e96a2 100644
--- a/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
+++ b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
@@ -199,39 +199,43 @@
// Phones
ArrayList<ValuesDelta> phones = state.getMimeEntries(Phone.CONTENT_ITEM_TYPE);
if (phones != null) {
- for (int i = 0; i < phones.size(); i++) {
- ValuesDelta phone = phones.get(i);
- final String phoneNumber = PhoneNumberUtils.formatNumber(
- phone.getPhoneNumber(),
- phone.getPhoneNormalizedNumber(),
+ boolean isFirstPhoneBound = true;
+ for (ValuesDelta phone : phones) {
+ final String phoneNumber = phone.getPhoneNumber();
+ if (TextUtils.isEmpty(phoneNumber)) {
+ continue;
+ }
+ final String formattedNumber = PhoneNumberUtils.formatNumber(
+ phoneNumber, phone.getPhoneNormalizedNumber(),
GeoUtil.getCurrentCountryIso(getContext()));
- final CharSequence phoneType;
+ CharSequence phoneType = null;
if (phone.phoneHasType()) {
phoneType = Phone.getTypeLabel(
res, phone.getPhoneType(), phone.getPhoneLabel());
- } else {
- phoneType = null;
}
- bindData(mContext.getText(R.string.phoneLabelsGroup), phoneNumber, phoneType,
- i == 0, true);
+ bindData(mContext.getText(R.string.phoneLabelsGroup), formattedNumber,
+ phoneType, isFirstPhoneBound, true);
+ isFirstPhoneBound = false;
}
}
// Emails
ArrayList<ValuesDelta> emails = state.getMimeEntries(Email.CONTENT_ITEM_TYPE);
if (emails != null) {
- for (int i = 0; i < emails.size(); i++) {
- ValuesDelta email = emails.get(i);
+ boolean isFirstEmailBound = true;
+ for (ValuesDelta email : emails) {
final String emailAddress = email.getEmailData();
- final CharSequence emailType;
+ if (TextUtils.isEmpty(emailAddress)) {
+ continue;
+ }
+ CharSequence emailType = null;
if (email.emailHasType()) {
emailType = Email.getTypeLabel(
res, email.getEmailType(), email.getEmailLabel());
- } else {
- emailType = null;
}
bindData(mContext.getText(R.string.emailLabelsGroup), emailAddress, emailType,
- i == 0);
+ isFirstEmailBound);
+ isFirstEmailBound = false;
}
}
diff --git a/src/com/android/contacts/group/GroupBrowseListAdapter.java b/src/com/android/contacts/group/GroupBrowseListAdapter.java
index f48e0a7..48751e7 100644
--- a/src/com/android/contacts/group/GroupBrowseListAdapter.java
+++ b/src/com/android/contacts/group/GroupBrowseListAdapter.java
@@ -197,7 +197,7 @@
private void bindHeaderView(GroupListItem entry, GroupListItemViewCache viewCache) {
AccountType accountType = mAccountTypeManager.getAccountType(
entry.getAccountType(), entry.getDataSet());
- viewCache.accountType.setText(accountType.getDisplayLabel(mContext).toString());
+ viewCache.accountType.setText(accountType.getDisplayLabel(mContext));
viewCache.accountName.setText(entry.getAccountName());
}
diff --git a/src/com/android/contacts/group/GroupDetailFragment.java b/src/com/android/contacts/group/GroupDetailFragment.java
index 834e2c3..f2d70aa 100644
--- a/src/com/android/contacts/group/GroupDetailFragment.java
+++ b/src/com/android/contacts/group/GroupDetailFragment.java
@@ -319,12 +319,18 @@
if (size == -1) {
groupSizeString = null;
} else {
- String groupSizeTemplateString = getResources().getQuantityString(
- R.plurals.num_contacts_in_group, size);
AccountType accountType = mAccountTypeManager.getAccountType(mAccountTypeString,
mDataSet);
- groupSizeString = String.format(groupSizeTemplateString, size,
- accountType.getDisplayLabel(mContext));
+ final CharSequence dispLabel = accountType.getDisplayLabel(mContext);
+ if (!TextUtils.isEmpty(dispLabel)) {
+ String groupSizeTemplateString = getResources().getQuantityString(
+ R.plurals.num_contacts_in_group, size);
+ groupSizeString = String.format(groupSizeTemplateString, size, dispLabel);
+ } else {
+ String groupSizeTemplateString = getResources().getQuantityString(
+ R.plurals.group_list_num_contacts_in_group, size);
+ groupSizeString = String.format(groupSizeTemplateString, size);
+ }
}
if (mGroupSize != null) {
diff --git a/src/com/android/contacts/group/SuggestedMemberListAdapter.java b/src/com/android/contacts/group/SuggestedMemberListAdapter.java
index 067c052..6d60a3e 100644
--- a/src/com/android/contacts/group/SuggestedMemberListAdapter.java
+++ b/src/com/android/contacts/group/SuggestedMemberListAdapter.java
@@ -263,31 +263,33 @@
"=?) AND " + rawContactIdSelectionBuilder.toString(),
selectionArgs.toArray(new String[0]), null);
- try {
- memberDataCursor.moveToPosition(-1);
- while (memberDataCursor.moveToNext()) {
- long rawContactId = memberDataCursor.getLong(RAW_CONTACT_ID_COLUMN_INDEX);
- SuggestedMember member = suggestionsMap.get(rawContactId);
- if (member == null) {
- continue;
- }
- String mimetype = memberDataCursor.getString(MIMETYPE_COLUMN_INDEX);
- if (Photo.CONTENT_ITEM_TYPE.equals(mimetype)) {
- // Set photo
- byte[] bitmapArray = memberDataCursor.getBlob(PHOTO_COLUMN_INDEX);
- member.setPhotoByteArray(bitmapArray);
- } else if (Email.CONTENT_ITEM_TYPE.equals(mimetype) ||
- Phone.CONTENT_ITEM_TYPE.equals(mimetype)) {
- // Set at most 1 extra piece of contact info that can be a phone number or
- // email
- if (!member.hasExtraInfo()) {
- String info = memberDataCursor.getString(DATA_COLUMN_INDEX);
- member.setExtraInfo(info);
+ if (memberDataCursor != null) {
+ try {
+ memberDataCursor.moveToPosition(-1);
+ while (memberDataCursor.moveToNext()) {
+ long rawContactId = memberDataCursor.getLong(RAW_CONTACT_ID_COLUMN_INDEX);
+ SuggestedMember member = suggestionsMap.get(rawContactId);
+ if (member == null) {
+ continue;
+ }
+ String mimetype = memberDataCursor.getString(MIMETYPE_COLUMN_INDEX);
+ if (Photo.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ // Set photo
+ byte[] bitmapArray = memberDataCursor.getBlob(PHOTO_COLUMN_INDEX);
+ member.setPhotoByteArray(bitmapArray);
+ } else if (Email.CONTENT_ITEM_TYPE.equals(mimetype) ||
+ Phone.CONTENT_ITEM_TYPE.equals(mimetype)) {
+ // Set at most 1 extra piece of contact info that can be a phone number or
+ // email
+ if (!member.hasExtraInfo()) {
+ String info = memberDataCursor.getString(DATA_COLUMN_INDEX);
+ member.setExtraInfo(info);
+ }
}
}
+ } finally {
+ memberDataCursor.close();
}
- } finally {
- memberDataCursor.close();
}
results.values = suggestionsList;
return results;