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