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