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