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