Merge "Deprecating ConfirmAddDetail dialog to use full contact editor."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 74a8a91..b764f17 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.contacts"
- android:versionCode="10401"
- android:versionName="1.4.1">
+ android:versionCode="10402"
+ android:versionName="1.4.2">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="23" />
<original-package android:name="com.android.contacts" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 07bfdc0..31552b6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -678,9 +678,6 @@
contact filter state. [CHAR LIMIT=64] -->
<string name="toast_displaying_all_contacts">Displaying all contacts</string>
- <!-- Message in the standard "no account" prompt that encourages the user to add a Google account before continuing to use the People app [CHAR LIMIT=NONE] -->
- <string name="no_account_prompt">Contacts works better with a Google Account.\n\n\u2022 Access from any web browser.\n\u2022 Back up your contacts securely.</string>
-
<!-- Message in the standard "no account" prompt that encourages the user to add any account (non Google-specific) before continuing to use the People app [CHAR LIMIT=NONE] -->
<string name="generic_no_account_prompt">Keep your contacts safe even if you lose your phone: synchronize with an online service.</string>
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index c3a7f24..10f8f9c 100755
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -56,6 +56,7 @@
import com.android.contacts.common.model.RawContactDelta;
import com.android.contacts.common.model.RawContactDeltaList;
import com.android.contacts.common.model.RawContactModifier;
+import com.android.contacts.common.model.account.AccountType;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.compat.PinnedPositionsCompat;
@@ -66,6 +67,7 @@
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -372,8 +374,12 @@
String saveModeExtraKey, int saveMode, boolean isProfile,
Class<? extends Activity> callbackActivity, String callbackAction,
Bundle updatedPhotos, String joinContactIdExtraKey, Long joinContactId) {
- Intent serviceIntent = new Intent(
- context, ContactSaveService.class);
+ // Don't pass read-only RawContactDeltas in RawContactDeltaList to contact save service,
+ // because 1. read-only RawContactDeltas are not writable anyway; 2. read-only
+ // RawContactDeltas may be problematic, see b/23896510.
+ removeReadOnlyContacts(context, state);
+
+ Intent serviceIntent = new Intent(context, ContactSaveService.class);
serviceIntent.setAction(ContactSaveService.ACTION_SAVE_CONTACT);
serviceIntent.putExtra(EXTRA_CONTACT_STATE, (Parcelable) state);
serviceIntent.putExtra(EXTRA_SAVE_IS_PROFILE, isProfile);
@@ -398,6 +404,26 @@
return serviceIntent;
}
+ private static void removeReadOnlyContacts(Context context, RawContactDeltaList state) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Before trimming: " + state.size());
+ }
+ int countReadOnly = 0;
+ final Iterator<RawContactDelta> iterator = state.iterator();
+ while (iterator.hasNext()) {
+ final RawContactDelta rawContactDelta = iterator.next();
+ final AccountType accountType = rawContactDelta.getRawContactAccountType(context);
+ if (accountType != null && !accountType.areContactsWritable()) {
+ countReadOnly++;
+ iterator.remove();
+ }
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "# of read-only removed: " + countReadOnly);
+ Log.v(TAG, "After trimming: " + state.size());
+ }
+ }
+
private void saveContact(Intent intent) {
RawContactDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE);
boolean isProfile = intent.getBooleanExtra(EXTRA_SAVE_IS_PROFILE, false);
diff --git a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
index 617ef0d..9748e46 100644
--- a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
@@ -36,7 +36,7 @@
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.util.AccountsListAdapter;
import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter;
-import com.android.contacts.util.AccountPromptUtils;
+import com.android.contacts.common.util.ImplicitIntentsUtil;
import java.util.List;
@@ -70,7 +70,7 @@
private final OnClickListener mAddAccountClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
- final Intent intent = AccountPromptUtils.getIntentForAddingAccount();
+ final Intent intent = ImplicitIntentsUtil.getIntentForAddingAccount();
startActivityForResult(intent, SUBACTIVITY_ADD_NEW_ACCOUNT);
}
};
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index d887ef9..7643a05 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -59,8 +59,6 @@
import com.android.contacts.common.ContactsUtils;
import com.android.contacts.common.activity.RequestPermissionsActivity;
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.common.interactions.ImportExportDialogFragment;
import com.android.contacts.common.list.ContactEntryListFragment;
import com.android.contacts.common.list.ContactListFilter;
@@ -74,7 +72,9 @@
import com.android.contacts.common.preference.DisplayOptionsPreferenceFragment;
import com.android.contacts.common.util.AccountFilterUtil;
import com.android.contacts.common.util.Constants;
+import com.android.contacts.common.util.ImplicitIntentsUtil;
import com.android.contacts.common.util.ViewUtil;
+import com.android.contacts.common.widget.FloatingActionButtonController;
import com.android.contacts.editor.EditorIntents;
import com.android.contacts.interactions.ContactDeletionInteraction;
import com.android.contacts.interactions.ContactMultiDeletionInteraction;
@@ -92,7 +92,6 @@
import com.android.contacts.list.ProviderStatusWatcher;
import com.android.contacts.list.ProviderStatusWatcher.ProviderStatusListener;
import com.android.contacts.quickcontact.QuickContactActivity;
-import com.android.contacts.util.AccountPromptUtils;
import com.android.contacts.util.DialogManager;
import com.android.contacts.util.PhoneCapabilityTester;
import com.android.contactsbind.HelpUtils;
@@ -196,10 +195,6 @@
return (mProviderStatus != null) && mProviderStatus.equals(ProviderStatus.STATUS_NORMAL);
}
- private boolean areContactWritableAccountsAvailable() {
- return ContactsUtils.areContactWritableAccountsAvailable(this);
- }
-
private boolean areGroupWritableAccountsAvailable() {
return ContactsUtils.areGroupWritableAccountsAvailable(this);
}
@@ -899,24 +894,8 @@
mAllFragment.setEnabled(true);
}
} else {
- // If there are no accounts on the device and we should show the "no account" prompt
- // (based on {@link SharedPreferences}), then launch the account setup activity so the
- // user can sign-in or create an account.
- //
- // Also check for ability to modify accounts. In limited user mode, you can't modify
- // accounts so there is no point sending users to account setup activity.
- final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
- final boolean disallowModifyAccounts = userManager.getUserRestrictions().getBoolean(
- UserManager.DISALLOW_MODIFY_ACCOUNTS);
- if (!disallowModifyAccounts && !areContactWritableAccountsAvailable() &&
- AccountPromptUtils.shouldShowAccountPrompt(this)) {
- AccountPromptUtils.neverShowAccountPromptAgain(this);
- AccountPromptUtils.launchAccountPrompt(this);
- return;
- }
-
- // Otherwise, continue setting up the page so that the user can still use the app
- // without an account.
+ // Setting up the page so that the user can still use the app
+ // even without an account.
if (mAllFragment != null) {
mAllFragment.setEnabled(false);
}
@@ -1024,7 +1003,7 @@
@Override
public void onAddAccountAction() {
- final Intent intent = AccountPromptUtils.getIntentForAddingAccount();
+ final Intent intent = ImplicitIntentsUtil.getIntentForAddingAccount();
ImplicitIntentsUtil.startActivityOutsideApp(PeopleActivity.this, intent);
}
diff --git a/src/com/android/contacts/editor/ContactEditorBaseFragment.java b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
index 181bc44..1d7ace6 100644
--- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
@@ -979,8 +979,7 @@
abstract protected boolean doSaveAction(int saveMode, Long joinContactId);
protected boolean startSaveService(Context context, Intent intent, int saveMode) {
- final boolean result = ContactSaveService.startService(
- context, intent, saveMode);
+ final boolean result = ContactSaveService.startService(context, intent, saveMode);
if (!result) {
onCancelEditConfirmed();
}
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index ad41f2c..90cd350 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -1151,6 +1151,12 @@
destroyInteractionLoaders();
mContactLoader = (ContactLoader) getLoaderManager().restartLoader(
LOADER_CONTACT_ID, null, mLoaderContactCallbacks);
+ // mContactLoader may not be in the state of "started". If not, onContentChanged() will
+ // not call forceLoad(), so QuickContact will not get the newly updated hi-res
+ // photo. If this is the case, we call forceLoad explicitly. See b/25204200.
+ if (!mContactLoader.isStarted()) {
+ mContactLoader.forceLoad();
+ }
mCachedCp2DataCardModel = null;
}
diff --git a/src/com/android/contacts/util/AccountPromptUtils.java b/src/com/android/contacts/util/AccountPromptUtils.java
deleted file mode 100644
index 4fc95a5..0000000
--- a/src/com/android/contacts/util/AccountPromptUtils.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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.util;
-
-import android.accounts.AccountManager;
-import android.accounts.AccountManagerCallback;
-import android.accounts.AccountManagerFuture;
-import android.accounts.AuthenticatorDescription;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.provider.ContactsContract;
-import android.provider.Settings;
-import android.util.Log;
-
-import com.android.contacts.R;
-import com.android.contacts.common.model.account.GoogleAccountType;
-
-import java.io.IOException;
-
-/**
- * Utility class for controlling whether the standard "no account" prompt on launch is shown.
- */
-public class AccountPromptUtils {
-
- private static final String TAG = AccountPromptUtils.class.getSimpleName();
-
- /** {@link SharedPreferences} key for whether or not the "no account" prompt should be shown. */
- private static final String KEY_SHOW_ACCOUNT_PROMPT = "settings.showAccountPrompt";
-
- /**
- * The following intent keys are understood by the {@link AccountManager} and should not be
- * changed unless the API changes.
- */
- private static final String KEY_INTRO_MESSAGE = "introMessage";
- private static final String KEY_ALLOW_SKIP_ACCOUNT_SETUP = "allowSkip";
- private static final String KEY_USER_SKIPPED_ACCOUNT_SETUP = "setupSkipped";
-
- private static SharedPreferences getSharedPreferences(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context);
- }
-
- /**
- * Returns true if the "no account" prompt should be shown
- * (according to {@link SharedPreferences}), otherwise return false. Since this prompt is
- * Google-specific for the time being, this method will also return false if the Google
- * account type is not available from the {@link AccountManager}.
- */
- public static boolean shouldShowAccountPrompt(Context context) {
- // TODO: Remove the filtering of account types once there is an API in
- // {@link AccountManager} to show a similar account prompt
- // (see {@link AccountManager#addAccount()} in {@link #launchAccountPrompt()}
- // for any type of account. Bug: 5375902
- AuthenticatorDescription[] allTypes =
- AccountManager.get(context).getAuthenticatorTypes();
- for (AuthenticatorDescription authenticatorType : allTypes) {
- if (GoogleAccountType.ACCOUNT_TYPE.equals(authenticatorType.type)) {
- return getSharedPreferences(context).getBoolean(KEY_SHOW_ACCOUNT_PROMPT, true);
- }
- }
- return false;
- }
-
- /**
- * Remember to never show the "no account" prompt again by saving this to
- * {@link SharedPreferences}.
- */
- public static void neverShowAccountPromptAgain(Context context) {
- getSharedPreferences(context).edit()
- .putBoolean(KEY_SHOW_ACCOUNT_PROMPT, false)
- .apply();
- }
-
- /**
- * Launch the "no account" prompt. (We assume the caller has already verified that the prompt
- * can be shown, so checking the {@link #KEY_SHOW_ACCOUNT_PROMPT} value in
- * {@link SharedPreferences} will not be done in this method).
- */
- public static void launchAccountPrompt(Activity activity) {
- Bundle options = new Bundle();
- options.putCharSequence(KEY_INTRO_MESSAGE, activity.getString(R.string.no_account_prompt));
- options.putBoolean(KEY_ALLOW_SKIP_ACCOUNT_SETUP, true);
- AccountManager.get(activity).addAccount(GoogleAccountType.ACCOUNT_TYPE, null, null, options,
- activity, getAccountManagerCallback(activity), null);
- }
-
- /**
- * When adding account
- * open the same UI screen for user to choose account
- */
- public static Intent getIntentForAddingAccount() {
- final Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
- intent.putExtra(Settings.EXTRA_AUTHORITIES,
- new String[]{ContactsContract.AUTHORITY});
- return intent;
- }
-
- private static AccountManagerCallback<Bundle> getAccountManagerCallback(
- final Activity activity) {
- return new AccountManagerCallback<Bundle>() {
- @Override
- public void run(AccountManagerFuture<Bundle> future) {
- if (future.isCancelled()) {
- // The account creation process was canceled
- return;
- }
- try {
- Bundle result = future.getResult();
- if (result.getBoolean(KEY_USER_SKIPPED_ACCOUNT_SETUP)) {
- AccountPromptUtils.neverShowAccountPromptAgain(activity);
- }
- } catch (OperationCanceledException ignore) {
- Log.e(TAG, "Account setup error: account creation process canceled");
- } catch (IOException ignore) {
- Log.e(TAG, "Account setup error: No authenticator was registered for this"
- + "account type or the authenticator failed to respond");
- } catch (AuthenticatorException ignore) {
- Log.e(TAG, "Account setup error: Authenticator experienced an I/O problem");
- }
- }
- };
- }
-}