Merge "Remove extra line in status updates"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index df65877..18782b9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -503,6 +503,13 @@
android:windowSoftInputMode="adjustResize"
android:exported="false"/>
+ <!-- Accounts changed prompt that can appear when creating a new contact. -->
+ <activity
+ android:name=".activities.ContactEditorAccountsChangedActivity"
+ android:theme="@style/ContactEditorAccountsChangedActivityTheme"
+ android:windowSoftInputMode="adjustResize"
+ android:exported="false"/>
+
<!-- Create a new or edit an existing contact -->
<activity
android:name=".activities.ContactEditorActivity"
diff --git a/res/layout/account_selector_list_item.xml b/res/layout/account_selector_list_item.xml
index a700866..4cba3e9 100644
--- a/res/layout/account_selector_list_item.xml
+++ b/res/layout/account_selector_list_item.xml
@@ -44,6 +44,7 @@
android:layout_height="wrap_content"
android:layout_marginRight="8dip"
android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
android:singleLine="true"
android:ellipsize="end"/>
</LinearLayout>
diff --git a/res/layout/contact_editor_accounts_changed_activity_with_picker.xml b/res/layout/contact_editor_accounts_changed_activity_with_picker.xml
new file mode 100644
index 0000000..a5aab20
--- /dev/null
+++ b/res/layout/contact_editor_accounts_changed_activity_with_picker.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+ Layout for account prompt (which includes a ListView) that can appear when
+ the user creates a new contact.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="15dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider"/>
+
+ <ListView android:id="@+id/account_list"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:fadingEdge="none"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider"/>
+
+ <Button
+ android:id="@+id/add_account_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+</LinearLayout>
diff --git a/res/layout/contact_editor_accounts_changed_activity_with_text.xml b/res/layout/contact_editor_accounts_changed_activity_with_text.xml
new file mode 100644
index 0000000..33714ea
--- /dev/null
+++ b/res/layout/contact_editor_accounts_changed_activity_with_text.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!--
+ Layout for account prompt (which just includes text and 2 buttons) that can appear when the user
+ creates a new contact.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="15dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/listDivider"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ style="?android:attr/buttonBarStyle">
+
+ <Button
+ android:id="@+id/left_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+
+ <Button
+ android:id="@+id/right_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/group_detail_fragment.xml b/res/layout/group_detail_fragment.xml
index d95a6db..2b020c9 100644
--- a/res/layout/group_detail_fragment.xml
+++ b/res/layout/group_detail_fragment.xml
@@ -40,7 +40,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdge="none"
- android:scrollbarStyle="outsideOverlay"/>
+ android:scrollbarStyle="outsideOverlay"
+ android:divider="@null"/>
<!--
Shadow overlay over the list of group members (since we have a fake stacked
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 5a94324..4decc19 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -156,8 +156,12 @@
<dimen name="contact_browser_list_item_text_indent">8dip</dimen>
<dimen name="contact_browser_list_top_margin">8dip</dimen>
- <!-- ContactTile Layouts -->
- <dimen name="contact_tile_shadowbox_height">48dip</dimen>
+ <!-- ContactTile Layouts -->
+ <!--
+ Use sp instead of dip so that the shadowbox heights can all scale uniformly
+ when the font size is scaled for accessibility purposes
+ -->
+ <dimen name="contact_tile_shadowbox_height">48sp</dimen>
<!-- Call Log -->
<dimen name="call_log_call_action_size">32dip</dimen>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 6a64dfd..e3de72b 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -208,6 +208,12 @@
<item name="android:windowCloseOnTouchOutside">true</item>
</style>
+ <style name="ContactEditorAccountsChangedActivityTheme" parent="@android:style/Theme.Holo.Light.Dialog.NoActionBar.MinWidth">
+ <item name="android:windowCloseOnTouchOutside">true</item>
+ <item name="android:textColorPrimary">@color/primary_text_color</item>
+ <item name="android:textColorSecondary">@color/secondary_text_color</item>
+ </style>
+
<style name="SectionDivider">
<item name="android:background">#7e7e87</item>
<item name="android:layout_height">1dip</item>
diff --git a/src/com/android/contacts/ContactsUtils.java b/src/com/android/contacts/ContactsUtils.java
index 4de62b6..9a3f2ef 100644
--- a/src/com/android/contacts/ContactsUtils.java
+++ b/src/com/android/contacts/ContactsUtils.java
@@ -16,6 +16,7 @@
package com.android.contacts;
+import com.android.contacts.model.AccountType;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.AccountWithDataSet;
import com.android.contacts.test.NeededForTesting;
@@ -172,11 +173,15 @@
return detector.detectCountry().getCountryIso();
}
- public static boolean areAccountsAvailable(Context context) {
+ public static boolean areContactWritableAccountsAvailable(Context context) {
final List<AccountWithDataSet> accounts =
AccountTypeManager.getInstance(context).getAccounts(true /* writeable */);
return !accounts.isEmpty();
}
-
+ public static boolean areGroupWritableAccountsAvailable(Context context) {
+ final List<AccountWithDataSet> accounts =
+ AccountTypeManager.getInstance(context).getGroupWritableAccounts();
+ return !accounts.isEmpty();
+ }
}
diff --git a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
new file mode 100644
index 0000000..3e2a893
--- /dev/null
+++ b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.contacts.activities;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.ContactsContract.Intents;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.contacts.R;
+import com.android.contacts.editor.ContactEditorUtils;
+import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.util.AccountsListAdapter;
+import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
+
+import java.util.List;
+
+/**
+ * This activity can be shown to the user when creating a new contact to inform the user about
+ * which account the contact will be saved in. There is also an option to add an account at
+ * this time. The {@link Intent} in the activity result will contain an extra
+ * {@link #Intents.Insert.ACCOUNT} that contains the {@link AccountWithDataSet} to create
+ * the new contact in. If the activity result doesn't contain intent data, then there is no
+ * account for this contact.
+ */
+public class ContactEditorAccountsChangedActivity extends Activity {
+
+ private static final String TAG = ContactEditorAccountsChangedActivity.class.getSimpleName();
+
+ private static final int SUBACTIVITY_ADD_NEW_ACCOUNT = 1;
+
+ private AccountsListAdapter mAccountListAdapter;
+ private ContactEditorUtils mEditorUtils;
+
+ private final OnItemClickListener mAccountListItemClickListener = new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ if (mAccountListAdapter == null) {
+ return;
+ }
+ saveAccountAndReturnResult(mAccountListAdapter.getItem(position));
+ }
+ };
+
+ private final OnClickListener mAddAccountClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startActivityForResult(mEditorUtils.createAddWritableAccountIntent(),
+ SUBACTIVITY_ADD_NEW_ACCOUNT);
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mEditorUtils = ContactEditorUtils.getInstance(this);
+ final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(this).
+ getAccounts(true);
+ final int numAccounts = accounts.size();
+ if (numAccounts < 0) {
+ throw new IllegalStateException("Cannot have a negative number of accounts");
+ }
+
+ if (numAccounts >= 2) {
+ // When the user has 2+ writable accounts, show a list of accounts so the user can pick
+ // which account to create a contact in.
+ setContentView(R.layout.contact_editor_accounts_changed_activity_with_picker);
+
+ final TextView textView = (TextView) findViewById(R.id.text);
+ textView.setText(getString(R.string.contact_editor_prompt_multiple_accounts));
+
+ final Button button = (Button) findViewById(R.id.add_account_button);
+ button.setText(getString(R.string.add_new_account));
+ button.setOnClickListener(mAddAccountClickListener);
+
+ final ListView accountListView = (ListView) findViewById(R.id.account_list);
+ mAccountListAdapter = new AccountsListAdapter(this,
+ AccountListFilter.ACCOUNTS_CONTACT_WRITABLE);
+ accountListView.setAdapter(mAccountListAdapter);
+ accountListView.setOnItemClickListener(mAccountListItemClickListener);
+ } else if (numAccounts == 1) {
+ // If the user has 1 writable account we will just show the user a message with 2
+ // possible action buttons.
+ setContentView(R.layout.contact_editor_accounts_changed_activity_with_text);
+
+ final TextView textView = (TextView) findViewById(R.id.text);
+ final Button leftButton = (Button) findViewById(R.id.left_button);
+ final Button rightButton = (Button) findViewById(R.id.right_button);
+
+ final AccountWithDataSet account = accounts.get(0);
+ textView.setText(getString(R.string.contact_editor_prompt_one_account,
+ account.name));
+
+ // This button allows the user to add a new account to the device and return to
+ // this app afterwards.
+ leftButton.setText(getString(R.string.add_new_account));
+ leftButton.setOnClickListener(mAddAccountClickListener);
+
+ // This button allows the user to continue creating the contact in the specified
+ // account.
+ rightButton.setText(getString(android.R.string.ok));
+ rightButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ saveAccountAndReturnResult(account);
+ }
+ });
+ } else {
+ // If the user has 0 writable accounts, we will just show the user a message with 2
+ // possible action buttons.
+ setContentView(R.layout.contact_editor_accounts_changed_activity_with_text);
+
+ final TextView textView = (TextView) findViewById(R.id.text);
+ final Button leftButton = (Button) findViewById(R.id.left_button);
+ final Button rightButton = (Button) findViewById(R.id.right_button);
+
+ textView.setText(getString(R.string.contact_editor_prompt_zero_accounts));
+
+ // This button allows the user to continue editing the contact as a phone-only
+ // local contact.
+ leftButton.setText(getString(R.string.keep_local));
+ leftButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Remember that the user wants to create local contacts, so the user is not
+ // prompted again with this activity.
+ mEditorUtils.saveDefaultAndAllAccounts(null);
+ setResult(RESULT_OK);
+ finish();
+ }
+ });
+
+ // This button allows the user to add a new account to the device and return to
+ // this app afterwards.
+ rightButton.setText(getString(R.string.add_account));
+ rightButton.setOnClickListener(mAddAccountClickListener);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == SUBACTIVITY_ADD_NEW_ACCOUNT) {
+ // If the user canceled the account setup process, then keep this activity visible to
+ // the user.
+ if (resultCode != RESULT_OK) {
+ return;
+ }
+ // Subactivity was successful, so pass the result back and finish the activity.
+ AccountWithDataSet account = mEditorUtils.getCreatedAccount(resultCode, data);
+ if (account == null) {
+ setResult(resultCode);
+ finish();
+ return;
+ }
+ saveAccountAndReturnResult(account);
+ }
+ }
+
+ private void saveAccountAndReturnResult(AccountWithDataSet account) {
+ // Save this as the default account
+ mEditorUtils.saveDefaultAndAllAccounts(account);
+
+ // Pass account info in activity result intent
+ Intent intent = new Intent();
+ intent.putExtra(Intents.Insert.ACCOUNT, account);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 3430109..b088b89 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -56,6 +56,7 @@
import com.android.contacts.util.AccountPromptUtils;
import com.android.contacts.util.AccountSelectionUtil;
import com.android.contacts.util.AccountsListAdapter;
+import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
import com.android.contacts.util.Constants;
import com.android.contacts.util.DialogManager;
import com.android.contacts.util.PhoneCapabilityTester;
@@ -200,10 +201,13 @@
return mProviderStatus == ProviderStatus.STATUS_NORMAL;
}
- private boolean areAccountsAvailable() {
- return ContactsUtils.areAccountsAvailable(this);
+ private boolean areContactWritableAccountsAvailable() {
+ return ContactsUtils.areContactWritableAccountsAvailable(this);
}
+ private boolean areGroupWritableAccountsAvailable() {
+ return ContactsUtils.areGroupWritableAccountsAvailable(this);
+ }
/**
* Initialize fragments that are (or may not be) in the layout.
@@ -604,7 +608,7 @@
invalidateOptionsMenu();
showEmptyStateForTab(tab);
if (tab == TabState.GROUPS) {
- mGroupsFragment.setAddAccountsVisibility(!areAccountsAvailable());
+ mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
}
return;
}
@@ -625,7 +629,7 @@
mFavoritesView.setVisibility(View.GONE);
mBrowserView.setVisibility(View.VISIBLE);
mDetailsView.setVisibility(View.VISIBLE);
- mGroupsFragment.setAddAccountsVisibility(!areAccountsAvailable());
+ mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
break;
case ALL:
mFavoritesView.setVisibility(View.GONE);
@@ -686,7 +690,7 @@
break;
case GROUPS:
mContactsUnavailableFragment.setMessageText(R.string.noGroups,
- areAccountsAvailable() ? -1 : R.string.noAccounts);
+ areGroupWritableAccountsAvailable() ? -1 : R.string.noAccounts);
break;
case ALL:
mContactsUnavailableFragment.setMessageText(R.string.noContacts, -1);
@@ -712,7 +716,7 @@
mActionBarAdapter.setCurrentTab(selectedTab, false);
showEmptyStateForTab(selectedTab);
if (selectedTab == TabState.GROUPS) {
- mGroupsFragment.setAddAccountsVisibility(!areAccountsAvailable());
+ mGroupsFragment.setAddAccountsVisibility(!areGroupWritableAccountsAvailable());
}
invalidateOptionsMenu();
}
@@ -922,7 +926,8 @@
// 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.
- if (!areAccountsAvailable() && AccountPromptUtils.shouldShowAccountPrompt(this)) {
+ if (!areContactWritableAccountsAvailable() &&
+ AccountPromptUtils.shouldShowAccountPrompt(this)) {
AccountPromptUtils.launchAccountPrompt(this);
return;
}
@@ -1304,7 +1309,7 @@
break;
case GROUPS:
// Do not display the "new group" button if no accounts are available
- if (areAccountsAvailable()) {
+ if (areGroupWritableAccountsAvailable()) {
addGroupMenu.setVisible(true);
} else {
addGroupMenu.setVisible(false);
@@ -1410,7 +1415,8 @@
popup.setAnchorView(mAddGroupImageView);
// Create a list adapter with all writeable accounts (assume that the writeable accounts all
// allow group creation).
- final AccountsListAdapter adapter = new AccountsListAdapter(this, true);
+ final AccountsListAdapter adapter = new AccountsListAdapter(this,
+ AccountListFilter.ACCOUNTS_GROUP_WRITABLE);
popup.setAdapter(adapter);
popup.setOnItemClickListener(new OnItemClickListener() {
@Override
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 2d93a98..0628db4 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -337,7 +337,15 @@
@Override
public void onVisibilityChanged(boolean visible) {
- mShowOptionsMenu = visible;
+ if (mShowOptionsMenu != visible) {
+ mShowOptionsMenu = visible;
+ // Invalidate the options menu since we are changing the list of options shown in it.
+ Activity activity = getActivity();
+ if (activity != null) {
+ activity.invalidateOptionsMenu();
+ }
+ }
+
if (visible && isResumed()) {
refreshData();
}
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index 412ceff..e9fbbbb 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -44,6 +44,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.provider.Contacts.Intents.Insert;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
@@ -882,9 +883,13 @@
} else {
final String number = mDigits.getText().toString();
+ // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated
+ // test equipment.
+ // TODO: clean it up.
if (number != null
&& !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp)
- && number.matches(mProhibitedPhoneNumberRegexp)) {
+ && number.matches(mProhibitedPhoneNumberRegexp)
+ && (SystemProperties.getInt("persist.radio.otaspdial", 0) != 1)) {
Log.i(TAG, "The phone number is prohibited explicitly by a rule.");
if (getActivity() != null) {
DialogFragment dialogFragment = CallProhibitedDialogFragment.newInstance();
diff --git a/src/com/android/contacts/dialpad/DigitsEditText.java b/src/com/android/contacts/dialpad/DigitsEditText.java
index 753afd4..68335da 100644
--- a/src/com/android/contacts/dialpad/DigitsEditText.java
+++ b/src/com/android/contacts/dialpad/DigitsEditText.java
@@ -59,9 +59,22 @@
@Override
public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED) {
- // AsYouTypeFormatter frequently replaces digits with formatted ones, which makes
- // tts too verbose. Let's ignore the whole event.
- return;
+ // Since we're replacing the text every time we add or remove a
+ // character, only read the difference. (issue 5337550)
+ final int added = event.getAddedCount();
+ final int removed = event.getRemovedCount();
+ final int length = event.getBeforeText().length();
+ if (added > removed) {
+ event.setRemovedCount(0);
+ event.setAddedCount(1);
+ event.setFromIndex(length);
+ } else if (removed > added) {
+ event.setRemovedCount(1);
+ event.setAddedCount(0);
+ event.setFromIndex(length - 1);
+ } else {
+ return;
+ }
} else if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
// The parent EditText class lets tts read "edit box" when this View has a focus, which
// confuses users on app launch (issue 5275935).
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index a8c0b36..b341f83 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -20,6 +20,7 @@
import com.android.contacts.ContactSaveService;
import com.android.contacts.GroupMetaDataLoader;
import com.android.contacts.R;
+import com.android.contacts.activities.ContactEditorAccountsChangedActivity;
import com.android.contacts.activities.ContactEditorActivity;
import com.android.contacts.activities.JoinContactActivity;
import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion;
@@ -33,6 +34,7 @@
import com.android.contacts.model.EntityModifier;
import com.android.contacts.model.GoogleAccountType;
import com.android.contacts.util.AccountsListAdapter;
+import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
import android.accounts.Account;
import android.app.Activity;
@@ -192,6 +194,7 @@
private static final int REQUEST_CODE_JOIN = 0;
private static final int REQUEST_CODE_CAMERA_WITH_DATA = 1;
private static final int REQUEST_CODE_PHOTO_PICKED_WITH_DATA = 2;
+ private static final int REQUEST_CODE_ACCOUNTS_CHANGED = 3;
private Bitmap mPhoto = null;
private long mRawContactIdRequestingPhoto = -1;
@@ -218,6 +221,8 @@
private long mContactIdForJoin;
private boolean mContactWritableForJoin;
+ private ContactEditorUtils mEditorUtils;
+
private LinearLayout mContent;
private EntityDeltaList mState;
@@ -316,6 +321,7 @@
public void onAttach(Activity activity) {
super.onAttach(activity);
mContext = activity;
+ mEditorUtils = ContactEditorUtils.getInstance(mContext);
loadPhotoPickSize();
}
@@ -370,7 +376,7 @@
} else {
// No Account specified. Let the user choose
// Load Accounts async so that we can present them
- createContact();
+ selectAccountAndCreateContact();
}
} else if (ContactEditorActivity.ACTION_SAVE_COMPLETED.equals(mAction)) {
// do nothing
@@ -528,17 +534,43 @@
}
}
+ private void selectAccountAndCreateContact() {
+ // If this is a local profile, then skip the logic about showing the accounts changed
+ // activity and create a phone-local contact.
+ if (mNewLocalProfile) {
+ createContact(null);
+ return;
+ }
+
+ // If there is no default account or the accounts have changed such that we need to
+ // prompt the user again, then launch the account prompt.
+ if (mEditorUtils.shouldShowAccountChangedNotification()) {
+ Intent intent = new Intent(mContext, ContactEditorAccountsChangedActivity.class);
+ mStatus = Status.SUB_ACTIVITY;
+ startActivityForResult(intent, REQUEST_CODE_ACCOUNTS_CHANGED);
+ } else {
+ // Otherwise, there should be a default account. Then either create a local contact
+ // (if default account is null) or create a contact with the specified account.
+ AccountWithDataSet defaultAccount = mEditorUtils.getDefaultAccount();
+ if (defaultAccount == null) {
+ createContact(null);
+ } else {
+ createContact(defaultAccount);
+ }
+ }
+ }
+
/**
- * Shows the account creation screen. An account associated with the contact is automatically
- * selected. If there's no available account, device-local contact should be created.
+ * Create a contact by automatically selecting the first account. If there's no available
+ * account, a device-local contact should be created.
*/
private void createContact() {
final List<AccountWithDataSet> accounts =
AccountTypeManager.getInstance(mContext).getAccounts(true);
- // No Accounts available or creating a local profile. Create a phone-local contact.
- if (accounts.isEmpty() || mNewLocalProfile) {
+ // No Accounts available. Create a phone-local contact.
+ if (accounts.isEmpty()) {
createContact(null);
- return; // Don't show a dialog.
+ return;
}
// We have an account switcher in "create-account" screen, so don't need to ask a user to
@@ -546,7 +578,6 @@
createContact(accounts.get(0));
}
-
/**
* Shows account creation screen associated with a given account.
*
@@ -760,6 +791,28 @@
}
}
+ private void saveDefaultAccountIfNecessary() {
+ // Verify that this is a newly created contact, that the contact is composed of only
+ // 1 raw contact, and that the contact is not a user profile.
+ if (!Intent.ACTION_INSERT.equals(mAction) && mState.size() == 1 &&
+ !isEditingUserProfile()) {
+ return;
+ }
+
+ // Find the associated account for this contact (retrieve it here because there are
+ // multiple paths to creating a contact and this ensures we always have the correct
+ // account).
+ final EntityDelta entity = mState.get(0);
+ final ValuesDelta values = entity.getValues();
+ String name = values.getAsString(RawContacts.ACCOUNT_NAME);
+ String type = values.getAsString(RawContacts.ACCOUNT_TYPE);
+ String dataSet = values.getAsString(RawContacts.DATA_SET);
+
+ AccountWithDataSet account = (name == null || type == null) ? null :
+ new AccountWithDataSet(name, type, dataSet);
+ mEditorUtils.saveDefaultAndAllAccounts(account);
+ }
+
private void addAccountSwitcher(
final EntityDelta currentState, BaseRawContactEditorView editor) {
ValuesDelta values = currentState.getValues();
@@ -774,7 +827,8 @@
public void onClick(View v) {
final ListPopupWindow popup = new ListPopupWindow(mContext, null);
final AccountsListAdapter adapter =
- new AccountsListAdapter(mContext, true, currentAccount);
+ new AccountsListAdapter(mContext,
+ AccountListFilter.ACCOUNTS_CONTACT_WRITABLE, currentAccount);
popup.setWidth(anchorView.getWidth());
popup.setAnchorView(anchorView);
popup.setAdapter(adapter);
@@ -985,6 +1039,10 @@
setEnabled(false);
+ // Store account as default account, only if this is a new contact
+ saveDefaultAccountIfNecessary();
+
+ // Save contact
Intent intent = ContactSaveService.createSaveContactIntent(getActivity(), mState,
SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(),
getActivity().getClass(), ContactEditorActivity.ACTION_SAVE_COMPLETED);
@@ -1536,10 +1594,10 @@
mStatus = Status.EDITING;
}
- // Ignore failed requests
- if (resultCode != Activity.RESULT_OK) return;
switch (requestCode) {
case REQUEST_CODE_PHOTO_PICKED_WITH_DATA: {
+ // Ignore failed requests
+ if (resultCode != Activity.RESULT_OK) return;
// As we are coming back to this view, the editor will be reloaded automatically,
// which will cause the photo that is set here to disappear. To prevent this,
// we remember to set a flag which is interpreted after loading.
@@ -1552,16 +1610,40 @@
break;
}
case REQUEST_CODE_CAMERA_WITH_DATA: {
+ // Ignore failed requests
+ if (resultCode != Activity.RESULT_OK) return;
doCropPhoto(mCurrentPhotoFile);
break;
}
case REQUEST_CODE_JOIN: {
+ // Ignore failed requests
+ if (resultCode != Activity.RESULT_OK) return;
if (data != null) {
final long contactId = ContentUris.parseId(data.getData());
joinAggregate(contactId);
}
break;
}
+ case REQUEST_CODE_ACCOUNTS_CHANGED: {
+ // Bail if the account selector was not successful.
+ if (resultCode != Activity.RESULT_OK) {
+ mListener.onReverted();
+ return;
+ }
+ // If there's an account specified, use it.
+ if (data != null) {
+ AccountWithDataSet account = data.getParcelableExtra(Intents.Insert.ACCOUNT);
+ if (account != null) {
+ createContact(account);
+ return;
+ }
+ }
+ // If there isn't an account specified, then this is likely a phone-local
+ // contact, so we should continue setting up the editor by automatically selecting
+ // the most appropriate account.
+ createContact();
+ break;
+ }
}
}
diff --git a/src/com/android/contacts/editor/SelectAccountDialogFragment.java b/src/com/android/contacts/editor/SelectAccountDialogFragment.java
index 9dbe20a..3a8681a 100644
--- a/src/com/android/contacts/editor/SelectAccountDialogFragment.java
+++ b/src/com/android/contacts/editor/SelectAccountDialogFragment.java
@@ -19,6 +19,7 @@
import com.android.contacts.R;
import com.android.contacts.model.AccountWithDataSet;
import com.android.contacts.util.AccountsListAdapter;
+import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -37,13 +38,18 @@
public class SelectAccountDialogFragment extends DialogFragment {
public static final String TAG = "SelectAccountDialogFragment";
- private int mTitleResourceId = R.string.dialog_new_contact_account;
+ // TODO: This dialog is used in the context of group editing by default, but should be generic
+ // to work for contact editing as well. Save/restore the resource ID and account list filter
+ // that are passed in as parameters on device rotation. Bug: 5369853
+ private int mTitleResourceId = R.string.dialog_new_group_account;
+ private AccountListFilter mAccountListFilter = AccountListFilter.ACCOUNTS_GROUP_WRITABLE;
public SelectAccountDialogFragment() {
}
- public SelectAccountDialogFragment(int titleResourceId) {
+ public SelectAccountDialogFragment(int titleResourceId, AccountListFilter accountListFilter) {
mTitleResourceId = titleResourceId;
+ mAccountListFilter = accountListFilter;
}
@Override
@@ -51,7 +57,7 @@
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
final AccountsListAdapter accountAdapter = new AccountsListAdapter(builder.getContext(),
- true);
+ mAccountListFilter);
final DialogInterface.OnClickListener clickListener =
new DialogInterface.OnClickListener() {
diff --git a/src/com/android/contacts/group/GroupBrowseListFragment.java b/src/com/android/contacts/group/GroupBrowseListFragment.java
index 82539e8..79bdd09 100644
--- a/src/com/android/contacts/group/GroupBrowseListFragment.java
+++ b/src/com/android/contacts/group/GroupBrowseListFragment.java
@@ -144,7 +144,7 @@
startActivity(intent);
}
});
- setAddAccountsVisibility(!ContactsUtils.areAccountsAvailable(mContext));
+ setAddAccountsVisibility(!ContactsUtils.areGroupWritableAccountsAvailable(mContext));
return mRootView;
}
@@ -214,7 +214,7 @@
private void bindGroupList() {
mEmptyView.setText(R.string.noGroups);
- setAddAccountsVisibility(!ContactsUtils.areAccountsAvailable(mContext));
+ setAddAccountsVisibility(!ContactsUtils.areGroupWritableAccountsAvailable(mContext));
if (mGroupListCursor == null) {
return;
}
diff --git a/src/com/android/contacts/group/GroupEditorFragment.java b/src/com/android/contacts/group/GroupEditorFragment.java
index 99e6b48..1d1237e 100644
--- a/src/com/android/contacts/group/GroupEditorFragment.java
+++ b/src/com/android/contacts/group/GroupEditorFragment.java
@@ -28,6 +28,7 @@
import com.android.contacts.model.AccountType;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
import com.android.internal.util.Objects;
import android.accounts.Account;
@@ -332,7 +333,7 @@
mStatus = Status.SELECTING_ACCOUNT;
final SelectAccountDialogFragment dialog = new SelectAccountDialogFragment(
- R.string.dialog_new_group_account);
+ R.string.dialog_new_group_account, AccountListFilter.ACCOUNTS_GROUP_WRITABLE);
dialog.setTargetFragment(this, 0);
dialog.show(getFragmentManager(), SelectAccountDialogFragment.TAG);
}
diff --git a/src/com/android/contacts/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
index 938d43a..015a364 100644
--- a/src/com/android/contacts/list/PhoneNumberPickerFragment.java
+++ b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
@@ -55,6 +55,9 @@
private static final String KEY_FILTER = "filter";
+ /** true if the loader has started at least once. */
+ private boolean mLoaderStarted;
+
// A complete copy from DefaultContactBrowserListFragment
// TODO: should be able to share logic around filter header.
private class FilterHeaderClickListener implements OnClickListener {
@@ -204,6 +207,12 @@
}
@Override
+ protected void startLoading() {
+ mLoaderStarted = true;
+ super.startLoading();
+ }
+
+ @Override
protected ContactEntryListAdapter createListAdapter() {
if (!isLegacyCompatibilityMode()) {
PhoneNumberListAdapter adapter = new PhoneNumberListAdapter(getActivity());
@@ -272,7 +281,11 @@
ContactListFilter.storeToPreferences(mPrefs, mFilter);
}
- reloadData();
+ // This method can be called before {@link #onStart} where we start the loader. In that
+ // case we shouldn't start the loader yet, as we haven't done all initialization yet.
+ if (mLoaderStarted) {
+ reloadData();
+ }
updateFilterHeaderView();
}
}
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index dc2fb0d..5443196 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -85,7 +85,17 @@
return new AccountTypeManagerImpl(context);
}
- public abstract List<AccountWithDataSet> getAccounts(boolean writableOnly);
+ /**
+ * Returns the list of all accounts (if contactWritableOnly is false) or just the list of
+ * contact writable accounts (if contactWritableOnly is true).
+ */
+ // TODO: Consider splitting this into getContactWritableAccounts() and getAllAccounts()
+ public abstract List<AccountWithDataSet> getAccounts(boolean contactWritableOnly);
+
+ /**
+ * Returns the list of accounts that are group writable.
+ */
+ public abstract List<AccountWithDataSet> getGroupWritableAccounts();
public abstract AccountType getAccountType(AccountTypeWithDataSet accountTypeWithDataSet);
@@ -130,7 +140,8 @@
private AccountType mFallbackAccountType;
private List<AccountWithDataSet> mAccounts = Lists.newArrayList();
- private List<AccountWithDataSet> mWritableAccounts = Lists.newArrayList();
+ private List<AccountWithDataSet> mContactWritableAccounts = Lists.newArrayList();
+ private List<AccountWithDataSet> mGroupWritableAccounts = Lists.newArrayList();
private Map<AccountTypeWithDataSet, AccountType> mAccountTypesWithDataSets = Maps.newHashMap();
private Map<AccountTypeWithDataSet, AccountType> mInvitableAccountTypes =
Collections.unmodifiableMap(new HashMap<AccountTypeWithDataSet, AccountType>());
@@ -288,16 +299,18 @@
final long startTimeWall = SystemClock.elapsedRealtime();
// Account types, keyed off the account type and data set concatenation.
- Map<AccountTypeWithDataSet, AccountType> accountTypesByTypeAndDataSet = Maps.newHashMap();
+ final Map<AccountTypeWithDataSet, AccountType> accountTypesByTypeAndDataSet =
+ Maps.newHashMap();
// The same AccountTypes, but keyed off {@link RawContacts#ACCOUNT_TYPE}. Since there can
// be multiple account types (with different data sets) for the same type of account, each
// type string may have multiple AccountType entries.
- Map<String, List<AccountType>> accountTypesByType = Maps.newHashMap();
+ final Map<String, List<AccountType>> accountTypesByType = Maps.newHashMap();
- List<AccountWithDataSet> allAccounts = Lists.newArrayList();
- List<AccountWithDataSet> writableAccounts = Lists.newArrayList();
- Set<String> extensionPackages = Sets.newHashSet();
+ final List<AccountWithDataSet> allAccounts = Lists.newArrayList();
+ final List<AccountWithDataSet> contactWritableAccounts = Lists.newArrayList();
+ final List<AccountWithDataSet> groupWritableAccounts = Lists.newArrayList();
+ final Set<String> extensionPackages = Sets.newHashSet();
final AccountManager am = mAccountManager;
final IContentService cs = ContentResolver.getContentService();
@@ -402,7 +415,10 @@
account.name, account.type, accountType.dataSet);
allAccounts.add(accountWithDataSet);
if (accountType.areContactsWritable()) {
- writableAccounts.add(accountWithDataSet);
+ contactWritableAccounts.add(accountWithDataSet);
+ }
+ if (accountType.isGroupMembershipEditable()) {
+ groupWritableAccounts.add(accountWithDataSet);
}
}
}
@@ -410,14 +426,16 @@
}
Collections.sort(allAccounts, ACCOUNT_COMPARATOR);
- Collections.sort(writableAccounts, ACCOUNT_COMPARATOR);
+ Collections.sort(contactWritableAccounts, ACCOUNT_COMPARATOR);
+ Collections.sort(groupWritableAccounts, ACCOUNT_COMPARATOR);
timings.addSplit("Loaded accounts");
synchronized (this) {
mAccountTypesWithDataSets = accountTypesByTypeAndDataSet;
mAccounts = allAccounts;
- mWritableAccounts = writableAccounts;
+ mContactWritableAccounts = contactWritableAccounts;
+ mGroupWritableAccounts = groupWritableAccounts;
mInvitableAccountTypes = findInvitableAccountTypes(
mContext, allAccounts, accountTypesByTypeAndDataSet);
}
@@ -467,12 +485,20 @@
}
/**
- * Return list of all known, writable {@link AccountWithDataSet}'s.
+ * Return list of all known, contact writable {@link AccountWithDataSet}'s.
*/
@Override
- public List<AccountWithDataSet> getAccounts(boolean writableOnly) {
+ public List<AccountWithDataSet> getAccounts(boolean contactWritableOnly) {
ensureAccountsLoaded();
- return writableOnly ? mWritableAccounts : mAccounts;
+ return contactWritableOnly ? mContactWritableAccounts : mAccounts;
+ }
+
+ /**
+ * Return the list of all known, group writable {@link AccountWithDataSet}'s.
+ */
+ public List<AccountWithDataSet> getGroupWritableAccounts() {
+ ensureAccountsLoaded();
+ return mGroupWritableAccounts;
}
/**
diff --git a/src/com/android/contacts/util/AccountsListAdapter.java b/src/com/android/contacts/util/AccountsListAdapter.java
index d065255..058cf84 100644
--- a/src/com/android/contacts/util/AccountsListAdapter.java
+++ b/src/com/android/contacts/util/AccountsListAdapter.java
@@ -42,20 +42,28 @@
private final AccountTypeManager mAccountTypes;
private final Context mContext;
- public AccountsListAdapter(Context context, boolean writableOnly) {
- this(context, writableOnly, null);
+ /**
+ * Filters that affect the list of accounts that is displayed by this adapter.
+ */
+ public enum AccountListFilter {
+ ALL_ACCOUNTS, // All read-only and writable accounts
+ ACCOUNTS_CONTACT_WRITABLE, // Only where the account type is contact writable
+ ACCOUNTS_GROUP_WRITABLE // Only accounts where the account type is group writable
+ }
+
+ public AccountsListAdapter(Context context, AccountListFilter accountListFilter) {
+ this(context, accountListFilter, null);
}
/**
* @param currentAccount the Account currently selected by the user, which should come
* first in the list. Can be null.
*/
- public AccountsListAdapter(Context context, boolean writableOnly,
+ public AccountsListAdapter(Context context, AccountListFilter accountListFilter,
AccountWithDataSet currentAccount) {
mContext = context;
mAccountTypes = AccountTypeManager.getInstance(context);
- // We don't want possible side-effect toward AccountTypeManager
- mAccounts = new ArrayList<AccountWithDataSet>(mAccountTypes.getAccounts(writableOnly));
+ mAccounts = getAccounts(accountListFilter);
if (currentAccount != null
&& !mAccounts.isEmpty()
&& !mAccounts.get(0).equals(currentAccount)
@@ -65,24 +73,33 @@
mInflater = LayoutInflater.from(context);
}
+ private List<AccountWithDataSet> getAccounts(AccountListFilter accountListFilter) {
+ if (accountListFilter == AccountListFilter.ACCOUNTS_GROUP_WRITABLE) {
+ return new ArrayList<AccountWithDataSet>(mAccountTypes.getGroupWritableAccounts());
+ }
+ return new ArrayList<AccountWithDataSet>(mAccountTypes.getAccounts(
+ accountListFilter == AccountListFilter.ACCOUNTS_CONTACT_WRITABLE));
+ }
+
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final View resultView = convertView != null ? convertView
: mInflater.inflate(R.layout.account_selector_list_item, parent, false);
- final TextView text1 = (TextView)resultView.findViewById(android.R.id.text1);
- final TextView text2 = (TextView)resultView.findViewById(android.R.id.text2);
- final ImageView icon = (ImageView)resultView.findViewById(android.R.id.icon);
+ final TextView text1 = (TextView) resultView.findViewById(android.R.id.text1);
+ final TextView text2 = (TextView) resultView.findViewById(android.R.id.text2);
+ final ImageView icon = (ImageView) resultView.findViewById(android.R.id.icon);
final AccountWithDataSet account = mAccounts.get(position);
final AccountType accountType = mAccountTypes.getAccountType(account.type, account.dataSet);
- text1.setText(account.name);
+ text1.setText(accountType.getDisplayLabel(mContext));
// For email addresses, we don't want to truncate at end, which might cut off the domain
// name.
- text1.setEllipsize(TruncateAt.MIDDLE);
- text2.setText(accountType.getDisplayLabel(mContext));
+ text2.setText(account.name);
+ text2.setEllipsize(TruncateAt.MIDDLE);
+
icon.setImageDrawable(accountType.getDisplayIcon(mContext));
return resultView;
diff --git a/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java b/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
index 3b712c7..5ca1ccd 100644
--- a/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
+++ b/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
@@ -59,6 +59,11 @@
}
@Override
+ public List<AccountWithDataSet> getGroupWritableAccounts() {
+ return Arrays.asList(mAccounts);
+ }
+
+ @Override
public Map<AccountTypeWithDataSet, AccountType> getInvitableAccountTypes() {
return Maps.newHashMap(); // Always returns empty
}