Merge "Fix delete label a11y action not spoken." into ub-contactsdialer-h-dev
diff --git a/res/layout/fragment_sim_import.xml b/res/layout/fragment_sim_import.xml
index 6688898..168bf22 100644
--- a/res/layout/fragment_sim_import.xml
+++ b/res/layout/fragment_sim_import.xml
@@ -54,6 +54,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:colorBackground"
+ android:focusable="true"
android:minHeight="48dp"
android:orientation="horizontal"
android:paddingEnd="@dimen/activity_horizontal_margin"
diff --git a/res/layout/menu_item_action_view.xml b/res/layout/menu_item_action_view.xml
new file mode 100644
index 0000000..62eb758
--- /dev/null
+++ b/res/layout/menu_item_action_view.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+ -->
+
+<!-- A dummy action view to attach extra hidden content description to menuItem for Talkback. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+ <View
+ android:layout_width="1dp"
+ android:layout_height= "1dp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a544c08..1615cf7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -158,7 +158,7 @@
<!-- Menu item that links an aggregate with another aggregate -->
<string name="menu_joinAggregate">Link</string>
- <!-- Menu item that opens a dialog that shows all linked contacts that make up this one. [CHAR LIMIT=30] -->
+ <!-- Menu item that opens a dialog that shows all linked contacts that make up this one. [CHAR LIMIT=35] -->
<string name="menu_linkedContacts">View linked contacts</string>
<!-- Menu item (in the action bar) to indicate that changes should be saved [CHAR LIMIT=20] -->
@@ -234,7 +234,8 @@
<!-- Positive button text of the warning dialog contents after users select to delete a ReadOnly contact. [CHAR LIMIT=30]-->
<string name="readOnlyContactWarning_positive_button">Hide Contact</string>
- <!-- Warning dialog contents after users selects to delete a contact with ReadOnly and Writable sources. [CHAR LIMIT=NONE]-->
+ <!-- Warning dialog contents after users selects to delete a contact with ReadOnly and Writable data sources.
+ Tells the user that data from the read-only accounts will be hidden and not deleted. [CHAR LIMIT=NONE]-->
<string name="readOnlyContactDeleteConfirmation">Read-only accounts in this contact will be hidden, not deleted.</string>
<!-- Confirmation dialog. Shown after user selects to delete one writable contact [CHAR LIMIT=NONE] -->
@@ -1883,8 +1884,8 @@
when the window is first opened [CHAR LIMIT=40] -->
<string name="sim_import_title">Import from SIM</string>
- <!-- Content description of the cancel navigation icon shown in SIM import screen toolbar -->
- <string name="sim_import_cancel_content_description">Cancel import</string>
+ <!-- Content description of the cancel navigation icon shown in SIM import screen toolbar [CHAR LIMIT=NONE]-->
+ <string name="sim_import_cancel_content_description">Cancel</string>
<!-- Alert for letting user know that their device auto-sync setting is turned off,
in case they are wondering why they are not seeing any contact. [CHAR LIMIT=150] -->
diff --git a/src/com/android/contacts/ContactsDrawerActivity.java b/src/com/android/contacts/ContactsDrawerActivity.java
index b786940..2e024d4 100644
--- a/src/com/android/contacts/ContactsDrawerActivity.java
+++ b/src/com/android/contacts/ContactsDrawerActivity.java
@@ -437,6 +437,8 @@
return true;
}
});
+
+ updateMenuContentDescription(menuItem, getString(R.string.group_name_dialog_hint));
}
}
@@ -528,7 +530,6 @@
return;
}
-
for (int i = 0; i < accountFilterItems.size(); i++) {
final ContactListFilter filter = accountFilterItems.get(i);
final AccountDisplayInfo displayableAccount =
@@ -556,14 +557,8 @@
// Get rid of the default menu item overlay and show original account icons.
menuItem.getIcon().setColorFilter(Color.TRANSPARENT, PorterDuff.Mode.SRC_ATOP);
}
- // Create a dummy action view to attach extra hidden content description to the menuItem
- // for Talkback. We want Talkback to read out the account type but not have it be part
- // of the menuItem title.
- LinearLayout view = (LinearLayout) LayoutInflater.from(this)
- .inflate(R.layout.account_type_info, null);
- view.setContentDescription(displayableAccount.getTypeLabel());
- view.setVisibility(View.VISIBLE);
- menuItem.setActionView(view);
+
+ updateMenuContentDescription(menuItem, displayableAccount.getTypeLabel());
}
if (isAccountView()) {
@@ -571,6 +566,17 @@
}
}
+ private void updateMenuContentDescription(MenuItem menuItem, CharSequence contentDescription) {
+ // Create a dummy action view to attach extra hidden content description to the menuItem
+ // for Talkback. We want Talkback to read out the account type but not have it be part
+ // of the menuItem title.
+ final LinearLayout view = (LinearLayout) LayoutInflater.from(this)
+ .inflate(R.layout.menu_item_action_view, null);
+ view.setContentDescription(contentDescription);
+ view.setVisibility(View.VISIBLE);
+ menuItem.setActionView(view);
+ }
+
public void updateFilterMenu(ContactListFilter filter) {
clearCheckedMenus();
if (filter != null && filter.isContactsFilterType()) {
diff --git a/src/com/android/contacts/activities/ContactEditorSpringBoardActivity.java b/src/com/android/contacts/activities/ContactEditorSpringBoardActivity.java
index 17c3392..8ce6ecb 100644
--- a/src/com/android/contacts/activities/ContactEditorSpringBoardActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorSpringBoardActivity.java
@@ -200,7 +200,6 @@
intent.setClass(this, ContactEditorActivity.class);
}
startEditorAndForwardExtras(intent);
- finish();
}
/**
@@ -233,6 +232,7 @@
intent.putExtras(extras);
}
ImplicitIntentsUtil.startActivityInApp(this, intent);
+ finish();
}
private void toastErrorAndFinish() {
diff --git a/src/com/android/contacts/common/database/SimContactDao.java b/src/com/android/contacts/common/database/SimContactDao.java
index 3175ce4..b5b6626 100644
--- a/src/com/android/contacts/common/database/SimContactDao.java
+++ b/src/com/android/contacts/common/database/SimContactDao.java
@@ -29,6 +29,8 @@
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.support.annotation.VisibleForTesting;
import android.support.v4.util.ArrayMap;
@@ -36,6 +38,7 @@
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.SparseArray;
import com.android.contacts.R;
@@ -51,6 +54,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -183,6 +187,10 @@
return null;
}
+ /**
+ * Finds SIM contacts that exist in CP2 and associates the account of the CP2 contact with
+ * the SIM contact
+ */
public Map<AccountWithDataSet, Set<SimContact>> findAccountsOfExistingSimContacts(
List<SimContact> contacts) {
final Map<AccountWithDataSet, Set<SimContact>> result = new ArrayMap<>();
@@ -238,7 +246,6 @@
}
}
-
private ContentProviderResult[] importBatch(List<SimContact> contacts,
AccountWithDataSet targetAccount)
throws RemoteException, OperationApplicationException {
@@ -305,27 +312,49 @@
final StringBuilder selectionBuilder = new StringBuilder();
int phoneCount = 0;
+ int nameCount = 0;
for (SimContact contact : contacts) {
if (contact.hasPhone()) {
phoneCount++;
+ } else if (contact.hasName()) {
+ nameCount++;
}
}
List<String> selectionArgs = new ArrayList<>(phoneCount + 1);
- selectionBuilder.append(ContactsContract.Data.MIMETYPE).append("=? AND ");
+ selectionBuilder.append('(');
+ selectionBuilder.append(Data.MIMETYPE).append("=? AND ");
selectionArgs.add(Phone.CONTENT_ITEM_TYPE);
selectionBuilder.append(Phone.NUMBER).append(" IN (")
.append(Joiner.on(',').join(Collections.nCopies(phoneCount, '?')))
- .append(")");
+ .append(')');
for (SimContact contact : contacts) {
if (contact.hasPhone()) {
selectionArgs.add(contact.getPhone());
}
}
+ selectionBuilder.append(')');
- return mResolver.query(ContactsContract.Data.CONTENT_URI.buildUpon()
- .appendQueryParameter(ContactsContract.Data.VISIBLE_CONTACTS_ONLY, "true")
+ if (nameCount > 0) {
+ selectionBuilder.append(" OR (");
+
+ selectionBuilder.append(Data.MIMETYPE).append("=? AND ");
+ selectionArgs.add(StructuredName.CONTENT_ITEM_TYPE);
+
+ selectionBuilder.append(Data.DISPLAY_NAME).append(" IN (")
+ .append(Joiner.on(',').join(Collections.nCopies(nameCount, '?')))
+ .append(')');
+ for (SimContact contact : contacts) {
+ if (!contact.hasPhone() && contact.hasName()) {
+ selectionArgs.add(contact.getName());
+ }
+ }
+ selectionBuilder.append(')');
+ }
+
+ return mResolver.query(Data.CONTENT_URI.buildUpon()
+ .appendQueryParameter(Data.VISIBLE_CONTACTS_ONLY, "true")
.build(),
DataQuery.PROJECTION,
selectionBuilder.toString(),
@@ -439,24 +468,29 @@
private static final class DataQuery {
public static final String[] PROJECTION = new String[] {
- ContactsContract.Data.RAW_CONTACT_ID, Phone.NUMBER, Phone.DISPLAY_NAME
+ Data.RAW_CONTACT_ID, Phone.NUMBER, Data.DISPLAY_NAME, Data.MIMETYPE
};
public static final int RAW_CONTACT_ID = 0;
public static final int PHONE_NUMBER = 1;
public static final int DISPLAY_NAME = 2;
+ public static final int MIMETYPE = 3;
public static long getRawContactId(Cursor cursor) {
return cursor.getLong(RAW_CONTACT_ID);
}
public static String getPhoneNumber(Cursor cursor) {
- return cursor.getString(PHONE_NUMBER);
+ return isPhoneNumber(cursor) ? cursor.getString(PHONE_NUMBER) : null;
}
public static String getDisplayName(Cursor cursor) {
return cursor.getString(DISPLAY_NAME);
}
+
+ public static boolean isPhoneNumber(Cursor cursor) {
+ return Phone.CONTENT_ITEM_TYPE.equals(cursor.getString(MIMETYPE));
+ }
}
private static final class AccountQuery {
diff --git a/src/com/android/contacts/common/model/ContactLoader.java b/src/com/android/contacts/common/model/ContactLoader.java
index 0c569ba..2cafc1f 100644
--- a/src/com/android/contacts/common/model/ContactLoader.java
+++ b/src/com/android/contacts/common/model/ContactLoader.java
@@ -299,8 +299,9 @@
public static final int EXPORT_SUPPORT = 5;
}
- public void setLookupUri(Uri lookupUri) {
+ public void setNewLookup(Uri lookupUri) {
mLookupUri = lookupUri;
+ mContact = null;
}
@Override
diff --git a/src/com/android/contacts/common/model/SimContact.java b/src/com/android/contacts/common/model/SimContact.java
index 25af5f8..2d26029 100644
--- a/src/com/android/contacts/common/model/SimContact.java
+++ b/src/com/android/contacts/common/model/SimContact.java
@@ -48,7 +48,7 @@
public SimContact(long id, String name, String phone, String[] emails) {
mId = id;
mName = name;
- mPhone = phone;
+ mPhone = phone == null ? "" : phone.trim();
mEmails = emails;
}
@@ -70,8 +70,8 @@
public void appendCreateContactOperations(List<ContentProviderOperation> ops,
AccountWithDataSet targetAccount) {
- // nothing to save.
- if (mName == null && mPhone == null && mEmails == null) return;
+ // There is nothing to save so skip it.
+ if (!hasName() && !hasPhone() && !hasEmails()) return;
final int rawContactOpIndex = ops.size();
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
@@ -84,7 +84,7 @@
ops.add(createInsertOp(rawContactOpIndex, StructuredName.CONTENT_ITEM_TYPE,
StructuredName.DISPLAY_NAME, mName));
}
- if (mPhone != null) {
+ if (!mPhone.isEmpty()) {
ops.add(createInsertOp(rawContactOpIndex, Phone.CONTENT_ITEM_TYPE,
Phone.NUMBER, mPhone));
}
@@ -116,7 +116,7 @@
}
public boolean hasPhone() {
- return mPhone != null;
+ return !mPhone.isEmpty();
}
public boolean hasEmails() {
@@ -230,8 +230,7 @@
@Override
public int compare(SimContact lhs, SimContact rhs) {
return ComparisonChain.start()
- .compare(lhs.mPhone, rhs.mPhone,
- Ordering.<String>natural().nullsFirst())
+ .compare(lhs.mPhone, rhs.mPhone)
.compare(lhs.mName, rhs.mName, Ordering.<String>natural().nullsFirst())
.result();
}
diff --git a/src/com/android/contacts/common/model/account/ExternalAccountType.java b/src/com/android/contacts/common/model/account/ExternalAccountType.java
index 6803a6b..5c5a26a 100644
--- a/src/com/android/contacts/common/model/account/ExternalAccountType.java
+++ b/src/com/android/contacts/common/model/account/ExternalAccountType.java
@@ -418,7 +418,7 @@
return -1; // Empty text is okay.
}
if (resourceName.charAt(0) != '@') {
- if (Log.isLoggable(TAG, Log.WARN)) {
+ if (Log.isLoggable(TAG, Log.WARN) && !isFromTestApp(packageName)) {
Log.w(TAG, xmlAttributeName + " must be a resource name beginnig with '@'");
}
return -1;
@@ -428,18 +428,23 @@
try {
res = context.getPackageManager().getResourcesForApplication(packageName);
} catch (NameNotFoundException e) {
- if (Log.isLoggable(TAG, Log.WARN)) {
+ if (Log.isLoggable(TAG, Log.WARN) && !isFromTestApp(packageName)) {
Log.w(TAG, "Unable to load package " + packageName);
}
return -1;
}
final int resId = res.getIdentifier(name, null, packageName);
if (resId == 0) {
- if (Log.isLoggable(TAG, Log.WARN)) {
+ if (Log.isLoggable(TAG, Log.WARN) && !isFromTestApp(packageName)) {
Log.w(TAG, "Unable to load " + resourceName + " from package " + packageName);
}
return -1;
}
return resId;
}
+
+ @VisibleForTesting
+ static boolean isFromTestApp(String packageName) {
+ return TextUtils.equals(packageName, "com.google.android.contacts.tests");
+ }
}
diff --git a/src/com/android/contacts/editor/AccountHeaderPresenter.java b/src/com/android/contacts/editor/AccountHeaderPresenter.java
index 440df94..61a440f 100644
--- a/src/com/android/contacts/editor/AccountHeaderPresenter.java
+++ b/src/com/android/contacts/editor/AccountHeaderPresenter.java
@@ -189,6 +189,9 @@
UiClosables.closeQuietly(popup);
final AccountWithDataSet newAccount = adapter.getItem(position);
setCurrentAccount(newAccount);
+ // Make sure the new selection will be announced once it's changed
+ mAccountHeaderContainer.setAccessibilityLiveRegion(
+ View.ACCESSIBILITY_LIVE_REGION_POLITE);
}
});
mAccountHeaderContainer.post(new Runnable() {
diff --git a/src/com/android/contacts/editor/AggregationSuggestionEngine.java b/src/com/android/contacts/editor/AggregationSuggestionEngine.java
index ecd963b..42776ff 100644
--- a/src/com/android/contacts/editor/AggregationSuggestionEngine.java
+++ b/src/com/android/contacts/editor/AggregationSuggestionEngine.java
@@ -207,10 +207,6 @@
appendValue(nameSb, values, StructuredName.FAMILY_NAME);
appendValue(nameSb, values, StructuredName.SUFFIX);
- if (nameSb.length() == 0) {
- appendValue(nameSb, values, StructuredName.DISPLAY_NAME);
- }
-
StringBuilder phoneticNameSb = new StringBuilder();
appendValue(phoneticNameSb, values, StructuredName.PHONETIC_FAMILY_NAME);
appendValue(phoneticNameSb, values, StructuredName.PHONETIC_MIDDLE_NAME);
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index a98baad..81a3741 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -1015,7 +1015,7 @@
destroyInteractionLoaders();
mContactLoader = (ContactLoader) (Loader<?>) getLoaderManager().getLoader(
LOADER_CONTACT_ID);
- mContactLoader.setLookupUri(mLookupUri);
+ mContactLoader.setNewLookup(mLookupUri);
mCachedCp2DataCardModel = null;
}
mContactLoader.forceLoad();
diff --git a/tests/src/com/android/contacts/common/database/SimContactDaoTests.java b/tests/src/com/android/contacts/common/database/SimContactDaoTests.java
index 265600c..ec03f0f 100644
--- a/tests/src/com/android/contacts/common/database/SimContactDaoTests.java
+++ b/tests/src/com/android/contacts/common/database/SimContactDaoTests.java
@@ -25,6 +25,7 @@
import android.support.annotation.RequiresApi;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
+import android.support.test.filters.MediumTest;
import android.support.test.filters.SdkSuppress;
import android.support.test.filters.Suppress;
import android.support.test.runner.AndroidJUnit4;
@@ -45,10 +46,14 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import static android.os.Build.VERSION_CODES;
import static org.hamcrest.Matchers.allOf;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
@RunWith(Enclosed.class)
public class SimContactDaoTests {
@@ -197,6 +202,54 @@
}
}
+ @SdkSuppress(minSdkVersion = VERSION_CODES.M)
+ // Lollipop MR1 is required for removeAccountExplicitly
+ @RequiresApi(api = VERSION_CODES.LOLLIPOP_MR1)
+ @MediumTest
+ @RunWith(AndroidJUnit4.class)
+ public static class ExistingContactsTest {
+
+ private Context mContext;
+ private AccountsTestHelper mAccountHelper;
+ private AccountWithDataSet mAccount;
+ // We need to generate something distinct to prevent flakiness on devices that may not
+ // start with an empty CP2 DB
+ private String mNameSuffix = "";
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mAccountHelper = new AccountsTestHelper(InstrumentationRegistry.getContext());
+ mAccount = mAccountHelper.addTestAccount();
+ mNameSuffix = "testAt" + System.nanoTime();
+ }
+
+ @After
+ public void tearDown() {
+ mAccountHelper.cleanup();
+ }
+
+ @Test
+ public void findAccountsOfExistingContactsReturnsEmptyMapWhenNoMatchingContactsExist() {
+ final SimContactDao sut = createDao();
+
+ final List<SimContact> contacts = Arrays.asList(
+ new SimContact(1, "Name 1 " + mNameSuffix, "15095550101", null),
+ new SimContact(2, "Name 2 " + mNameSuffix, "15095550102", null),
+ new SimContact(3, "Name 3 " + mNameSuffix, "15095550103", null),
+ new SimContact(4, "Name 4 " + mNameSuffix, "15095550104", null));
+
+ final Map<AccountWithDataSet, Set<SimContact>> existing = sut
+ .findAccountsOfExistingSimContacts(contacts);
+
+ assertTrue(existing.isEmpty());
+ }
+
+ private SimContactDao createDao() {
+ return SimContactDao.create(mContext);
+ }
+ }
+
@LargeTest
// suppressed because failed assumptions are reported as test failures by the build server
@Suppress
diff --git a/tests/src/com/android/contacts/common/model/account/ExternalAccountTypeTest.java b/tests/src/com/android/contacts/common/model/account/ExternalAccountTypeTest.java
index 96e776f..0ddeb67 100644
--- a/tests/src/com/android/contacts/common/model/account/ExternalAccountTypeTest.java
+++ b/tests/src/com/android/contacts/common/model/account/ExternalAccountTypeTest.java
@@ -79,6 +79,11 @@
"@string/test_string", packageName, ""));
}
+ public void testIsFromTestApp() {
+ assertTrue(ExternalAccountType.isFromTestApp("com.google.android.contacts.tests"));
+ assertFalse(ExternalAccountType.isFromTestApp("com.google.android.contacts"));
+ }
+
/**
* Initialize with an invalid package name and see if type will be initialized, but empty.
*/