Adding support for display and sort order preferences.
Bug: 2267198
Change-Id: I8153287896b03d798de163ea231b6ae2360cd6dc
diff --git a/src/com/android/contacts/TextHighlightingAnimation.java b/src/com/android/contacts/TextHighlightingAnimation.java
new file mode 100644
index 0000000..3bf6499
--- /dev/null
+++ b/src/com/android/contacts/TextHighlightingAnimation.java
@@ -0,0 +1,289 @@
+/*
+ * 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