<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.mail" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.VIBRATE" />
android:orientation="vertical"
>
- <include layout="@layout/edit_contact_entry_ringtone" android:id="@+id/ringtone" />
+ <include layout="@layout/preference_with_more_button" android:id="@+id/ringtone" />
<View
android:layout_width="match_parent"
android:layout_height="1dip"
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="20dip"
+ android:layout_marginLeft="14dip"
android:layout_marginRight="6dip"
android:layout_marginTop="6dip"
android:layout_marginBottom="6dip"
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/entry_ringtone"
+ android:id="@+id/preference"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingRight="?android:attr/scrollbarSize"
<!-- Text describing that a contact has no information available other than name and photo -->
<string name="no_contact_details">No additional information for this contact</string>
+
+ <!-- Label of the "sort list by" display option -->
+ <string name="display_options_sort_list_by">Sort list by</string>
+
+ <!-- An allowable value for the "sort list by" contact display option -->
+ <string name="display_options_sort_by_given_name">Given name</string>
+
+ <!-- An allowable value for the "sort list by" contact display option -->
+ <string name="display_options_sort_by_family_name">Family name</string>
+
+ <!-- Label of the "view names as" display option -->
+ <string name="display_options_view_names_as">View contact names as</string>
+
+ <!-- An allowable value for the "view names as" contact display option -->
+ <string name="display_options_view_given_name_first">Given name first</string>
+
+ <!-- An allowable value for the "view names as" contact display option -->
+ <string name="display_options_view_family_name_first">Family name first</string>
</resources>
package com.android.contacts;
+import com.android.contacts.TextHighlightingAnimation.TextWithHighlighting;
import com.android.contacts.model.ContactsSource;
import com.android.contacts.model.Sources;
+import com.android.contacts.ui.ContactsPreferences;
import com.android.contacts.ui.DisplayGroupsActivity;
import com.android.contacts.ui.DisplayGroupsActivity.Prefs;
import com.android.contacts.util.AccountSelectionUtil;
import android.provider.ContactsContract.Intents.UI;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextThemeWrapper;
import android.widget.SectionIndexer;
import android.widget.TextView;
import android.widget.AbsListView.OnScrollListener;
-import android.*;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
private static final int SUBACTIVITY_VIEW_CONTACT = 2;
private static final int SUBACTIVITY_DISPLAY_GROUP = 3;
+ private static final int TEXT_HIGHLIGHTING_ANIMATION_DURATION = 350;
+
/**
* The action for the join contact activity.
* <p>
static final int MAX_SUGGESTIONS = 4;
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
- Contacts._ID, // 0
- Contacts.DISPLAY_NAME, // 1
- Contacts.STARRED, //2
- Contacts.TIMES_CONTACTED, //3
- Contacts.CONTACT_PRESENCE, //4
- Contacts.PHOTO_ID, //5
- Contacts.LOOKUP_KEY, //6
- Contacts.HAS_PHONE_NUMBER, //7
- Contacts.SORT_KEY_PRIMARY, //8
+ Contacts._ID, // 0
+ Contacts.DISPLAY_NAME_PRIMARY, // 1
+ Contacts.DISPLAY_NAME_ALTERNATIVE, // 2
+ Contacts.SORT_KEY_PRIMARY, // 3
+ Contacts.STARRED, // 4
+ Contacts.TIMES_CONTACTED, // 5
+ Contacts.CONTACT_PRESENCE, // 6
+ Contacts.PHOTO_ID, // 7
+ Contacts.LOOKUP_KEY, // 8
+ Contacts.HAS_PHONE_NUMBER, // 9
};
static final String[] CONTACTS_SUMMARY_PROJECTION_FROM_EMAIL = new String[] {
- Contacts._ID, // 0
- Contacts.DISPLAY_NAME, // 1
- Contacts.STARRED, //2
- Contacts.TIMES_CONTACTED, //3
- Contacts.CONTACT_PRESENCE, //4
- Contacts.PHOTO_ID, //5
- Contacts.LOOKUP_KEY, //6
+ Contacts._ID, // 0
+ Contacts.DISPLAY_NAME_PRIMARY, // 1
+ Contacts.DISPLAY_NAME_ALTERNATIVE, // 2
+ Contacts.SORT_KEY_PRIMARY, // 3
+ Contacts.STARRED, // 4
+ Contacts.TIMES_CONTACTED, // 5
+ Contacts.CONTACT_PRESENCE, // 6
+ Contacts.PHOTO_ID, // 7
+ Contacts.LOOKUP_KEY, // 8
// email lookup doesn't included HAS_PHONE_NUMBER OR LOOKUP_KEY in projection
};
static final String[] LEGACY_PEOPLE_PROJECTION = new String[] {
- People._ID, // 0
- People.DISPLAY_NAME, // 1
- People.STARRED, //2
- PeopleColumns.TIMES_CONTACTED, //3
- People.PRESENCE_STATUS, //4
+ People._ID, // 0
+ People.DISPLAY_NAME, // 1
+ People.DISPLAY_NAME, // 2
+ People.DISPLAY_NAME, // 3
+ People.STARRED, // 4
+ PeopleColumns.TIMES_CONTACTED, // 5
+ People.PRESENCE_STATUS, // 6
};
static final int SUMMARY_ID_COLUMN_INDEX = 0;
- static final int SUMMARY_NAME_COLUMN_INDEX = 1;
- static final int SUMMARY_STARRED_COLUMN_INDEX = 2;
- static final int SUMMARY_TIMES_CONTACTED_COLUMN_INDEX = 3;
- static final int SUMMARY_PRESENCE_STATUS_COLUMN_INDEX = 4;
- static final int SUMMARY_PHOTO_ID_COLUMN_INDEX = 5;
- static final int SUMMARY_LOOKUP_KEY = 6;
- static final int SUMMARY_HAS_PHONE_COLUMN_INDEX = 7;
- static final int SUMMARY_SORT_KEY_PRIMARY = 8;
+ static final int SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX = 1;
+ static final int SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX = 2;
+ static final int SUMMARY_SORT_KEY_PRIMARY_COLUMN_INDEX = 3;
+ static final int SUMMARY_STARRED_COLUMN_INDEX = 4;
+ static final int SUMMARY_TIMES_CONTACTED_COLUMN_INDEX = 5;
+ static final int SUMMARY_PRESENCE_STATUS_COLUMN_INDEX = 6;
+ static final int SUMMARY_PHOTO_ID_COLUMN_INDEX = 7;
+ static final int SUMMARY_LOOKUP_KEY_COLUMN_INDEX = 8;
+ static final int SUMMARY_HAS_PHONE_COLUMN_INDEX = 9;
static final String[] PHONES_PROJECTION = new String[] {
Phone._ID, //0
}
}
+ /**
+ * A {@link TextHighlightingAnimation} that redraws just the contact display name in a
+ * list item.
+ */
+ private static class NameHighlightingAnimation extends TextHighlightingAnimation {
+ private final ListView mListView;
+
+ private NameHighlightingAnimation(ListView listView, int duration) {
+ super(duration);
+ this.mListView = listView;
+ }
+
+ /**
+ * Redraws all visible items of the list corresponding to contacts
+ */
+ @Override
+ protected void invalidate() {
+ int childCount = mListView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View listItem = mListView.getChildAt(i);
+ Object tag = listItem.getTag();
+ if (tag instanceof ContactListItemCache) {
+ ((ContactListItemCache)tag).nameView.invalidate();
+ }
+ }
+ }
+
+ @Override
+ protected void onAnimationStarted() {
+ mListView.setScrollingCacheEnabled(false);
+ }
+
+ @Override
+ protected void onAnimationEnded() {
+ mListView.setScrollingCacheEnabled(true);
+ }
+ }
+
// The size of a home screen shortcut icon.
private int mIconSize;
+ private ContactsPreferences mContactsPrefs;
+ private int mDisplayOrder;
+ private int mSortOrder;
+ private boolean mHighlightWhenScrolling;
+ private TextHighlightingAnimation mHighlightingAnimation;
@Override
protected void onCreate(Bundle icicle) {
final Intent intent = getIntent();
mIconSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
+ mContactsPrefs = new ContactsPreferences(this);
// Allow the title to be set to a custom String using an extra on the intent
String title = intent.getStringExtra(UI.TITLE_EXTRA_KEY);
// Setup the UI
final ListView list = getListView();
+ mHighlightingAnimation =
+ new NameHighlightingAnimation(list, TEXT_HIGHLIGHTING_ANIMATION_DURATION);
// Tell list view to not show dividers. We'll do it ourself so that we can *not* show
// them when an A-Z headers is visible.
return contactName;
}
- private int[] mLocation = new int[2];
- private Rect mRect = new Rect();
+
+ private int getSummaryDisplayNameColumnIndex() {
+ if (mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+ return SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
+ } else {
+ return SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
+ }
+ }
/** {@inheritDoc} */
public void onClick(View v) {
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
// Setup the menu header
- menu.setHeaderTitle(cursor.getString(SUMMARY_NAME_COLUMN_INDEX));
+ menu.setHeaderTitle(cursor.getString(getSummaryDisplayNameColumnIndex()));
// View contact details
menu.add(0, MENU_ITEM_VIEW_CONTACT, 0, R.string.menu_viewContact)
|| mMode == MODE_LEGACY_PICK_OR_CREATE_PERSON) {
if (mShortcutAction != null) {
Cursor c = (Cursor) mAdapter.getItem(position);
- returnPickerResult(c, c.getString(SUMMARY_NAME_COLUMN_INDEX), uri, id);
+ returnPickerResult(c, c.getString(getSummaryDisplayNameColumnIndex()), uri, id);
} else {
returnPickerResult(null, null, uri, id);
}
default: {
// Build and return soft, lookup reference
final long contactId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
- final String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY);
+ final String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
return Contacts.getLookupUri(contactId, lookupKey);
}
}
return builder.build();
}
- private static String getSortOrder(String[] projectionType) {
- /* if (Locale.getDefault().equals(Locale.JAPAN) &&
- projectionType == AGGREGATES_PRIMARY_PHONE_PROJECTION) {
- return SORT_STRING + " ASC";
+ private String getSortOrder(String[] projectionType) {
+ if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
+ return Contacts.SORT_KEY_PRIMARY;
} else {
- return NAME_COLUMN + " COLLATE LOCALIZED ASC";
- } */
-
- return Contacts.SORT_KEY_PRIMARY;
+ return Contacts.SORT_KEY_ALTERNATIVE;
+ }
}
void startQuery() {
mQueryHandler.cancelOperation(QUERY_TOKEN);
mQueryHandler.setLoadingJoinSuggestions(false);
+ mSortOrder = mContactsPrefs.getSortOrder();
+ mDisplayOrder = mContactsPrefs.getDisplayOrder();
+
+ // When sort order and display order contradict each other, we want to
+ // highlight the part of the name used for sorting.
+ mHighlightWhenScrolling = false;
+ if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY &&
+ mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE) {
+ mHighlightWhenScrolling = true;
+ } else if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_ALTERNATIVE &&
+ mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+ mHighlightWhenScrolling = true;
+ }
+
String[] projection = getProjectionForQuery();
String callingPackage = getCallingPackage();
Uri uri = getUriToQuery();
CONTACTS_SUMMARY_PROJECTION,
Contacts._ID + " != " + activity.mQueryAggregateId
+ " AND " + CLAUSE_ONLY_VISIBLE, null,
- getSortOrder(CONTACTS_SUMMARY_PROJECTION));
+ activity.getSortOrder(CONTACTS_SUMMARY_PROJECTION));
return;
}
public ImageView callButton;
public CharArrayBuffer nameBuffer = new CharArrayBuffer(128);
public TextView labelView;
- public CharArrayBuffer labelBuffer = new CharArrayBuffer(128);
public TextView dataView;
public CharArrayBuffer dataBuffer = new CharArrayBuffer(128);
public ImageView presenceView;
public QuickContactBadge photoView;
public ImageView nonQuickContactPhotoView;
+ public CharArrayBuffer highlightedTextBuffer = new CharArrayBuffer(128);
+ public TextWithHighlighting textWithHighlighting;
}
final static class PhotoInfo {
private int mSuggestionsCursorCount;
private ImageFetchHandler mHandler;
private ImageDbFetcher mImageFetcher;
+
private static final int FETCH_IMAGE_MSG = 1;
public ContactItemListAdapter(Context context) {
private SectionIndexer getNewIndexer(Cursor cursor) {
if (Locale.getDefault().getLanguage().equals(Locale.JAPAN.getLanguage())) {
- return new JapaneseContactListIndexer(cursor, SUMMARY_SORT_KEY_PRIMARY);
+ return new JapaneseContactListIndexer(cursor,
+ SUMMARY_SORT_KEY_PRIMARY_COLUMN_INDEX);
} else {
- return new AlphabetIndexer(cursor, SUMMARY_NAME_COLUMN_INDEX, mAlphabet);
+ return new AlphabetIndexer(cursor, getSummaryDisplayNameColumnIndex(), mAlphabet);
}
}
cache.photoView.setExcludeMimes(new String[] {Contacts.CONTENT_ITEM_TYPE});
}
cache.nonQuickContactPhotoView = (ImageView) view.findViewById(R.id.noQuickContactPhoto);
+ cache.textWithHighlighting = mHighlightingAnimation.createTextWithHighlighting();
view.setTag(cache);
-
return view;
}
int defaultType;
int nameColumnIndex;
boolean displayAdditionalData = mDisplayAdditionalData;
+ boolean highlightingEnabled = false;
switch(mMode) {
case MODE_PICK_PHONE:
case MODE_LEGACY_PICK_PHONE: {
break;
}
default: {
- nameColumnIndex = SUMMARY_NAME_COLUMN_INDEX;
+ nameColumnIndex = getSummaryDisplayNameColumnIndex();
dataColumnIndex = -1;
typeColumnIndex = -1;
labelColumnIndex = -1;
defaultType = Phone.TYPE_HOME;
displayAdditionalData = false;
+ highlightingEnabled = mHighlightWhenScrolling && mMode != MODE_STREQUENT;
}
}
cursor.copyStringToBuffer(nameColumnIndex, cache.nameBuffer);
int size = cache.nameBuffer.sizeCopied;
if (size != 0) {
- cache.nameView.setText(cache.nameBuffer.data, 0, size);
+ if (highlightingEnabled) {
+ buildDisplayNameWithHighlighting(cache.nameView, cursor, cache.nameBuffer,
+ cache.highlightedTextBuffer, cache.textWithHighlighting);
+ } else {
+ cache.nameView.setText(cache.nameBuffer.data, 0, size);
+ }
} else {
cache.nameView.setText(mUnknownNameText);
}
viewToUse = cache.photoView;
// Build soft lookup reference
final long contactId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
- final String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY);
+ final String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
cache.photoView.assignContactUri(Contacts.getLookupUri(contactId, lookupKey));
cache.photoView.setVisibility(View.VISIBLE);
cache.nonQuickContactPhotoView.setVisibility(View.INVISIBLE);
}
}
+ /**
+ * Computes the span of the display name that has highlighted parts and configures
+ * the display name text view accordingly.
+ */
+ private void buildDisplayNameWithHighlighting(TextView textView, Cursor cursor,
+ CharArrayBuffer buffer1, CharArrayBuffer buffer2,
+ TextWithHighlighting textWithHighlighting) {
+ int oppositeDisplayOrderColumnIndex;
+ if (mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+ oppositeDisplayOrderColumnIndex = SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
+ } else {
+ oppositeDisplayOrderColumnIndex = SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
+ }
+ cursor.copyStringToBuffer(oppositeDisplayOrderColumnIndex, buffer2);
+
+ textWithHighlighting.setText(buffer1, buffer2);
+ textView.setText(textWithHighlighting);
+ }
+
private void bindSectionHeader(View view, int position, boolean displaySectionHeaders) {
final ContactListItemCache cache = (ContactListItemCache) view.getTag();
if (!displaySectionHeaders) {
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
+ if (mHighlightWhenScrolling) {
+ if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
+ mHighlightingAnimation.startHighlighting();
+ } else {
+ mHighlightingAnimation.stopHighlighting();
+ }
+ }
+
mScrollState = scrollState;
if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
// If we are in a fling, stop loading images.
--- /dev/null
+/*
+ * Copyright (C) 2010 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;
+
+import com.android.internal.R;
+
+import android.database.CharArrayBuffer;
+import android.graphics.Color;
+import android.os.Handler;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.style.CharacterStyle;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * An animation that alternately dims and brightens the non-highlighted portion of text.
+ */
+public abstract class TextHighlightingAnimation implements Runnable {
+
+ private static final int MAX_ALPHA = 255;
+ private static final int MIN_ALPHA = 50;
+
+ private AccelerateInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
+ private DecelerateInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
+
+ private final static DimmingSpan[] sEmptySpans = new DimmingSpan[0];
+
+ private DimmingSpan mDimmingSpan;
+ private Handler mHandler;
+ private boolean mAnimating;
+ private boolean mDimming;
+ private long mTargetTime;
+ private final int mDuration;
+
+ /**
+ * A Spanned that highlights a part of text by dimming another part of that text.
+ */
+ public class TextWithHighlighting implements Spanned {
+
+ private final DimmingSpan[] mSpans;
+ private boolean mDimmingEnabled;
+ private CharArrayBuffer mText;
+ private int mDimmingSpanStart;
+ private int mDimmingSpanEnd;
+ private String mString;
+
+ public TextWithHighlighting() {
+ mSpans = new DimmingSpan[] { mDimmingSpan };
+ }
+
+ public void setText(CharArrayBuffer baseText, CharArrayBuffer highlightedText) {
+ mText = baseText;
+
+ // TODO figure out a way to avoid string allocation
+ mString = new String(mText.data, 0, mText.sizeCopied);
+
+ int index = indexOf(baseText, highlightedText);
+
+ if (index == 0 || index == -1) {
+ mDimmingEnabled = false;
+ } else {
+ mDimmingEnabled = true;
+ mDimmingSpanStart = 0;
+ mDimmingSpanEnd = index;
+ }
+ }
+
+ /**
+ * An implementation of indexOf on CharArrayBuffers that finds the first match of
+ * the start of buffer2 in buffer1. For example, indexOf("abcd", "cdef") == 2
+ */
+ private int indexOf(CharArrayBuffer buffer1, CharArrayBuffer buffer2) {
+ char[] string1 = buffer1.data;
+ char[] string2 = buffer2.data;
+ int count1 = buffer1.sizeCopied;
+ int count2 = buffer2.sizeCopied;
+ int size = count2;
+ for (int i = 0; i < count1; i++) {
+ if (i + size > count1) {
+ size = count1 - i;
+ }
+ int j;
+ for (j = 0; j < size; j++) {
+ if (string1[i+j] != string2[j]) {
+ break;
+ }
+ }
+ if (j == size) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+
+ @SuppressWarnings("unchecked")
+ public <T> T[] getSpans(int start, int end, Class<T> type) {
+ if (mDimmingEnabled) {
+ return (T[])mSpans;
+ } else {
+ return (T[])sEmptySpans;
+ }
+ }
+
+ public int getSpanStart(Object tag) {
+ // We only have one span - no need to check the tag parameter
+ return mDimmingSpanStart;
+ }
+
+ public int getSpanEnd(Object tag) {
+ // We only have one span - no need to check the tag parameter
+ return mDimmingSpanEnd;
+ }
+
+ public int getSpanFlags(Object tag) {
+ // String is immutable - flags not needed
+ return 0;
+ }
+
+ public int nextSpanTransition(int start, int limit, Class type) {
+ // Never called since we only have one span
+ return 0;
+ }
+
+ public char charAt(int index) {
+ return mText.data[index];
+ }
+
+ public int length() {
+ return mText.sizeCopied;
+ }
+
+ public CharSequence subSequence(int start, int end) {
+ // Never called - implementing for completeness
+ return new String(mText.data, start, end);
+ }
+
+ @Override
+ public String toString() {
+ return mString;
+ }
+ }
+
+ /**
+ * A Span that modifies alpha of the default foreground color.
+ */
+ private static class DimmingSpan extends CharacterStyle {
+ private int mAlpha;
+
+ public void setAlpha(int alpha) {
+ mAlpha = alpha;
+ }
+
+ @Override
+ public void updateDrawState(TextPaint ds) {
+
+ // Only dim the text in the basic state; not selected, focused or pressed
+ int[] states = ds.drawableState;
+ if (states != null) {
+ int count = states.length;
+ for (int i = 0; i < count; i++) {
+ switch (states[i]) {
+ case R.attr.state_pressed:
+ case R.attr.state_selected:
+ case R.attr.state_focused:
+ // We can simply return, because the supplied text
+ // paint is already configured with defaults.
+ return;
+ }
+ }
+ }
+
+ int color = ds.getColor();
+ color = Color.argb(mAlpha, Color.red(color), Color.green(color), Color.blue(color));
+ ds.setColor(color);
+ }
+ }
+
+ /**
+ * Constructor.
+ */
+ public TextHighlightingAnimation(int duration) {
+ mDuration = duration;
+ mHandler = new Handler();
+ mDimmingSpan = new DimmingSpan();
+ mDimmingSpan.setAlpha(MAX_ALPHA);
+ }
+
+ /**
+ * Returns a Spanned that can be used by a text view to show text with highlighting.
+ */
+ public TextWithHighlighting createTextWithHighlighting() {
+ return new TextWithHighlighting();
+ }
+
+ /**
+ * Override and invalidate (redraw) TextViews showing {@link TextWithHighlighting}.
+ */
+ protected abstract void invalidate();
+
+ /**
+ * Starts the highlighting animation, which will dim portions of text.
+ */
+ public void startHighlighting() {
+ startAnimation(true);
+ }
+
+ /**
+ * Starts un-highlighting animation, which will brighten the dimmed portions of text
+ * to the brightness level of the rest of text.
+ */
+ public void stopHighlighting() {
+ startAnimation(false);
+ }
+
+ /**
+ * Called when the animation starts.
+ */
+ protected void onAnimationStarted() {
+ }
+
+ /**
+ * Called when the animation has stopped.
+ */
+ protected void onAnimationEnded() {
+ }
+
+ private void startAnimation(boolean dim) {
+ if (mDimming != dim) {
+ mDimming = dim;
+ long now = System.currentTimeMillis();
+ if (!mAnimating) {
+ mAnimating = true;
+ mTargetTime = now + mDuration;
+ onAnimationStarted();
+ mHandler.post(this);
+ } else {
+
+ // If we have started dimming, reverse the direction and adjust the target
+ // time accordingly.
+ mTargetTime = (now + mDuration) - (mTargetTime - now);
+ }
+ }
+ }
+
+ /**
+ * Animation step.
+ */
+ public void run() {
+ long now = System.currentTimeMillis();
+ long timeLeft = mTargetTime - now;
+ if (timeLeft < 0) {
+ mDimmingSpan.setAlpha(mDimming ? MIN_ALPHA : MAX_ALPHA);
+ mAnimating = false;
+ onAnimationEnded();
+ return;
+ }
+
+ // Start=1, end=0
+ float virtualTime = (float)timeLeft / mDuration;
+ if (mDimming) {
+ float interpolatedTime = DECELERATE_INTERPOLATOR.getInterpolation(virtualTime);
+ mDimmingSpan.setAlpha((int)(MIN_ALPHA + (MAX_ALPHA-MIN_ALPHA) * interpolatedTime));
+ } else {
+ float interpolatedTime = ACCELERATE_INTERPOLATOR.getInterpolation(virtualTime);
+ mDimmingSpan.setAlpha((int)(MIN_ALPHA + (MAX_ALPHA-MIN_ALPHA) * (1-interpolatedTime)));
+ }
+
+ invalidate();
+
+ // Repeat
+ mHandler.post(this);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) 2010 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.ui;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.provider.ContactsContract;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+
+/**
+ * Manages user preferences for contacts.
+ */
+public final class ContactsPreferences {
+
+ private Context mContext;
+ private ContentResolver mContentResolver;
+ private int mSortOrder = -1;
+ private int mDisplayOrder = -1;
+ private SettingsObserver mSettingsObserver;
+
+ // TODO listen to locale changes
+ public ContactsPreferences(Context context) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+
+ mSettingsObserver = new SettingsObserver();
+ mSettingsObserver.register();
+ }
+
+ public boolean isSortOrderUserChangeable() {
+ // TODO this should be locale-specific
+ return true;
+ }
+
+ private int getDefaultSortOrder() {
+ // TODO this should be locale-specific
+ return ContactsContract.Preferences.SORT_ORDER_PRIMARY;
+ }
+
+ public int getSortOrder() {
+ if (mSortOrder == -1) {
+ try {
+ mSortOrder = Settings.System.getInt(mContext.getContentResolver(),
+ ContactsContract.Preferences.SORT_ORDER);
+ } catch (SettingNotFoundException e) {
+ mSortOrder = getDefaultSortOrder();
+ }
+ }
+ return mSortOrder;
+ }
+
+ public void setSortOrder(int sortOrder) {
+ mSortOrder = sortOrder;
+ Settings.System.putInt(mContext.getContentResolver(),
+ ContactsContract.Preferences.SORT_ORDER, sortOrder);
+ }
+
+ public boolean isDisplayOrderUserChangeable() {
+ // TOD this should be locale-specific
+ return true;
+ }
+
+ private int getDefaultDisplayOrder() {
+ // TODO this should be locale-specific
+ return ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY;
+ }
+
+ public int getDisplayOrder() {
+ if (mDisplayOrder == -1) {
+ try {
+ mDisplayOrder = Settings.System.getInt(mContext.getContentResolver(),
+ ContactsContract.Preferences.DISPLAY_ORDER);
+ } catch (SettingNotFoundException e) {
+ mDisplayOrder = getDefaultDisplayOrder();
+ }
+ }
+ return mDisplayOrder;
+ }
+
+ public void setDisplayOrder(int displayOrder) {
+ mDisplayOrder = displayOrder;
+ Settings.System.putInt(mContext.getContentResolver(),
+ ContactsContract.Preferences.DISPLAY_ORDER, displayOrder);
+ }
+
+ private class SettingsObserver extends ContentObserver {
+
+ public SettingsObserver() {
+ super(null);
+ }
+
+ public void register() {
+ mContentResolver.registerContentObserver(
+ Settings.System.getUriFor(
+ ContactsContract.Preferences.SORT_ORDER), false, this);
+ mContentResolver.registerContentObserver(
+ Settings.System.getUriFor(
+ ContactsContract.Preferences.DISPLAY_ORDER), false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mSortOrder = -1;
+ mDisplayOrder = -1;
+
+ // TODO send a message to parent context to notify of the change
+ }
+ }
+}
import android.accounts.Account;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.Dialog;
import android.app.ExpandableListActivity;
import android.app.ProgressDialog;
import android.content.ContentProviderOperation;
import android.widget.CheckBox;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
+import android.widget.ListView;
import android.widget.TextView;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
}
+ private static final int DIALOG_SORT_ORDER = 1;
+ private static final int DIALOG_DISPLAY_ORDER = 2;
+
private ExpandableListView mList;
private DisplayAdapter mAdapter;
private SharedPreferences mPrefs;
+ private ContactsPreferences mContactsPrefs;
private CheckBox mDisplayPhones;
private View mHeaderPhones;
private View mHeaderSeparator;
+ private View mSortOrderView;
+ private TextView mSortOrderTextView;
+ private int mSortOrder;
+
+ private View mDisplayOrderView;
+ private TextView mDisplayOrderTextView;
+ private int mDisplayOrder;
+
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
mList = getExpandableListView();
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+ mContactsPrefs = new ContactsPreferences(this);
final LayoutInflater inflater = getLayoutInflater();
// Add the "Only contacts with phones" header modifier.
- mHeaderPhones = inflater.inflate(R.layout.display_header, mList, false);
+ mHeaderPhones = inflater.inflate(R.layout.display_options_phones_only, mList, false);
mHeaderPhones.setId(R.id.header_phones);
mDisplayPhones = (CheckBox) mHeaderPhones.findViewById(android.R.id.checkbox);
mDisplayPhones.setChecked(mPrefs.getBoolean(Prefs.DISPLAY_ONLY_PHONES,
text1.setText(R.string.showFilterPhones);
text2.setText(R.string.showFilterPhonesDescrip);
}
+
mList.addHeaderView(mHeaderPhones, null, true);
+ addSortOrderView();
+ addDisplayOrderView();
+
// Add the separator before showing the detailed group list.
mHeaderSeparator = inflater.inflate(R.layout.list_separator, mList, false);
{
mList.setOnItemClickListener(this);
mList.setOnCreateContextMenuListener(this);
+ mSortOrder = mContactsPrefs.getSortOrder();
+ mDisplayOrder = mContactsPrefs.getDisplayOrder();
+
// Start background query to find account details
new QueryGroupsTask(this).execute();
}
+ private void addSortOrderView() {
+ final LayoutInflater inflater = getLayoutInflater();
+ mSortOrderView = inflater.inflate(R.layout.preference_with_more_button, mList, false);
+ View preferenceLayout = mSortOrderView.findViewById(R.id.preference);
+ preferenceLayout.setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View v) {
+ showDialog(DIALOG_SORT_ORDER);
+ }
+ });
+
+ TextView label = (TextView)preferenceLayout.findViewById(R.id.label);
+ label.setText(getString(R.string.display_options_sort_list_by));
+
+ mSortOrderTextView = (TextView)preferenceLayout.findViewById(R.id.data);
+ mList.addHeaderView(mSortOrderView, null, false);
+ }
+
+ private void addDisplayOrderView() {
+ final LayoutInflater inflater = getLayoutInflater();
+
+ mDisplayOrderView = inflater.inflate(R.layout.preference_with_more_button, mList, false);
+ View preferenceLayout = mDisplayOrderView.findViewById(R.id.preference);
+ preferenceLayout.setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View v) {
+ showDialog(DIALOG_DISPLAY_ORDER);
+ }
+ });
+
+ TextView label = (TextView)preferenceLayout.findViewById(R.id.label);
+ label.setText(getString(R.string.display_options_view_names_as));
+
+ mDisplayOrderTextView = (TextView)preferenceLayout.findViewById(R.id.data);
+ mList.addHeaderView(mDisplayOrderView, null, false);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ bindView();
+ }
+
+ private void bindView() {
+ mSortOrderTextView.setText(
+ mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY
+ ? getString(R.string.display_options_sort_by_given_name)
+ : getString(R.string.display_options_sort_by_family_name));
+
+ mDisplayOrderTextView.setText(
+ mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY
+ ? getString(R.string.display_options_view_given_name_first)
+ : getString(R.string.display_options_view_family_name_first));
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ switch (id) {
+ case DIALOG_SORT_ORDER:
+ return createSortOrderDialog();
+ case DIALOG_DISPLAY_ORDER:
+ return createDisplayOrderDialog();
+ }
+
+ return null;
+ }
+
+ private Dialog createSortOrderDialog() {
+ String[] items = new String[] {
+ getString(R.string.display_options_sort_by_given_name),
+ getString(R.string.display_options_sort_by_family_name),
+ };
+
+ return new AlertDialog.Builder(this)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setTitle(R.string.display_options_sort_list_by)
+ .setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ setSortOrder(dialog);
+ dialog.dismiss();
+ }
+ })
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ setSortOrder(dialog);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+ }
+
+ private Dialog createDisplayOrderDialog() {
+ String[] items = new String[] {
+ getString(R.string.display_options_view_given_name_first),
+ getString(R.string.display_options_view_family_name_first),
+ };
+
+ return new AlertDialog.Builder(this)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setTitle(R.string.display_options_view_names_as)
+ .setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ setDisplayOrder(dialog);
+ dialog.dismiss();
+ }
+ })
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ setDisplayOrder(dialog);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+ }
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog) {
+ switch (id) {
+ case DIALOG_SORT_ORDER:
+ setCheckedItem(dialog,
+ mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY ? 0 : 1);
+ break;
+ case DIALOG_DISPLAY_ORDER:
+ setCheckedItem(dialog,
+ mSortOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY ? 0 : 1);
+ break;
+ }
+ }
+
+ private void setCheckedItem(Dialog dialog, int position) {
+ ListView listView = ((AlertDialog)dialog).getListView();
+ listView.setItemChecked(position, true);
+ listView.setSelection(position);
+ }
+
+ protected void setSortOrder(DialogInterface dialog) {
+ ListView listView = ((AlertDialog)dialog).getListView();
+ int checked = listView.getCheckedItemPosition();
+ mSortOrder = checked == 0
+ ? ContactsContract.Preferences.SORT_ORDER_PRIMARY
+ : ContactsContract.Preferences.SORT_ORDER_ALTERNATIVE;
+
+ bindView();
+ }
+
+ protected void setDisplayOrder(DialogInterface dialog) {
+ ListView listView = ((AlertDialog)dialog).getListView();
+ int checked = listView.getCheckedItemPosition();
+ mDisplayOrder = checked == 0
+ ? ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY
+ : ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE;
+
+ bindView();
+ }
+
/**
* Background operation to build set of {@link AccountDisplay} for each
* {@link Sources#getAccounts(boolean)} that provides groups.
}
private void doSaveAction() {
+ mContactsPrefs.setSortOrder(mSortOrder);
+ mContactsPrefs.setDisplayOrder(mDisplayOrder);
+
if (mAdapter == null) return;
setDisplayOnlyPhones(mDisplayPhones.isChecked());
new UpdateTask(this).execute(mAdapter.mAccounts);