Home | History | Annotate | Download | only in statusbar
      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 
     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 
     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 */
     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 */
     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 
    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 
    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 
    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 
    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 
    224     void halt() {
    225         mHandler.removeCallbacks(mAdvanceTicker);
    226         mSegments.clear();
    227         tickerHalting();
    228     }
    229 
    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 
    265     private void scheduleAdvance() {
    266         mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY);
    267     }
    268 
    269     abstract void tickerStarting();
    270     abstract void tickerDone();
    271     abstract void tickerHalting();
    272 }
    273 
    274