blob: f3f6a8007901909fc44f7f6b03a760874171da48 [file] [log] [blame]
Kenny Root15a4d2f2010-03-11 18:20:12 -08001/*
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
Joe Onoratofd52b182010-11-10 18:00:52 -080017package com.android.systemui.statusbar.phone;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080018
Daniel Sandler5feceeb2013-03-22 18:29:23 -070019import android.service.notification.StatusBarNotification;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.content.Context;
Daniel Sandler645e0992011-10-13 16:29:57 -040021import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.graphics.drawable.Drawable;
23import android.os.Handler;
24import android.text.StaticLayout;
25import android.text.Layout.Alignment;
26import android.text.TextPaint;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.view.View;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.view.animation.AnimationUtils;
29import android.widget.TextSwitcher;
30import android.widget.TextView;
31import android.widget.ImageSwitcher;
32
33import java.util.ArrayList;
34
Joe Onoratof5510542010-06-01 07:46:41 -070035import com.android.internal.statusbar.StatusBarIcon;
Joe Onoratof5510542010-06-01 07:46:41 -070036import com.android.internal.util.CharSequences;
Joe Onoratofd52b182010-11-10 18:00:52 -080037
Joe Onorato79de0c52010-05-26 17:03:26 -040038import com.android.systemui.R;
Joe Onoratofd52b182010-11-10 18:00:52 -080039import com.android.systemui.statusbar.StatusBarIconView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040
Joe Onorato503007d2010-04-16 09:20:55 -070041public abstract class Ticker {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042 private static final int TICKER_SEGMENT_DELAY = 3000;
43
Joe Onoratof5510542010-06-01 07:46:41 -070044 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;
Daniel Sandler645e0992011-10-13 16:29:57 -040051 private float mIconScale;
Joe Onoratof5510542010-06-01 07:46:41 -070052
Daniel Sandlerb9d36642012-10-19 13:36:58 -040053 public static boolean isGraphicOrEmoji(char c) {
54 int gc = Character.getType(c);
55 return gc != Character.CONTROL
56 && gc != Character.FORMAT
57 && gc != Character.UNASSIGNED
58 && gc != Character.LINE_SEPARATOR
59 && gc != Character.PARAGRAPH_SEPARATOR
60 && gc != Character.SPACE_SEPARATOR;
61 }
62
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063 private final class Segment {
Joe Onoratof5510542010-06-01 07:46:41 -070064 StatusBarNotification notification;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 Drawable icon;
66 CharSequence text;
67 int current;
68 int next;
69 boolean first;
70
71 StaticLayout getLayout(CharSequence substr) {
72 int w = mTextSwitcher.getWidth() - mTextSwitcher.getPaddingLeft()
73 - mTextSwitcher.getPaddingRight();
74 return new StaticLayout(substr, mPaint, w, Alignment.ALIGN_NORMAL, 1, 0, true);
75 }
76
77 CharSequence rtrim(CharSequence substr, int start, int end) {
Daniel Sandlerb9d36642012-10-19 13:36:58 -040078 while (end > start && !isGraphicOrEmoji(substr.charAt(end-1))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079 end--;
80 }
81 if (end > start) {
82 return substr.subSequence(start, end);
83 }
84 return null;
85 }
86
87 /** returns null if there is no more text */
88 CharSequence getText() {
89 if (this.current > this.text.length()) {
90 return null;
91 }
92 CharSequence substr = this.text.subSequence(this.current, this.text.length());
93 StaticLayout l = getLayout(substr);
94 int lineCount = l.getLineCount();
95 if (lineCount > 0) {
96 int start = l.getLineStart(0);
97 int end = l.getLineEnd(0);
98 this.next = this.current + end;
99 return rtrim(substr, start, end);
100 } else {
101 throw new RuntimeException("lineCount=" + lineCount + " current=" + current +
102 " text=" + text);
103 }
104 }
105
106 /** returns null if there is no more text */
107 CharSequence advance() {
108 this.first = false;
109 int index = this.next;
110 final int len = this.text.length();
Daniel Sandlerb9d36642012-10-19 13:36:58 -0400111 while (index < len && !isGraphicOrEmoji(this.text.charAt(index))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 index++;
113 }
114 if (index >= len) {
115 return null;
116 }
117
118 CharSequence substr = this.text.subSequence(index, this.text.length());
119 StaticLayout l = getLayout(substr);
120 final int lineCount = l.getLineCount();
121 int i;
122 for (i=0; i<lineCount; i++) {
123 int start = l.getLineStart(i);
124 int end = l.getLineEnd(i);
125 if (i == lineCount-1) {
126 this.next = len;
127 } else {
128 this.next = index + l.getLineStart(i+1);
129 }
130 CharSequence result = rtrim(substr, start, end);
131 if (result != null) {
132 this.current = index + start;
133 return result;
134 }
135 }
136 this.current = len;
137 return null;
138 }
139
Joe Onoratoe345fff2010-05-23 15:18:27 -0400140 Segment(StatusBarNotification n, Drawable icon, CharSequence text) {
Joe Onoratof5510542010-06-01 07:46:41 -0700141 this.notification = n;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 this.icon = icon;
143 this.text = text;
144 int index = 0;
145 final int len = text.length();
Daniel Sandlerb9d36642012-10-19 13:36:58 -0400146 while (index < len && !isGraphicOrEmoji(text.charAt(index))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800147 index++;
148 }
149 this.current = index;
150 this.next = index;
151 this.first = true;
152 }
153 };
154
Daniel Sandlerdfa08db2010-08-05 16:18:42 -0400155 public Ticker(Context context, View sb) {
Joe Onoratof5510542010-06-01 07:46:41 -0700156 mContext = context;
Daniel Sandler645e0992011-10-13 16:29:57 -0400157 final Resources res = context.getResources();
158 final int outerBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
159 final int imageBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
160 mIconScale = (float)imageBounds / (float)outerBounds;
161
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800162 mTickerView = sb.findViewById(R.id.ticker);
163
164 mIconSwitcher = (ImageSwitcher)sb.findViewById(R.id.tickerIcon);
165 mIconSwitcher.setInAnimation(
166 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in));
167 mIconSwitcher.setOutAnimation(
168 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out));
Daniel Sandler645e0992011-10-13 16:29:57 -0400169 mIconSwitcher.setScaleX(mIconScale);
170 mIconSwitcher.setScaleY(mIconScale);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171
172 mTextSwitcher = (TextSwitcher)sb.findViewById(R.id.tickerText);
173 mTextSwitcher.setInAnimation(
174 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in));
175 mTextSwitcher.setOutAnimation(
176 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out));
177
178 // Copy the paint style of one of the TextSwitchers children to use later for measuring
179 TextView text = (TextView)mTextSwitcher.getChildAt(0);
180 mPaint = text.getPaint();
181 }
182
Joe Onoratof5510542010-06-01 07:46:41 -0700183
Daniel Sandlerdfa08db2010-08-05 16:18:42 -0400184 public void addEntry(StatusBarNotification n) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800185 int initialCount = mSegments.size();
186
Joe Onoratof5510542010-06-01 07:46:41 -0700187 // If what's being displayed has the same text and icon, just drop it
188 // (which will let the current one finish, this happens when apps do
189 // a notification storm).
190 if (initialCount > 0) {
191 final Segment seg = mSegments.get(0);
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400192 if (n.getPackageName().equals(seg.notification.getPackageName())
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400193 && n.getNotification().icon == seg.notification.getNotification().icon
194 && n.getNotification().iconLevel == seg.notification.getNotification().iconLevel
195 && CharSequences.equals(seg.notification.getNotification().tickerText,
196 n.getNotification().tickerText)) {
Joe Onoratof5510542010-06-01 07:46:41 -0700197 return;
198 }
199 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200
Joe Onoratof5510542010-06-01 07:46:41 -0700201 final Drawable icon = StatusBarIconView.getIcon(mContext,
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400202 new StatusBarIcon(n.getPackageName(), n.getUser(), n.getNotification().icon, n.getNotification().iconLevel, 0,
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400203 n.getNotification().tickerText));
204 final CharSequence text = n.getNotification().tickerText;
Daniel Sandlerb9d36642012-10-19 13:36:58 -0400205 final Segment newSegment = new Segment(n, icon, text);
Joe Onoratof5510542010-06-01 07:46:41 -0700206
207 // If there's already a notification schedule for this package and id, remove it.
Daniel Sandlerf8d58092010-08-16 15:15:41 -0400208 for (int i=0; i<mSegments.size(); i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209 Segment seg = mSegments.get(i);
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400210 if (n.getId() == seg.notification.getId() && n.getPackageName().equals(seg.notification.getPackageName())) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800211 // just update that one to use this new data instead
Daniel Sandlerf8d58092010-08-16 15:15:41 -0400212 mSegments.remove(i--); // restart iteration here
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800213 }
214 }
215
216 mSegments.add(newSegment);
217
218 if (initialCount == 0 && mSegments.size() > 0) {
219 Segment seg = mSegments.get(0);
220 seg.first = false;
221
222 mIconSwitcher.setAnimateFirstView(false);
223 mIconSwitcher.reset();
224 mIconSwitcher.setImageDrawable(seg.icon);
225
226 mTextSwitcher.setAnimateFirstView(false);
227 mTextSwitcher.reset();
228 mTextSwitcher.setText(seg.getText());
229
230 tickerStarting();
231 scheduleAdvance();
232 }
233 }
234
Daniel Sandlerdfa08db2010-08-05 16:18:42 -0400235 public void removeEntry(StatusBarNotification n) {
Joe Onoratof5510542010-06-01 07:46:41 -0700236 for (int i=mSegments.size()-1; i>=0; i--) {
237 Segment seg = mSegments.get(i);
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400238 if (n.getId() == seg.notification.getId() && n.getPackageName().equals(seg.notification.getPackageName())) {
Joe Onoratof5510542010-06-01 07:46:41 -0700239 mSegments.remove(i);
240 }
241 }
242 }
243
Daniel Sandlerdfa08db2010-08-05 16:18:42 -0400244 public void halt() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 mHandler.removeCallbacks(mAdvanceTicker);
246 mSegments.clear();
247 tickerHalting();
248 }
249
Daniel Sandlerdfa08db2010-08-05 16:18:42 -0400250 public void reflowText() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 if (mSegments.size() > 0) {
252 Segment seg = mSegments.get(0);
253 CharSequence text = seg.getText();
254 mTextSwitcher.setCurrentText(text);
255 }
256 }
257
258 private Runnable mAdvanceTicker = new Runnable() {
259 public void run() {
260 while (mSegments.size() > 0) {
261 Segment seg = mSegments.get(0);
262
263 if (seg.first) {
264 // this makes the icon slide in for the first one for a given
265 // notification even if there are two notifications with the
266 // same icon in a row
267 mIconSwitcher.setImageDrawable(seg.icon);
268 }
269 CharSequence text = seg.advance();
270 if (text == null) {
271 mSegments.remove(0);
272 continue;
273 }
274 mTextSwitcher.setText(text);
275
276 scheduleAdvance();
277 break;
278 }
279 if (mSegments.size() == 0) {
280 tickerDone();
281 }
282 }
283 };
284
285 private void scheduleAdvance() {
286 mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY);
287 }
288
Daniel Sandlerdfa08db2010-08-05 16:18:42 -0400289 public abstract void tickerStarting();
290 public abstract void tickerDone();
291 public abstract void tickerHalting();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292}
293