package com.android.server.status; import com.android.internal.R; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Handler; import android.text.StaticLayout; import android.text.Layout.Alignment; import android.text.TextPaint; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.TextSwitcher; import android.widget.TextView; import android.widget.ImageSwitcher; import java.util.ArrayList; abstract class Ticker { private static final int TICKER_SEGMENT_DELAY = 3000; private final class Segment { NotificationData notificationData; Drawable icon; CharSequence text; int current; int next; boolean first; StaticLayout getLayout(CharSequence substr) { int w = mTextSwitcher.getWidth() - mTextSwitcher.getPaddingLeft() - mTextSwitcher.getPaddingRight(); return new StaticLayout(substr, mPaint, w, Alignment.ALIGN_NORMAL, 1, 0, true); } CharSequence rtrim(CharSequence substr, int start, int end) { while (end > start && !TextUtils.isGraphic(substr.charAt(end-1))) { end--; } if (end > start) { return substr.subSequence(start, end); } return null; } /** returns null if there is no more text */ CharSequence getText() { if (this.current > this.text.length()) { return null; } CharSequence substr = this.text.subSequence(this.current, this.text.length()); StaticLayout l = getLayout(substr); int lineCount = l.getLineCount(); if (lineCount > 0) { int start = l.getLineStart(0); int end = l.getLineEnd(0); this.next = this.current + end; return rtrim(substr, start, end); } else { throw new RuntimeException("lineCount=" + lineCount + " current=" + current + " text=" + text); } } /** returns null if there is no more text */ CharSequence advance() { this.first = false; int index = this.next; final int len = this.text.length(); while (index < len && !TextUtils.isGraphic(this.text.charAt(index))) { index++; } if (index >= len) { return null; } CharSequence substr = this.text.subSequence(index, this.text.length()); StaticLayout l = getLayout(substr); final int lineCount = l.getLineCount(); int i; for (i=0; i<lineCount; i++) { int start = l.getLineStart(i); int end = l.getLineEnd(i); if (i == lineCount-1) { this.next = len; } else { this.next = index + l.getLineStart(i+1); } CharSequence result = rtrim(substr, start, end); if (result != null) { this.current = index + start; return result; } } this.current = len; return null; } Segment(NotificationData n, Drawable icon, CharSequence text) { this.notificationData = n; this.icon = icon; this.text = text; int index = 0; final int len = text.length(); while (index < len && !TextUtils.isGraphic(text.charAt(index))) { index++; } this.current = index; this.next = index; this.first = true; } }; private Handler mHandler = new Handler(); private ArrayList<Segment> mSegments = new ArrayList(); private TextPaint mPaint; private View mTickerView; private ImageSwitcher mIconSwitcher; private TextSwitcher mTextSwitcher; Ticker(Context context, StatusBarView sb) { mTickerView = sb.findViewById(R.id.ticker); mIconSwitcher = (ImageSwitcher)sb.findViewById(R.id.tickerIcon); mIconSwitcher.setInAnimation( AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in)); mIconSwitcher.setOutAnimation( AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out)); mTextSwitcher = (TextSwitcher)sb.findViewById(R.id.tickerText); mTextSwitcher.setInAnimation( AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in)); mTextSwitcher.setOutAnimation( AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out)); // Copy the paint style of one of the TextSwitchers children to use later for measuring TextView text = (TextView)mTextSwitcher.getChildAt(0); mPaint = text.getPaint(); } void addEntry(NotificationData n, Drawable icon, CharSequence text) { int initialCount = mSegments.size(); Segment newSegment = new Segment(n, icon, text); // prune out any preexisting ones for this notification, but not the current one. // let that finish, even if it's the same id for (int i=1; i<initialCount; i++) { Segment seg = mSegments.get(i); if (n.id == seg.notificationData.id && n.pkg.equals(seg.notificationData.pkg)) { // just update that one to use this new data instead mSegments.set(i, newSegment); // and since we know initialCount != 0, just return return ; } } mSegments.add(newSegment); if (initialCount == 0 && mSegments.size() > 0) { Segment seg = mSegments.get(0); seg.first = false; mIconSwitcher.setAnimateFirstView(false); mIconSwitcher.reset(); mIconSwitcher.setImageDrawable(seg.icon); mTextSwitcher.setAnimateFirstView(false); mTextSwitcher.reset(); mTextSwitcher.setText(seg.getText()); tickerStarting(); scheduleAdvance(); } } void halt() { mHandler.removeCallbacks(mAdvanceTicker); mSegments.clear(); tickerHalting(); } void reflowText() { if (mSegments.size() > 0) { Segment seg = mSegments.get(0); CharSequence text = seg.getText(); mTextSwitcher.setCurrentText(text); } } private Runnable mAdvanceTicker = new Runnable() { public void run() { while (mSegments.size() > 0) { Segment seg = mSegments.get(0); if (seg.first) { // this makes the icon slide in for the first one for a given // notification even if there are two notifications with the // same icon in a row mIconSwitcher.setImageDrawable(seg.icon); } CharSequence text = seg.advance(); if (text == null) { mSegments.remove(0); continue; } mTextSwitcher.setText(text); scheduleAdvance(); break; } if (mSegments.size() == 0) { tickerDone(); } } }; private void scheduleAdvance() { mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY); } abstract void tickerStarting(); abstract void tickerDone(); abstract void tickerHalting(); }