blob: a6ce288a53701c45ec67c62a626ec839f692ffde [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
19import android.content.Context;
Daniel Sandler645e0992011-10-13 16:29:57 -040020import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.graphics.drawable.Drawable;
22import android.os.Handler;
John Spurlockde84f0e2013-06-12 12:41:00 -040023import android.service.notification.StatusBarNotification;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.text.Layout.Alignment;
John Spurlockde84f0e2013-06-12 12:41:00 -040025import android.text.StaticLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import 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;
John Spurlockde84f0e2013-06-12 12:41:00 -040029import android.widget.ImageSwitcher;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.widget.TextSwitcher;
31import android.widget.TextView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032
Joe Onoratof5510542010-06-01 07:46:41 -070033import com.android.internal.statusbar.StatusBarIcon;
Joe Onorato79de0c52010-05-26 17:03:26 -040034import com.android.systemui.R;
Joe Onoratofd52b182010-11-10 18:00:52 -080035import com.android.systemui.statusbar.StatusBarIconView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036
John Spurlockde84f0e2013-06-12 12:41:00 -040037import java.util.ArrayList;
38
Joe Onorato503007d2010-04-16 09:20:55 -070039public abstract class Ticker {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040 private static final int TICKER_SEGMENT_DELAY = 3000;
John Spurlock209bede2013-07-17 12:23:27 -040041
Joe Onoratof5510542010-06-01 07:46:41 -070042 private Context mContext;
43 private Handler mHandler = new Handler();
44 private ArrayList<Segment> mSegments = new ArrayList();
45 private TextPaint mPaint;
46 private View mTickerView;
47 private ImageSwitcher mIconSwitcher;
48 private TextSwitcher mTextSwitcher;
Daniel Sandler645e0992011-10-13 16:29:57 -040049 private float mIconScale;
Joe Onoratof5510542010-06-01 07:46:41 -070050
Daniel Sandlerb9d36642012-10-19 13:36:58 -040051 public static boolean isGraphicOrEmoji(char c) {
52 int gc = Character.getType(c);
53 return gc != Character.CONTROL
54 && gc != Character.FORMAT
55 && gc != Character.UNASSIGNED
56 && gc != Character.LINE_SEPARATOR
57 && gc != Character.PARAGRAPH_SEPARATOR
58 && gc != Character.SPACE_SEPARATOR;
59 }
60
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 private final class Segment {
Joe Onoratof5510542010-06-01 07:46:41 -070062 StatusBarNotification notification;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063 Drawable icon;
64 CharSequence text;
65 int current;
66 int next;
67 boolean first;
68
69 StaticLayout getLayout(CharSequence substr) {
70 int w = mTextSwitcher.getWidth() - mTextSwitcher.getPaddingLeft()
71 - mTextSwitcher.getPaddingRight();
72 return new StaticLayout(substr, mPaint, w, Alignment.ALIGN_NORMAL, 1, 0, true);
73 }
74
75 CharSequence rtrim(CharSequence substr, int start, int end) {
Daniel Sandlerb9d36642012-10-19 13:36:58 -040076 while (end > start && !isGraphicOrEmoji(substr.charAt(end-1))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077 end--;
78 }
79 if (end > start) {
80 return substr.subSequence(start, end);
81 }
82 return null;
83 }
84
85 /** returns null if there is no more text */
86 CharSequence getText() {
87 if (this.current > this.text.length()) {
88 return null;
89 }
90 CharSequence substr = this.text.subSequence(this.current, this.text.length());
91 StaticLayout l = getLayout(substr);
92 int lineCount = l.getLineCount();
93 if (lineCount > 0) {
94 int start = l.getLineStart(0);
95 int end = l.getLineEnd(0);
96 this.next = this.current + end;
97 return rtrim(substr, start, end);
98 } else {
99 throw new RuntimeException("lineCount=" + lineCount + " current=" + current +
100 " text=" + text);
101 }
102 }
103
104 /** returns null if there is no more text */
105 CharSequence advance() {
106 this.first = false;
107 int index = this.next;
108 final int len = this.text.length();
Daniel Sandlerb9d36642012-10-19 13:36:58 -0400109 while (index < len && !isGraphicOrEmoji(this.text.charAt(index))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 index++;
111 }
112 if (index >= len) {
113 return null;
114 }
115
116 CharSequence substr = this.text.subSequence(index, this.text.length());
117 StaticLayout l = getLayout(substr);
118 final int lineCount = l.getLineCount();
119 int i;
120 for (i=0; i<lineCount; i++) {
121 int start = l.getLineStart(i);
122 int end = l.getLineEnd(i);
123 if (i == lineCount-1) {
124 this.next = len;
125 } else {
126 this.next = index + l.getLineStart(i+1);
127 }
128 CharSequence result = rtrim(substr, start, end);
129 if (result != null) {
130 this.current = index + start;
131 return result;
132 }
133 }
134 this.current = len;
135 return null;
136 }
137
Joe Onoratoe345fff2010-05-23 15:18:27 -0400138 Segment(StatusBarNotification n, Drawable icon, CharSequence text) {
Joe Onoratof5510542010-06-01 07:46:41 -0700139 this.notification = n;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 this.icon = icon;
141 this.text = text;
142 int index = 0;
143 final int len = text.length();
Daniel Sandlerb9d36642012-10-19 13:36:58 -0400144 while (index < len && !isGraphicOrEmoji(text.charAt(index))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 index++;
146 }
147 this.current = index;
148 this.next = index;
149 this.first = true;
150 }
151 };
152
Daniel Sandlerdfa08db2010-08-05 16:18:42 -0400153 public Ticker(Context context, View sb) {
Joe Onoratof5510542010-06-01 07:46:41 -0700154 mContext = context;
Daniel Sandler645e0992011-10-13 16:29:57 -0400155 final Resources res = context.getResources();
156 final int outerBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
157 final int imageBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
158 mIconScale = (float)imageBounds / (float)outerBounds;
159
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160 mTickerView = sb.findViewById(R.id.ticker);
161
162 mIconSwitcher = (ImageSwitcher)sb.findViewById(R.id.tickerIcon);
163 mIconSwitcher.setInAnimation(
164 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in));
165 mIconSwitcher.setOutAnimation(
166 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out));
Daniel Sandler645e0992011-10-13 16:29:57 -0400167 mIconSwitcher.setScaleX(mIconScale);
168 mIconSwitcher.setScaleY(mIconScale);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169
170 mTextSwitcher = (TextSwitcher)sb.findViewById(R.id.tickerText);
171 mTextSwitcher.setInAnimation(
172 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_in));
173 mTextSwitcher.setOutAnimation(
174 AnimationUtils.loadAnimation(context, com.android.internal.R.anim.push_up_out));
175
176 // Copy the paint style of one of the TextSwitchers children to use later for measuring
177 TextView text = (TextView)mTextSwitcher.getChildAt(0);
178 mPaint = text.getPaint();
179 }
180
Joe Onoratof5510542010-06-01 07:46:41 -0700181
Daniel Sandlerdfa08db2010-08-05 16:18:42 -0400182 public void addEntry(StatusBarNotification n) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 int initialCount = mSegments.size();
184
Joe Onoratof5510542010-06-01 07:46:41 -0700185 // If what's being displayed has the same text and icon, just drop it
186 // (which will let the current one finish, this happens when apps do
187 // a notification storm).
188 if (initialCount > 0) {
189 final Segment seg = mSegments.get(0);
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400190 if (n.getPackageName().equals(seg.notification.getPackageName())
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400191 && n.getNotification().icon == seg.notification.getNotification().icon
192 && n.getNotification().iconLevel == seg.notification.getNotification().iconLevel
John Spurlockb8baccc2013-06-05 12:59:01 -0400193 && charSequencesEqual(seg.notification.getNotification().tickerText,
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400194 n.getNotification().tickerText)) {
Joe Onoratof5510542010-06-01 07:46:41 -0700195 return;
196 }
197 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800198
Joe Onoratof5510542010-06-01 07:46:41 -0700199 final Drawable icon = StatusBarIconView.getIcon(mContext,
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400200 new StatusBarIcon(n.getPackageName(), n.getUser(), n.getNotification().icon, n.getNotification().iconLevel, 0,
Daniel Sandlere6f7f2e2013-04-25 15:44:16 -0400201 n.getNotification().tickerText));
202 final CharSequence text = n.getNotification().tickerText;
Daniel Sandlerb9d36642012-10-19 13:36:58 -0400203 final Segment newSegment = new Segment(n, icon, text);
Joe Onoratof5510542010-06-01 07:46:41 -0700204
205 // If there's already a notification schedule for this package and id, remove it.
Daniel Sandlerf8d58092010-08-16 15:15:41 -0400206 for (int i=0; i<mSegments.size(); i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800207 Segment seg = mSegments.get(i);
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400208 if (n.getId() == seg.notification.getId() && n.getPackageName().equals(seg.notification.getPackageName())) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209 // just update that one to use this new data instead
Daniel Sandlerf8d58092010-08-16 15:15:41 -0400210 mSegments.remove(i--); // restart iteration here
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800211 }
212 }
213
214 mSegments.add(newSegment);
215
216 if (initialCount == 0 && mSegments.size() > 0) {
217 Segment seg = mSegments.get(0);
218 seg.first = false;
John Spurlock209bede2013-07-17 12:23:27 -0400219
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 mIconSwitcher.setAnimateFirstView(false);
221 mIconSwitcher.reset();
222 mIconSwitcher.setImageDrawable(seg.icon);
John Spurlock209bede2013-07-17 12:23:27 -0400223
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224 mTextSwitcher.setAnimateFirstView(false);
225 mTextSwitcher.reset();
226 mTextSwitcher.setText(seg.getText());
John Spurlock209bede2013-07-17 12:23:27 -0400227
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 tickerStarting();
229 scheduleAdvance();
230 }
231 }
232
John Spurlockb8baccc2013-06-05 12:59:01 -0400233 private static boolean charSequencesEqual(CharSequence a, CharSequence b) {
234 if (a.length() != b.length()) {
235 return false;
236 }
237
238 int length = a.length();
239 for (int i = 0; i < length; i++) {
240 if (a.charAt(i) != b.charAt(i)) {
241 return false;
242 }
243 }
244 return true;
245 }
246
Daniel Sandlerdfa08db2010-08-05 16:18:42 -0400247 public void removeEntry(StatusBarNotification n) {
Joe Onoratof5510542010-06-01 07:46:41 -0700248 for (int i=mSegments.size()-1; i>=0; i--) {
249 Segment seg = mSegments.get(i);
Daniel Sandler4f91efd2013-04-25 16:38:41 -0400250 if (n.getId() == seg.notification.getId() && n.getPackageName().equals(seg.notification.getPackageName())) {
Joe Onoratof5510542010-06-01 07:46:41 -0700251 mSegments.remove(i);
252 }
253 }
254 }
255
Daniel Sandlerdfa08db2010-08-05 16:18:42 -0400256 public void halt() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800257 mHandler.removeCallbacks(mAdvanceTicker);
258 mSegments.clear();
259 tickerHalting();
260 }
261
Daniel Sandlerdfa08db2010-08-05 16:18:42 -0400262 public void reflowText() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800263 if (mSegments.size() > 0) {
264 Segment seg = mSegments.get(0);
265 CharSequence text = seg.getText();
266 mTextSwitcher.setCurrentText(text);
267 }
268 }
269
270 private Runnable mAdvanceTicker = new Runnable() {
271 public void run() {
272 while (mSegments.size() > 0) {
273 Segment seg = mSegments.get(0);
274
275 if (seg.first) {
276 // this makes the icon slide in for the first one for a given
277 // notification even if there are two notifications with the
278 // same icon in a row
279 mIconSwitcher.setImageDrawable(seg.icon);
280 }
281 CharSequence text = seg.advance();
282 if (text == null) {
283 mSegments.remove(0);
284 continue;
285 }
286 mTextSwitcher.setText(text);
287
288 scheduleAdvance();
289 break;
290 }
291 if (mSegments.size() == 0) {
292 tickerDone();
293 }
294 }
295 };
296
297 private void scheduleAdvance() {
298 mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY);
299 }
300
Daniel Sandlerdfa08db2010-08-05 16:18:42 -0400301 public abstract void tickerStarting();
302 public abstract void tickerDone();
303 public abstract void tickerHalting();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800304}
305