/packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java

https://github.com/aizuzi/platform_frameworks_base · Java · 305 lines · 239 code · 40 blank · 26 comment · 58 complexity · 6522f44c079c3d3a7674dcd60fbd234f MD5 · raw file

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