Ticker.java revision 6179ea3196e9306d3f14361fe9ef14191b1edba6
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.statusbar.phone; 18 19import android.content.Context; 20import android.graphics.drawable.Drawable; 21import android.os.Handler; 22import android.text.StaticLayout; 23import android.text.Layout.Alignment; 24import android.text.TextPaint; 25import android.text.TextUtils; 26import android.util.Slog; 27import android.view.View; 28import android.view.animation.Animation; 29import android.view.animation.AnimationUtils; 30import android.widget.TextSwitcher; 31import android.widget.TextView; 32import android.widget.ImageSwitcher; 33 34import java.util.ArrayList; 35 36import com.android.internal.statusbar.StatusBarIcon; 37import com.android.internal.statusbar.StatusBarNotification; 38import com.android.internal.util.CharSequences; 39 40import com.android.systemui.R; 41import com.android.systemui.statusbar.StatusBarIconView; 42 43public abstract class Ticker { 44 private static final int TICKER_SEGMENT_DELAY = 3000; 45 46 private Context mContext; 47 private Handler mHandler = new Handler(); 48 private ArrayList<Segment> mSegments = new ArrayList(); 49 private TextPaint mPaint; 50 private View mTickerView; 51 private ImageSwitcher mIconSwitcher; 52 private TextSwitcher mTextSwitcher; 53 54 private final class Segment { 55 StatusBarNotification notification; 56 Drawable icon; 57 CharSequence text; 58 int current; 59 int next; 60 boolean first; 61 62 StaticLayout getLayout(CharSequence substr) { 63 int w = mTextSwitcher.getWidth() - mTextSwitcher.getPaddingLeft() 64 - mTextSwitcher.getPaddingRight(); 65 return new StaticLayout(substr, mPaint, w, Alignment.ALIGN_NORMAL, 1, 0, true); 66 } 67 68 CharSequence rtrim(CharSequence substr, int start, int end) { 69 while (end > start && !TextUtils.isGraphic(substr.charAt(end-1))) { 70 end--; 71 } 72 if (end > start) { 73 return substr.subSequence(start, end); 74 } 75 return null; 76 } 77 78 /** returns null if there is no more text */ 79 CharSequence getText() { 80 if (this.current > this.text.length()) { 81 return null; 82 } 83 CharSequence substr = this.text.subSequence(this.current, this.text.length()); 84 StaticLayout l = getLayout(substr); 85 int lineCount = l.getLineCount(); 86 if (lineCount > 0) { 87 int start = l.getLineStart(0); 88 int end = l.getLineEnd(0); 89 this.next = this.current + end; 90 return rtrim(substr, start, end); 91 } else { 92 throw new RuntimeException("lineCount=" + lineCount + " current=" + current + 93 " text=" + text); 94 } 95 } 96 97 /** returns null if there is no more text */ 98 CharSequence advance() { 99 this.first = false; 100 int index = this.next; 101 final int len = this.text.length(); 102 while (index < len && !TextUtils.isGraphic(this.text.charAt(index))) { 103 index++; 104 } 105 if (index >= len) { 106 return null; 107 } 108 109 CharSequence substr = this.text.subSequence(index, this.text.length()); 110 StaticLayout l = getLayout(substr); 111 final int lineCount = l.getLineCount(); 112 int i; 113 for (i=0; i<lineCount; i++) { 114 int start = l.getLineStart(i); 115 int end = l.getLineEnd(i); 116 if (i == lineCount-1) { 117 this.next = len; 118 } else { 119 this.next = index + l.getLineStart(i+1); 120 } 121 CharSequence result = rtrim(substr, start, end); 122 if (result != null) { 123 this.current = index + start; 124 return result; 125 } 126 } 127 this.current = len; 128 return null; 129 } 130 131 Segment(StatusBarNotification n, Drawable icon, CharSequence text) { 132 this.notification = n; 133 this.icon = icon; 134 this.text = text; 135 int index = 0; 136 final int len = text.length(); 137 while (index < len && !TextUtils.isGraphic(text.charAt(index))) { 138 index++; 139 } 140 this.current = index; 141 this.next = index; 142 this.first = true; 143 } 144 }; 145 146 public Ticker(Context context, View sb) { 147 mContext = context; 148 mTickerView = sb.findViewById(R.id.ticker); 149 150 mIconSwitcher = (ImageSwitcher)sb.findViewById(R.id.tickerIcon); 151 mIconSwitcher.setInAnimation( 152 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in)); 153 mIconSwitcher.setOutAnimation( 154 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out)); 155 156 mTextSwitcher = (TextSwitcher)sb.findViewById(R.id.tickerText); 157 mTextSwitcher.setInAnimation( 158 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in)); 159 mTextSwitcher.setOutAnimation( 160 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out)); 161 162 // Copy the paint style of one of the TextSwitchers children to use later for measuring 163 TextView text = (TextView)mTextSwitcher.getChildAt(0); 164 mPaint = text.getPaint(); 165 } 166 167 168 public void addEntry(StatusBarNotification n) { 169 int initialCount = mSegments.size(); 170 171 // If what's being displayed has the same text and icon, just drop it 172 // (which will let the current one finish, this happens when apps do 173 // a notification storm). 174 if (initialCount > 0) { 175 final Segment seg = mSegments.get(0); 176 if (n.pkg.equals(seg.notification.pkg) 177 && n.notification.icon == seg.notification.notification.icon 178 && n.notification.iconLevel == seg.notification.notification.iconLevel 179 && CharSequences.equals(seg.notification.notification.tickerText, 180 n.notification.tickerText)) { 181 return; 182 } 183 } 184 185 final Drawable icon = StatusBarIconView.getIcon(mContext, 186 new StatusBarIcon(n.pkg, n.notification.icon, n.notification.iconLevel, 0, 187 n.notification.tickerText)); 188 final Segment newSegment = new Segment(n, icon, n.notification.tickerText); 189 190 // If there's already a notification schedule for this package and id, remove it. 191 for (int i=0; i<mSegments.size(); i++) { 192 Segment seg = mSegments.get(i); 193 if (n.id == seg.notification.id && n.pkg.equals(seg.notification.pkg)) { 194 // just update that one to use this new data instead 195 mSegments.remove(i--); // restart iteration here 196 } 197 } 198 199 mSegments.add(newSegment); 200 201 if (initialCount == 0 && mSegments.size() > 0) { 202 Segment seg = mSegments.get(0); 203 seg.first = false; 204 205 mIconSwitcher.setAnimateFirstView(false); 206 mIconSwitcher.reset(); 207 mIconSwitcher.setImageDrawable(seg.icon); 208 209 mTextSwitcher.setAnimateFirstView(false); 210 mTextSwitcher.reset(); 211 mTextSwitcher.setText(seg.getText()); 212 213 tickerStarting(); 214 scheduleAdvance(); 215 } 216 } 217 218 public void removeEntry(StatusBarNotification n) { 219 for (int i=mSegments.size()-1; i>=0; i--) { 220 Segment seg = mSegments.get(i); 221 if (n.id == seg.notification.id && n.pkg.equals(seg.notification.pkg)) { 222 mSegments.remove(i); 223 } 224 } 225 } 226 227 public void halt() { 228 mHandler.removeCallbacks(mAdvanceTicker); 229 mSegments.clear(); 230 tickerHalting(); 231 } 232 233 public void reflowText() { 234 if (mSegments.size() > 0) { 235 Segment seg = mSegments.get(0); 236 CharSequence text = seg.getText(); 237 mTextSwitcher.setCurrentText(text); 238 } 239 } 240 241 private Runnable mAdvanceTicker = new Runnable() { 242 public void run() { 243 while (mSegments.size() > 0) { 244 Segment seg = mSegments.get(0); 245 246 if (seg.first) { 247 // this makes the icon slide in for the first one for a given 248 // notification even if there are two notifications with the 249 // same icon in a row 250 mIconSwitcher.setImageDrawable(seg.icon); 251 } 252 CharSequence text = seg.advance(); 253 if (text == null) { 254 mSegments.remove(0); 255 continue; 256 } 257 mTextSwitcher.setText(text); 258 259 scheduleAdvance(); 260 break; 261 } 262 if (mSegments.size() == 0) { 263 tickerDone(); 264 } 265 } 266 }; 267 268 private void scheduleAdvance() { 269 mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY); 270 } 271 272 public abstract void tickerStarting(); 273 public abstract void tickerDone(); 274 public abstract void tickerHalting(); 275} 276 277