Merge "Make photo loader low-memory device friendly" into ics-mr1
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2877820..11b9cde 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -44,6 +44,7 @@
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
<uses-permission android:name="com.android.voicemail.permission.READ_WRITE_ALL_VOICEMAIL" />
+ <uses-permission android:name="android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK" />
<!-- allow broadcasting secret code intents that reboot the phone -->
<uses-permission android:name="android.permission.REBOOT" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
diff --git a/res/layout-sw580dp/contact_picker.xml b/res/layout-sw580dp/contact_picker.xml
index c6582e0..127021c 100644
--- a/res/layout-sw580dp/contact_picker.xml
+++ b/res/layout-sw580dp/contact_picker.xml
@@ -20,19 +20,32 @@
android:orientation="vertical"
android:layout_height="match_parent">
<!-- Right bound should be aligned to ListView's right edge. -->
+ <!--
+ The SearchView should have a max width to prevent the dialog from resizing to the
+ full screen width of the device. The precise value of the max width is not as important
+ because the SearchView can take on a smaller width than the max width, so in some cases it
+ will take on the automatically computed width of a dialog (based on the dialog contents)
+ from the framework.
+ -->
<view
class="android.widget.SearchView"
android:id="@+id/search_view"
android:layout_width="match_parent"
+ android:maxWidth="@dimen/contact_picker_search_view_max_width"
android:layout_height="wrap_content"
android:layout_marginLeft="0dip"
android:layout_marginRight="@dimen/list_visible_scrollbar_padding"
android:paddingRight="0dip"
android:iconifiedByDefault="false" />
- <!-- will contain an appropriate contacts list -->
+ <!--
+ This will contain an appropriate contacts list. Add a min height to prevent
+ the dialog from resizing too much when the search results change. The activity dialog
+ is wrap content for height in the framework, so there is no way around this.
+ -->
<FrameLayout
android:id="@+id/list_container"
android:layout_width="match_parent"
+ android:minHeight="@dimen/contact_picker_contact_list_min_height"
android:layout_height="0dip"
android:layout_weight="1" />
diff --git a/res/layout/call_log_fragment.xml b/res/layout/call_log_fragment.xml
index 4735d03..f87e981 100644
--- a/res/layout/call_log_fragment.xml
+++ b/res/layout/call_log_fragment.xml
@@ -33,7 +33,7 @@
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
>
<ListView android:id="@android:id/list"
android:layout_width="match_parent"
diff --git a/res/layout/dialtacts_activity.xml b/res/layout/dialtacts_activity.xml
index af85bba..d1af632 100644
--- a/res/layout/dialtacts_activity.xml
+++ b/res/layout/dialtacts_activity.xml
@@ -17,18 +17,11 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginTop="?android:attr/actionBarSize">
-
+ android:layout_marginTop="?android:attr/actionBarSize"
+ android:id="@+id/dialtacts_frame"
+ >
<com.android.contacts.activities.DialtactsViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
-
- <!-- For phone search UI -->
- <fragment
- android:id="@+id/phone_number_picker_fragment"
- class="com.android.contacts.list.PhoneNumberPickerFragment"
- android:layout_height="match_parent"
- android:layout_width="match_parent" />
-
</FrameLayout>
diff --git a/res/values-sw680dp/dimens.xml b/res/values-sw680dp/dimens.xml
index 0876995..0bed1ae 100644
--- a/res/values-sw680dp/dimens.xml
+++ b/res/values-sw680dp/dimens.xml
@@ -19,4 +19,5 @@
<dimen name="editor_round_button_padding_right">8dip</dimen>
<dimen name="group_editor_side_padding">16dip</dimen>
<dimen name="quick_contact_photo_container_height">400dip</dimen>
+ <dimen name="contact_picker_contact_list_min_height">650dip</dimen>
</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 9f5766e..e856d67 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -234,4 +234,12 @@
Right now the drawable has implicit 32dip minimal height, which is confusing.
This value is for making the hidden configuration explicit in xml. -->
<dimen name="list_section_divider_min_height">32dip</dimen>
+
+ <!-- Max width of the SearchView when the contact picker is a dialog (on wide
+ screen devices). -->
+ <dimen name="contact_picker_search_view_max_width">550dip</dimen>
+
+ <!-- Min height of the list of contacts when the contact picker is a dialog (on
+ wide screen devices). -->
+ <dimen name="contact_picker_contact_list_min_height">550dip</dimen>
</resources>
diff --git a/src/com/android/contacts/activities/ActionBarAdapter.java b/src/com/android/contacts/activities/ActionBarAdapter.java
index 77caaf7..b90d4c6 100644
--- a/src/com/android/contacts/activities/ActionBarAdapter.java
+++ b/src/com/android/contacts/activities/ActionBarAdapter.java
@@ -374,6 +374,18 @@
outState.putInt(EXTRA_KEY_SELECTED_TAB, mCurrentTab.ordinal());
}
+ /**
+ * Clears the focus from the {@link SearchView} if we are in search mode.
+ * This will suppress the IME if it is visible.
+ */
+ public void clearFocusOnSearchView() {
+ if (isSearchMode()) {
+ if (mSearchView != null) {
+ mSearchView.clearFocus();
+ }
+ }
+ }
+
private void setFocusOnSearchView() {
mSearchView.requestFocus();
mSearchView.setIconified(false); // Workaround for the "IME not popping up" issue.
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index 1a8e383..b949176 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -57,15 +57,16 @@
private static final String TAG = "ContactDetailActivity";
/**
- * Intent key for a boolean that specifies whether the "up" afforance in this activity should
- * behave as default (return user back to {@link PeopleActivity}) or whether the activity should
- * instead be finished.
+ * Boolean intent key that specifies whether pressing the "up" affordance in this activity
+ * should cause it to finish itself or launch an intent to bring the user back to a specific
+ * parent activity - the {@link PeopleActivity}.
*/
- public static final String INTENT_KEY_IGNORE_DEFAULT_UP_BEHAVIOR = "ignoreDefaultUpBehavior";
+ public static final String INTENT_KEY_FINISH_ACTIVITY_ON_UP_SELECTED =
+ "finishActivityOnUpSelected";
private ContactLoader.Result mContactData;
private Uri mLookupUri;
- private boolean mIgnoreDefaultUpBehavior;
+ private boolean mFinishActivityOnUpSelected;
private ContactDetailLayoutController mContactDetailLayoutController;
private ContactLoaderFragment mLoaderFragment;
@@ -92,8 +93,8 @@
return;
}
- mIgnoreDefaultUpBehavior = getIntent().getBooleanExtra(
- INTENT_KEY_IGNORE_DEFAULT_UP_BEHAVIOR, false);
+ mFinishActivityOnUpSelected = getIntent().getBooleanExtra(
+ INTENT_KEY_FINISH_ACTIVITY_ON_UP_SELECTED, false);
setContentView(R.layout.contact_detail_activity);
@@ -211,8 +212,13 @@
@Override
public void onEditRequested(Uri contactLookupUri) {
- startActivity(new Intent(Intent.ACTION_EDIT, contactLookupUri));
- finish();
+ Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri);
+ intent.putExtra(
+ ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, true);
+ // Don't finish the detail activity after launching the editor because when the
+ // editor is done, we will still want to show the updated contact details using
+ // this activity.
+ startActivity(intent);
}
@Override
@@ -285,7 +291,7 @@
switch (item.getItemId()) {
case android.R.id.home:
- if (mIgnoreDefaultUpBehavior) {
+ if (mFinishActivityOnUpSelected) {
finish();
return true;
}
diff --git a/src/com/android/contacts/activities/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
index 07f340e..d591b42 100644
--- a/src/com/android/contacts/activities/ContactEditorActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -50,14 +50,31 @@
public static final String ACTION_JOIN_COMPLETED = "joinCompleted";
public static final String ACTION_SAVE_COMPLETED = "saveCompleted";
+ /**
+ * Boolean intent key that specifies that this activity should finish itself
+ * (instead of launching a new view intent) after the editor changes have been
+ * saved.
+ */
+ public static final String INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED =
+ "finishActivityOnSaveCompleted";
+
private ContactEditorFragment mFragment;
+ private boolean mFinishActivityOnSaveCompleted;
private DialogManager mDialogManager = new DialogManager(this);
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
- String action = getIntent().getAction();
+
+ final Intent intent = getIntent();
+ final String action = intent.getAction();
+
+ // Determine whether or not this activity should be finished after the user is done
+ // editing the contact or if this activity should launch another activity to view the
+ // contact's details.
+ mFinishActivityOnSaveCompleted = intent.getBooleanExtra(
+ INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, false);
// The only situation where action could be ACTION_JOIN_COMPLETED is if the
// user joined the contact with another and closed the activity before
@@ -146,7 +163,9 @@
@Override
public void onSaveFinished(Intent resultIntent) {
- if (resultIntent != null) {
+ if (mFinishActivityOnSaveCompleted) {
+ setResult(resultIntent == null ? RESULT_CANCELED : RESULT_OK, resultIntent);
+ } else if (resultIntent != null) {
startActivity(resultIntent);
}
finish();
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
index ac13ba3..b5b6c1d 100644
--- a/src/com/android/contacts/activities/ContactSelectionActivity.java
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -153,8 +153,10 @@
}
private void prepareSearchViewAndActionBar() {
- // Postal address picker doesn't support search, so just show "HomeAsUp" button and title.
- if (mRequest.getActionCode() == ContactsRequest.ACTION_PICK_POSTAL) {
+ // Postal address pickers (and legacy pickers) don't support search, so just show
+ // "HomeAsUp" button and title.
+ if (mRequest.getActionCode() == ContactsRequest.ACTION_PICK_POSTAL ||
+ mRequest.isLegacyCompatibilityMode()) {
findViewById(R.id.search_view).setVisibility(View.GONE);
final ActionBar actionBar = getActionBar();
if (actionBar != null) {
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index 2268743..7e95fed 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -120,10 +120,6 @@
}
public class ViewPagerAdapter extends FragmentPagerAdapter {
- private DialpadFragment mDialpadFragment;
- private CallLogFragment mCallLogFragment;
- private PhoneFavoriteFragment mPhoneFavoriteFragment;
-
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
@@ -132,20 +128,11 @@
public Fragment getItem(int position) {
switch (position) {
case TAB_INDEX_DIALER:
- if (mDialpadFragment == null) {
- mDialpadFragment = new DialpadFragment();
- }
- return mDialpadFragment;
+ return new DialpadFragment();
case TAB_INDEX_CALL_LOG:
- if (mCallLogFragment == null) {
- mCallLogFragment = new CallLogFragment();
- }
- return mCallLogFragment;
+ return new CallLogFragment();
case TAB_INDEX_FAVORITES:
- if (mPhoneFavoriteFragment == null) {
- mPhoneFavoriteFragment = new PhoneFavoriteFragment();
- }
- return mPhoneFavoriteFragment;
+ return new PhoneFavoriteFragment();
}
throw new IllegalStateException("No fragment at position " + position);
}
@@ -362,7 +349,9 @@
@Override
public boolean onQueryTextChange(String newText) {
// Show search result with non-empty text. Show a bare list otherwise.
- mSearchFragment.setQueryString(newText, true);
+ if (mSearchFragment != null) {
+ mSearchFragment.setQueryString(newText, true);
+ }
return true;
}
};
@@ -385,6 +374,16 @@
}
};
+ private final View.OnLayoutChangeListener mFirstLayoutListener
+ = new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ v.removeOnLayoutChangeListener(this); // Unregister self.
+ addSearchFragment();
+ }
+ };
+
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -397,6 +396,8 @@
mContactListFilterController = ContactListFilterController.getInstance(this);
mContactListFilterController.addListener(mContactListFilterListener);
+ findViewById(R.id.dialtacts_frame).addOnLayoutChangeListener(mFirstLayoutListener);
+
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
mViewPager.setOnPageChangeListener(mPageChangeListener);
@@ -443,6 +444,29 @@
mContactListFilterController.removeListener(mContactListFilterListener);
}
+ /**
+ * Add search fragment. Note this is called during onLayout, so there's some restrictions,
+ * such as executePendingTransaction can't be used in it.
+ */
+ private void addSearchFragment() {
+ // In order to take full advantage of "fragment deferred start", we need to create the
+ // search fragment after all other fragments are created.
+ // The other fragments are created by the ViewPager on the first onMeasure().
+ // We use the first onLayout call, which is after onMeasure().
+
+ // Just return if the fragment is already created, which happens after configuration
+ // changes.
+ if (mSearchFragment != null) return;
+
+ final FragmentTransaction ft = getFragmentManager().beginTransaction();
+ final Fragment searchFragment = new PhoneNumberPickerFragment();
+
+ searchFragment.setUserVisibleHint(false);
+ ft.add(R.id.dialtacts_frame, searchFragment);
+ ft.hide(searchFragment);
+ ft.commitAllowingStateLoss();
+ }
+
private void prepareSearchView() {
final View searchViewLayout =
getLayoutInflater().inflate(R.layout.dialtacts_custom_action_bar, null);
@@ -508,14 +532,25 @@
mSearchFragment.setQuickContactEnabled(true);
mSearchFragment.setDarkTheme(true);
mSearchFragment.setPhotoPosition(ContactListItemView.PhotoPosition.LEFT);
- mSearchFragment.setUserVisibleHint(false);
- final FragmentTransaction transaction = getFragmentManager().beginTransaction();
- if (mInSearchUi) {
- transaction.show(mSearchFragment);
- } else {
- transaction.hide(mSearchFragment);
+ if (mContactListFilterController != null
+ && mContactListFilterController.getFilter() != null) {
+ mSearchFragment.setFilter(mContactListFilterController.getFilter());
}
- transaction.commitAllowingStateLoss();
+ // Here we assume that we're not on the search mode, so let's hide the fragment.
+ //
+ // We get here either when the fragment is created (normal case), or after configuration
+ // changes. In the former case, we're not in search mode because we can only
+ // enter search mode if the fragment is created. (see enterSearchUi())
+ // In the latter case we're not in search mode either because we don't retain
+ // mInSearchUi -- ideally we should but at this point it's not supported.
+ mSearchFragment.setUserVisibleHint(false);
+ // After configuration changes fragments will forget their "hidden" state, so make
+ // sure to hide it.
+ if (!mSearchFragment.isHidden()) {
+ final FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ transaction.hide(mSearchFragment);
+ transaction.commitAllowingStateLoss();
+ }
}
}
@@ -635,7 +670,7 @@
if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
setupFilterText(newIntent);
}
- if (mInSearchUi || mSearchFragment.isVisible()) {
+ if (mInSearchUi || (mSearchFragment != null && mSearchFragment.isVisible())) {
exitSearchUi();
}
@@ -814,6 +849,15 @@
* Hides every tab and shows search UI for phone lookup.
*/
private void enterSearchUi() {
+ if (mSearchFragment == null) {
+ // We add the search fragment dynamically in the first onLayoutChange() and
+ // mSearchFragment is set sometime later when the fragment transaction is actually
+ // executed, which means there's a window when users are able to hit the (physical)
+ // search key but mSearchFragment is still null.
+ // It's quite hard to handle this case right, so let's just ignore the search key
+ // in this case. Users can just hit it again and it will work this time.
+ return;
+ }
if (mSearchView == null) {
prepareSearchView();
}
@@ -837,6 +881,7 @@
sendFragmentVisibilityChange(mViewPager.getCurrentItem(), false);
// Show the search fragment and hide everything else.
+ mSearchFragment.setUserVisibleHint(true);
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.show(mSearchFragment);
transaction.commitAllowingStateLoss();
@@ -846,7 +891,6 @@
// layout instead of asking the search menu item to take care of SearchView.
mSearchView.onActionViewExpanded();
mInSearchUi = true;
- mSearchFragment.setUserVisibleHint(true);
}
private void showInputMethod(View view) {
@@ -872,9 +916,14 @@
private void exitSearchUi() {
final ActionBar actionBar = getActionBar();
- final FragmentTransaction transaction = getFragmentManager().beginTransaction();
- transaction.hide(mSearchFragment);
- transaction.commitAllowingStateLoss();
+ // Hide the search fragment, if exists.
+ if (mSearchFragment != null) {
+ mSearchFragment.setUserVisibleHint(false);
+
+ final FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ transaction.hide(mSearchFragment);
+ transaction.commitAllowingStateLoss();
+ }
// We want to hide SearchView and show Tabs. Also focus on previously selected one.
actionBar.setDisplayShowCustomEnabled(false);
diff --git a/src/com/android/contacts/activities/GroupDetailActivity.java b/src/com/android/contacts/activities/GroupDetailActivity.java
index b0355fc..6c36b5d 100644
--- a/src/com/android/contacts/activities/GroupDetailActivity.java
+++ b/src/com/android/contacts/activities/GroupDetailActivity.java
@@ -106,7 +106,7 @@
@Override
public void onContactSelected(Uri contactUri) {
Intent intent = new Intent(Intent.ACTION_VIEW, contactUri);
- intent.putExtra(ContactDetailActivity.INTENT_KEY_IGNORE_DEFAULT_UP_BEHAVIOR, true);
+ intent.putExtra(ContactDetailActivity.INTENT_KEY_FINISH_ACTIVITY_ON_UP_SELECTED, true);
startActivity(intent);
}
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 3ba5957..1c873ff 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -110,9 +110,12 @@
private static final String TAG = "PeopleActivity";
- private static final int SUBACTIVITY_NEW_GROUP = 2;
- private static final int SUBACTIVITY_EDIT_GROUP = 3;
- private static final int SUBACTIVITY_ACCOUNT_FILTER = 4;
+ // These values needs to start at 2. See {@link ContactEntryListFragment}.
+ private static final int SUBACTIVITY_NEW_CONTACT = 2;
+ private static final int SUBACTIVITY_EDIT_CONTACT = 3;
+ private static final int SUBACTIVITY_NEW_GROUP = 4;
+ private static final int SUBACTIVITY_EDIT_GROUP = 5;
+ private static final int SUBACTIVITY_ACCOUNT_FILTER = 6;
private final DialogManager mDialogManager = new DialogManager(this);
@@ -989,11 +992,10 @@
} else {
Intent intent = new Intent(Intent.ACTION_VIEW, contactLookupUri);
// In search mode, the "up" affordance in the contact detail page should return the
- // user to the search results, so suppress the normal behavior which would re-launch
- // {@link PeopleActivity} when the "up" affordance is clicked.
+ // user to the search results, so finish the activity when that button is selected.
if (mActionBarAdapter.isSearchMode()) {
- intent.putExtra(ContactDetailActivity.INTENT_KEY_IGNORE_DEFAULT_UP_BEHAVIOR,
- true);
+ intent.putExtra(
+ ContactDetailActivity.INTENT_KEY_FINISH_ACTIVITY_ON_UP_SELECTED, true);
}
startActivity(intent);
}
@@ -1016,7 +1018,9 @@
if (extras != null) {
intent.putExtras(extras);
}
- startActivity(intent);
+ intent.putExtra(
+ ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, true);
+ startActivityForResult(intent, SUBACTIVITY_EDIT_CONTACT);
}
@Override
@@ -1101,7 +1105,10 @@
@Override
public void onEditRequested(Uri contactLookupUri) {
- startActivity(new Intent(Intent.ACTION_EDIT, contactLookupUri));
+ Intent intent = new Intent(Intent.ACTION_EDIT, contactLookupUri);
+ intent.putExtra(
+ ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, true);
+ startActivityForResult(intent, SUBACTIVITY_EDIT_CONTACT);
}
@Override
@@ -1390,7 +1397,17 @@
}
case R.id.menu_add_contact: {
final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
- startActivity(intent);
+ // On 2-pane UI, we can let the editor activity finish itself and return
+ // to this activity to display the new contact.
+ if (PhoneCapabilityTester.isUsingTwoPanes(this)) {
+ intent.putExtra(
+ ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, true);
+ startActivityForResult(intent, SUBACTIVITY_NEW_CONTACT);
+ } else {
+ // Otherwise, on 1-pane UI, we need the editor to launch the view contact
+ // intent itself.
+ startActivity(intent);
+ }
return true;
}
case R.id.menu_add_group: {
@@ -1465,6 +1482,19 @@
break;
}
+ case SUBACTIVITY_NEW_CONTACT:
+ case SUBACTIVITY_EDIT_CONTACT: {
+ if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) {
+ mRequest.setActionCode(ContactsRequest.ACTION_VIEW_CONTACT);
+ mAllFragment.reloadDataAndSetSelectedUri(data.getData());
+ // Suppress IME if in search mode
+ if (mActionBarAdapter != null) {
+ mActionBarAdapter.clearFocusOnSearchView();
+ }
+ }
+ break;
+ }
+
case SUBACTIVITY_NEW_GROUP:
case SUBACTIVITY_EDIT_GROUP: {
if (resultCode == RESULT_OK && PhoneCapabilityTester.isUsingTwoPanes(this)) {
diff --git a/src/com/android/contacts/format/FormatUtils.java b/src/com/android/contacts/format/FormatUtils.java
index 8e2bb63..4b076cf 100644
--- a/src/com/android/contacts/format/FormatUtils.java
+++ b/src/com/android/contacts/format/FormatUtils.java
@@ -125,6 +125,7 @@
}
/** Returns a String that represents the content of the given {@link CharArrayBuffer}. */
+ @NeededForTesting
public static String charArrayBufferToString(CharArrayBuffer buffer) {
return new String(buffer.data, 0, buffer.sizeCopied);
}
diff --git a/src/com/android/contacts/list/AccountFilterActivity.java b/src/com/android/contacts/list/AccountFilterActivity.java
index 0b4c6e0..14db634 100644
--- a/src/com/android/contacts/list/AccountFilterActivity.java
+++ b/src/com/android/contacts/list/AccountFilterActivity.java
@@ -118,8 +118,8 @@
continue;
}
Drawable icon = accountType != null ? accountType.getDisplayIcon(context) : null;
- accountFilters.add(ContactListFilter.createAccountFilter(account.type, account.name,
- account.dataSet, icon, account.name));
+ accountFilters.add(ContactListFilter.createAccountFilter(
+ account.type, account.name, account.dataSet, icon));
}
// Always show "All", even when there's no accounts. (We may have local contacts)
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index 061646e..b63b402 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -142,12 +142,13 @@
@Override
protected void onPostExecute(Uri uri) {
// Make sure the {@link Fragment} is at least still attached to the {@link Activity}
- // before continuing.
- if (mIsCancelled || !isAdded() || uri == null) {
+ // before continuing. Null URIs should still be allowed so that the list can be
+ // refreshed and a default contact can be selected (i.e. the case of deleted
+ // contacts).
+ if (mIsCancelled || !isAdded()) {
return;
}
- mSelectedContactUri = uri;
- onContactUriQueryFinished(mSelectedContactUri);
+ onContactUriQueryFinished(uri);
}
}
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index 1592004..a8caf3b 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -39,56 +39,64 @@
public abstract class ContactListAdapter extends ContactEntryListAdapter {
protected static class ContactQuery {
- public static final String[] PROJECTION_CONTACT = new String[] {
+ private static final String[] CONTACT_PROJECTION_PRIMARY = new String[] {
Contacts._ID, // 0
Contacts.DISPLAY_NAME_PRIMARY, // 1
- Contacts.DISPLAY_NAME_ALTERNATIVE, // 2
- Contacts.CONTACT_PRESENCE, // 3
- Contacts.CONTACT_STATUS, // 4
- Contacts.PHOTO_ID, // 5
- Contacts.PHOTO_THUMBNAIL_URI, // 6
- Contacts.LOOKUP_KEY, // 7
- Contacts.IS_USER_PROFILE, // 8
+ Contacts.CONTACT_PRESENCE, // 2
+ Contacts.CONTACT_STATUS, // 3
+ Contacts.PHOTO_ID, // 4
+ Contacts.PHOTO_THUMBNAIL_URI, // 5
+ Contacts.LOOKUP_KEY, // 6
+ Contacts.IS_USER_PROFILE, // 7
};
- public static final String[] PROJECTION_DATA = new String[] {
- Data.CONTACT_ID, // 0
- Data.DISPLAY_NAME_PRIMARY, // 1
- Data.DISPLAY_NAME_ALTERNATIVE, // 2
- Data.CONTACT_PRESENCE, // 3
- Data.CONTACT_STATUS, // 4
- Data.PHOTO_ID, // 5
- Data.PHOTO_THUMBNAIL_URI, // 6
- Data.LOOKUP_KEY, // 7
+ private static final String[] CONTACT_PROJECTION_ALTERNATIVE = new String[] {
+ Contacts._ID, // 0
+ Contacts.DISPLAY_NAME_ALTERNATIVE, // 1
+ Contacts.CONTACT_PRESENCE, // 2
+ Contacts.CONTACT_STATUS, // 3
+ Contacts.PHOTO_ID, // 4
+ Contacts.PHOTO_THUMBNAIL_URI, // 5
+ Contacts.LOOKUP_KEY, // 6
+ Contacts.IS_USER_PROFILE, // 7
};
- public static final String[] FILTER_PROJECTION = new String[] {
+ private static final String[] FILTER_PROJECTION_PRIMARY = new String[] {
Contacts._ID, // 0
Contacts.DISPLAY_NAME_PRIMARY, // 1
- Contacts.DISPLAY_NAME_ALTERNATIVE, // 2
- Contacts.CONTACT_PRESENCE, // 3
- Contacts.CONTACT_STATUS, // 4
- Contacts.PHOTO_ID, // 5
- Contacts.PHOTO_THUMBNAIL_URI, // 6
- Contacts.LOOKUP_KEY, // 7
- Contacts.IS_USER_PROFILE, // 8
- SearchSnippetColumns.SNIPPET, // 9
+ Contacts.CONTACT_PRESENCE, // 2
+ Contacts.CONTACT_STATUS, // 3
+ Contacts.PHOTO_ID, // 4
+ Contacts.PHOTO_THUMBNAIL_URI, // 5
+ Contacts.LOOKUP_KEY, // 6
+ Contacts.IS_USER_PROFILE, // 7
+ SearchSnippetColumns.SNIPPET, // 8
};
- public static final int CONTACT_ID = 0;
- public static final int CONTACT_DISPLAY_NAME_PRIMARY = 1;
- public static final int CONTACT_DISPLAY_NAME_ALTERNATIVE = 2;
- public static final int CONTACT_PRESENCE_STATUS = 3;
- public static final int CONTACT_CONTACT_STATUS = 4;
- public static final int CONTACT_PHOTO_ID = 5;
- public static final int CONTACT_PHOTO_URI = 6;
- public static final int CONTACT_LOOKUP_KEY = 7;
- public static final int CONTACT_IS_USER_PROFILE = 8;
- public static final int CONTACT_SNIPPET = 9;
+ private static final String[] FILTER_PROJECTION_ALTERNATIVE = new String[] {
+ Contacts._ID, // 0
+ Contacts.DISPLAY_NAME_ALTERNATIVE, // 1
+ Contacts.CONTACT_PRESENCE, // 2
+ Contacts.CONTACT_STATUS, // 3
+ Contacts.PHOTO_ID, // 4
+ Contacts.PHOTO_THUMBNAIL_URI, // 5
+ Contacts.LOOKUP_KEY, // 6
+ Contacts.IS_USER_PROFILE, // 7
+ SearchSnippetColumns.SNIPPET, // 8
+ };
+
+ public static final int CONTACT_ID = 0;
+ public static final int CONTACT_DISPLAY_NAME = 1;
+ public static final int CONTACT_PRESENCE_STATUS = 2;
+ public static final int CONTACT_CONTACT_STATUS = 3;
+ public static final int CONTACT_PHOTO_ID = 4;
+ public static final int CONTACT_PHOTO_URI = 5;
+ public static final int CONTACT_LOOKUP_KEY = 6;
+ public static final int CONTACT_IS_USER_PROFILE = 7;
+ public static final int CONTACT_SNIPPET = 8;
}
private CharSequence mUnknownNameText;
- private int mDisplayNameColumnIndex;
private long mSelectedContactDirectoryId;
private String mSelectedContactLookupKey;
@@ -129,17 +137,7 @@
@Override
public String getContactDisplayName(int position) {
- return ((Cursor)getItem(position)).getString(mDisplayNameColumnIndex);
- }
-
- @Override
- public void setContactNameDisplayOrder(int displayOrder) {
- super.setContactNameDisplayOrder(displayOrder);
- if (getContactNameDisplayOrder() == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
- mDisplayNameColumnIndex = ContactQuery.CONTACT_DISPLAY_NAME_PRIMARY;
- } else {
- mDisplayNameColumnIndex = ContactQuery.CONTACT_DISPLAY_NAME_ALTERNATIVE;
- }
+ return ((Cursor) getItem(position)).getString(ContactQuery.CONTACT_DISPLAY_NAME);
}
/**
@@ -237,7 +235,8 @@
}
protected void bindName(final ContactListItemView view, Cursor cursor) {
- view.showDisplayName(cursor, mDisplayNameColumnIndex, getContactNameDisplayOrder());
+ view.showDisplayName(
+ cursor, ContactQuery.CONTACT_DISPLAY_NAME, getContactNameDisplayOrder());
// Note: we don't show phonetic any more (See issue 5265330)
}
@@ -341,4 +340,24 @@
setProfileExists(cursor.getInt(ContactQuery.CONTACT_IS_USER_PROFILE) == 1);
}
}
+
+ /**
+ * @return Projection useful for children.
+ */
+ protected final String[] getProjection(boolean forSearch) {
+ final int sortOrder = getContactNameDisplayOrder();
+ if (forSearch) {
+ if (sortOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+ return ContactQuery.FILTER_PROJECTION_PRIMARY;
+ } else {
+ return ContactQuery.FILTER_PROJECTION_ALTERNATIVE;
+ }
+ } else {
+ if (sortOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+ return ContactQuery.CONTACT_PROJECTION_PRIMARY;
+ } else {
+ return ContactQuery.CONTACT_PROJECTION_ALTERNATIVE;
+ }
+ }
+ }
}
diff --git a/src/com/android/contacts/list/ContactListFilter.java b/src/com/android/contacts/list/ContactListFilter.java
index 152b152..6e4e949 100644
--- a/src/com/android/contacts/list/ContactListFilter.java
+++ b/src/com/android/contacts/list/ContactListFilter.java
@@ -35,69 +35,51 @@
public static final int FILTER_TYPE_SINGLE_CONTACT = -6;
public static final int FILTER_TYPE_ACCOUNT = 0;
- public static final int FILTER_TYPE_GROUP = 1;
+
+ /**
+ * Obsolete filter which had been used in Honeycomb. This may be stored in
+ * {@link SharedPreferences}, but should be replaced with ALL filter when it is found.
+ *
+ * TODO: "group" filter and relevant variables are all obsolete. Remove them.
+ */
+ private static final int FILTER_TYPE_GROUP = 1;
private static final String KEY_FILTER_TYPE = "filter.type";
private static final String KEY_ACCOUNT_NAME = "filter.accountName";
private static final String KEY_ACCOUNT_TYPE = "filter.accountType";
private static final String KEY_DATA_SET = "filter.dataSet";
- private static final String KEY_GROUP_ID = "filter.groupId";
- private static final String KEY_GROUP_SOURCE_ID = "filter.groupSourceId";
- private static final String KEY_GROUP_READ_ONLY = "filter.groupReadOnly";
- private static final String KEY_GROUP_TITLE = "filter.groupTitle";
public final int filterType;
public final String accountType;
public final String accountName;
public final String dataSet;
public final Drawable icon;
- public long groupId;
- public String groupSourceId;
- public final boolean groupReadOnly;
- public final String title;
private String mId;
public ContactListFilter(int filterType, String accountType, String accountName, String dataSet,
- Drawable icon, long groupId, String groupSourceId, boolean groupReadOnly,
- String title) {
+ Drawable icon) {
this.filterType = filterType;
this.accountType = accountType;
this.accountName = accountName;
this.dataSet = dataSet;
this.icon = icon;
- this.groupId = groupId;
- this.groupSourceId = groupSourceId;
- this.groupReadOnly = groupReadOnly;
- this.title = title;
}
public static ContactListFilter createFilterWithType(int filterType) {
- return new ContactListFilter(filterType, null, null, null, null, 0, null, false, null);
- }
-
- public static ContactListFilter createGroupFilter(long groupId) {
- return new ContactListFilter(ContactListFilter.FILTER_TYPE_GROUP, null, null, null, null,
- groupId, null, false, null);
- }
-
- public static ContactListFilter createGroupFilter(String accountType, String accountName,
- String dataSet, long groupId, String groupSourceId, boolean groupReadOnly,
- String title) {
- return new ContactListFilter(ContactListFilter.FILTER_TYPE_GROUP, accountType, accountName,
- dataSet, null, groupId, groupSourceId, groupReadOnly, title);
+ return new ContactListFilter(filterType, null, null, null, null);
}
public static ContactListFilter createAccountFilter(String accountType, String accountName,
- String dataSet, Drawable icon, String title) {
+ String dataSet, Drawable icon) {
return new ContactListFilter(ContactListFilter.FILTER_TYPE_ACCOUNT, accountType,
- accountName, dataSet, icon, 0, null, false, title);
+ accountName, dataSet, icon);
}
/**
* Returns true if this filter is based on data and may become invalid over time.
*/
public boolean isValidationRequired() {
- return filterType == FILTER_TYPE_ACCOUNT || filterType == FILTER_TYPE_GROUP;
+ return filterType == FILTER_TYPE_ACCOUNT;
}
@Override
@@ -118,9 +100,6 @@
case FILTER_TYPE_ACCOUNT:
return "account: " + accountType + (dataSet != null ? "/" + dataSet : "")
+ " " + accountName;
- case FILTER_TYPE_GROUP:
- return "group: " + accountType + (dataSet != null ? "/" + dataSet : "")
- + " " + accountName + " " + title + "(" + groupId + ")";
}
return super.toString();
}
@@ -137,13 +116,7 @@
return res;
}
- if (filterType != another.filterType) {
- return filterType - another.filterType;
- }
-
- String title1 = title != null ? title : "";
- String title2 = another.title != null ? another.title : "";
- return title1.compareTo(title2);
+ return filterType - another.filterType;
}
@Override
@@ -156,11 +129,6 @@
if (dataSet != null) {
code = code * 31 + dataSet.hashCode();
}
- if (groupSourceId != null) {
- code = code * 31 + groupSourceId.hashCode();
- } else if (groupId != 0) {
- code = code * 31 + (int) groupId;
- }
return code;
}
@@ -182,11 +150,7 @@
return false;
}
- if (groupSourceId != null && otherFilter.groupSourceId != null) {
- return groupSourceId.equals(otherFilter.groupSourceId);
- }
-
- return groupId == otherFilter.groupId;
+ return true;
}
public static void storeToPreferences(SharedPreferences prefs, ContactListFilter filter) {
@@ -195,10 +159,6 @@
.putString(KEY_ACCOUNT_NAME, filter == null ? null : filter.accountName)
.putString(KEY_ACCOUNT_TYPE, filter == null ? null : filter.accountType)
.putString(KEY_DATA_SET, filter == null ? null : filter.dataSet)
- .putLong(KEY_GROUP_ID, filter == null ? -1 : filter.groupId)
- .putString(KEY_GROUP_SOURCE_ID, filter == null ? null : filter.groupSourceId)
- .putBoolean(KEY_GROUP_READ_ONLY, filter == null ? false : filter.groupReadOnly)
- .putString(KEY_GROUP_TITLE, filter == null ? null : filter.title)
.apply();
}
@@ -209,13 +169,16 @@
public static ContactListFilter restoreDefaultPreferences(SharedPreferences prefs) {
ContactListFilter filter = restoreFromPreferences(prefs);
if (filter == null) {
- filter = ContactListFilter.createFilterWithType(
- ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS);
+ filter = ContactListFilter.createFilterWithType(FILTER_TYPE_ALL_ACCOUNTS);
+ }
+ // "Group" filter is obsolete and thus is not exposed anymore.
+ if (filter.filterType == FILTER_TYPE_GROUP) {
+ filter = ContactListFilter.createFilterWithType(FILTER_TYPE_ALL_ACCOUNTS);
}
return filter;
}
- public static ContactListFilter restoreFromPreferences(SharedPreferences prefs) {
+ private static ContactListFilter restoreFromPreferences(SharedPreferences prefs) {
int filterType = prefs.getInt(KEY_FILTER_TYPE, FILTER_TYPE_DEFAULT);
if (filterType == FILTER_TYPE_DEFAULT) {
return null;
@@ -224,12 +187,7 @@
String accountName = prefs.getString(KEY_ACCOUNT_NAME, null);
String accountType = prefs.getString(KEY_ACCOUNT_TYPE, null);
String dataSet = prefs.getString(KEY_DATA_SET, null);
- long groupId = prefs.getLong(KEY_GROUP_ID, -1);
- String groupSourceId = prefs.getString(KEY_GROUP_SOURCE_ID, null);
- boolean groupReadOnly = prefs.getBoolean(KEY_GROUP_READ_ONLY, false);
- String title = prefs.getString(KEY_GROUP_TITLE, "group");
- return new ContactListFilter(filterType, accountType, accountName, dataSet, null, groupId,
- groupSourceId, groupReadOnly, title);
+ return new ContactListFilter(filterType, accountType, accountName, dataSet, null);
}
@@ -239,9 +197,6 @@
dest.writeString(accountName);
dest.writeString(accountType);
dest.writeString(dataSet);
- dest.writeLong(groupId);
- dest.writeString(groupSourceId);
- dest.writeInt(groupReadOnly ? 1 : 0);
}
public static final Parcelable.Creator<ContactListFilter> CREATOR =
@@ -252,11 +207,7 @@
String accountName = source.readString();
String accountType = source.readString();
String dataSet = source.readString();
- long groupId = source.readLong();
- String groupSourceId = source.readString();
- boolean groupReadOnly = source.readInt() != 0;
- return new ContactListFilter(filterType, accountType, accountName, dataSet, null,
- groupId, groupSourceId, groupReadOnly, null);
+ return new ContactListFilter(filterType, accountType, accountName, dataSet, null);
}
@Override
@@ -286,11 +237,6 @@
if (accountName != null) {
sb.append('-').append(accountName.replace('-', '_'));
}
- if (groupSourceId != null) {
- sb.append('-').append(groupSourceId);
- } else if (groupId != 0) {
- sb.append('-').append(groupId);
- }
mId = sb.toString();
}
return mId;
@@ -304,12 +250,6 @@
.append(", accountName: " + accountName)
.append(", dataSet: " + dataSet);
}
- if (filterType == FILTER_TYPE_GROUP) {
- builder.append(", groupId: " + groupId)
- .append(", groupSourceId: " + groupSourceId)
- .append(", groupReadOnly: " + groupReadOnly)
- .append("title: " + title);
- }
builder.append(", icon: " + icon + "]");
return builder.toString();
}
@@ -330,8 +270,6 @@
return "FILTER_TYPE_SINGLE_CONTACT";
case FILTER_TYPE_ACCOUNT:
return "FILTER_TYPE_ACCOUNT";
- case FILTER_TYPE_GROUP:
- return "FILTER_TYPE_GROUP";
default:
return "(unknown)";
}
diff --git a/src/com/android/contacts/list/ContactListFilterView.java b/src/com/android/contacts/list/ContactListFilterView.java
index 6dc9bc3..398a864 100644
--- a/src/com/android/contacts/list/ContactListFilterView.java
+++ b/src/com/android/contacts/list/ContactListFilterView.java
@@ -115,15 +115,6 @@
}
break;
}
- case ContactListFilter.FILTER_TYPE_GROUP: {
- mIcon.setVisibility(View.VISIBLE);
- mIcon.setImageResource(R.drawable.ic_menu_display_all_holo_light);
- mLabel.setText(mFilter.title);
- if (dropdown) {
- mIndent.setVisibility(mSingleAccount ? View.GONE : View.VISIBLE);
- }
- break;
- }
}
}
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 0cc211a..4bf1a04 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -177,10 +177,6 @@
mCounterHeaderView.setText(getString(
R.string.listTotalAllContactsZeroGroup, filter.accountName));
break;
- case ContactListFilter.FILTER_TYPE_GROUP:
- mCounterHeaderView.setText(
- getString(R.string.listTotalAllContactsZeroGroup, filter.title));
- break;
case ContactListFilter.FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY:
mCounterHeaderView.setText(R.string.listTotalPhoneContactsZero);
break;
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
index deab8ab..348abb9 100644
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/list/DefaultContactListAdapter.java
@@ -72,7 +72,7 @@
// Regardless of the directory, we don't want anything returned,
// so let's just send a "nothing" query to the local directory.
loader.setUri(Contacts.CONTENT_URI);
- loader.setProjection(ContactQuery.PROJECTION_CONTACT);
+ loader.setProjection(getProjection(false));
loader.setSelection("0");
} else {
Builder builder = Contacts.CONTENT_FILTER_URI.buildUpon();
@@ -87,11 +87,11 @@
SNIPPET_ARGS);
builder.appendQueryParameter(SearchSnippetColumns.DEFERRED_SNIPPETING_KEY,"1");
loader.setUri(builder.build());
- loader.setProjection(ContactQuery.FILTER_PROJECTION);
+ loader.setProjection(getProjection(true));
}
} else {
configureUri(loader, directoryId, filter);
- configureProjection(loader, directoryId, filter);
+ loader.setProjection(getProjection(false));
configureSelection(loader, directoryId, filter);
}
@@ -107,16 +107,12 @@
protected void configureUri(CursorLoader loader, long directoryId, ContactListFilter filter) {
Uri uri = Contacts.CONTENT_URI;
- if (filter != null) {
- if (filter.filterType == ContactListFilter.FILTER_TYPE_GROUP) {
- uri = Data.CONTENT_URI;
- } else if (filter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
- String lookupKey = getSelectedContactLookupKey();
- if (lookupKey != null) {
- uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
- } else {
- uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, getSelectedContactId());
- }
+ if (filter != null && filter.filterType == ContactListFilter.FILTER_TYPE_SINGLE_CONTACT) {
+ String lookupKey = getSelectedContactLookupKey();
+ if (lookupKey != null) {
+ uri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey);
+ } else {
+ uri = ContentUris.withAppendedId(Contacts.CONTENT_URI, getSelectedContactId());
}
}
@@ -136,15 +132,6 @@
loader.setUri(uri);
}
- protected void configureProjection(
- CursorLoader loader, long directoryId, ContactListFilter filter) {
- if (filter != null && filter.filterType == ContactListFilter.FILTER_TYPE_GROUP) {
- loader.setProjection(ContactQuery.PROJECTION_DATA);
- } else {
- loader.setProjection(ContactQuery.PROJECTION_CONTACT);
- }
- }
-
private void configureSelection(
CursorLoader loader, long directoryId, ContactListFilter filter) {
if (filter == null) {
@@ -203,13 +190,6 @@
selection.append(")");
break;
}
- case ContactListFilter.FILTER_TYPE_GROUP: {
- selection.append(Data.MIMETYPE + "=?"
- + " AND " + GroupMembership.GROUP_ROW_ID + "=?");
- selectionArgs.add(GroupMembership.CONTENT_ITEM_TYPE);
- selectionArgs.add(String.valueOf(filter.groupId));
- break;
- }
}
loader.setSelection(selection.toString());
loader.setSelectionArgs(selectionArgs.toArray(new String[0]));
diff --git a/src/com/android/contacts/list/JoinContactListAdapter.java b/src/com/android/contacts/list/JoinContactListAdapter.java
index b81dd3b..bfe8c53 100644
--- a/src/com/android/contacts/list/JoinContactListAdapter.java
+++ b/src/com/android/contacts/list/JoinContactListAdapter.java
@@ -67,7 +67,7 @@
@Override
public void configureLoader(CursorLoader cursorLoader, long directoryId) {
- JoinContactLoader loader = (JoinContactLoader)cursorLoader;
+ JoinContactLoader loader = (JoinContactLoader) cursorLoader;
Builder builder = Contacts.CONTENT_URI.buildUpon();
builder.appendEncodedPath(String.valueOf(mTargetContactId));
@@ -83,7 +83,7 @@
loader.setSuggestionUri(builder.build());
// TODO simplify projection
- loader.setProjection(ContactQuery.PROJECTION_CONTACT);
+ loader.setProjection(getProjection(false));
Uri allContactsUri = buildSectionIndexerUri(Contacts.CONTENT_URI).buildUpon()
.appendQueryParameter(
ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(Directory.DEFAULT))
@@ -189,13 +189,6 @@
}
}
- public Cursor getShowAllContactsLabelCursor() {
- MatrixCursor matrixCursor = new MatrixCursor(ContactQuery.PROJECTION_CONTACT);
- Object[] row = new Object[ContactQuery.PROJECTION_CONTACT.length];
- matrixCursor.addRow(row);
- return matrixCursor;
- }
-
@Override
public Uri getContactUri(int partitionIndex, Cursor cursor) {
long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
diff --git a/src/com/android/contacts/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
index 052c32f..c96956b 100644
--- a/src/com/android/contacts/list/PhoneNumberPickerFragment.java
+++ b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
@@ -70,7 +70,6 @@
public PhoneNumberPickerFragment() {
setQuickContactEnabled(false);
setPhotoLoaderEnabled(true);
- setVisibleScrollbarEnabled(true);
setSectionHeaderDisplayEnabled(true);
setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_DATA_SHORTCUT);
@@ -93,6 +92,8 @@
mAccountFilterHeader = getView().findViewById(R.id.account_filter_header_container);
mAccountFilterHeader.setOnClickListener(mFilterHeaderClickListener);
updateFilterHeaderView();
+
+ setVisibleScrollbarEnabled(!isLegacyCompatibilityMode());
}
@Override
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index 23d39d3..22ea884 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -298,6 +298,9 @@
* Add given {@link DataKind} to list of those provided by this source.
*/
public DataKind addKind(DataKind kind) throws DefinitionException {
+ if (kind.mimeType == null) {
+ throw new DefinitionException("null is not a valid mime type");
+ }
if (mMimeKinds.get(kind.mimeType) != null) {
throw new DefinitionException(
"mime type '" + kind.mimeType + "' is already registered");
diff --git a/src/com/android/contacts/model/BaseAccountType.java b/src/com/android/contacts/model/BaseAccountType.java
index 109a7c5..cd113eb 100644
--- a/src/com/android/contacts/model/BaseAccountType.java
+++ b/src/com/android/contacts/model/BaseAccountType.java
@@ -497,6 +497,10 @@
+ " mStringRes=" + mStringRes
+ " mColumnName" + mColumnName;
}
+
+ public String getColumnNameForTest() {
+ return mColumnName;
+ }
}
public static abstract class CommonInflater implements StringInflater {
@@ -699,8 +703,7 @@
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
final int depth = parser.getDepth();
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT
- || depth != outerDepth + 1) {
+ if (type != XmlPullParser.START_TAG || depth != outerDepth + 1) {
continue; // Not direct child tag
}
@@ -845,8 +848,7 @@
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
final int depth = parser.getDepth();
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT
- || depth != outerDepth + 1) {
+ if (type != XmlPullParser.START_TAG || depth != outerDepth + 1) {
continue; // Not direct child tag
}
diff --git a/src/com/android/contacts/model/ExternalAccountType.java b/src/com/android/contacts/model/ExternalAccountType.java
index 1870992..f36e9e0 100644
--- a/src/com/android/contacts/model/ExternalAccountType.java
+++ b/src/com/android/contacts/model/ExternalAccountType.java
@@ -140,11 +140,16 @@
addDataKindPhoto(context);
}
} catch (DefinitionException e) {
- String message = "Problem reading XML";
+ final StringBuilder error = new StringBuilder();
+ error.append("Problem reading XML");
if (needLineNumberInErrorLog && (parser != null)) {
- message = message + " in line " + parser.getLineNumber();
+ error.append(" in line ");
+ error.append(parser.getLineNumber());
}
- Log.e(TAG, message, e);
+ error.append(" for external package ");
+ error.append(resPackageName);
+
+ Log.e(TAG, error.toString(), e);
return;
} finally {
if (parser != null) {
@@ -341,10 +346,15 @@
}
// Parse all children kinds
- final int depth = parser.getDepth();
- while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+ final int startDepth = parser.getDepth();
+ while (((type = parser.next()) != XmlPullParser.END_TAG
+ || parser.getDepth() > startDepth)
&& type != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG || parser.getDepth() != startDepth + 1) {
+ continue; // Not a direct child tag
+ }
+
String tag = parser.getName();
if (TAG_EDIT_SCHEMA.equals(tag)) {
mHasEditSchema = true;
diff --git a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
index 2ce15ff..593c4b2 100644
--- a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
+++ b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
@@ -209,6 +209,9 @@
// TODO: Rotate between all the stream items?
StreamItemEntry streamItem = streamItems.get(0);
CharSequence status = HtmlUtils.fromHtml(context, streamItem.getText());
+ if (status == null) {
+ status = "";
+ }
if (status.length() <= SHORT_SNIPPET_LENGTH) {
sb.append("\n");
} else {
diff --git a/src/com/android/contacts/util/AccountPromptUtils.java b/src/com/android/contacts/util/AccountPromptUtils.java
index 58865d0..111c52e 100644
--- a/src/com/android/contacts/util/AccountPromptUtils.java
+++ b/src/com/android/contacts/util/AccountPromptUtils.java
@@ -22,6 +22,7 @@
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;
@@ -57,10 +58,23 @@
/**
* Returns true if the "no account" prompt should be shown
- * (according to {@link SharedPreferences}), otherwise return false.
+ * (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) {
- return getSharedPreferences(context).getBoolean(KEY_SHOW_ACCOUNT_PROMPT, true);
+ // 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;
}
/**
diff --git a/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
index 9a84f7c..7aecf08 100644
--- a/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
+++ b/src/com/android/contacts/voicemail/VoicemailPlaybackPresenter.java
@@ -37,7 +37,6 @@
import android.view.View;
import android.widget.SeekBar;
-import java.io.IOException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -101,6 +100,7 @@
CHECK_FOR_CONTENT,
CHECK_CONTENT_AFTER_CHANGE,
PREPARE_MEDIA_PLAYER,
+ RESET_PREPARE_START_MEDIA_PLAYER,
}
/** Update rate for the slider, 30fps. */
@@ -169,6 +169,7 @@
*/
private FetchResultHandler mFetchResultHandler;
private PowerManager.WakeLock mWakeLock;
+ private AsyncTask<Void, ?, ?> mPrepareTask;
public VoicemailPlaybackPresenter(PlaybackView view, MediaPlayerProxy player,
Uri voicemailUri, ScheduledExecutorService executorService,
@@ -431,29 +432,49 @@
}
}
- private void resetPrepareStartPlaying(int clipPositionInMillis) {
- try {
- mPlayer.reset();
- mPlayer.setDataSource(mView.getDataSourceContext(), mVoicemailUri);
- mPlayer.setAudioStreamType(PLAYBACK_STREAM);
- mPlayer.prepare();
- mDuration.set(mPlayer.getDuration());
- int startPosition = constrain(clipPositionInMillis, 0, mDuration.get());
- mView.setClipPosition(startPosition, mDuration.get());
- mPlayer.seekTo(startPosition);
- mPlayer.start();
- mView.playbackStarted();
- if (!mWakeLock.isHeld()) {
- mWakeLock.acquire();
- }
- // Only enable if we are not currently using the speaker phone.
- if (!mView.isSpeakerPhoneOn()) {
- mView.enableProximitySensor();
- }
- mPositionUpdater.startUpdating(startPosition, mDuration.get());
- } catch (IOException e) {
- handleError(e);
+ private void resetPrepareStartPlaying(final int clipPositionInMillis) {
+ if (mPrepareTask != null) {
+ mPrepareTask.cancel(false);
}
+ mPrepareTask = mAsyncTaskExecutor.submit(Tasks.RESET_PREPARE_START_MEDIA_PLAYER,
+ new AsyncTask<Void, Void, Exception>() {
+ @Override
+ public Exception doInBackground(Void... params) {
+ try {
+ mPlayer.reset();
+ mPlayer.setDataSource(mView.getDataSourceContext(), mVoicemailUri);
+ mPlayer.setAudioStreamType(PLAYBACK_STREAM);
+ mPlayer.prepare();
+ return null;
+ } catch (Exception e) {
+ return e;
+ }
+ }
+
+ @Override
+ public void onPostExecute(Exception exception) {
+ mPrepareTask = null;
+ if (exception == null) {
+ mDuration.set(mPlayer.getDuration());
+ int startPosition =
+ constrain(clipPositionInMillis, 0, mDuration.get());
+ mView.setClipPosition(startPosition, mDuration.get());
+ mPlayer.seekTo(startPosition);
+ mPlayer.start();
+ mView.playbackStarted();
+ if (!mWakeLock.isHeld()) {
+ mWakeLock.acquire();
+ }
+ // Only enable if we are not currently using the speaker phone.
+ if (!mView.isSpeakerPhoneOn()) {
+ mView.enableProximitySensor();
+ }
+ mPositionUpdater.startUpdating(startPosition, mDuration.get());
+ } else {
+ handleError(exception);
+ }
+ }
+ });
}
private void handleError(Exception e) {
@@ -599,6 +620,9 @@
if (mPlayer.isPlaying()) {
stopPlaybackAtPosition(mPlayer.getCurrentPosition(), mDuration.get());
}
+ if (mPrepareTask != null) {
+ mPrepareTask.cancel(false);
+ }
if (mWakeLock.isHeld()) {
mWakeLock.release();
}
diff --git a/tests/res/xml/contacts_readonly.xml b/tests/res/xml/contacts_readonly.xml
new file mode 100644
index 0000000..df8d9c0
--- /dev/null
+++ b/tests/res/xml/contacts_readonly.xml
@@ -0,0 +1,51 @@
+<?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.
+ */
+-->
+
+<!--
+ Contacts.xml without EditSchema.
+-->
+
+<ContactsAccountType
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ >
+ <ContactsDataKind
+ android:icon="@drawable/android"
+ android:mimeType="vnd.android.cursor.item/a.b.c"
+ android:summaryColumn="data1"
+ android:detailColumn="data2"
+ android:detailSocialSummary="true"
+ >
+ </ContactsDataKind>
+ <ContactsDataKind
+ android:icon="@drawable/default_icon"
+ android:mimeType="vnd.android.cursor.item/d.e.f"
+ android:summaryColumn="data3"
+ android:detailColumn="data4"
+ android:detailSocialSummary="false"
+ >
+ </ContactsDataKind>
+ <ContactsDataKind
+ android:icon="@drawable/android"
+ android:mimeType="vnd.android.cursor.item/xyz"
+ android:summaryColumn="data5"
+ android:detailColumn="data6"
+ android:detailSocialSummary="true"
+ >
+ </ContactsDataKind>
+</ContactsAccountType>
diff --git a/tests/src/com/android/contacts/model/ExternalAccountTypeTest.java b/tests/src/com/android/contacts/model/ExternalAccountTypeTest.java
index a020c8a..27c645a 100644
--- a/tests/src/com/android/contacts/model/ExternalAccountTypeTest.java
+++ b/tests/src/com/android/contacts/model/ExternalAccountTypeTest.java
@@ -19,8 +19,6 @@
import com.android.contacts.tests.R;
import android.content.Context;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.XmlResourceParser;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -156,6 +154,55 @@
assertEquals(expectInitialized, type.isInitialized());
}
+ /**
+ * Initialize with "contacts_readonly.xml" and see if all data kinds are correctly registered.
+ */
+ public void testReadOnlyDefinition() {
+ final ExternalAccountType type = new ExternalAccountType(getContext(),
+ getTestContext().getPackageName(), false,
+ getTestContext().getResources().getXml(R.xml.contacts_readonly)
+ );
+ assertTrue(type.isInitialized());
+
+ // Shouldn't have a "null" mimetype.
+ assertTrue(type.getKindForMimetype(null) == null);
+
+ // 3 kinds are defined in XML and 4 are added by default.
+ assertEquals(4 + 3, type.getSortedDataKinds().size());
+
+ // Check for the default kinds.
+ assertNotNull(type.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE));
+ assertNotNull(type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME));
+ assertNotNull(type.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME));
+ assertNotNull(type.getKindForMimetype(Photo.CONTENT_ITEM_TYPE));
+
+ // Check for type specific kinds.
+ DataKind kind = type.getKindForMimetype("vnd.android.cursor.item/a.b.c");
+ assertNotNull(kind);
+ // No check for icon -- we actually just ignore it.
+ assertEquals("data1", ((BaseAccountType.SimpleInflater) kind.actionHeader)
+ .getColumnNameForTest());
+ assertEquals("data2", ((BaseAccountType.SimpleInflater) kind.actionBody)
+ .getColumnNameForTest());
+ assertEquals(true, kind.actionBodySocial);
+
+ kind = type.getKindForMimetype("vnd.android.cursor.item/d.e.f");
+ assertNotNull(kind);
+ assertEquals("data3", ((BaseAccountType.SimpleInflater) kind.actionHeader)
+ .getColumnNameForTest());
+ assertEquals("data4", ((BaseAccountType.SimpleInflater) kind.actionBody)
+ .getColumnNameForTest());
+ assertEquals(false, kind.actionBodySocial);
+
+ kind = type.getKindForMimetype("vnd.android.cursor.item/xyz");
+ assertNotNull(kind);
+ assertEquals("data5", ((BaseAccountType.SimpleInflater) kind.actionHeader)
+ .getColumnNameForTest());
+ assertEquals("data6", ((BaseAccountType.SimpleInflater) kind.actionBody)
+ .getColumnNameForTest());
+ assertEquals(true, kind.actionBodySocial);
+ }
+
private static void assertsDataKindEquals(List<DataKind> expectedKinds,
List<DataKind> actualKinds) {
final int count = Math.max(actualKinds.size(), expectedKinds.size());
diff --git a/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java b/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
index 4d24f54..555b339 100644
--- a/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
+++ b/tests/src/com/android/contacts/tests/allintents/AllIntentsActivity.java
@@ -16,10 +16,10 @@
package com.android.contacts.tests.allintents;
-import com.android.contacts.model.AccountWithDataSet;
import com.android.contacts.tests.R;
import com.google.android.collect.Lists;
+import android.accounts.Account;
import android.app.ListActivity;
import android.app.SearchManager;
import android.content.ComponentName;
@@ -51,6 +51,8 @@
/**
* An activity that provides access to various modes of the contacts application.
* Useful for manual and scripted tests.
+ * <p>
+ * Note: this class cannot depend (directly on indirectly) on anything outside the test package.
*/
@SuppressWarnings("deprecation")
public class AllIntentsActivity extends ListActivity
@@ -631,12 +633,12 @@
}
@Override
- public void onAccountChosen(AccountWithDataSet account, int tag) {
+ public void onAccountChosen(Account account, String dataSet, int tag) {
switch (ContactsIntent.get(tag)) {
case EDIT_NEW_CONTACT_FOR_ACCOUNT: {
final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
intent.putExtra(Insert.ACCOUNT, account);
- intent.putExtra(Insert.DATA_SET, account.dataSet);
+ intent.putExtra(Insert.DATA_SET, dataSet);
startActivity(intent);
break;
}
@@ -644,7 +646,7 @@
final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
intent.putExtra(Insert.ACCOUNT, account);
- intent.putExtra(Insert.DATA_SET, account.dataSet);
+ intent.putExtra(Insert.DATA_SET, dataSet);
putDataExtra(intent);
startActivity(intent);
diff --git a/tests/src/com/android/contacts/tests/allintents/SelectAccountDialogFragment.java b/tests/src/com/android/contacts/tests/allintents/SelectAccountDialogFragment.java
index c261553..f0c2df4 100644
--- a/tests/src/com/android/contacts/tests/allintents/SelectAccountDialogFragment.java
+++ b/tests/src/com/android/contacts/tests/allintents/SelectAccountDialogFragment.java
@@ -16,9 +16,8 @@
package com.android.contacts.tests.allintents;
-import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
-
+import android.accounts.Account;
+import android.accounts.AccountManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
@@ -30,8 +29,6 @@
import android.widget.ArrayAdapter;
import android.widget.TextView;
-import java.util.List;
-
/**
* Shows a dialog asking the user which account to chose.
* The result is passed back to the owning Activity
@@ -46,14 +43,14 @@
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle parameters = getArguments();
- final List<AccountWithDataSet> accounts =
- AccountTypeManager.getInstance(getActivity()).getAccounts(false);
+ AccountManager accountManager = AccountManager.get(getActivity());
+ Account[] accounts = accountManager.getAccounts();
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
final LayoutInflater inflater = LayoutInflater.from(builder.getContext());
- final ArrayAdapter<AccountWithDataSet> accountAdapter =
- new ArrayAdapter<AccountWithDataSet>(builder.getContext(),
+ final ArrayAdapter<Account> accountAdapter =
+ new ArrayAdapter<Account>(builder.getContext(),
android.R.layout.simple_list_item_2, accounts) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
@@ -64,7 +61,7 @@
final TextView text1 = (TextView)resultView.findViewById(android.R.id.text1);
final TextView text2 = (TextView)resultView.findViewById(android.R.id.text2);
- final AccountWithDataSet account = getItem(position);
+ final Account account = getItem(position);
text1.setText("Name: " + account.name);
text2.setText("Type: " + account.type);
@@ -79,8 +76,10 @@
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
+ // We currently do not pass the dataSet argument to the listener. To do so, we would
+ // have to determine the dataSet as it is done in AccountTypeManager.
((Listener) getActivity()).onAccountChosen(accountAdapter.getItem(which),
- parameters.getInt(EXTRA_TAG));
+ null, parameters.getInt(EXTRA_TAG));
}
};
@@ -97,6 +96,6 @@
}
public interface Listener {
- void onAccountChosen(AccountWithDataSet account, int tag);
+ void onAccountChosen(Account account, String dataSet, int tag);
}
}