summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorke Lee <yorkelee@google.com>2013-05-07 11:59:36 -0700
committerYorke Lee <yorkelee@google.com>2013-05-14 13:55:21 -0700
commit3a18654bf87df4f17880551b6cfa45c871917960 (patch)
tree55a360b02c7e799def416e9b672c52a4028b97e6
parentdbb6c6fc024bbacf3b495a44bdddccf8502793ae (diff)
Animation for smart dialing suggestions
Suggestions now appear with a fade in and slide up animation. Suggestions vanish with a fade out and slide down animation. If a suggestion is moved into the middle, it slides to the left/right as appropriate. Change the layout containing suggestions to a LinearLayout, in order to better support animations. Renamed SmartDialAdapter to SmartDialController, and also refactored it to handle entries for a LinearLayout instead of a GridView, as well as adding animation support and view management. Use null object pattern in SmartDialEntry to better handle null entries. Start displaying suggestions on the first digit entered. Bug 8840240 Change-Id: If4e16006c0b36d2244434e0b2d8f3d3b997b0ad2
-rw-r--r--res/layout/dialpad_fragment.xml30
-rw-r--r--res/layout/dialpad_smartdial_item.xml12
-rw-r--r--res/values/colors.xml7
-rw-r--r--src/com/android/dialer/dialpad/DialpadFragment.java33
-rw-r--r--src/com/android/dialer/dialpad/SmartDialAdapter.java166
-rw-r--r--src/com/android/dialer/dialpad/SmartDialCache.java2
-rw-r--r--src/com/android/dialer/dialpad/SmartDialController.java389
-rw-r--r--src/com/android/dialer/dialpad/SmartDialEntry.java3
-rw-r--r--src/com/android/dialer/dialpad/SmartDialLoaderTask.java2
-rw-r--r--src/com/android/dialer/dialpad/SmartDialMatchPosition.java4
-rw-r--r--src/com/android/dialer/dialpad/SmartDialTextView.java47
11 files changed, 457 insertions, 238 deletions
diff --git a/res/layout/dialpad_fragment.xml b/res/layout/dialpad_fragment.xml
index 13d91bd27..f3bd2a213 100644
--- a/res/layout/dialpad_fragment.xml
+++ b/res/layout/dialpad_fragment.xml
@@ -57,17 +57,29 @@
android:src="@drawable/ic_dial_action_delete" />
</LinearLayout>
- <!-- Smard dial suggestion section -->
- <GridView
- android:id="@+id/dialpad_smartdial_list"
+ <!-- Smart dial suggestion section.
+ sp is used here for this layout instead of dp in order for it to resize as
+ appropriate when the font size increases. This is a one-time exception that is
+ ok in this case because there is space for the suggestion strip to expand. -->
+ <RelativeLayout
+ android:id="@+id/dialpad_smartdial_container"
android:layout_width="match_parent"
android:layout_height="50sp"
- android:columnWidth="0dp"
- android:numColumns="3"
- android:stretchMode="columnWidth"
- android:gravity="center"
- android:layout_marginTop="@dimen/dialpad_vertical_margin"
- android:background="@drawable/dialpad_background"/>
+ android:layout_marginTop="@dimen/dialpad_vertical_margin">
+ <View
+ android:id="@+id/dialpad_smartdial_list_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/dialpad_background">
+ </View>
+ <LinearLayout
+ android:id="@+id/dialpad_smartdial_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:gravity="center">
+ </LinearLayout>
+ </RelativeLayout>
<!-- Keypad section -->
<include layout="@layout/dialpad" />
diff --git a/res/layout/dialpad_smartdial_item.xml b/res/layout/dialpad_smartdial_item.xml
index 54f2a084e..32d801e80 100644
--- a/res/layout/dialpad_smartdial_item.xml
+++ b/res/layout/dialpad_smartdial_item.xml
@@ -16,15 +16,19 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="46sp">
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:layout_marginTop="2dp"
+ android:layout_marginBottom="2dp"
+ android:background="?android:attr/selectableItemBackground">
<com.android.dialer.dialpad.SmartDialTextView
android:id="@+id/contact_name"
android:layout_width="match_parent"
android:layout_height="28sp"
android:padding="@dimen/smartdial_suggestions_padding"
- android:textColor="@color/smartdial_primary_text_color"
+ android:textColor="@color/smartdial_name_primary_text_color"
android:textSize="16sp"
android:singleLine="true"
android:ellipsize="none"
@@ -34,7 +38,7 @@
android:id="@+id/contact_number"
android:layout_width="match_parent"
android:layout_height="16sp"
- android:textColor="@color/dialtacts_secondary_text_color"
+ android:textColor="@color/smartdial_number_primary_text_color"
android:textSize="13sp"
android:gravity="center"
/>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 288f58b64..1aa217fe8 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -18,9 +18,10 @@
<!-- Secondary text color in the Phone app -->
<color name="dialtacts_secondary_text_color">#888888</color>
- <color name="smartdial_confidence_drawable_color">#39caff</color>
- <color name="smartdial_primary_text_color">#39caff</color>
- <color name="smartdial_highlighted_text_color">#ffffff</color>
+ <color name="smartdial_name_primary_text_color">#0099cc</color>
+ <color name="smartdial_name_highlighted_text_color">#39c9ff</color>
+ <color name="smartdial_number_primary_text_color">#bbbbbb</color>
+ <color name="smartdial_number_highlighted_text_color">#ffffff</color>
<!-- Color of the text describing an unconsumed missed call. -->
<color name="call_log_missed_call_highlight_color">#FF0000</color>
diff --git a/src/com/android/dialer/dialpad/DialpadFragment.java b/src/com/android/dialer/dialpad/DialpadFragment.java
index f571d892e..3d75e3983 100644
--- a/src/com/android/dialer/dialpad/DialpadFragment.java
+++ b/src/com/android/dialer/dialpad/DialpadFragment.java
@@ -67,8 +67,10 @@ import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupMenu;
+import android.widget.RelativeLayout;
import android.widget.TextView;
import com.android.contacts.common.CallUtil;
@@ -147,13 +149,12 @@ public class DialpadFragment extends Fragment
private DialpadChooserAdapter mDialpadChooserAdapter;
/** Will be set only if the view has the smart dialing section. */
- private AbsListView mSmartDialList;
+ private RelativeLayout mSmartDialContainer;
/**
- * Adapter for {@link #mSmartDialList}.
* Will be set only if the view has the smart dialing section.
*/
- private SmartDialAdapter mSmartDialAdapter;
+ private SmartDialController mSmartDialAdapter;
private SmartDialCache mSmartDialCache;
/**
@@ -368,12 +369,12 @@ public class DialpadFragment extends Fragment
mDialpadChooser.setOnItemClickListener(this);
// Smart dial
- mSmartDialList = (AbsListView) fragmentView.findViewById(R.id.dialpad_smartdial_list);
- if (mSmartDialList != null) {
- mSmartDialAdapter = new SmartDialAdapter(getActivity());
- mSmartDialList.setAdapter(mSmartDialAdapter);
- mSmartDialList.setOnItemClickListener(new OnSmartDialItemClick());
- mSmartDialList.setOnItemLongClickListener(new OnSmartDialLongClick());
+ mSmartDialContainer = (RelativeLayout) fragmentView.findViewById(
+ R.id.dialpad_smartdial_container);
+
+ if (mSmartDialContainer != null) {
+ mSmartDialAdapter = new SmartDialController(getActivity(), mSmartDialContainer,
+ new OnSmartDialShortClick(), new OnSmartDialLongClick());
}
return fragmentView;
}
@@ -1697,7 +1698,7 @@ public class DialpadFragment extends Fragment
}
mLastDigitsForSmartDial = digits;
- if (digits.length() < 2) {
+ if (digits.length() < 1) {
mSmartDialAdapter.clear();
} else {
final SmartDialLoaderTask task = new SmartDialLoaderTask(this, digits, mSmartDialCache);
@@ -1716,7 +1717,7 @@ public class DialpadFragment extends Fragment
private void initializeSmartDialingState() {
// Handle smart dialing related state
if (mSmartDialEnabled) {
- mSmartDialList.setVisibility(View.VISIBLE);
+ mSmartDialContainer.setVisibility(View.VISIBLE);
mSmartDialCache = SmartDialCache.getInstance(getActivity(),
mContactsPrefs.getDisplayOrder());
// Don't force recache if this is the first time onResume is being called, since
@@ -1730,14 +1731,14 @@ public class DialpadFragment extends Fragment
mSmartDialCache.cacheIfNeeded(true);
}
} else {
- mSmartDialList.setVisibility(View.GONE);
+ mSmartDialContainer.setVisibility(View.GONE);
mSmartDialCache = null;
}
}
- private class OnSmartDialLongClick implements AdapterView.OnItemLongClickListener {
+ private class OnSmartDialLongClick implements View.OnLongClickListener {
@Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
+ public boolean onLongClick(View view) {
final SmartDialEntry entry = (SmartDialEntry) view.getTag();
if (entry == null) return false; // just in case.
mClearDigitsOnStop = true;
@@ -1749,9 +1750,9 @@ public class DialpadFragment extends Fragment
}
}
- private class OnSmartDialItemClick implements AdapterView.OnItemClickListener {
+ private class OnSmartDialShortClick implements View.OnClickListener {
@Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ public void onClick(View view) {
final SmartDialEntry entry = (SmartDialEntry) view.getTag();
if (entry == null) return; // just in case.
// Dial the displayed phone number immediately
diff --git a/src/com/android/dialer/dialpad/SmartDialAdapter.java b/src/com/android/dialer/dialpad/SmartDialAdapter.java
deleted file mode 100644
index 0a246e3e5..000000000
--- a/src/com/android/dialer/dialpad/SmartDialAdapter.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2012 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.dialer.dialpad;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.TextUtils;
-import android.text.style.ForegroundColorSpan;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.LinearLayout;
-
-import com.android.dialer.R;
-import com.google.common.collect.Lists;
-
-import java.util.List;
-
-public class SmartDialAdapter extends BaseAdapter {
- public static final String LOG_TAG = "SmartDial";
- private final LayoutInflater mInflater;
-
- private List<SmartDialEntry> mEntries;
-
- private final int mHighlightedTextColor;
-
- public SmartDialAdapter(Context context) {
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- final Resources res = context.getResources();
- mHighlightedTextColor = res.getColor(R.color.smartdial_highlighted_text_color);
- clear();
- }
-
- /** Remove all entries. */
- public void clear() {
- mEntries = Lists.newArrayList();
- notifyDataSetChanged();
- }
-
- /** Set entries. */
- public void setEntries(List<SmartDialEntry> entries) {
- if (entries == null) throw new IllegalArgumentException();
- mEntries = entries;
-
- if (mEntries.size() <= 1) {
- // add a null entry to push the single entry into the middle
- mEntries.add(0, null);
- } else if (mEntries.size() >= 2){
- // swap the 1st and 2nd entries so that the highest confidence match goes into the
- // middle
- final SmartDialEntry temp = mEntries.get(0);
- mEntries.set(0, mEntries.get(1));
- mEntries.set(1, temp);
- }
-
- notifyDataSetChanged();
- }
-
- @Override
- public boolean isEnabled(int position) {
- return !(mEntries.get(position) == null);
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return false;
- }
-
- @Override
- public int getCount() {
- return mEntries.size();
- }
-
- @Override
- public Object getItem(int position) {
- return mEntries.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position; // Just use the position as the ID, so it's not stable.
- }
-
- @Override
- public boolean hasStableIds() {
- return false; // Not stable because we just use the position as the ID.
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final LinearLayout view;
- if (convertView == null) {
- view = (LinearLayout) mInflater.inflate(
- R.layout.dialpad_smartdial_item, parent, false);
- } else {
- view = (LinearLayout) convertView;
- }
-
- final SmartDialTextView nameView = (SmartDialTextView) view.findViewById(R.id.contact_name);
-
- final SmartDialTextView numberView = (SmartDialTextView) view.findViewById(
- R.id.contact_number);
-
- final SmartDialEntry item = mEntries.get(position);
-
- if (item == null) {
- // Clear the text in case the view was reused.
- nameView.setText("");
- numberView.setText("");
- // Empty view. We use this to force a single entry to be in the middle
- return view;
- }
-
- // Highlight the display name with the provided match positions
- if (!TextUtils.isEmpty(item.displayName)) {
- final SpannableString displayName = new SpannableString(item.displayName);
- for (final SmartDialMatchPosition p : item.matchPositions) {
- if (p.start < p.end) {
- if (p.end > displayName.length()) {
- p.end = displayName.length();
- }
- // Create a new ForegroundColorSpan for each section of the name to highlight,
- // otherwise multiple highlights won't work.
- displayName.setSpan(new ForegroundColorSpan(mHighlightedTextColor), p.start,
- p.end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
- nameView.setText(displayName);
- }
-
- // Highlight the phone number with the provided match positions
- if (!TextUtils.isEmpty(item.phoneNumber)) {
- final SmartDialMatchPosition p = item.phoneNumberMatchPosition;
- final SpannableString phoneNumber = new SpannableString(item.phoneNumber);
- if (p != null && p.start < p.end) {
- if (p.end > phoneNumber.length()) {
- p.end = phoneNumber.length();
- }
- phoneNumber.setSpan(new ForegroundColorSpan(mHighlightedTextColor), p.start, p.end,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- numberView.setText(phoneNumber);
- }
- view.setTag(item);
-
- return view;
- }
-}
diff --git a/src/com/android/dialer/dialpad/SmartDialCache.java b/src/com/android/dialer/dialpad/SmartDialCache.java
index 51e900ada..3294bfbb6 100644
--- a/src/com/android/dialer/dialpad/SmartDialCache.java
+++ b/src/com/android/dialer/dialpad/SmartDialCache.java
@@ -16,7 +16,7 @@
package com.android.dialer.dialpad;
-import static com.android.dialer.dialpad.SmartDialAdapter.LOG_TAG;
+import static com.android.dialer.dialpad.SmartDialController.LOG_TAG;
import android.content.Context;
import android.content.SharedPreferences;
diff --git a/src/com/android/dialer/dialpad/SmartDialController.java b/src/com/android/dialer/dialpad/SmartDialController.java
new file mode 100644
index 000000000..5ce993be1
--- /dev/null
+++ b/src/com/android/dialer/dialpad/SmartDialController.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2012 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.dialer.dialpad;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.View.OnLongClickListener;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.OvershootInterpolator;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.dialer.R;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+* This class controls the display and animation logic behind the smart dialing suggestion strip.
+*
+* It allows a list of SmartDialEntries to be assigned to the suggestion strip via
+* {@link #setEntries}, and also animates the removal of old suggestions.
+*
+* To avoid creating new views every time new entries are assigned, references to 2 *
+* {@link #NUM_SUGGESTIONS} views are kept in {@link #mViews} and {@link #mViewOverlays}.
+*
+* {@code mViews} contains the active views that are currently being displayed to the user,
+* while {@code mViewOverlays} contains the views that are used as view overlays. The view
+* overlays are used to provide the illusion of the former suggestions fading out. These two
+* lists of views are rotated each time a new set of entries is assigned to achieve the appropriate
+* cross fade animations using the new {@link View#getOverlay()} API.
+*/
+public class SmartDialController {
+ public static final String LOG_TAG = "SmartDial";
+
+ /**
+ * Handtuned interpolator used to achieve the bounce effect when suggestions slide up. It
+ * uses a combination of a decelerate interpolator and overshoot interpolator to first
+ * decelerate, and then overshoot its top bounds and bounce back to its final position.
+ */
+ private class DecelerateAndOvershootInterpolator implements Interpolator {
+ private DecelerateInterpolator a;
+ private OvershootInterpolator b;
+
+ public DecelerateAndOvershootInterpolator() {
+ a = new DecelerateInterpolator(1.5f);
+ b = new OvershootInterpolator(1.3f);
+ }
+
+ @Override
+ public float getInterpolation(float input) {
+ if (input > 0.6) {
+ return b.getInterpolation(input);
+ } else {
+ return a.getInterpolation(input);
+ }
+ }
+
+ }
+
+ private DecelerateAndOvershootInterpolator mDecelerateAndOvershootInterpolator =
+ new DecelerateAndOvershootInterpolator();
+ private AccelerateDecelerateInterpolator mAccelerateDecelerateInterpolator =
+ new AccelerateDecelerateInterpolator();
+
+ private List<SmartDialEntry> mEntries;
+ private List<SmartDialEntry> mOldEntries;
+
+ private final int mNameHighlightedTextColor;
+ private final int mNumberHighlightedTextColor;
+
+ private final LinearLayout mList;
+ private final View mBackground;
+
+ private final List<LinearLayout> mViewOverlays = Lists.newArrayList();
+ private final List<LinearLayout> mViews = Lists.newArrayList();
+
+ private static final int NUM_SUGGESTIONS = 3;
+
+ private static final long ANIM_DURATION = 200;
+
+ private static final float BACKGROUND_FADE_AMOUNT = 0.25f;
+
+ Resources mResources;
+
+ public SmartDialController(Context context, ViewGroup parent,
+ OnClickListener shortClickListener, OnLongClickListener longClickListener) {
+ final Resources res = context.getResources();
+ mResources = res;
+
+ mNameHighlightedTextColor = res.getColor(R.color.smartdial_name_highlighted_text_color);
+ mNumberHighlightedTextColor = res.getColor(
+ R.color.smartdial_number_highlighted_text_color);
+
+ mList = (LinearLayout) parent.findViewById(R.id.dialpad_smartdial_list);
+ mBackground = parent.findViewById(R.id.dialpad_smartdial_list_background);
+
+ mEntries = Lists.newArrayList();
+ for (int i = 0; i < NUM_SUGGESTIONS; i++) {
+ mEntries.add(SmartDialEntry.NULL);
+ }
+
+ mOldEntries = mEntries;
+
+ final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ for (int i = 0; i < NUM_SUGGESTIONS * 2; i++) {
+ final LinearLayout view = (LinearLayout) inflater.inflate(
+ R.layout.dialpad_smartdial_item, mList, false);
+ view.setOnClickListener(shortClickListener);
+ view.setOnLongClickListener(longClickListener);
+ if (i < NUM_SUGGESTIONS) {
+ mViews.add(view);
+ } else {
+ mViewOverlays.add(view);
+ }
+ // Add all the views to mList so that they can get measured properly for animation
+ // purposes. Once setEntries is called they will be removed and added as appropriate.
+ view.setEnabled(false);
+ mList.addView(view);
+ }
+ }
+
+ /** Remove all entries. */
+ public void clear() {
+ mOldEntries = mEntries;
+ mEntries = Lists.newArrayList();
+ for (int i = 0; i < NUM_SUGGESTIONS; i++) {
+ mEntries.add(SmartDialEntry.NULL);
+ }
+ updateViews();
+ }
+
+ /** Set entries. At the end of this method {@link #mEntries} should contain exactly
+ * {@link #NUM_SUGGESTIONS} entries.*/
+ public void setEntries(List<SmartDialEntry> entries) {
+ if (entries == null) throw new IllegalArgumentException();
+ mOldEntries = mEntries;
+ mEntries = entries;
+
+ final int size = mEntries.size();
+ if (size <= 1) {
+ if (size == 0) {
+ mEntries.add(SmartDialEntry.NULL);
+ }
+ // add a null entry to push the single entry into the middle
+ mEntries.add(0, SmartDialEntry.NULL);
+ } else if (size >= 2) {
+ // swap the 1st and 2nd entries so that the highest confidence match goes into the
+ // middle
+ swap(0, 1);
+ }
+
+ while (mEntries.size() < NUM_SUGGESTIONS) {
+ mEntries.add(SmartDialEntry.NULL);
+ }
+
+ updateViews();
+ }
+
+ /**
+ * This method is called every time a new set of SmartDialEntries is to be assigned to the
+ * suggestions view. The current set of active views are to be used as view overlays and
+ * faded out, while the former view overlays are assigned the current entries, added to
+ * {@link #mList} and faded into view.
+ */
+ private void updateViews() {
+ // Remove all views from the root in preparation to swap the two sets of views
+ mList.removeAllViews();
+ try {
+ mList.getOverlay().clear();
+ } catch (NullPointerException e) {
+ // Catch possible NPE b/8895794
+ }
+
+ // Used to track whether or not to animate the overlay. In the case where the suggestion
+ // at position i will slide from the left or right, or if the suggestion at position i
+ // has not changed, the overlay at i should be hidden immediately. Overlay animations are
+ // set in a separate loop from the active views to avoid unnecessarily reanimating the same
+ // overlay multiple times.
+ boolean[] dontAnimateOverlay = new boolean[NUM_SUGGESTIONS];
+ boolean noSuggestions = true;
+
+ // At this point in time {@link #mViews} contains the former active views with old
+ // suggestions that will be swapped out to serve as view overlays, while
+ // {@link #mViewOverlays} contains the former overlays that will now serve as active
+ // views.
+ for (int i = 0; i < NUM_SUGGESTIONS; i++) {
+ // Retrieve the former overlay to be used as the new active view
+ final LinearLayout active = mViewOverlays.get(i);
+ final SmartDialEntry item = mEntries.get(i);
+
+ noSuggestions &= (item == SmartDialEntry.NULL);
+
+ assignEntryToView(active, mEntries.get(i));
+ final SmartDialEntry oldItem = mOldEntries.get(i);
+ // The former active view will now be used as an overlay for the cross-fade effect
+ final LinearLayout overlay = mViews.get(i);
+ show(active);
+ if (!containsSameContact(oldItem, item)) {
+ // Determine what kind of animation to use for the new view
+ if (i == 1) { // Middle suggestion
+ if (containsSameContact(item, mOldEntries.get(0))) {
+ // Suggestion went from the left to the middle, slide it left to right
+ animateSlideFromLeft(active);
+ dontAnimateOverlay[0] = true;
+ } else if (containsSameContact(item, mOldEntries.get(2))) {
+ // Suggestion sent from the right to the middle, slide it right to left
+ animateSlideFromRight(active);
+ dontAnimateOverlay[2] = true;
+ } else {
+ animateFadeInAndSlideUp(active);
+ }
+ } else { // Left/Right suggestion
+ if (i == 2 && containsSameContact(item, mOldEntries.get(1))) {
+ // Suggestion went from middle to the right, slide it left to right
+ animateSlideFromLeft(active);
+ dontAnimateOverlay[1] = true;
+ } else if (i == 0 && containsSameContact(item, mOldEntries.get(1))) {
+ // Suggestion went from middle to the left, slide it right to left
+ animateSlideFromRight(active);
+ dontAnimateOverlay[1] = true;
+ } else {
+ animateFadeInAndSlideUp(active);
+ }
+ }
+ } else {
+ // Since the same item is in the same spot, don't do any animations and just
+ // show the new view.
+ dontAnimateOverlay[i] = true;
+ }
+ mList.getOverlay().add(overlay);
+ mList.addView(active);
+ // Keep track of active views and view overlays
+ mViews.set(i, active);
+ mViewOverlays.set(i, overlay);
+ }
+
+ // Separate loop for overlay animations. At this point in time {@link #mViewOverlays}
+ // contains the actual overlays.
+ for (int i = 0; i < NUM_SUGGESTIONS; i++) {
+ final LinearLayout overlay = mViewOverlays.get(i);
+ if (!dontAnimateOverlay[i]) {
+ animateFadeOutAndSlideDown(overlay);
+ } else {
+ hide(overlay);
+ }
+ }
+
+ // Fade out the background to 25% opacity if there are suggestions. If there are no
+ // suggestions, display the background as usual.
+ mBackground.animate().withLayer().alpha(noSuggestions ? 1.0f : BACKGROUND_FADE_AMOUNT);
+ }
+
+ private void show(View view) {
+ view.animate().cancel();
+ view.setAlpha(1);
+ view.setTranslationX(0);
+ view.setTranslationY(0);
+ }
+
+ private void hide(View view) {
+ view.animate().cancel();
+ view.setAlpha(0);
+ }
+
+ private void animateFadeInAndSlideUp(View view) {
+ view.animate().cancel();
+ view.setAlpha(0.2f);
+ view.setTranslationY(view.getHeight());
+ view.animate().withLayer().alpha(1).translationY(0).setDuration(ANIM_DURATION).
+ setInterpolator(mDecelerateAndOvershootInterpolator);
+ }
+
+ private void animateFadeOutAndSlideDown(View view) {
+ view.animate().cancel();
+ view.setAlpha(1);
+ view.setTranslationY(0);
+ view.animate().withLayer().alpha(0).translationY(view.getHeight()).setDuration(
+ ANIM_DURATION).setInterpolator(mAccelerateDecelerateInterpolator);
+ }
+
+ private void animateSlideFromLeft(View view) {
+ view.animate().cancel();
+ view.setAlpha(1);
+ view.setTranslationX(-1 * view.getWidth());
+ view.animate().withLayer().translationX(0).setDuration(ANIM_DURATION).setInterpolator(
+ mAccelerateDecelerateInterpolator);
+ }
+
+ private void animateSlideFromRight(View view) {
+ view.animate().cancel();
+ view.setAlpha(1);
+ view.setTranslationX(view.getWidth());
+ view.animate().withLayer().translationX(0).setDuration(ANIM_DURATION).setInterpolator(
+ mAccelerateDecelerateInterpolator);
+ }
+
+ // Swaps the items in pos1 and pos2 of mEntries
+ private void swap(int pos1, int pos2) {
+ if (pos1 == pos2) {
+ return;
+ }
+ final SmartDialEntry temp = mEntries.get(pos1);
+ mEntries.set(pos1, mEntries.get(pos2));
+ mEntries.set(pos2, temp);
+ }
+
+ // Returns whether two SmartDialEntries contain the same contact
+ private boolean containsSameContact(SmartDialEntry x, SmartDialEntry y) {
+ return x.contactUri.equals(y.contactUri);
+ }
+
+ // Sets the information within a SmartDialEntry to the provided view
+ private void assignEntryToView(LinearLayout view, SmartDialEntry item) {
+ final TextView nameView = (TextView) view.findViewById(R.id.contact_name);
+
+ final TextView numberView = (TextView) view.findViewById(
+ R.id.contact_number);
+
+ if (item == SmartDialEntry.NULL) {
+ // Clear the text in case the view was reused.
+ nameView.setText("");
+ numberView.setText("");
+ view.setEnabled(false);
+ return;
+ }
+
+ // Highlight the display name with the provided match positions
+ if (!TextUtils.isEmpty(item.displayName)) {
+ final SpannableString displayName = new SpannableString(item.displayName);
+ for (final SmartDialMatchPosition p : item.matchPositions) {
+ if (p.start < p.end) {
+ if (p.end > displayName.length()) {
+ p.end = displayName.length();
+ }
+ // Create a new ForegroundColorSpan for each section of the name to highlight,
+ // otherwise multiple highlights won't work.
+ displayName.setSpan(new ForegroundColorSpan(mNameHighlightedTextColor), p.start,
+ p.end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+ nameView.setText(displayName);
+ }
+
+ // Highlight the phone number with the provided match positions
+ if (!TextUtils.isEmpty(item.phoneNumber)) {
+ final SmartDialMatchPosition p = item.phoneNumberMatchPosition;
+ final SpannableString phoneNumber = new SpannableString(item.phoneNumber);
+ if (p != null && p.start < p.end) {
+ if (p.end > phoneNumber.length()) {
+ p.end = phoneNumber.length();
+ }
+ phoneNumber.setSpan(new ForegroundColorSpan(mNumberHighlightedTextColor), p.start,
+ p.end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ numberView.setText(phoneNumber);
+ }
+ view.setEnabled(true);
+ view.setTag(item);
+ }
+}
diff --git a/src/com/android/dialer/dialpad/SmartDialEntry.java b/src/com/android/dialer/dialpad/SmartDialEntry.java
index d658f3dca..9ff491293 100644
--- a/src/com/android/dialer/dialpad/SmartDialEntry.java
+++ b/src/com/android/dialer/dialpad/SmartDialEntry.java
@@ -29,6 +29,9 @@ public class SmartDialEntry {
public final ArrayList<SmartDialMatchPosition> matchPositions;
public final SmartDialMatchPosition phoneNumberMatchPosition;
+ public static final SmartDialEntry NULL = new SmartDialEntry("", Uri.EMPTY, "",
+ new ArrayList<SmartDialMatchPosition>(), null);
+
public SmartDialEntry(CharSequence displayName, Uri contactUri, CharSequence phoneNumber,
ArrayList<SmartDialMatchPosition> matchPositions,
SmartDialMatchPosition phoneNumberMatchPosition) {
diff --git a/src/com/android/dialer/dialpad/SmartDialLoaderTask.java b/src/com/android/dialer/dialpad/SmartDialLoaderTask.java
index 08c526503..216697d20 100644
--- a/src/com/android/dialer/dialpad/SmartDialLoaderTask.java
+++ b/src/com/android/dialer/dialpad/SmartDialLoaderTask.java
@@ -16,7 +16,7 @@
package com.android.dialer.dialpad;
-import static com.android.dialer.dialpad.SmartDialAdapter.LOG_TAG;
+import static com.android.dialer.dialpad.SmartDialController.LOG_TAG;
import android.os.AsyncTask;
import android.provider.ContactsContract;
diff --git a/src/com/android/dialer/dialpad/SmartDialMatchPosition.java b/src/com/android/dialer/dialpad/SmartDialMatchPosition.java
index 3d248ccb2..434874646 100644
--- a/src/com/android/dialer/dialpad/SmartDialMatchPosition.java
+++ b/src/com/android/dialer/dialpad/SmartDialMatchPosition.java
@@ -16,7 +16,7 @@
package com.android.dialer.dialpad;
-import static com.android.dialer.dialpad.SmartDialAdapter.LOG_TAG;
+import static com.android.dialer.dialpad.SmartDialController.LOG_TAG;
import android.util.Log;
@@ -25,7 +25,7 @@ import java.util.ArrayList;
/**
* Stores information about a range of characters matched in a display name The integers
* start and end indicate that the range start to end (exclusive) correspond to some characters
- * in the query. Used by {@link SmartDialAdapter} to highlight certain parts of the contact's
+ * in the query. Used by {@link SmartDialController} to highlight certain parts of the contact's
* display name to indicate that those ranges matched the user's query.
*/
class SmartDialMatchPosition {
diff --git a/src/com/android/dialer/dialpad/SmartDialTextView.java b/src/com/android/dialer/dialpad/SmartDialTextView.java
index d1d570658..398f99ba5 100644
--- a/src/com/android/dialer/dialpad/SmartDialTextView.java
+++ b/src/com/android/dialer/dialpad/SmartDialTextView.java
@@ -17,14 +17,8 @@
package com.android.dialer.dialpad;
import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.TextView;
@@ -34,7 +28,6 @@ public class SmartDialTextView extends TextView {
private final float mPadding;
private final float mExtraPadding;
- private static final String HIGH_CONFIDENCE_HINT = "\u2026";
public SmartDialTextView(Context context) {
this(context, null);
@@ -46,33 +39,6 @@ public class SmartDialTextView extends TextView {
mExtraPadding = getResources().getDimension(R.dimen.smartdial_suggestions_extra_padding);
}
- /**
- * Returns a drawable that resembles a sideways overflow icon. Used to indicate the presence
- * of a high confidence match.
- *
- * @param res Resources that we will use to create our BitmapDrawable with
- * @param textSize Size of drawable to create
- * @param color Color of drawable to create
- * @return The drawable drawn according to the given parameters
- */
- public static Drawable getHighConfidenceHintDrawable(final Resources res, final float textSize,
- final int color) {
- final Paint paint = new Paint();
- paint.setAntiAlias(true);
- paint.setTextAlign(Align.CENTER);
- paint.setTextSize(textSize);
- paint.setColor(color);
- final Rect bounds = new Rect();
- paint.getTextBounds(HIGH_CONFIDENCE_HINT, 0, HIGH_CONFIDENCE_HINT.length(), bounds);
- final int width = bounds.width();
- final int height = bounds.height();
- final Bitmap buffer = Bitmap.createBitmap(
- width, (height * 3 / 2), Bitmap.Config.ARGB_8888);
- final Canvas canvas = new Canvas(buffer);
- canvas.drawText(HIGH_CONFIDENCE_HINT, width / 2, height, paint);
- return new BitmapDrawable(res, buffer);
- }
-
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
@@ -94,8 +60,17 @@ public class SmartDialTextView extends TextView {
float width = w - 2 * mPadding - 2 * mExtraPadding;
float ratio = width / paint.measureText(getText().toString());
+ TextUtils.TruncateAt ellipsizeAt = null;
if (ratio < 1.0f) {
- setTextScaleX(ratio);
+ if (ratio < 0.8f) {
+ // If the text is too big to fit even after scaling to 80%, just ellipsize it
+ // instead.
+ ellipsizeAt = TextUtils.TruncateAt.END;
+ setTextScaleX(0.8f);
+ } else {
+ setTextScaleX(ratio);
+ }
}
+ setEllipsize(ellipsizeAt);
}
}