/TalkBack/src/com/google/android/marvin/talkback/formatter/NotificationFormatter.java

http://eyes-free.googlecode.com/ · Java · 239 lines · 123 code · 43 blank · 73 comment · 28 complexity · 0a7f45c484ccc4403846c0f0df4ad9d8 MD5 · raw file

  1. /*
  2. * Copyright (C) 2009 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.google.android.marvin.talkback.formatter;
  17. import android.app.Notification;
  18. import android.content.Context;
  19. import android.os.Bundle;
  20. import android.os.Parcelable;
  21. import android.os.SystemClock;
  22. import android.text.TextUtils;
  23. import android.view.accessibility.AccessibilityEvent;
  24. import android.widget.RemoteViews;
  25. import com.google.android.marvin.talkback.Formatter;
  26. import com.google.android.marvin.talkback.NotificationType;
  27. import com.google.android.marvin.talkback.Utils;
  28. import com.google.android.marvin.talkback.Utterance;
  29. import java.util.Iterator;
  30. import java.util.LinkedList;
  31. /**
  32. * Formatter that returns an utterance to announce text replacement.
  33. *
  34. * @author svetoslavganov@google.conm (Svetoslav R. Ganov)
  35. * @author alanv@google.com (Alan Viverette)
  36. */
  37. public class NotificationFormatter implements Formatter {
  38. private static final String SEPARATOR = " ";
  39. /** The maximum number of history items to keep track of. */
  40. private static final int MAX_HISTORY_SIZE = 1;
  41. /**
  42. * The maximum age of a history item before it's flushed. Repeating
  43. * notifications will be spoken every {@link #MAX_HISTORY_AGE} milliseconds.
  44. */
  45. private static final long MAX_HISTORY_AGE = (60 * 1000);
  46. /** The notification history. Used to detect duplicate notifications. */
  47. private final LinkedList<Notification> mNotificationHistory = new LinkedList<Notification>();
  48. @Override
  49. public boolean format(AccessibilityEvent event, Context context, Utterance utterance,
  50. Bundle args) {
  51. if (isRecentNotification(event)) {
  52. return false;
  53. }
  54. final StringBuilder utteranceText = utterance.getText();
  55. final CharSequence typeText = getTypeText(context, event);
  56. if (!TextUtils.isEmpty(typeText)) {
  57. utteranceText.append(typeText);
  58. utteranceText.append(SEPARATOR);
  59. }
  60. final CharSequence eventText = Utils.getEventText(context, event);
  61. if (!TextUtils.isEmpty(eventText)) {
  62. utteranceText.append(eventText);
  63. utteranceText.append(SEPARATOR);
  64. }
  65. return true;
  66. }
  67. /**
  68. * Manages the notification history and returns whether a notification event
  69. * is recent. Returns false if the {@link AccessibilityEvent} does not
  70. * contain a {@link Notification}.
  71. *
  72. * @param event The accessibility event that contains the notification.
  73. * @return {@code true} if the notification is recent.
  74. */
  75. private synchronized boolean isRecentNotification(AccessibilityEvent event) {
  76. final Parcelable parcelable = event.getParcelableData();
  77. if (!(parcelable instanceof Notification)) {
  78. return false;
  79. }
  80. final Notification notification = (Notification) parcelable;
  81. final Notification foundInHistory = removeFromHistory(notification);
  82. // If we didn't find the notification in history, set the notification
  83. // time to now. Otherwise, keep the event time of the previous entry.
  84. if (foundInHistory != null) {
  85. notification.when = foundInHistory.when;
  86. } else {
  87. notification.when = SystemClock.uptimeMillis();
  88. }
  89. addToHistory(notification);
  90. return (foundInHistory != null);
  91. }
  92. /**
  93. * Searches for and removes a {@link Notification} from history.
  94. *
  95. * @param notification The notification to remove from history.
  96. * @return {@code true} if the notification was found and removed.
  97. */
  98. private Notification removeFromHistory(Notification notification) {
  99. final Iterator<Notification> historyIterator = mNotificationHistory.iterator();
  100. while (historyIterator.hasNext()) {
  101. final Notification recentNotification = historyIterator.next();
  102. final long age = SystemClock.uptimeMillis() - recentNotification.when;
  103. // Don't compare against old entries, just remove them.
  104. if (age > MAX_HISTORY_AGE) {
  105. historyIterator.remove();
  106. continue;
  107. }
  108. if (notificationsAreEqual(notification, recentNotification)) {
  109. historyIterator.remove();
  110. return recentNotification;
  111. }
  112. }
  113. return null;
  114. }
  115. /**
  116. * Adds the specified {@link Notification} to history and ensures the size
  117. * of history does not exceed the specified limit.
  118. *
  119. * @param notification The notification to add.
  120. */
  121. private void addToHistory(Notification notification) {
  122. mNotificationHistory.addFirst(notification);
  123. if (mNotificationHistory.size() > MAX_HISTORY_SIZE) {
  124. mNotificationHistory.removeLast();
  125. }
  126. }
  127. /**
  128. * Compares two {@link Notification}s.
  129. *
  130. * @param first The first notification to compare.
  131. * @param second The second notification to compare.
  132. * @return {@code true} is the notifications are equal.
  133. */
  134. private boolean notificationsAreEqual(Notification first, Notification second) {
  135. if (!TextUtils.equals(first.tickerText, second.tickerText)) {
  136. return false;
  137. }
  138. final RemoteViews firstView = first.contentView;
  139. final RemoteViews secondView = second.contentView;
  140. if (!remoteViewsAreEqual(firstView, secondView)) {
  141. return false;
  142. }
  143. return true;
  144. }
  145. /**
  146. * Compares to {@link RemoteViews} objects.
  147. *
  148. * @param firstView The first view to compare.
  149. * @param secondView The second view to compare.
  150. */
  151. private boolean remoteViewsAreEqual(RemoteViews firstView, RemoteViews secondView) {
  152. if (firstView == secondView) {
  153. return true;
  154. }
  155. if (firstView == null || secondView == null) {
  156. return false;
  157. }
  158. final String firstPackage = firstView.getPackage();
  159. final String secondPackage = secondView.getPackage();
  160. if (!TextUtils.equals(firstPackage, secondPackage)) {
  161. return false;
  162. }
  163. final int firstLayoutId = firstView.getLayoutId();
  164. final int secondLayoutId = secondView.getLayoutId();
  165. if (firstLayoutId != secondLayoutId) {
  166. return false;
  167. }
  168. return true;
  169. }
  170. /**
  171. * Returns text describing the type of {@link Notification} contained within
  172. * an {@link AccessibilityEvent}.
  173. *
  174. * @param context The parent context.
  175. * @param event The event containing the notification.
  176. * @return Text describing the type of notification.
  177. */
  178. private CharSequence getTypeText(Context context, AccessibilityEvent event) {
  179. final Parcelable parcelable = event.getParcelableData();
  180. if (!(parcelable instanceof Notification)) {
  181. return null;
  182. }
  183. final Notification notification = (Notification) parcelable;
  184. final int icon = notification.icon;
  185. // TODO(alanv): This may be obsolete now that we keep track of duplicate
  186. // notifications.
  187. if (icon == NotificationType.ICON_PHONE_CALL) {
  188. return null;
  189. }
  190. final NotificationType type = NotificationType.getNotificationTypeFromIcon(icon);
  191. if (type == null) {
  192. return null;
  193. }
  194. return context.getResources().getString(type.getValue());
  195. }
  196. }