Home | History | Annotate | Download | only in phone
      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 
     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.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