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