Merge "Replace switches on resources with if-else" into ub-contactsdialer-i-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5674fc5..053d0e3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -439,11 +439,6 @@
<!-- The message in a confirmation dialog shown when the user selects a
contact aggregation suggestion in Contact editor. [CHAR LIMIT=512]-->
- <string name="aggregation_suggestion_join_dialog_message">Link
- the current contact with the selected contact?</string>
-
- <!-- The message in a confirmation dialog shown when the user selects a
- contact aggregation suggestion in Contact editor. [CHAR LIMIT=512]-->
<string name="aggregation_suggestion_edit_dialog_message">Switch to editing
the selected contact? Information you entered so far will be copied.</string>
diff --git a/src/com/android/contacts/CallUtil.java b/src/com/android/contacts/CallUtil.java
index d83b63d..ddde01c 100644
--- a/src/com/android/contacts/CallUtil.java
+++ b/src/com/android/contacts/CallUtil.java
@@ -116,13 +116,6 @@
}
/**
- * @return Uri that directly dials a user's voicemail inbox.
- */
- public static Uri getVoicemailUri() {
- return Uri.fromParts(PhoneAccount.SCHEME_VOICEMAIL, "", null);
- }
-
- /**
* Determines if video calling is available, and if so whether presence checking is available
* as well.
*
@@ -166,23 +159,12 @@
return VIDEO_CALLING_DISABLED;
} catch (SecurityException e) {
FeedbackHelper.sendFeedback(context, TAG,
- "Security exception when querying intent activities", e);
+ "Security exception when getting call capable phone accounts", e);
return VIDEO_CALLING_DISABLED;
}
}
/**
- * Determines if one of the call capable phone accounts defined supports video calling.
- *
- * @param context The context.
- * @return {@code true} if one of the call capable phone accounts supports video calling,
- * {@code false} otherwise.
- */
- public static boolean isVideoEnabled(Context context) {
- return (getVideoCallingAvailability(context) & VIDEO_CALLING_ENABLED) != 0;
- }
-
- /**
* Determines if one of the call capable phone accounts defined supports calling with a subject
* specified.
*
@@ -201,13 +183,20 @@
return false;
}
- List<PhoneAccountHandle> accountHandles = telecommMgr.getCallCapablePhoneAccounts();
- for (PhoneAccountHandle accountHandle : accountHandles) {
- PhoneAccount account = telecommMgr.getPhoneAccount(accountHandle);
- if (account != null && account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT)) {
- return true;
+ try {
+ List<PhoneAccountHandle> accountHandles = telecommMgr.getCallCapablePhoneAccounts();
+ for (PhoneAccountHandle accountHandle : accountHandles) {
+ PhoneAccount account = telecommMgr.getPhoneAccount(accountHandle);
+ if (account != null && account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT)) {
+ return true;
+ }
}
+ return false;
+ } catch (SecurityException e) {
+ FeedbackHelper.sendFeedback(context, TAG,
+ "Security exception when getting call capable phone accounts", e);
+ return false;
}
- return false;
+
}
}
diff --git a/src/com/android/contacts/activities/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
index abb8463..326ff93 100644
--- a/src/com/android/contacts/activities/ContactEditorActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -468,7 +468,12 @@
*/
public void changePhoto(int photoMode) {
mPhotoMode = photoMode;
- PhotoSourceDialogFragment.show(this, mPhotoMode);
+ // This method is called from an onClick handler in the PhotoEditorView. It's possible for
+ // onClick methods to run after onSaveInstanceState is called for the activity, so check
+ // if it's safe to commit transactions before trying.
+ if (isSafeToCommitTransactions()) {
+ PhotoSourceDialogFragment.show(this, mPhotoMode);
+ }
}
public Toolbar getToolbar() {
diff --git a/src/com/android/contacts/compat/telecom/TelecomManagerCompat.java b/src/com/android/contacts/compat/telecom/TelecomManagerCompat.java
index fbbc589..881c1a4 100644
--- a/src/com/android/contacts/compat/telecom/TelecomManagerCompat.java
+++ b/src/com/android/contacts/compat/telecom/TelecomManagerCompat.java
@@ -17,25 +17,15 @@
import android.app.Activity;
import android.content.Intent;
-import android.net.Uri;
import android.support.annotation.Nullable;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
import com.android.contacts.compat.CompatUtils;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* Compatibility class for {@link android.telecom.TelecomManager}.
*/
public class TelecomManagerCompat {
- public static final String TELECOM_MANAGER_CLASS = "android.telecom.TelecomManager";
/**
* Places a new outgoing call to the provided address using the system telecom service with
* the specified intent.
@@ -55,226 +45,4 @@
}
activity.startActivityForResult(intent, 0);
}
-
- /**
- * Get the URI for running an adn query.
- *
- * @param telecomManager the {@link TelecomManager} used for method calls, if possible.
- * @param accountHandle The handle for the account to derive an adn query URI for or
- * {@code null} to return a URI which will use the default account.
- * @return The URI (with the content:// scheme) specific to the specified {@link PhoneAccount}
- * for the the content retrieve.
- */
- public static Uri getAdnUriForPhoneAccount(@Nullable TelecomManager telecomManager,
- PhoneAccountHandle accountHandle) {
- if (telecomManager != null && (CompatUtils.isMarshmallowCompatible()
- || CompatUtils.isMethodAvailable(TELECOM_MANAGER_CLASS, "getAdnUriForPhoneAccount",
- PhoneAccountHandle.class))) {
- return telecomManager.getAdnUriForPhoneAccount(accountHandle);
- }
- return Uri.parse("content://icc/adn");
- }
-
- /**
- * Returns a list of {@link PhoneAccountHandle}s which can be used to make and receive phone
- * calls. The returned list includes only those accounts which have been explicitly enabled
- * by the user.
- *
- * @param telecomManager the {@link TelecomManager} used for method calls, if possible.
- * @return A list of PhoneAccountHandle objects.
- */
- public static List<PhoneAccountHandle> getCallCapablePhoneAccounts(
- @Nullable TelecomManager telecomManager) {
- if (telecomManager != null && (CompatUtils.isMarshmallowCompatible()
- || CompatUtils.isMethodAvailable(TELECOM_MANAGER_CLASS,
- "getCallCapablePhoneAccounts"))) {
- return telecomManager.getCallCapablePhoneAccounts();
- }
- return new ArrayList<>();
- }
-
- /**
- * Used to determine the currently selected default dialer package.
- *
- * @param telecomManager the {@link TelecomManager} used for method calls, if possible.
- * @return package name for the default dialer package or null if no package has been
- * selected as the default dialer.
- */
- @Nullable
- public static String getDefaultDialerPackage(@Nullable TelecomManager telecomManager) {
- if (telecomManager != null && CompatUtils.isDefaultDialerCompatible()) {
- return telecomManager.getDefaultDialerPackage();
- }
- return null;
- }
-
- /**
- * Return the {@link PhoneAccount} which will be used to place outgoing calls to addresses with
- * the specified {@code uriScheme}. This PhoneAccount will always be a member of the
- * list which is returned from invoking {@link TelecomManager#getCallCapablePhoneAccounts()}.
- * The specific account returned depends on the following priorities:
- *
- * 1. If the user-selected default PhoneAccount supports the specified scheme, it will
- * be returned.
- * 2. If there exists only one PhoneAccount that supports the specified scheme, it
- * will be returned.
- *
- * If no PhoneAccount fits the criteria above, this method will return {@code null}.
- *
- * @param telecomManager the {@link TelecomManager} used for method calls, if possible.
- * @param uriScheme The URI scheme.
- * @return The {@link PhoneAccountHandle} corresponding to the account to be used.
- */
- @Nullable
- public static PhoneAccountHandle getDefaultOutgoingPhoneAccount(
- @Nullable TelecomManager telecomManager, @Nullable String uriScheme) {
- if (telecomManager != null && (CompatUtils.isMarshmallowCompatible()
- || CompatUtils.isMethodAvailable(TELECOM_MANAGER_CLASS,
- "getDefaultOutgoingPhoneAccount", String.class))) {
- return telecomManager.getDefaultOutgoingPhoneAccount(uriScheme);
- }
- return null;
- }
-
- /**
- * Return the line 1 phone number for given phone account.
- *
- * @param telecomManager the {@link TelecomManager} to use in the event that
- * {@link TelecomManager#getLine1Number(PhoneAccountHandle)} is available
- * @param telephonyManager the {@link TelephonyManager} to use if TelecomManager#getLine1Number
- * is unavailable
- * @param phoneAccountHandle the phoneAccountHandle upon which to check the line one number
- * @return the line one number
- */
- @Nullable
- public static String getLine1Number(@Nullable TelecomManager telecomManager,
- @Nullable TelephonyManager telephonyManager,
- @Nullable PhoneAccountHandle phoneAccountHandle) {
- if (telecomManager != null && CompatUtils.isMarshmallowCompatible()) {
- return telecomManager.getLine1Number(phoneAccountHandle);
- }
- if (telephonyManager != null) {
- return telephonyManager.getLine1Number();
- }
- return null;
- }
-
- /**
- * Return whether a given phone number is the configured voicemail number for a
- * particular phone account.
- *
- * @param telecomManager the {@link TelecomManager} to use for checking the number.
- * @param accountHandle The handle for the account to check the voicemail number against
- * @param number The number to look up.
- */
- public static boolean isVoiceMailNumber(@Nullable TelecomManager telecomManager,
- @Nullable PhoneAccountHandle accountHandle, @Nullable String number) {
- if (telecomManager != null && (CompatUtils.isMarshmallowCompatible()
- || CompatUtils.isMethodAvailable(TELECOM_MANAGER_CLASS, "isVoiceMailNumber",
- PhoneAccountHandle.class, String.class))) {
- return telecomManager.isVoiceMailNumber(accountHandle, number);
- }
- return PhoneNumberUtils.isVoiceMailNumber(number);
- }
-
- /**
- * Return the {@link PhoneAccount} for a specified {@link PhoneAccountHandle}. Object includes
- * resources which can be used in a user interface.
- *
- * @param telecomManager the {@link TelecomManager} used for method calls, if possible.
- * @param account The {@link PhoneAccountHandle}.
- * @return The {@link PhoneAccount} object or null if it doesn't exist.
- */
- @Nullable
- public static PhoneAccount getPhoneAccount(@Nullable TelecomManager telecomManager,
- @Nullable PhoneAccountHandle accountHandle) {
- if (telecomManager != null && (CompatUtils.isMethodAvailable(
- TELECOM_MANAGER_CLASS, "getPhoneAccount", PhoneAccountHandle.class))) {
- return telecomManager.getPhoneAccount(accountHandle);
- }
- return null;
- }
-
- /**
- * Return the voicemail number for a given phone account.
- *
- * @param telecomManager The {@link TelecomManager} object to use for retrieving the voicemail
- * number if accountHandle is specified.
- * @param telephonyManager The {@link TelephonyManager} object to use for retrieving the
- * voicemail number if accountHandle is null.
- * @param accountHandle The handle for the phone account.
- * @return The voicemail number for the phone account, and {@code null} if one has not been
- * configured.
- */
- @Nullable
- public static String getVoiceMailNumber(@Nullable TelecomManager telecomManager,
- @Nullable TelephonyManager telephonyManager,
- @Nullable PhoneAccountHandle accountHandle) {
- if (telecomManager != null && (CompatUtils.isMethodAvailable(
- TELECOM_MANAGER_CLASS, "getVoiceMailNumber", PhoneAccountHandle.class))) {
- return telecomManager.getVoiceMailNumber(accountHandle);
- } else if (telephonyManager != null){
- return telephonyManager.getVoiceMailNumber();
- }
- return null;
- }
-
- /**
- * Processes the specified dial string as an MMI code.
- * MMI codes are any sequence of characters entered into the dialpad that contain a "*" or "#".
- * Some of these sequences launch special behavior through handled by Telephony.
- *
- * @param telecomManager The {@link TelecomManager} object to use for handling MMI.
- * @param dialString The digits to dial.
- * @return {@code true} if the digits were processed as an MMI code, {@code false} otherwise.
- */
- public static boolean handleMmi(@Nullable TelecomManager telecomManager,
- @Nullable String dialString, @Nullable PhoneAccountHandle accountHandle) {
- if (telecomManager == null || TextUtils.isEmpty(dialString)) {
- return false;
- }
- if (CompatUtils.isMarshmallowCompatible()) {
- return telecomManager.handleMmi(dialString, accountHandle);
- }
-
- Object handleMmiResult = CompatUtils.invokeMethod(
- telecomManager,
- "handleMmi",
- new Class<?>[] {PhoneAccountHandle.class, String.class},
- new Object[] {accountHandle, dialString});
- if (handleMmiResult != null) {
- return (boolean) handleMmiResult;
- }
-
- return telecomManager.handleMmi(dialString);
- }
-
- /**
- * Silences the ringer if a ringing call exists. Noop if {@link TelecomManager#silenceRinger()}
- * is unavailable.
- *
- * @param telecomManager the TelecomManager to use to silence the ringer.
- */
- public static void silenceRinger(@Nullable TelecomManager telecomManager) {
- if (telecomManager != null && (CompatUtils.isMarshmallowCompatible() || CompatUtils
- .isMethodAvailable(TELECOM_MANAGER_CLASS, "silenceRinger"))) {
- telecomManager.silenceRinger();
- }
- }
-
- /**
- * Returns the current SIM call manager. Apps must be prepared for this method to return null,
- * indicating that there currently exists no registered SIM call manager.
- *
- * @param telecomManager the {@link TelecomManager} to use to fetch the SIM call manager.
- * @return The phone account handle of the current sim call manager.
- */
- @Nullable
- public static PhoneAccountHandle getSimCallManager(TelecomManager telecomManager) {
- if (telecomManager != null && (CompatUtils.isMarshmallowCompatible() || CompatUtils
- .isMethodAvailable(TELECOM_MANAGER_CLASS, "getSimCallManager"))) {
- return telecomManager.getSimCallManager();
- }
- return null;
- }
}
diff --git a/src/com/android/contacts/database/SimContactDaoImpl.java b/src/com/android/contacts/database/SimContactDaoImpl.java
index 8d47824..4eb5fd3 100644
--- a/src/com/android/contacts/database/SimContactDaoImpl.java
+++ b/src/com/android/contacts/database/SimContactDaoImpl.java
@@ -89,9 +89,15 @@
private final TelephonyManager mTelephonyManager;
public SimContactDaoImpl(Context context) {
+ this(context, context.getContentResolver(),
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
+ }
+
+ public SimContactDaoImpl(Context context, ContentResolver resolver,
+ TelephonyManager telephonyManager) {
mContext = context;
- mResolver = context.getContentResolver();
- mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ mResolver = resolver;
+ mTelephonyManager = telephonyManager;
}
public Context getContext() {
diff --git a/src/com/android/contacts/editor/JoinSuggestedContactDialogFragment.java b/src/com/android/contacts/editor/JoinSuggestedContactDialogFragment.java
deleted file mode 100644
index be3313b..0000000
--- a/src/com/android/contacts/editor/JoinSuggestedContactDialogFragment.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2015 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.editor;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.DialogInterface;
-import android.os.Bundle;
-
-import com.android.contacts.R;
-
-public class JoinSuggestedContactDialogFragment extends DialogFragment {
-
- private static final String ARG_RAW_CONTACT_IDS = "rawContactIds";
-
- public static void show(ContactEditorFragment fragment, long[] rawContactIds) {
- final Bundle args = new Bundle();
- args.putLongArray(ARG_RAW_CONTACT_IDS, rawContactIds);
-
- final JoinSuggestedContactDialogFragment dialog = new JoinSuggestedContactDialogFragment();
- dialog.setArguments(args);
- dialog.setTargetFragment(fragment, 0);
- dialog.show(fragment.getFragmentManager(), "join");
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- return new AlertDialog.Builder(getActivity())
- .setIconAttribute(android.R.attr.alertDialogIcon)
- .setMessage(R.string.aggregation_suggestion_join_dialog_message)
- .setPositiveButton(android.R.string.yes,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- ContactEditorFragment targetFragment =
- (ContactEditorFragment) getTargetFragment();
- long rawContactIds[] =
- getArguments().getLongArray(ARG_RAW_CONTACT_IDS);
- targetFragment.doJoinSuggestedContact(rawContactIds);
- }
- }
- )
- .setNegativeButton(android.R.string.no, null)
- .create();
- }
-}
diff --git a/tests/src/com/android/contacts/activities/SimImportActivityTest.java b/tests/src/com/android/contacts/activities/SimImportActivityTest.java
index 8ebe69b..64b86e2 100644
--- a/tests/src/com/android/contacts/activities/SimImportActivityTest.java
+++ b/tests/src/com/android/contacts/activities/SimImportActivityTest.java
@@ -1,18 +1,37 @@
+/*
+ * 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.
+ */
package com.android.contacts.activities;
import static com.android.contacts.tests.ContactsMatchers.DataCursor.hasMimeType;
import static com.android.contacts.tests.ContactsMatchers.hasRowMatching;
import static com.android.contacts.tests.ContactsMatchers.hasValueForColumn;
-
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Instrumentation;
+import android.content.BroadcastReceiver;
+import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.database.Cursor;
import android.os.Build;
@@ -20,52 +39,60 @@
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Data;
import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
+import android.support.test.filters.LargeTest;
import android.support.test.filters.SdkSuppress;
-import android.support.test.filters.Suppress;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.Until;
+import android.support.v4.content.LocalBroadcastManager;
+import android.telephony.TelephonyManager;
+import android.test.mock.MockContentResolver;
+import com.android.contacts.SimImportService;
import com.android.contacts.database.SimContactDao;
+import com.android.contacts.database.SimContactDaoImpl;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.SimCard;
import com.android.contacts.model.SimContact;
import com.android.contacts.model.account.AccountWithDataSet;
+import com.android.contacts.test.mocks.ForwardingContentProvider;
+import com.android.contacts.test.mocks.MockContentProvider;
import com.android.contacts.tests.AccountsTestHelper;
import com.android.contacts.tests.ContactsMatchers;
import com.android.contacts.tests.FakeSimContactDao;
import com.android.contacts.tests.StringableCursor;
-
+import com.google.common.base.Function;
import com.google.common.base.Functions;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.TimeUnit;
+
/**
* UI Tests for {@link SimImportActivity}
*
* These should probably be converted to espresso tests because espresso does a better job of
* waiting for the app to be idle once espresso library is added
*/
-@MediumTest
+@LargeTest
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
@TargetApi(Build.VERSION_CODES.M)
public class SimImportActivityTest {
- public static final int TIMEOUT = 1000;
+ public static final int TIMEOUT = 3000;
private Context mContext;
private UiDevice mDevice;
private Instrumentation mInstrumentation;
private FakeSimContactDao mDao;
private AccountsTestHelper mAccountHelper;
+ private Activity mActivity;
@Before
public void setUp() throws Exception {
@@ -87,6 +114,10 @@
SimContactDao.setFactoryForTest(SimContactDao.DEFAULT_FACTORY);
mAccountHelper.cleanup();
AccountTypeManager.setInstanceForTest(null);
+ if (mActivity != null) {
+ mActivity.finish();
+ mInstrumentation.waitForIdleSync();
+ }
}
@Test
@@ -96,15 +127,17 @@
new SimContact(2, "Sim Two", null),
new SimContact(3, null, "5550103")
);
- mInstrumentation.startActivitySync(new Intent(mContext, SimImportActivity.class)
+ mActivity = mInstrumentation.startActivitySync(new Intent(mContext, SimImportActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
mDevice.waitForIdle();
+ assertTrue(mDevice.wait(Until.hasObject(By.text("Sim One")), TIMEOUT));
+
assertTrue(mDevice.hasObject(By.text("Sim One")));
assertTrue(mDevice.hasObject(By.text("Sim Two")));
assertTrue(mDevice.hasObject(By.text("5550103")));
-}
+ }
@Test
public void shouldHaveEmptyState() {
@@ -115,24 +148,24 @@
mDevice.waitForIdle();
- assertTrue(mDevice.hasObject(By.textStartsWith("No contacts")));
+ assertTrue(mDevice.wait(Until.hasObject(By.textStartsWith("No contacts")), TIMEOUT));
}
@Test
public void smokeRotateInEmptyState() {
mDao.addSim(someSimCard());
- final Activity activity = mInstrumentation.startActivitySync(
+ mActivity = mInstrumentation.startActivitySync(
new Intent(mContext, SimImportActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
assertTrue(mDevice.wait(Until.hasObject(By.textStartsWith("No contacts")), TIMEOUT));
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
mDevice.waitForIdle();
- assertTrue(mDevice.hasObject(By.textStartsWith("No contacts")));
+ assertTrue(mDevice.wait(Until.hasObject(By.textStartsWith("No contacts")), TIMEOUT));
}
@Test
@@ -140,42 +173,79 @@
mDao.addSim(someSimCard(), new SimContact(1, "Name One", "5550101"),
new SimContact(2, "Name Two", "5550102"));
- final Activity activity = mInstrumentation.startActivitySync(
+ mActivity = mInstrumentation.startActivitySync(
new Intent(mContext, SimImportActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
assertTrue(mDevice.wait(Until.hasObject(By.textStartsWith("Name One")), TIMEOUT));
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
mDevice.waitForIdle();
- assertTrue(mDevice.hasObject(By.textStartsWith("Name One")));
+ assertTrue(mDevice.wait(Until.hasObject(By.textStartsWith("Name One")), TIMEOUT));
}
-
- // TODO: fix this test. This doesn't work because AccountTypeManager returns a stale account
- // list (it doesn't contain the accounts added during the current test run).
- // Could use MockAccountTypeManager but probably ought to look at improving how
- // AccountTypeManager updates it's account list.
- @Suppress
+ /**
+ * Tests a complete import flow
+ *
+ * <p>Test case outline:</p>
+ * <ul>
+ * <li>Load SIM contacts
+ * <li>Change to a specific target account
+ * <li>Deselect 3 specific SIM contacts
+ * <li>Rotate the screen to landscape
+ * <li>Rotate the screen back to portrait
+ * <li>Press the import button
+ * <li>Wait for import to complete
+ * <li>Query contacts in target account and verify that they match selected contacts
+ * <li>Start import activity again
+ * <li>Switch to target account
+ * <li>Verify that previously imported contacts are disabled and not checked
+ * </ul>
+ *
+ * <p>This mocks out the IccProvider and stubs the canReadSimContacts method to make it work on
+ * an emulator but otherwise uses real dependency.
+ * </p>
+ */
@Test
- public void selectionsAreImportedAndDisabledOnSubsequentViews() throws Exception {
+ public void selectionsAreImportedAndDisabledOnSubsequentImports() throws Exception {
// Clear out the instance so that it will have the most recent accounts when reloaded
AccountTypeManager.setInstanceForTest(null);
final AccountWithDataSet targetAccount = mAccountHelper.addTestAccount(
mAccountHelper.generateAccountName("SimImportActivity_target_"));
- mDao.addSim(someSimCard(),
- new SimContact(1, "Import One", "5550101"),
- new SimContact(2, "Skip Two", "5550102"),
- new SimContact(3, "Import Three", "5550103"),
- new SimContact(4, "Skip Four", "5550104"),
- new SimContact(5, "Skip Five", "5550105"),
- new SimContact(6, "Import Six", "5550106"));
+ final MockContentProvider iccProvider = new MockContentProvider();
+ iccProvider.expect(MockContentProvider.Query.forAnyUri())
+ .withDefaultProjection(new String[] {SimContactDaoImpl._ID, SimContactDaoImpl.NAME,
+ SimContactDaoImpl.NUMBER, SimContactDaoImpl.EMAILS })
+ .anyNumberOfTimes()
+ .returnRow(toCursorRow(new SimContact(1, "Import One", "5550101")))
+ .returnRow(toCursorRow(new SimContact(2, "Skip Two", "5550102")))
+ .returnRow(toCursorRow(new SimContact(3, "Import Three", "5550103")))
+ .returnRow(toCursorRow(new SimContact(4, "Skip Four", "5550104")))
+ .returnRow(toCursorRow(new SimContact(5, "Skip Five", "5550105")))
+ .returnRow(toCursorRow(new SimContact(6, "Import Six", "5550106")));
+ final MockContentResolver mockResolver = new MockContentResolver();
+ mockResolver.addProvider("icc", iccProvider);
+ final ContentProviderClient contactsProviderClient = mContext.getContentResolver()
+ .acquireContentProviderClient(ContactsContract.AUTHORITY);
+ mockResolver.addProvider(ContactsContract.AUTHORITY, new ForwardingContentProvider(
+ contactsProviderClient));
- final Activity activity = mInstrumentation.startActivitySync(
+ SimContactDao.setFactoryForTest(new Function<Context, SimContactDao>() {
+ @Override
+ public SimContactDao apply(Context input) {
+ final SimContactDaoImpl spy = spy(new SimContactDaoImpl(
+ mContext, mockResolver,
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)));
+ when(spy.canReadSimContacts()).thenReturn(true);
+ return spy;
+ }
+ });
+
+ mActivity = mInstrumentation.startActivitySync(
new Intent(mContext, SimImportActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
@@ -183,21 +253,28 @@
mDevice.findObject(By.desc("Show more")).clickAndWait(Until.newWindow(), TIMEOUT);
mDevice.findObject(By.textStartsWith("SimImportActivity_target_")).click();
- mDevice.waitForIdle();
+
+ assertTrue(mDevice.wait(Until.hasObject(By.text("Skip Two")), TIMEOUT));
mDevice.findObject(By.text("Skip Two")).click();
+ mDevice.findObject(By.text("Skip Four")).click();
mDevice.findObject(By.text("Skip Five")).click();
+ mDevice.waitForIdle();
assertTrue(mDevice.hasObject(By.text("Skip Two").checked(false)));
assertTrue(mDevice.hasObject(By.text("Skip Five").checked(false)));
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
mDevice.wait(Until.hasObject(By.text("Import One")), TIMEOUT);
- activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
mDevice.wait(Until.hasObject(By.text("Import One")), TIMEOUT);
+ ListenableFuture<?> nextImportFuture = nextImportCompleteBroadcast();
+
mDevice.findObject(By.text("IMPORT").clickable(true)).click();
- mDevice.waitForIdle();
+
+ // Block until import completes
+ nextImportFuture.get(TIMEOUT, TimeUnit.MILLISECONDS);
final Cursor cursor = new StringableCursor(
mContext.getContentResolver().query(Data.CONTENT_URI, null,
@@ -229,37 +306,44 @@
cursor.close();
- mInstrumentation.startActivitySync(
+ mActivity = mInstrumentation.startActivitySync(
new Intent(mContext, SimImportActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
assertTrue(mDevice.wait(Until.hasObject(By.text("Import One")), TIMEOUT));
- mDevice.findObject(By.descStartsWith("Saving to")).clickAndWait(Until.newWindow(), TIMEOUT);
+ mDevice.findObject(By.descStartsWith("Show more")).clickAndWait(Until.newWindow(), TIMEOUT);
mDevice.findObject(By.textContains(targetAccount.name)).click();
mDevice.waitForIdle();
- assertTrue(mDevice.hasObject(By.text("Import One").checked(false).enabled(false)));
+ assertTrue(mDevice.wait(Until.hasObject(By.text("Import One").checked(false).enabled(false)), TIMEOUT));
assertTrue(mDevice.hasObject(By.text("Import Three").checked(false).enabled(false)));
assertTrue(mDevice.hasObject(By.text("Import Six").checked(false).enabled(false)));
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ contactsProviderClient.close();
+ }
+ }
+
+ private ListenableFuture<Intent> nextImportCompleteBroadcast() {
+ final SettableFuture<Intent> result = SettableFuture.create();
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ result.set(intent);
+ LocalBroadcastManager.getInstance(mContext).unregisterReceiver(this);
+ }
+ };
+ LocalBroadcastManager.getInstance(mContext).registerReceiver(receiver, new IntentFilter(
+ SimImportService.BROADCAST_SIM_IMPORT_COMPLETE));
+ return result;
+ }
+
+ private Object[] toCursorRow(SimContact contact) {
+ return new Object[] { contact.getId(), contact.getName(), contact.getPhone(), null };
}
private SimCard someSimCard() {
return new SimCard("id", 1, "Carrier", "SIM", "18005550101", "us");
}
-
- private Matcher<SimContact> withContactId(final long id) {
- return new BaseMatcher<SimContact>() {
- @Override
- public boolean matches(Object o) {
- return (o instanceof SimContact) && ((SimContact) o).getId() == id;
- }
-
-
- @Override
- public void describeTo(Description description) {
- description.appendText("Expected SimContact with id=" + id);
- }
- };
- }
}
diff --git a/tests/src/com/android/contacts/test/mocks/ForwardingContentProvider.java b/tests/src/com/android/contacts/test/mocks/ForwardingContentProvider.java
new file mode 100644
index 0000000..b6ca983
--- /dev/null
+++ b/tests/src/com/android/contacts/test/mocks/ForwardingContentProvider.java
@@ -0,0 +1,206 @@
+/*
+ * 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.
+ */
+package com.android.contacts.test.mocks;
+
+import android.content.ContentProviderClient;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.content.OperationApplicationException;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.support.annotation.Nullable;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+
+/**
+ * Forwards calls to a {@link ContentProviderClient}
+ *
+ * <p>This allows mixing use of the system content providers in a
+ * {@link android.test.mock.MockContentResolver}
+ * </p>
+ */
+public class ForwardingContentProvider extends android.test.mock.MockContentProvider {
+
+ private final ContentProviderClient mClient;
+
+ public ForwardingContentProvider(ContentProviderClient client) {
+ mClient = client;
+ }
+
+ @Override
+ public synchronized Cursor query(Uri url, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ try {
+ return mClient.query(url, projection, selection, selectionArgs, sortOrder);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ @Override
+ public synchronized Cursor query(Uri url, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
+ try {
+ return mClient.query(url, projection, selection, selectionArgs, sortOrder,
+ cancellationSignal);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public synchronized String getType(Uri url) {
+ try {
+ return mClient.getType(url);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public synchronized String[] getStreamTypes(Uri url, String mimeTypeFilter) {
+ try {
+ return mClient.getStreamTypes(url, mimeTypeFilter);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public synchronized Uri insert(Uri url, ContentValues initialValues) {
+ try {
+ return mClient.insert(url, initialValues);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public synchronized int bulkInsert(Uri url, ContentValues[] initialValues) {
+ try {
+ return mClient.bulkInsert(url, initialValues);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public synchronized int delete(Uri url, String selection, String[] selectionArgs) {
+ try {
+ return mClient.delete(url, selection, selectionArgs);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public synchronized int update(Uri url, ContentValues values,
+ String selection, String[] selectionArgs) {
+ try {
+ return mClient.update(url, values, selection, selectionArgs);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ @Override
+ public synchronized ParcelFileDescriptor openFile(Uri url, String mode) {
+ try {
+ return mClient.openFile(url, mode);
+ } catch (RemoteException|FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ @Override
+ public synchronized ParcelFileDescriptor openFile(Uri url, String mode,
+ CancellationSignal signal) {
+ try {
+ return mClient.openFile(url, mode, signal);
+ } catch (RemoteException|FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ @Override
+ public synchronized AssetFileDescriptor openAssetFile(Uri url, String mode) {
+ try {
+ return mClient.openAssetFile(url, mode);
+ } catch (RemoteException|FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ @Override
+ public synchronized AssetFileDescriptor openAssetFile(Uri url, String mode,
+ CancellationSignal signal) {
+ try {
+ return mClient.openAssetFile(url, mode, signal);
+ } catch (RemoteException|FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public synchronized AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, String mimeType,
+ Bundle opts) {
+ try {
+ return mClient.openTypedAssetFileDescriptor(uri, mimeType, opts);
+ } catch (RemoteException|FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public synchronized AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, String mimeType,
+ Bundle opts, CancellationSignal signal) {
+ try {
+ return mClient.openTypedAssetFileDescriptor(uri, mimeType, opts, signal);
+ } catch (RemoteException|FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public synchronized ContentProviderResult[] applyBatch(
+ ArrayList<ContentProviderOperation> operations) {
+ try {
+ return mClient.applyBatch(operations);
+ } catch (RemoteException|OperationApplicationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Nullable
+ @Override
+ public synchronized Bundle call(String method, String arg, Bundle extras) {
+ try {
+ return mClient.call(method, arg, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}