1 package com.android.server.status; 2 3 import com.android.internal.R; 4 5 import android.content.Context; 6 import android.graphics.drawable.Drawable; 7 import android.os.Handler; 8 import android.text.StaticLayout; 9 import android.text.Layout.Alignment; 10 import android.text.TextPaint; 11 import android.text.TextUtils; 12 import android.util.Log; 13 import android.view.View; 14 import android.view.animation.Animation; 15 import android.view.animation.AnimationUtils; 16 import android.widget.TextSwitcher; 17 import android.widget.TextView; 18 import android.widget.ImageSwitcher; 19 20 import java.util.ArrayList; 21 22 23 abstract class Ticker { 24 private static final int TICKER_SEGMENT_DELAY = 3000; 25 26 private final class Segment { 27 NotificationData notificationData; 28 Drawable icon; 29 CharSequence text; 30 int current; 31 int next; 32 boolean first; 33 getLayout(CharSequence substr)34 StaticLayout getLayout(CharSequence substr) { 35 int w = mTextSwitcher.getWidth() - mTextSwitcher.getPaddingLeft() 36 - mTextSwitcher.getPaddingRight(); 37 return new StaticLayout(substr, mPaint, w, Alignment.ALIGN_NORMAL, 1, 0, true); 38 } 39 rtrim(CharSequence substr, int start, int end)40 CharSequence rtrim(CharSequence substr, int start, int end) { 41 while (end > start && !TextUtils.isGraphic(substr.charAt(end-1))) { 42 end--; 43 } 44 if (end > start) { 45 return substr.subSequence(start, end); 46 } 47 return null; 48 } 49 50 /** returns null if there is no more text */ getText()51 CharSequence getText() { 52 if (this.current > this.text.length()) { 53 return null; 54 } 55 CharSequence substr = this.text.subSequence(this.current, this.text.length()); 56 StaticLayout l = getLayout(substr); 57 int lineCount = l.getLineCount(); 58 if (lineCount > 0) { 59 int start = l.getLineStart(0); 60 int end = l.getLineEnd(0); 61 this.next = this.current + end; 62 return rtrim(substr, start, end); 63 } else { 64 throw new RuntimeException("lineCount=" + lineCount + " current=" + current + 65 " text=" + text); 66 } 67 } 68 69 /** returns null if there is no more text */ advance()70 CharSequence advance() { 71 this.first = false; 72 int index = this.next; 73 final int len = this.text.length(); 74 while (index < len && !TextUtils.isGraphic(this.text.charAt(index))) { 75 index++; 76 } 77 if (index >= len) { 78 return null; 79 } 80 81 CharSequence substr = this.text.subSequence(index, this.text.length()); 82 StaticLayout l = getLayout(substr); 83 final int lineCount = l.getLineCount(); 84 int i; 85 for (i=0; i<lineCount; i++) { 86 int start = l.getLineStart(i); 87 int end = l.getLineEnd(i); 88 if (i == lineCount-1) { 89 this.next = len; 90 } else { 91 this.next = index + l.getLineStart(i+1); 92 } 93 CharSequence result = rtrim(substr, start, end); 94 if (result != null) { 95 this.current = index + start; 96 return result; 97 } 98 } 99 this.current = len; 100 return null; 101 } 102 Segment(NotificationData n, Drawable icon, CharSequence text)103 Segment(NotificationData n, Drawable icon, CharSequence text) { 104 this.notificationData = n; 105 this.icon = icon; 106 this.text = text; 107 int index = 0; 108 final int len = text.length(); 109 while (index < len && !TextUtils.isGraphic(text.charAt(index))) { 110 index++; 111 } 112 this.current = index; 113 this.next = index; 114 this.first = true; 115 } 116 }; 117 118 private Handler mHandler = new Handler(); 119 private ArrayList<Segment> mSegments = new ArrayList(); 120 private TextPaint mPaint; 121 private View mTickerView; 122 private ImageSwitcher mIconSwitcher; 123 private TextSwitcher mTextSwitcher; 124 Ticker(Context context, StatusBarView sb)125 Ticker(Context context, StatusBarView sb) { 126 mTickerView = sb.findViewById(R.id.ticker); 127 128 mIconSwitcher = (ImageSwitcher)sb.findViewById(R.id.tickerIcon); 129 mIconSwitcher.setInAnimation( 130 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in)); 131 mIconSwitcher.setOutAnimation( 132 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out)); 133 134 mTextSwitcher = (TextSwitcher)sb.findViewById(R.id.tickerText); 135 mTextSwitcher.setInAnimation( 136 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in)); 137 mTextSwitcher.setOutAnimation( 138 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out)); 139 140 // Copy the paint style of one of the TextSwitchers children to use later for measuring 141 TextView text = (TextView)mTextSwitcher.getChildAt(0); 142 mPaint = text.getPaint(); 143 } 144 addEntry(NotificationData n, Drawable icon, CharSequence text)145 void addEntry(NotificationData n, Drawable icon, CharSequence text) { 146 int initialCount = mSegments.size(); 147 148 Segment newSegment = new Segment(n, icon, text); 149 150 // prune out any preexisting ones for this notification, but not the current one. 151 // let that finish, even if it's the same id 152 for (int i=1; i<initialCount; i++) { 153 Segment seg = mSegments.get(i); 154 if (n.id == seg.notificationData.id && n.pkg.equals(seg.notificationData.pkg)) { 155 // just update that one to use this new data instead 156 mSegments.set(i, newSegment); 157 // and since we know initialCount != 0, just return 158 return ; 159 } 160 } 161 162 mSegments.add(newSegment); 163 164 if (initialCount == 0 && mSegments.size() > 0) { 165 Segment seg = mSegments.get(0); 166 seg.first = false; 167 168 mIconSwitcher.setAnimateFirstView(false); 169 mIconSwitcher.reset(); 170 mIconSwitcher.setImageDrawable(seg.icon); 171 172 mTextSwitcher.setAnimateFirstView(false); 173 mTextSwitcher.reset(); 174 mTextSwitcher.setText(seg.getText()); 175 176 tickerStarting(); 177 scheduleAdvance(); 178 } 179 } 180 halt()181 void halt() { 182 mHandler.removeCallbacks(mAdvanceTicker); 183 mSegments.clear(); 184 tickerHalting(); 185 } 186 reflowText()187 void reflowText() { 188 if (mSegments.size() > 0) { 189 Segment seg = mSegments.get(0); 190 CharSequence text = seg.getText(); 191 mTextSwitcher.setCurrentText(text); 192 } 193 } 194 195 private Runnable mAdvanceTicker = new Runnable() { 196 public void run() { 197 while (mSegments.size() > 0) { 198 Segment seg = mSegments.get(0); 199 200 if (seg.first) { 201 // this makes the icon slide in for the first one for a given 202 // notification even if there are two notifications with the 203 // same icon in a row 204 mIconSwitcher.setImageDrawable(seg.icon); 205 } 206 CharSequence text = seg.advance(); 207 if (text == null) { 208 mSegments.remove(0); 209 continue; 210 } 211 mTextSwitcher.setText(text); 212 213 scheduleAdvance(); 214 break; 215 } 216 if (mSegments.size() == 0) { 217 tickerDone(); 218 } 219 } 220 }; 221 scheduleAdvance()222 private void scheduleAdvance() { 223 mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY); 224 } 225 tickerStarting()226 abstract void tickerStarting(); tickerDone()227 abstract void tickerDone(); tickerHalting()228 abstract void tickerHalting(); 229 } 230 231