• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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