Ticker.java revision 98edc951712823dbf5db2b7e9c203a0e98fc616b
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.content.res.Resources;
21import android.graphics.drawable.Drawable;
22import android.os.Handler;
23import android.text.StaticLayout;
24import android.text.Layout.Alignment;
25import android.text.TextPaint;
26import android.text.TextUtils;
27import android.util.Slog;
28import android.view.View;
29import android.view.animation.Animation;
30import android.view.animation.AnimationUtils;
31import android.widget.TextSwitcher;
32import android.widget.TextView;
33import android.widget.ImageSwitcher;
34
35import java.util.ArrayList;
36
37import com.android.internal.statusbar.StatusBarIcon;
38import com.android.internal.statusbar.StatusBarNotification;
39import com.android.internal.util.CharSequences;
40
41import com.android.systemui.R;
42import com.android.systemui.statusbar.StatusBarIconView;
43
44public 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
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
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 */
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 */
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
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
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
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.user, 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
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
236    public void halt() {
237        mHandler.removeCallbacks(mAdvanceTicker);
238        mSegments.clear();
239        tickerHalting();
240    }
241
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
277    private void scheduleAdvance() {
278        mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY);
279    }
280
281    public abstract void tickerStarting();
282    public abstract void tickerDone();
283    public abstract void tickerHalting();
284}
285
286