PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/talkback_preics/src/com/google/android/marvin/talkback/TalkBackService.java

http://eyes-free.googlecode.com/
Java | 1779 lines | 960 code | 239 blank | 580 comment | 234 complexity | 1f1106e0d79f39c25b9f2c6cf62a24e3 MD5 | raw file
Possible License(s): GPL-3.0, Apache-2.0
  1. /*
  2. * Copyright (C) 2009 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * 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, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.google.android.marvin.talkback;
  17. import com.google.android.marvin.commands.Command;
  18. import com.google.android.marvin.commands.CommandConstants;
  19. import com.google.android.marvin.commands.CommandsManager;
  20. import com.google.android.marvin.commands.impls.SavingPhoneStateListener;
  21. import com.google.android.marvin.talkback.ProximitySensor.ProximityChangeListener;
  22. import com.google.android.marvin.talkback.commands.TalkBackCommands;
  23. import com.google.tts.TextToSpeechBeta;
  24. import android.accessibilityservice.AccessibilityService;
  25. import android.accessibilityservice.AccessibilityServiceInfo;
  26. import android.app.ActivityManager;
  27. import android.app.Notification;
  28. import android.app.NotificationManager;
  29. import android.app.PendingIntent;
  30. import android.app.Service;
  31. import android.content.BroadcastReceiver;
  32. import android.content.Context;
  33. import android.content.Intent;
  34. import android.content.IntentFilter;
  35. import android.content.SharedPreferences;
  36. import android.content.pm.PackageManager;
  37. import android.content.pm.ResolveInfo;
  38. import android.content.pm.PackageManager.NameNotFoundException;
  39. import android.media.AudioManager;
  40. import android.net.Uri;
  41. import android.os.Build;
  42. import android.os.Environment;
  43. import android.os.Handler;
  44. import android.os.Message;
  45. import android.os.Parcelable;
  46. import android.os.SystemClock;
  47. import android.preference.PreferenceManager;
  48. import android.speech.tts.TextToSpeech;
  49. import android.telephony.PhoneStateListener;
  50. import android.telephony.TelephonyManager;
  51. import android.text.format.DateFormat;
  52. import android.text.format.DateUtils;
  53. import android.util.Log;
  54. import android.view.accessibility.AccessibilityEvent;
  55. import android.widget.EditText;
  56. import java.io.BufferedReader;
  57. import java.io.FileNotFoundException;
  58. import java.io.FileOutputStream;
  59. import java.io.FileReader;
  60. import java.io.IOException;
  61. import java.lang.Thread.UncaughtExceptionHandler;
  62. import java.lang.reflect.InvocationTargetException;
  63. import java.lang.reflect.Method;
  64. import java.util.ArrayList;
  65. import java.util.Arrays;
  66. import java.util.Collection;
  67. import java.util.Date;
  68. import java.util.HashMap;
  69. import java.util.Iterator;
  70. import java.util.List;
  71. import java.util.Map;
  72. import java.util.regex.Matcher;
  73. import java.util.regex.Pattern;
  74. /**
  75. * {@link AccessibilityService} that provides spoken feedback.
  76. *
  77. * @author svetoslavganov@google.com (Svetoslav R. Ganov)
  78. * @author clchen@google.com (Charles L. Chen)
  79. */
  80. public class TalkBackService extends AccessibilityService {
  81. // BEGIN WORKAROUND FOR ECLAIRE COMPATIBILITY
  82. private static Method TextToSpeech_getDefaultEngine;
  83. private static Method TextToSpeech_setEngineByPackageName;
  84. static {
  85. initCompatibility();
  86. }
  87. private static void initCompatibility() {
  88. try {
  89. TextToSpeech_getDefaultEngine = TextToSpeech.class.getMethod("getDefaultEngine", new Class[] {});
  90. TextToSpeech_setEngineByPackageName = TextToSpeech.class.getMethod("setEngineByPackageName", new Class[] {
  91. String.class
  92. });
  93. /* success, this is a newer device */
  94. } catch (NoSuchMethodException nsme) {
  95. /* failure, must be older device */
  96. }
  97. }
  98. private static String getDefaultEngine(TextToSpeech tts) {
  99. try {
  100. Object retobj = TextToSpeech_getDefaultEngine.invoke(tts);
  101. return (String) retobj;
  102. } catch (IllegalAccessException e) {
  103. e.printStackTrace();
  104. } catch (IllegalArgumentException e) {
  105. e.printStackTrace();
  106. } catch (InvocationTargetException e) {
  107. e.printStackTrace();
  108. }
  109. return null;
  110. }
  111. private static int setEngineByPackageName(TextToSpeech tts, String packageName) {
  112. try {
  113. Object retobj = TextToSpeech_setEngineByPackageName.invoke(tts, packageName);
  114. return (Integer) retobj;
  115. } catch (IllegalAccessException e) {
  116. e.printStackTrace();
  117. } catch (IllegalArgumentException e) {
  118. e.printStackTrace();
  119. } catch (InvocationTargetException e) {
  120. e.printStackTrace();
  121. }
  122. return -1;
  123. }
  124. // END OF WORKAROUND FOR ECLAIRE COMPATIBILITY
  125. /**
  126. * {@link Intent} broadcast action for announcing the notifications state.
  127. * </p> Note: Sending intent broadcast commands to TalkBack must be
  128. * performed through {@link Context#sendBroadcast(Intent, String)}
  129. */
  130. public static final String ACTION_ANNOUNCE_STATUS_SUMMARY_COMMAND = "com.google.android.marvin.talkback.ACTION_ANNOUNCE_STATUS_SUMMARY_COMMAND";
  131. public static final String TALKBACK_USER_EVENT_COMMAND = "com.google.android.marvin.commands.TALKBACK_USER_EVENT_COMMAND";
  132. /**
  133. * {@link Intent} broadcast action for resetting the TalkBack service. </p>
  134. * Note: Sending intent broadcast commands to TalkBack must be performed
  135. * through {@link Context#sendBroadcast(Intent, String)}
  136. */
  137. public static final String ACTION_RESET_TALKBACK_COMMAND = "com.google.android.marvin.talkback.ACTION_RESET_TALKBACK_COMMAND";
  138. /**
  139. * {@link Intent} broadcast action for querying the state of TalkBack. </p>
  140. * Note: Sending intent broadcast commands to TalkBack must be performed
  141. * through {@link Context#sendBroadcast(Intent, String)}
  142. */
  143. @Deprecated
  144. // TODO(caseyburkhardt): Remove when we decide to no longer support intent broadcasts for
  145. // querying the current state of TalkBack.
  146. public static final String ACTION_QUERY_TALKBACK_ENABLED_COMMAND = "com.google.android.marvin.talkback.ACTION_QUERY_TALKBACK_ENABLED_COMMAND";
  147. /**
  148. * Result that TalkBack is enabled.
  149. *
  150. * @see #ACTION_QUERY_TALKBACK_ENABLED_COMMAND
  151. */
  152. public static final int RESULT_TALKBACK_ENABLED = 0x00000001;
  153. /**
  154. * Result that TalkBack is disabled.
  155. *
  156. * @see #ACTION_QUERY_TALKBACK_ENABLED_COMMAND
  157. */
  158. public static final int RESULT_TALKBACK_DISABLED = 0x00000002;
  159. /**
  160. * Permission to send {@link Intent} broadcast commands to TalkBack.
  161. */
  162. public static final String PERMISSION_SEND_INTENT_BROADCAST_COMMANDS_TO_TALKBACK = "com.google.android.marvin.talkback.PERMISSION_SEND_INTENT_BROADCAST_COMMANDS_TO_TALKBACK";
  163. /**
  164. * Tag for logging.
  165. */
  166. private static final String LOG_TAG = "TalkBackService";
  167. /**
  168. * To account for SVox camel-case trouble.
  169. */
  170. private static final Pattern sCamelCasePrefixPattern = Pattern.compile("([a-z0-9])([A-Z])");
  171. /**
  172. * To account for SVox camel-case trouble.
  173. */
  174. private static final Pattern sCamelCaseSuffixPattern = Pattern.compile("([A-Z])([a-z0-9])");
  175. /**
  176. * To recognize strings with only capital letters, that have no vowels or
  177. * are three letters or shorter. Use this to find acronyms that should be
  178. * spelled out instead of spoken.
  179. */
  180. private static final Pattern sAcronymPattern = Pattern.compile("\\b(([A-Z&&[^AEIOU]]{2,})|([A-Z]{2,3}))\\b");
  181. /**
  182. * To add spaces between two consecutive capital letters.
  183. */
  184. private static final Pattern sConsecutiveCapsPattern = Pattern.compile("([A-Z])(?=[A-Z])");
  185. /**
  186. * Manages the pending notifications.
  187. */
  188. private static final NotificationCache sNotificationCache = new NotificationCache();
  189. /**
  190. * Timeout for waiting the events to settle down before speaking
  191. */
  192. private static final long EVENT_TIMEOUT = 200;
  193. /**
  194. * Timeout for waiting the events to settle down before speaking
  195. */
  196. private static final long EVENT_TIMEOUT_IN_CALL_SCREEN = 3000;
  197. /**
  198. * The class name of the in-call screen.
  199. */
  200. private static final String CLASS_NAME_IN_CALL_SCREEN = "com.android.phone.InCallScreen";
  201. /**
  202. * The package name of the Accessibility Settings Manager
  203. */
  204. private static final String SETTINGS_MANAGER_PACKAGE = "com.marvin.preferences";
  205. /**
  206. * Speak action.
  207. */
  208. private static final int WHAT_SPEAK = 1;
  209. /**
  210. * Speak while the phone is ringing action.
  211. */
  212. private static final int WHAT_SPEAK_WHILE_IN_CALL = 2;
  213. /**
  214. * Stop speaking action.
  215. */
  216. private static final int WHAT_STOP_ALL_SPEAKING = 3;
  217. /**
  218. * Start the TTS service.
  219. */
  220. private static final int WHAT_START_TTS = 4;
  221. /**
  222. * Stop the TTS service.
  223. */
  224. private static final int WHAT_SHUTDOWN_TTS = 5;
  225. /**
  226. * Switch TTS systems (from or to TTS Extended).
  227. */
  228. private static final int WHAT_SWITCH_TTS = 6;
  229. /**
  230. * Space string constant.
  231. */
  232. private static final String SPACE = " ";
  233. /**
  234. * Opening bracket character constant.
  235. */
  236. private static final char OPEN_SQUARE_BRACKET = '[';
  237. /**
  238. * Closing bracket character constant.
  239. */
  240. private static final char CLOSE_SQUARE_BRACKET = ']';
  241. /**
  242. * The name of the contacts package used to fix a specific behavior in
  243. * Dialer
  244. */
  245. private static final String PACKAGE_NAME_CONTACTS = "com.android.contacts";
  246. /**
  247. * Prefix for utterance IDs.
  248. */
  249. private static final String UTTERANCE_ID_PREFIX = "talkback_";
  250. /**
  251. * {@link IntentFilter} with all commands that can be executed by third
  252. * party applications or services via intent broadcasting.
  253. */
  254. private static final IntentFilter sCommandInterfaceIntentFilter = new IntentFilter();
  255. static {
  256. sCommandInterfaceIntentFilter.addAction(ACTION_ANNOUNCE_STATUS_SUMMARY_COMMAND);
  257. // add other command intents here
  258. }
  259. /**
  260. * Notification ID for the "TalkBack Settings" notification
  261. */
  262. private static final int SETTINGS_NOTIFICATION_ID = 1;
  263. /**
  264. * Notification ID for the "TalkBack Crash Report" notification
  265. */
  266. private static final int CRASH_NOTIFICATION_ID = 2;
  267. /**
  268. * Queuing mode - interrupt the spoken utterance before speaking another one.
  269. */
  270. public static final int QUEUING_MODE_INTERRUPT = TextToSpeech.QUEUE_FLUSH;
  271. /**
  272. * Queuing mode - queue the utterance to be spoken.
  273. */
  274. public static final int QUEUING_MODE_QUEUE = TextToSpeech.QUEUE_ADD;
  275. /**
  276. * Queuing mode - compute the queuing mode base on previous event context.
  277. */
  278. public static final int QUEUING_MODE_COMPUTE_FROM_EVENT_CONTEXT = 2;
  279. /**
  280. * Queuing mode - uninterruptible utterance.
  281. */
  282. public static final int QUEUING_MODE_UNINTERRUPTIBLE = 3;
  283. /**
  284. * The maximal size to the queue of cached events.
  285. */
  286. private static final int EVENT_QUEUE_MAX_SIZE = 2;
  287. /**
  288. * Ringer preference - speak at all ringer volumes.
  289. */
  290. public static final int PREF_RINGER_ALL = 0;
  291. /**
  292. * Ringer preference - speak unless silent mode.
  293. */
  294. public static final int PREF_RINGER_NOT_SILENT = 1;
  295. /**
  296. * Ringer preference - speak unless silent or vibrate mode.
  297. */
  298. public static final int PREF_RINGER_NOT_SILENT_OR_VIBRATE = 2;
  299. /**
  300. * Ringer preference - default.
  301. */
  302. public static final int PREF_RINGER_DEFAULT = PREF_RINGER_ALL;
  303. /**
  304. * Screen preference - allow speech when screen is off.
  305. */
  306. public static final int PREF_SCREEN_OFF_ALLOWED = 0;
  307. /**
  308. * Screen preference - do not allow speech when screen is off.
  309. */
  310. public static final int PREF_SCREEN_OFF_DISALLOWED = 1;
  311. /**
  312. * Screen preference - default.
  313. */
  314. public static final int PREF_SCREEN_DEFAULT = PREF_SCREEN_OFF_ALLOWED;
  315. /**
  316. * Caller ID preference - default.
  317. */
  318. public static final boolean PREF_CALLER_ID_DEFAULT = true;
  319. /**
  320. * TTS Extended preference - default.
  321. */
  322. public static final boolean PREF_TTS_EXTENDED_DEFAULT = false;
  323. /**
  324. * Proximity Sensor preference - default.
  325. */
  326. public static final boolean PREF_PROXIMITY_DEFAULT = true;
  327. /**
  328. * Period for the proximity sensor to remain active after last utterance (in milliseconds).
  329. */
  330. public static final long PROXIMITY_SENSOR_CUTOFF_THRESHOLD = 1000;
  331. /**
  332. * The filename used for logging crash reports.
  333. */
  334. private static final String TALKBACK_CRASH_LOG = "talkback_crash.log";
  335. /**
  336. * The email address that receives TalkBack crash reports.
  337. */
  338. private static final String[] CRASH_REPORT_EMAILS = {"eyes.free.crash.reports@gmail.com"};
  339. /**
  340. * Flag if the infrastructure has been initialized.
  341. */
  342. private static boolean sInfrastructureInitialized = false;
  343. /**
  344. * Flag if the TTS service has been initialized.
  345. */
  346. private static boolean sTtsInitialized = false;
  347. /**
  348. * We keep the accessibility events to be processed. If a received event is
  349. * the same type as the previous one it replaces the latter, otherwise it is
  350. * added to the queue. All events in this queue are processed while we speak
  351. * and this occurs after a certain timeout since the last received event.
  352. */
  353. private final EventQueue mEventQueue = new EventQueue();
  354. /**
  355. * Reusable map used for passing parameters to the TextToSpeech.
  356. */
  357. private final HashMap<String, String> mSpeechParametersMap = new HashMap<String, String>();
  358. /**
  359. * Listeners interested in the TalkBack initialization state.
  360. */
  361. private final ArrayList<InfrastructureStateListener> mInfrastructureStateListeners = new ArrayList<InfrastructureStateListener>();
  362. /**
  363. * Flag if a notification is currently spoken.
  364. */
  365. private boolean mSpeakingNotification;
  366. /**
  367. * Runnable to clear mSpeakingNotification to false after the notification has stopped playing.
  368. */
  369. private Runnable mClearSpeakingNotification;
  370. /**
  371. * The TTS engine. Only one of this and mTtsExtended will be non-null at a time.
  372. */
  373. private TextToSpeech mTts = null;
  374. /**
  375. * The TTS engine being initialized.
  376. */
  377. private TextToSpeech mTtsInitializing = null;
  378. /**
  379. * The package name of the default TTS engine.
  380. */
  381. private String defaultTtsEngine = null;
  382. /**
  383. * A handler for switching the TTS engine asynchronously.
  384. */
  385. private Handler mTtsHandler;
  386. /**
  387. * {@link Runnable} used to recover the TTS engine in the event of a media state change.
  388. */
  389. private Runnable mTtsStateRecoverer;
  390. /**
  391. * The TTS extended engine. Only one of this and mTts will be non-null at a
  392. * time.
  393. */
  394. private TextToSpeechBeta mTtsExtended = null;
  395. /**
  396. * The TTS extended engine being initialized.
  397. */
  398. private TextToSpeechBeta mTtsExtendedInitializing = null;
  399. /**
  400. * Proximity sensor for implementing "shut up" functionality.
  401. */
  402. private ProximitySensor mProximitySensor;
  403. /**
  404. * processor for {@link AccessibilityEvent}s that populates
  405. * {@link Utterance}s.
  406. */
  407. private SpeechRuleProcessor mSpeechRuleProcessor;
  408. /**
  409. * Loader of speech rules.
  410. */
  411. private SpeechRuleLoader mSpeechRuleLoader;
  412. /**
  413. * The last event - used to auto-determine the speech queue mode.
  414. */
  415. private int mLastEventType;
  416. /**
  417. * The audio manager used for changing the ringer volume for incoming calls.
  418. */
  419. private AudioManager mAudioManager;
  420. /**
  421. * The activity manager used for determining currently open activities.
  422. */
  423. private ActivityManager mActivityManager;
  424. /**
  425. * The telephony manager used to determine the call state.
  426. */
  427. private TelephonyManager mTelephonyManager;
  428. /**
  429. * The manager for TalkBack plug-ins.
  430. */
  431. private PluginManager mPluginManager;
  432. /**
  433. * {@link BroadcastReceiver} for tracking the ringer mode and screen state.
  434. */
  435. private RingerModeAndScreenMonitor mRingerModeAndScreenMonitor;
  436. /**
  437. * {@link Runnable} for processing the standby of the proximity sensor.
  438. */
  439. private Runnable mProximitySensorSilencer;
  440. /**
  441. * The current ringer mode of the device.
  442. */
  443. private int mRingerMode;
  444. /**
  445. * Flag if the last utterance is uninterruptible.
  446. */
  447. private boolean mLastUtteranceUninterruptible;
  448. /**
  449. * Access to preferences.
  450. */
  451. SharedPreferences mPrefs;
  452. /**
  453. * Whether speech should be silenced based on the ringer mode.
  454. */
  455. private int mRingerPref;
  456. /**
  457. * Whether speech should be silenced based on screen status.
  458. */
  459. private int mScreenPref;
  460. /**
  461. * Whether Caller ID should be spoken.
  462. */
  463. private boolean mCallerIdPref;
  464. /**
  465. * Whether TTS Extended should be used.
  466. */
  467. private boolean mTtsExtendedPref;
  468. /**
  469. * Whether to use the proximity sensor to silence speech.
  470. */
  471. private boolean mProximityPref;
  472. /**
  473. * The version code of the last known running version of TalkBack
  474. */
  475. private int mLastVersion;
  476. /**
  477. * The version code of the currently running version of TalkBack
  478. */
  479. private int mCurVersion;
  480. /**
  481. * Whether the screen is off.
  482. */
  483. private boolean mScreenIsOff;
  484. /**
  485. * When the screen was last turned on.
  486. */
  487. private long mTimeScreenOn;
  488. /**
  489. * The last spoken accessibility event, used for crash reporting.
  490. */
  491. private AccessibilityEvent mLastSpokenEvent = null;
  492. /**
  493. * Flag if the device has a telephony feature, so we know if to initialize
  494. * phone specific stuff.
  495. */
  496. private boolean mDeviceIsPhone;
  497. /**
  498. * Flag if the device has a touchscreen feature, so we know to initialize
  499. * touchscreen-specific stuff.
  500. */
  501. private boolean mDeviceHasTouchscreen;
  502. /**
  503. * Array of actions to perform when an utterance completes.
  504. */
  505. private ArrayList<UtteranceCompleteAction> mUtteranceCompleteActions
  506. = new ArrayList<UtteranceCompleteAction>();
  507. /**
  508. * The next utterance index; each utterance id will be constructed from this
  509. * ever-increasing index.
  510. */
  511. private int mNextUtteranceIndex = 0;
  512. /**
  513. * Maps the name to command enum for user commands handled by TalkBack.
  514. */
  515. private Map<String, TalkBackCommands> mIdToUserCommands;
  516. /**
  517. * Listener for determining changes in the telephony state.
  518. */
  519. private PhoneStateListener mPhoneStateListener;
  520. /**
  521. * {@link BroadcastReceiver} for determining changes in the media state used
  522. * for switching the TTS engine.
  523. */
  524. private BroadcastReceiver mMediaBroadcastReceiver;
  525. /**
  526. * Static handle to TalkBack so CommandInterfaceBroadcastReceiver can access
  527. * it.
  528. */
  529. static TalkBackService sInstance;
  530. @Override
  531. public void onCreate() {
  532. super.onCreate();
  533. sInstance = this;
  534. }
  535. @Override
  536. public void onDestroy() {
  537. super.onDestroy();
  538. shutdownInfrastructure();
  539. }
  540. /**
  541. * Returns true if TalkBack is running and initialized.
  542. */
  543. public static boolean isServiceInitialized() {
  544. return sInfrastructureInitialized && sTtsInitialized;
  545. }
  546. /**
  547. * Returns the TalkBackService instance if it's running and initialized,
  548. * otherwise returns null.
  549. */
  550. public static TalkBackService getInstance() {
  551. if (sInfrastructureInitialized) {
  552. return sInstance;
  553. }
  554. return null;
  555. }
  556. /**
  557. * @return The service instance as {@link Context} if it has been
  558. * instantiated regardless if infrastructure has been initialized;
  559. */
  560. public static Context asContext() {
  561. return sInstance;
  562. }
  563. /**
  564. * Shuts down the infrastructure in case it has been initialized.
  565. */
  566. private void shutdownInfrastructure() {
  567. if (!sInfrastructureInitialized) {
  568. return;
  569. }
  570. if (mProximitySensor != null) {
  571. mProximitySensor.shutdown();
  572. }
  573. mSpeechHandler.obtainMessage(WHAT_SHUTDOWN_TTS).sendToTarget();
  574. if (mRingerModeAndScreenMonitor != null) {
  575. unregisterReceiver(mRingerModeAndScreenMonitor);
  576. }
  577. if (mPhoneStateListener != null) {
  578. mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
  579. }
  580. if (mMediaBroadcastReceiver != null) {
  581. unregisterReceiver(mMediaBroadcastReceiver);
  582. }
  583. if (mTtsHandler != null && mTtsStateRecoverer != null) {
  584. mTtsHandler.removeCallbacks(mTtsStateRecoverer);
  585. }
  586. sInfrastructureInitialized = false;
  587. notifyInfrastructureStateListeners();
  588. mInfrastructureStateListeners.clear();
  589. }
  590. @Override
  591. public void onServiceConnected() {
  592. shutdownInfrastructure();
  593. setServiceInfo();
  594. initializeInfrastructure();
  595. if (mDeviceIsPhone) {
  596. tryShowSettingsManagerAvailable();
  597. registerUncaughtExceptionHandler();
  598. processCrashLog();
  599. }
  600. }
  601. /**
  602. * Sets the {@link AccessibilityService} for configuring how the system
  603. * handles TalkBack.
  604. */
  605. public void setServiceInfo() {
  606. AccessibilityServiceInfo info = new AccessibilityServiceInfo();
  607. info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
  608. info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;
  609. info.notificationTimeout = 0;
  610. info.flags = AccessibilityServiceInfo.DEFAULT;
  611. setServiceInfo(info);
  612. }
  613. /**
  614. * Initializes the infrastructure.
  615. */
  616. private void initializeInfrastructure() {
  617. // first check if we are running on a phone
  618. mDeviceIsPhone = getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
  619. // check if the device has a touchscreen
  620. mDeviceHasTouchscreen = getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
  621. // start the TTS service
  622. mSpeechHandler.obtainMessage(WHAT_START_TTS).sendToTarget();
  623. // create a speech processor for generating utterances
  624. mSpeechRuleProcessor = new SpeechRuleProcessor(this);
  625. // initialize the speech rule loader and load speech rules
  626. mSpeechRuleLoader = new SpeechRuleLoader(getPackageName(), mSpeechRuleProcessor,
  627. mDeviceIsPhone);
  628. mSpeechRuleLoader.loadSpeechRules();
  629. addInfrastructureStateListener(mSpeechRuleLoader);
  630. // We initialize phone specific stuff only if needed
  631. if (mDeviceIsPhone) {
  632. // Create and register in a proximity sensor for stopping speech
  633. initializeProximitySensor();
  634. // get the AudioManager and configure according the current ring mode
  635. mAudioManager = (AudioManager) getSystemService(Service.AUDIO_SERVICE);
  636. // get the ringer mode on start
  637. mRingerMode = mAudioManager.getRingerMode();
  638. // get the TelephonyManager
  639. mTelephonyManager = (TelephonyManager) getSystemService(Service.TELEPHONY_SERVICE);
  640. // TODO(svetoslavganov): For now preferences are supported only on devices
  641. // with telephony feature i.e. phones
  642. // load preferences
  643. mPrefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
  644. reloadPreferences();
  645. // write preferences in case we set anything to its default value for the first time.
  646. SharedPreferences.Editor prefsEditor = mPrefs.edit();
  647. prefsEditor.putString(getString(R.string.pref_speak_ringer_key), "" + mRingerPref);
  648. prefsEditor.putString(getString(R.string.pref_speak_screenoff_key), "" + mScreenPref);
  649. prefsEditor.putBoolean(getString(R.string.pref_caller_id_key), mCallerIdPref);
  650. prefsEditor.putBoolean(getString(R.string.pref_tts_extended_key), mTtsExtendedPref);
  651. prefsEditor.putBoolean(getString(R.string.pref_proximity_key), mProximityPref);
  652. prefsEditor.commit();
  653. // register a phone state listener for the connectivity command.
  654. mPhoneStateListener = new SavingPhoneStateListener(this);
  655. mTelephonyManager.listen(mPhoneStateListener,
  656. PhoneStateListener.LISTEN_SIGNAL_STRENGTHS | PhoneStateListener.LISTEN_SERVICE_STATE);
  657. }
  658. if (mDeviceIsPhone || mDeviceHasTouchscreen) {
  659. /*
  660. * Although this receiver includes code responding to phone-specific
  661. * intents, it should also be registered for touch screen devices
  662. * without telephony.
  663. */
  664. mRingerModeAndScreenMonitor = new RingerModeAndScreenMonitor();
  665. mRingerModeAndScreenMonitor.register(this);
  666. }
  667. // get the ActivityManager
  668. mActivityManager = (ActivityManager) getSystemService(Service.ACTIVITY_SERVICE);
  669. // instantiate the plug-in manager and load plug-ins
  670. mPluginManager = new PluginManager(this, mSpeechRuleProcessor);
  671. addInfrastructureStateListener(mPluginManager);
  672. mScreenIsOff = false;
  673. mTimeScreenOn = SystemClock.uptimeMillis();
  674. mSpeakingNotification = false;
  675. mClearSpeakingNotification = new Runnable() {
  676. public void run() {
  677. mSpeakingNotification = false;
  678. }
  679. };
  680. // register the class loading manager
  681. addInfrastructureStateListener(ClassLoadingManager.getInstance());
  682. // add a listener to respond to sdcard
  683. IntentFilter mediaIntentFilter = new IntentFilter();
  684. mediaIntentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
  685. mediaIntentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
  686. mediaIntentFilter.addDataScheme("file");
  687. mTtsHandler = new Handler();
  688. mMediaBroadcastReceiver = new BroadcastReceiver() {
  689. @Override
  690. public void onReceive(Context context, Intent intent) {
  691. String action = intent.getAction();
  692. if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
  693. if (defaultTtsEngine != null) {
  694. mTtsStateRecoverer = new Runnable() {
  695. @Override
  696. public void run() {
  697. setTtsEngineKeepTrying(defaultTtsEngine, 3000, 20);
  698. }
  699. };
  700. mTtsHandler.postDelayed(mTtsStateRecoverer, 5000);
  701. }
  702. } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
  703. mTtsStateRecoverer = new Runnable() {
  704. @Override
  705. public void run() {
  706. setTtsEngineKeepTrying("com.svox.pico", 3000, 20);
  707. }
  708. };
  709. mTtsHandler.postDelayed(mTtsStateRecoverer, 5000);
  710. }
  711. }
  712. };
  713. registerReceiver(mMediaBroadcastReceiver, mediaIntentFilter);
  714. initializeCommands();
  715. sInfrastructureInitialized = true;
  716. notifyInfrastructureStateListeners();
  717. }
  718. /**
  719. * Try to switch the TTS engine, repeating if necessary.
  720. *
  721. * @param engine The package name of the desired TTS engine
  722. * @param retryInterval How often to retry switching, in milliseconds
  723. * @param maxTries How many times to try switching before giving up
  724. */
  725. private void setTtsEngineKeepTrying(
  726. final String engine, final int retryInterval, final int maxTries) {
  727. if (maxTries < 1 || mTts == null || TextToSpeech_setEngineByPackageName == null) {
  728. return;
  729. }
  730. int result = setEngineByPackageName(mTts, engine);
  731. if (result == TextToSpeech.ERROR) {
  732. mTtsHandler.postDelayed(new Runnable() {
  733. @Override
  734. public void run() {
  735. setTtsEngineKeepTrying(engine, retryInterval, maxTries - 1);
  736. }
  737. }, retryInterval);
  738. }
  739. }
  740. private void initializeCommands() {
  741. CommandsManager accessor = new CommandsManager();
  742. accessor.addCommands(
  743. Arrays.<Command>asList(TalkBackCommands.values()),
  744. getApplicationContext());
  745. mIdToUserCommands = new HashMap<String, TalkBackCommands>();
  746. for (TalkBackCommands command : TalkBackCommands.values()) {
  747. mIdToUserCommands.put(command.getDisplayName(), command);
  748. }
  749. }
  750. private void initializeProximitySensor() {
  751. mProximitySensor = new ProximitySensor(this, true, new ProximityChangeListener() {
  752. @Override
  753. public void onProximityChanged(float proximity) {
  754. if (proximity == 0) {
  755. // Stop all speech if the user is touching the proximity
  756. // sensor
  757. if (mTts != null || mTtsExtended != null) {
  758. mSpeechHandler.obtainMessage(WHAT_STOP_ALL_SPEAKING).sendToTarget();
  759. }
  760. }
  761. }
  762. });
  763. // Create a Runnable for causing the standby of the proximity sensor.
  764. mProximitySensorSilencer = new Runnable() {
  765. @Override
  766. public void run() {
  767. if (mProximitySensor != null) {
  768. mProximitySensor.standby();
  769. }
  770. }
  771. };
  772. }
  773. /**
  774. * Adds an {@link InfrastructureStateListener}.
  775. */
  776. public void addInfrastructureStateListener(InfrastructureStateListener listener) {
  777. mInfrastructureStateListeners.add(listener);
  778. }
  779. /**
  780. * Removes an {@link InfrastructureStateListener}.
  781. */
  782. public void removeInfrastructureStateListener(InfrastructureStateListener listener) {
  783. mInfrastructureStateListeners.remove(listener);
  784. }
  785. /**
  786. * Version 12 (2.4.0) introduced a preference for tracking the last run
  787. * version of TalkBack. This method processes one-time tasks defined to run
  788. * after upgrades or installations from a specific version.
  789. */
  790. private void tryShowSettingsManagerAvailable() {
  791. // this is method is a NOOP on non-phone devices
  792. if (!mDeviceIsPhone) {
  793. return;
  794. }
  795. // Obtain the current and previous version numbers
  796. mLastVersion = mPrefs.getInt(getString(R.string.pref_last_talkback_version), 0);
  797. PackageManager packageManager = getPackageManager();
  798. try {
  799. mCurVersion = packageManager.getPackageInfo(getPackageName(), 0).versionCode;
  800. } catch (NameNotFoundException e) {
  801. // TalkBack couldn't locate it's own PackageInfo, probably not a
  802. // good thing.
  803. mCurVersion = 0;
  804. }
  805. if (mLastVersion == 0) {
  806. // Display a notification about the settings activity only if
  807. // the user is running version 12 or higher and did a clean install
  808. // or upgraded from a version earlier than 12 and the application is
  809. // not yet already installed.
  810. Intent settingsManager = new Intent(Intent.ACTION_MAIN);
  811. settingsManager.setPackage(SETTINGS_MANAGER_PACKAGE);
  812. List<ResolveInfo> settingsManagerPresence = packageManager.queryIntentActivities(
  813. settingsManager, 0);
  814. if (settingsManagerPresence == null || settingsManagerPresence.isEmpty()) {
  815. displaySettingsAvailableNotification();
  816. }
  817. } else {
  818. // We want to track the number of times that TalkBack has started
  819. // without notifying the user about the new settings activity. This
  820. // will be used later to tune further announcements.
  821. int notificationlessLaunches = mPrefs.getInt(
  822. getString(R.string.pref_notificationless_launches), 0);
  823. SharedPreferences.Editor editor = mPrefs.edit();
  824. editor.putInt(getString(R.string.pref_notificationless_launches),
  825. notificationlessLaunches + 1);
  826. editor.commit();
  827. }
  828. if (mLastVersion != mCurVersion) {
  829. SharedPreferences.Editor editor = mPrefs.edit();
  830. editor.putInt(getString(R.string.pref_last_talkback_version), mCurVersion);
  831. editor.commit();
  832. }
  833. }
  834. /**
  835. * Displays a notification that TalkBack now offers customizable settings by
  836. * downloading the Accessibility Settings Manager application.
  837. */
  838. private void displaySettingsAvailableNotification() {
  839. NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  840. Intent launchMarketIntent = new Intent(Intent.ACTION_VIEW);
  841. launchMarketIntent.setData(Uri.parse(getString(R.string.settings_manager_market_uri)));
  842. Notification notification = new Notification(android.R.drawable.stat_notify_more,
  843. getString(R.string.title_talkback_settings_available), System.currentTimeMillis());
  844. notification.setLatestEventInfo(this,
  845. getString(R.string.title_talkback_settings_available),
  846. getString(R.string.message_talkback_settings_available), PendingIntent.getActivity(
  847. this, 0, launchMarketIntent, PendingIntent.FLAG_UPDATE_CURRENT));
  848. notification.defaults |= Notification.DEFAULT_SOUND;
  849. notification.flags |= Notification.FLAG_AUTO_CANCEL | Notification.FLAG_ONGOING_EVENT;
  850. nm.notify(SETTINGS_NOTIFICATION_ID, notification);
  851. }
  852. /**
  853. * Displays a notification that TalkBack has crashed and data can be sent to
  854. * developers to help improve TalkBack.
  855. */
  856. private void displayCrashReportNotification(String crashReport) {
  857. NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  858. Intent emailIntent = new Intent(Intent.ACTION_SEND);
  859. emailIntent.setType("plain/text");
  860. emailIntent.putExtra(Intent.EXTRA_EMAIL, CRASH_REPORT_EMAILS);
  861. emailIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.subject_crash_report_email));
  862. emailIntent.putExtra(android.content.Intent.EXTRA_TEXT,
  863. getString(R.string.header_crash_report_email) + crashReport);
  864. Notification notification = new Notification(android.R.drawable.stat_notify_error,
  865. getString(R.string.title_talkback_crash), System.currentTimeMillis());
  866. notification.setLatestEventInfo(this,
  867. getString(R.string.title_talkback_crash),
  868. getString(R.string.message_talkback_crash), PendingIntent.getActivity(
  869. this, 0, emailIntent, PendingIntent.FLAG_UPDATE_CURRENT));
  870. notification.defaults |= Notification.DEFAULT_SOUND;
  871. notification.flags |= Notification.FLAG_AUTO_CANCEL;
  872. nm.notify(CRASH_NOTIFICATION_ID, notification);
  873. }
  874. /**
  875. * Notifies the {@link InfrastructureStateListener}s.
  876. */
  877. private void notifyInfrastructureStateListeners() {
  878. ArrayList<InfrastructureStateListener> listeners = mInfrastructureStateListeners;
  879. for (int i = 0, count = listeners.size(); i < count; i++) {
  880. InfrastructureStateListener listener = listeners.get(i);
  881. listener.onInfrastructureStateChange(sInfrastructureInitialized);
  882. }
  883. }
  884. /**
  885. * Registers an uncaught exception handler for TalkBack. Causes TalkBack to
  886. * store crash data before the thread terminates.
  887. */
  888. private void registerUncaughtExceptionHandler() {
  889. Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
  890. private UncaughtExceptionHandler sysUeh = Thread.getDefaultUncaughtExceptionHandler();
  891. @Override
  892. public void uncaughtException(Thread thread, Throwable ex) {
  893. String timestamp = new Date().toString();
  894. String androidOsVersion = Build.VERSION.SDK_INT + " - " + Build.VERSION.INCREMENTAL;
  895. String deviceInfo = Build.MANUFACTURER + ", " + Build.MODEL + ", " + Build.PRODUCT;
  896. String talkBackVersion = "" + mCurVersion;
  897. StringBuilder stackTrace = new StringBuilder();
  898. stackTrace.append(ex.toString() + "\n");
  899. for (StackTraceElement element : ex.getStackTrace()) {
  900. stackTrace.append(element.toString() + "\n");
  901. }
  902. String lastEvent = mLastSpokenEvent != null ? mLastSpokenEvent.toString() : "null";
  903. writeCrashReport(
  904. String.format(getString(R.string.template_crash_report_message), timestamp,
  905. androidOsVersion, deviceInfo, talkBackVersion, stackTrace,
  906. lastEvent));
  907. // Show the standard "Force Close" alert dialog.
  908. if (sysUeh != null) {
  909. sysUeh.uncaughtException(thread, ex);
  910. }
  911. }
  912. private void writeCrashReport(String report) {
  913. FileOutputStream fos;
  914. try {
  915. fos = openFileOutput(TALKBACK_CRASH_LOG, Context.MODE_APPEND);
  916. fos.write(report.getBytes());
  917. fos.flush();
  918. fos.close();
  919. } catch (FileNotFoundException e) {
  920. // Ignored
  921. } catch (IOException e) {
  922. // Ignored
  923. }
  924. }
  925. });
  926. }
  927. /**
  928. * Checks the TalkBack crash log and prompts the user to submit reports if appropriate.
  929. */
  930. private void processCrashLog() {
  931. StringBuilder crashReport = new StringBuilder();
  932. String line = null;
  933. try {
  934. BufferedReader br = new BufferedReader(
  935. new FileReader(getFilesDir() + "/" + TALKBACK_CRASH_LOG));
  936. while ((line = br.readLine()) != null) {
  937. crashReport.append(line + "\n");
  938. }
  939. } catch (FileNotFoundException e) {
  940. // Handles the case where no crash log exists.
  941. return;
  942. } catch (IOException e) {
  943. // Don't bother the user with this.
  944. return;
  945. }
  946. deleteFile(TALKBACK_CRASH_LOG);
  947. displayCrashReportNotification(crashReport.toString());
  948. }
  949. /**
  950. * Reload the preferences from the SharedPreferences object.
  951. */
  952. public void reloadPreferences() {
  953. // This method is a NOOP on non-phone devices
  954. if (!mDeviceIsPhone) {
  955. return;
  956. }
  957. mRingerPref = Integer.parseInt(mPrefs.getString(
  958. getString(R.string.pref_speak_ringer_key), "" + PREF_RINGER_DEFAULT));
  959. mScreenPref = Integer.parseInt(mPrefs.getString(
  960. getString(R.string.pref_speak_screenoff_key), "" + PREF_SCREEN_DEFAULT));
  961. mCallerIdPref = mPrefs.getBoolean(
  962. getString(R.string.pref_caller_id_key), PREF_CALLER_ID_DEFAULT);
  963. mTtsExtendedPref = mPrefs.getBoolean(
  964. getString(R.string.pref_tts_extended_key), PREF_TTS_EXTENDED_DEFAULT);
  965. mProximityPref = mPrefs.getBoolean(
  966. getString(R.string.pref_proximity_key), PREF_PROXIMITY_DEFAULT);
  967. // Switch TTS engines if necessary.
  968. if (sInfrastructureInitialized) {
  969. if ((mTtsExtendedPref && mTts != null) ||
  970. (!mTtsExtendedPref && mTtsExtended != null)) {
  971. mSpeechHandler.obtainMessage(WHAT_SWITCH_TTS).sendToTarget();
  972. }
  973. }
  974. // Power off the proximity sensor if necessary.
  975. if (mProximitySensor != null) {
  976. if (!mProximityPref && mProximitySensor.getState() != ProximitySensor.STATE_STOPPED) {
  977. mProximitySensor.shutdown();
  978. // After calling shutdown, the instance loses state and must be
  979. // discarded.
  980. mProximitySensor = null;
  981. }
  982. }
  983. // Power on the proximity sensor if necessary.
  984. if (mProximityPref) {
  985. if (mProximitySensor == null
  986. || mProximitySensor.getState() == ProximitySensor.STATE_STOPPED) {
  987. initializeProximitySensor();
  988. }
  989. }
  990. }
  991. @Override
  992. public void onAccessibilityEvent(AccessibilityEvent event) {
  993. if (event == null) {
  994. Log.e(LOG_TAG, "Received null accessibility event.");
  995. return;
  996. }
  997. if (!isServiceInitialized()) {
  998. Log.w(LOG_TAG, "No TTS instance found - dropping event.");
  999. return;
  1000. }
  1001. if (mDeviceIsPhone) {
  1002. // Check ringer state
  1003. if (mRingerPref == PREF_RINGER_NOT_SILENT_OR_VIBRATE
  1004. && (mRingerMode == AudioManager.RINGER_MODE_VIBRATE
  1005. || mRingerMode == AudioManager.RINGER_MODE_SILENT)) {
  1006. return;
  1007. } else if (mRingerPref == PREF_RINGER_NOT_SILENT &&
  1008. mRingerMode == AudioManager.RINGER_MODE_SILENT) {
  1009. return;
  1010. }
  1011. // Check screen state; screen off can be silenced, but caller ID can override.
  1012. boolean silence = false;
  1013. if (mScreenPref == PREF_SCREEN_OFF_DISALLOWED && mScreenIsOff) {
  1014. silence = true;
  1015. if (event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
  1016. String text = Utils.getEventText(this, event).toString();
  1017. NotificationType type;
  1018. Parcelable parcelable = event.getParcelableData();
  1019. if (parcelable instanceof Notification) {
  1020. int icon = ((Notification) parcelable).icon;
  1021. // don't record phone calls in the cache because we get missed call anyway
  1022. if (icon == NotificationType.ICON_PHONE_CALL) {
  1023. type = null; // ignore this notification
  1024. } else {
  1025. type = NotificationType.getNotificationTypeFromIcon(icon);
  1026. }
  1027. } else {
  1028. type = null;
  1029. }
  1030. updateNotificationCache(type, text);
  1031. }
  1032. }
  1033. // If the state is ringing, then the Caller ID pref overrides what we do.
  1034. if (mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_RINGING) {
  1035. silence = (mCallerIdPref == false);
  1036. }
  1037. if (silence) {
  1038. return;
  1039. }
  1040. }
  1041. // avoid processing duplicate events
  1042. if (equals(mLastSpokenEvent, event)) {
  1043. Log.i(LOG_TAG, "duplicate");
  1044. return;
  1045. }
  1046. // Keep a representation of the last spoken event for crash reporting.
  1047. mLastSpokenEvent = clone(event);
  1048. synchronized (mEventQueue) {
  1049. enqueueEventLocked(event);
  1050. if (isSourceInCallScreenActivity(event)) {
  1051. sendSpeakMessageLocked(WHAT_SPEAK_WHILE_IN_CALL, EVENT_TIMEOUT_IN_CALL_SCREEN);
  1052. } else {
  1053. sendSpeakMessageLocked(WHAT_SPEAK, EVENT_TIMEOUT);
  1054. }
  1055. }
  1056. return;
  1057. }
  1058. public AccessibilityEvent getLastSpokenEvent() {
  1059. return clone(mLastSpokenEvent);
  1060. }
  1061. /**
  1062. * Returns if the <code>event</code> source is the in-call screen activity.
  1063. */
  1064. private boolean isSourceInCallScreenActivity(AccessibilityEvent event) {
  1065. return (AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED == event.getEventType()
  1066. && CLASS_NAME_IN_CALL_SCREEN.equals(event.getClassName()));
  1067. }
  1068. @Override
  1069. public void onInterrupt() {
  1070. mSpeechHandler.obtainMessage(WHAT_STOP_ALL_SPEAKING).sendToTarget();
  1071. }
  1072. /**
  1073. * Enqueues the an <code>event</code>. The queuing operates as follows: </p>
  1074. * 1. Events within the event timeout with type same as the last event
  1075. * replace the latter if they are not notification with different icon.
  1076. * </br> 2. All other events are enqueued.
  1077. *
  1078. * @param event The event to enqueue.
  1079. */
  1080. private void enqueueEventLocked(AccessibilityEvent event) {
  1081. AccessibilityEvent current = clone(event);
  1082. ArrayList<AccessibilityEvent> eventQueue = mEventQueue;
  1083. int lastIndex = eventQueue.size() - 1;
  1084. if (lastIndex > -1) {
  1085. AccessibilityEvent last = eventQueue.get(lastIndex);
  1086. if (isSameEventTypeAndSameNotificationIconAndTickerText(event, last)) {
  1087. // in this special case we want to keep the first event
  1088. // since the system is adding hyphens to the dialed number
  1089. // which generates events we want to disregard
  1090. if (isFromDialerInput(event)) {
  1091. return;
  1092. }
  1093. eventQueue.clear();
  1094. }
  1095. }
  1096. eventQueue.add(current);
  1097. }
  1098. /**
  1099. * Returns if the <code>currentEvent</code> has different type from the
  1100. * <code>lastEvent</code> or if they are
  1101. * {@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED} if the
  1102. * {@link Notification} instances they carry do not have the same icon and
  1103. * ticker text.
  1104. */
  1105. private boolean isSameEventTypeAndSameNotificationIconAndTickerText(
  1106. AccessibilityEvent currentEvent, AccessibilityEvent lastEvent) {
  1107. if (currentEvent.getEventType() != lastEvent.getEventType()) {
  1108. return false;
  1109. }
  1110. if (currentEvent.getEventType() != AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
  1111. return true;
  1112. }
  1113. Notification currentNotification = (Notification) currentEvent.getParcelableData();
  1114. Notification lastNotification = (Notification) lastEvent.getParcelableData();
  1115. if (currentNotification == null) {
  1116. if (lastNotification != null) {
  1117. return false;
  1118. }
  1119. return true;
  1120. } else if (lastNotification == null) {
  1121. return false;
  1122. }
  1123. if (currentNotification.icon != lastNotification.icon) {
  1124. return false;
  1125. }
  1126. if (currentNotification.tickerText == null) {
  1127. if (lastNotification.tickerText != null) {
  1128. return false;
  1129. }
  1130. return true;
  1131. } else {
  1132. return currentNotification.tickerText.equals(lastNotification.tickerText);
  1133. }
  1134. }
  1135. /**
  1136. * Returns if a given <code>event</code> is fired by the dialer input which
  1137. * contains the currently dialed number. </p> Note: The Android framework
  1138. * adds hyphens between the dialed number digits which fires accessibility
  1139. * events. Since TalkBackService processes only the last event of a given
  1140. * type in a given time frame the original event is replaced by a more
  1141. * recent hyphen adding event.
  1142. *
  1143. * @param event The event we are checking.
  1144. * @return True if the event comes from the dialer input box, false
  1145. * ohterwise.
  1146. */
  1147. private boolean isFromDialerInput(AccessibilityEvent event) {
  1148. return (PACKAGE_NAME_CONTACTS.equals(event.getPackageName()) && EditText.class
  1149. .getCanonicalName().equals(event.getClassName()));
  1150. }
  1151. /**
  1152. * Sends {@link #WHAT_SPEAK} to the speech handler. This method cancels the
  1153. * old message (if such exists) since it is no longer relevant.
  1154. *
  1155. * @param action The action to perform with the message.
  1156. * @param timeout The timeout after which to send the message.
  1157. */
  1158. public void sendSpeakMessageLocked(int action, long timeout) {
  1159. Handler handler = mSpeechHandler;
  1160. handler.removeMessages(action);
  1161. Message message = handler.obtainMessage(action);
  1162. handler.sendMessageDelayed(message, timeout);
  1163. }
  1164. /**
  1165. * Processes an <code>event</code> by asking the {@link SpeechRuleProcessor}
  1166. * to match it against its rules and in case an utterance is generated it is
  1167. * spoken. This method is responsible for recycling of the processed event.
  1168. *
  1169. * @param event The event to process.
  1170. * @param queueMode The queuing mode to use while processing events.
  1171. * @param action The action to perform with the message.
  1172. */
  1173. private void processAndRecycleEvent(AccessibilityEvent event, int queueMode, int action) {
  1174. String currentActivity = "";
  1175. List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
  1176. if (tasks != null && tasks.size() >= 1) {
  1177. currentActivity = tasks.get(0).topActivity.getClassName();
  1178. }
  1179. Log.d(LOG_TAG, "Processing event: " + event + " activity=" + currentActivity);
  1180. Utterance utterance = Utterance.obtain();
  1181. // For now we do not pass any filter/formatter arguments. Their purpose is to
  1182. // ensure future flexibility.
  1183. if (mSpeechRuleProcessor.processEvent(event, currentActivity, utterance, null, null)) {
  1184. HashMap<String, Object> metadata = utterance.getMetadata();
  1185. // notifications are never interruptible
  1186. boolean speakingNotification = mSpeakingNotification;
  1187. // The event filter was matched but no text generated.
  1188. // Do not process utterances that are empty since we either
  1189. // drop the event on the floor or the source has no text
  1190. // and contentDescription.
  1191. if (utterance.getText().length() == 0) {
  1192. utterance.recycle();
  1193. return;
  1194. }
  1195. if (isEarcon(utterance)) {
  1196. String earcon = utterance.getText().toString();
  1197. // earcons always use QUEUING_MODE_QUEUE
  1198. playEarcon(earcon, QUEUING_MODE_QUEUE, null);
  1199. return;
  1200. }
  1201. int collapsedEventType = collapseEventType(event.getEventType());
  1202. if (speakingNotification) {
  1203. // we never interrupt notification events
  1204. queueMode = QUEUING_MODE_QUEUE;
  1205. mLastUtteranceUninterruptible = false;
  1206. } else if (metadata.containsKey(Utterance.KEY_METADATA_QUEUING)) {
  1207. // speech rules queue mode overrides the default TalkBack behavior
  1208. int metadataQueueMode = (Integer) metadata.get(Utterance.KEY_METADATA_QUEUING);
  1209. if (metadataQueueMode == QUEUING_MODE_UNINTERRUPTIBLE
  1210. || metadataQueueMode == QUEUING_MODE_COMPUTE_FROM_EVENT_CONTEXT) {
  1211. queueMode = (mLastEventType == collapsedEventType) ? QUEUING_MODE_INTERRUPT
  1212. : QUEUING_MODE_QUEUE;
  1213. } else {
  1214. queueMode = metadataQueueMode;
  1215. }
  1216. mLastUtteranceUninterruptible = (metadataQueueMode == QUEUING_MODE_UNINTERRUPTIBLE);
  1217. } else {
  1218. if (mLastUtteranceUninterruptible) {
  1219. queueMode = QUEUING_MODE_QUEUE;
  1220. } else {
  1221. queueMode = (mLastEventType == collapsedEventType) ? QUEUING_MODE_INTERRUPT
  1222. : QUEUING_MODE_QUEUE;
  1223. }
  1224. mLastUtteranceUninterruptible = false;
  1225. }
  1226. mLastEventType = collapsedEventType;
  1227. boolean isNotification =
  1228. event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED;
  1229. cleanUpAndSpeak(utterance, queueMode, action, isNotification);
  1230. utterance.recycle();
  1231. event.recycle();
  1232. }
  1233. }
  1234. /**
  1235. * Updates the {@link NotificationCache}. If a notification is present in
  1236. * the cache it is removed, otherwise it is added.
  1237. *
  1238. * @param type The type of the notification.
  1239. * @param text The notification text to be cached.
  1240. */
  1241. static void updateNotificationCache(NotificationType type, CharSequence text) {
  1242. // if the cache has the notification - remove, otherwise add it
  1243. if (!sNotificationCache.removeNotification(type, text.toString())) {
  1244. sNotificationCache.addNotification(type, text.toString());
  1245. }
  1246. }
  1247. /**
  1248. * Returns event types, collapsed into fewer categories because in practice,
  1249. * focus and select events are of the same category and a new one of either type
  1250. * should interrupt the other.
  1251. *
  1252. * @param eventType The event type, from Accessibility.getEventType()
  1253. * @return An eventType that collapses focus and select to the same value.
  1254. */
  1255. private int collapseEventType(int eventType) {
  1256. if (eventType == AccessibilityEvent.TYPE_VIEW_SELECTED) {
  1257. eventType = AccessibilityEvent.TYPE_VIEW_FOCUSED;
  1258. }
  1259. return eventType;
  1260. }
  1261. /**
  1262. * Clones an <code>event</code>.
  1263. *
  1264. * @param event The event to clone.
  1265. */
  1266. private AccessibilityEvent clone(AccessibilityEvent event) {
  1267. AccessibilityEvent clone = AccessibilityEvent.obtain();
  1268. clone.setAddedCount(event.getAddedCount());
  1269. clone.setBeforeText(event.getBeforeText());
  1270. clone.setChecked(event.isChecked());
  1271. clone.setClassName(event.getClassName());
  1272. clone.setContentDescription(event.getContentDescription());
  1273. clone.setCurrentItemIndex(event.getCurrentItemIndex());
  1274. clone.setEventTime(event.getEventTime());
  1275. clone.setEventType(event.getEventType());
  1276. clone.setEnabled(event.isEnabled());
  1277. clone.setFromIndex(event.getFromIndex());
  1278. clone.setFullScreen(event.isFullScreen());
  1279. clone.setItemCount(event.getItemCount());
  1280. clone.setPackageName(event.getPackageName());
  1281. clone.setParcelableData(event.getParcelableData());
  1282. clone.setPassword(event.isPassword());
  1283. clone.setRemovedCount(event.getRemovedCount());
  1284. clone.getText().clear();
  1285. clone.getText().addAll(event.getText());
  1286. return clone;
  1287. }
  1288. /**
  1289. * @return If the <code>first</code> event is equal to the <code>second</code>.
  1290. */
  1291. private boolean equals(AccessibilityEvent first, AccessibilityEvent second) {
  1292. if (first == null || second == null) {
  1293. return false;
  1294. }
  1295. if (first.getEventType() != second.getEventType()) {
  1296. return false;
  1297. }
  1298. if (first.getPackageName() == null) {
  1299. if (second.getPackageName() != null) {
  1300. return false;
  1301. }
  1302. } else if (!first.getPackageName().equals(second.getPackageName())) {
  1303. return false;
  1304. }
  1305. if (first.getClassName() == null) {
  1306. if (second.getClassName() != null) {
  1307. return false;
  1308. }
  1309. } else if (!first.getClassName().equals(second.getClassName())) {
  1310. return false;
  1311. }
  1312. if (!first.getText().equals(second.getText())) { // never null
  1313. return false;
  1314. }
  1315. if (first.getContentDescription() == null) {
  1316. if (second.getContentDescription() != null) {
  1317. return false;
  1318. }
  1319. } else if (!first.getContentDescription().equals(second.getContentDescription())) {
  1320. return false;
  1321. }
  1322. if (first.getBeforeText() == null) {
  1323. if (second.getBeforeText() != null) {
  1324. return false;
  1325. }
  1326. } else if (!first.getBeforeText().equals(second.getBeforeText())) {
  1327. return false;
  1328. }
  1329. if (first.getParcelableData() != null) {
  1330. // do not compare parcelable data it may not implement equals correctly
  1331. return false;
  1332. }
  1333. if (first.getAddedCount() != second.getAddedCount()) {
  1334. return false;
  1335. }
  1336. if (first.isChecked() != second.isChecked()) {
  1337. return false;
  1338. }
  1339. if (first.isEnabled() != second.isEnabled()) {
  1340. return false;
  1341. }
  1342. if (first.getFromIndex() != second.getFromIndex()) {
  1343. return false;
  1344. }
  1345. if (first.isFullScreen() != second.isFullScreen()) {
  1346. return false;
  1347. }
  1348. if (first.getCurrentItemIndex() != second.getCurrentItemIndex()) {
  1349. return false;
  1350. }
  1351. if (first.getItemCount() != second.getItemCount()) {
  1352. return false;
  1353. }
  1354. if (first.isPassword() != second.isPassword()) {
  1355. return false;
  1356. }
  1357. if (first.getRemovedCount() != second.getRemovedCount()) {
  1358. return false;
  1359. }
  1360. if (first.getEventTime() != second.getEventTime()) {
  1361. return false;
  1362. }
  1363. return true;
  1364. }
  1365. /**
  1366. * Cleans up and speaks an <code>utterance</code>. The clean up is replacing
  1367. * special strings with predefined mappings and reordering of some RegExp
  1368. * matches to improve presentation. The <code>queueMode</code> determines if
  1369. * speaking the event interrupts the speaking of previous events
  1370. * {@link #QUEUING_MODE_INTERRUPT} or is queued {@link #QUEUING_MODE_QUEUE}.
  1371. *
  1372. * @param utterance The utterance to speak.
  1373. * @param queueMode The queue mode to use for speaking.
  1374. * @param action The action to perform with the message.
  1375. * @param isNotification If the utterance announces a notification.
  1376. */
  1377. private void cleanUpAndSpeak(Utterance utterance, int queueMode, int action,
  1378. boolean isNotification) {
  1379. if (!sTtsInitialized) {
  1380. return;
  1381. }
  1382. String text = cleanUpString(utterance.getText().toString());
  1383. if (text.equals("")) {
  1384. return;
  1385. }
  1386. HashMap<String, String> parameters = mSpeechParametersMap;
  1387. parameters.clear();
  1388. // Give every utterance an utterance Id with an unique prefix and an
  1389. // increasing index.
  1390. int utteranceIndex = mNextUtteranceIndex;
  1391. String utteranceId = UTTERANCE_ID_PREFIX + utteranceIndex;
  1392. mNextUtteranceIndex++;
  1393. parameters.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
  1394. if (action == WHAT_SPEAK_WHILE_IN_CALL) {
  1395. manageRingerVolume(utteranceIndex);
  1396. } else if (isNotification) {
  1397. manageSpeakingNotification(utteranceIndex);
  1398. }
  1399. if (mProximitySensor != null
  1400. && mProximitySensor.getState() != ProximitySensor.STATE_STOPPED) {
  1401. mSpeechHandler.removeCallbacks(mProximitySensorSilencer);
  1402. mProximitySensor.resume();
  1403. addUtteranceCompleteAction(utteranceIndex, mProximitySensorSilencer);
  1404. }
  1405. ttsSpeak(text, queueMode, parameters);
  1406. }
  1407. /**
  1408. * Decreases the ringer volume and registers a listener for the event of
  1409. * completing to speak which restores the volume to its previous level.
  1410. *
  1411. * @param utteranceIndex the index of this utterance, used to schedule an
  1412. * utterance completion action.
  1413. */
  1414. private void manageRingerVolume(int utteranceIndex) {
  1415. // this method is a NOOP on not-phone devices
  1416. if (!mDeviceIsPhone) {
  1417. return;
  1418. }
  1419. final AudioManager audioManger = mAudioManager;
  1420. final int currentRingerVolume = audioManger.getStreamVolume(AudioManager.STREAM_RING);
  1421. final int maxRingerVolume = audioManger.getStreamMaxVolume(AudioManager.STREAM_RING);
  1422. final int lowerEnoughVolume = Math.max((maxRingerVolume / 3), (currentRingerVolume / 2));
  1423. audioManger.setStreamVolume(AudioManager.STREAM_RING, lowerEnoughVolume, 0);
  1424. addUtteranceCompleteAction(utteranceIndex, new Runnable() {
  1425. public void run() {
  1426. audioManger.setStreamVolume(AudioManager.STREAM_RING, currentRingerVolume, 0);
  1427. }
  1428. });
  1429. }
  1430. /**
  1431. * Rises a flag that a notification has been spoken and adds a listener to
  1432. * clear the flag after speaking completes.
  1433. *
  1434. * @param utteranceIndex the index of this utterance, used to schedule an
  1435. * utterance completion action.
  1436. */
  1437. private void manageSpeakingNotification(int utteranceIndex) {
  1438. mSpeakingNotification = true;
  1439. addUtteranceCompleteAction(utteranceIndex, mClearSpeakingNotification);
  1440. }
  1441. /**
  1442. * Cleans up <code>text</text> by separating camel case words with space
  1443. * to compensate for the not robust pronounciation of the SVOX TTS engine
  1444. * and replacing the text with predefined strings.
  1445. *
  1446. * @param text The text to clean up.
  1447. * @return The cleaned text.
  1448. */
  1449. public String cleanUpString(String text) {
  1450. String cleanedText = text;
  1451. Matcher allCapsMatcher = sAcronymPattern.matcher(cleanedText);
  1452. while (allCapsMatcher.find()) {
  1453. String allCapsText = cleanedText
  1454. .substring(allCapsMatcher.start(), allCapsMatcher.end());
  1455. Matcher consequtiveCapsMatcher = sConsecutiveCapsPattern.matcher(allCapsText);
  1456. String formattedAllCapsText = consequtiveCapsMatcher.replaceAll("$1 ") + ", ";
  1457. cleanedText = cleanedText.replaceAll(allCapsText, formattedAllCapsText);
  1458. allCapsMatcher = sAcronymPattern.matcher(cleanedText);
  1459. }
  1460. Matcher camelCasePrefix = sCamelCasePrefixPattern.matcher(cleanedText);
  1461. cleanedText = camelCasePrefix.replaceAll("$1 $2");
  1462. Matcher camelCaseSuffix = sCamelCaseSuffixPattern.matcher(cleanedText);
  1463. cleanedText = camelCaseSuffix.replaceAll(" $1$2");
  1464. cleanedText = cleanedText.replaceAll(" & ", " and ");
  1465. return cleanedText;
  1466. }
  1467. /**
  1468. * Determines if an <code>utterance</code> refers to an earcon. The
  1469. * convention is that earcons are enclosed in square brackets.
  1470. *
  1471. * @param utterance The utterance.
  1472. * @return True if the utterance is an earcon, false otherwise.
  1473. */
  1474. private boolean isEarcon(Utterance utterance) {
  1475. StringBuilder text = utterance.getText();
  1476. if (text.length() > 0) {
  1477. return (text.charAt(0) == OPEN_SQUARE_BRACKET
  1478. && text.charAt(text.length() - 1) == CLOSE_SQUARE_BRACKET);
  1479. } else {
  1480. return false;
  1481. }
  1482. }
  1483. Handler mSpeechHandler = new Handler() {
  1484. @Override
  1485. public void handleMessage(Message message) {
  1486. switch (message.what) {
  1487. case WHAT_SPEAK:
  1488. case WHAT_SPEAK_WHILE_IN_CALL:
  1489. ArrayList<AccessibilityEvent> eventQueue = mEventQueue;
  1490. while (true) {
  1491. AccessibilityEvent event = null;
  1492. synchronized (mEventQueue) {
  1493. if (eventQueue.isEmpty()) {
  1494. return;
  1495. }
  1496. event = eventQueue.remove(0);
  1497. }
  1498. processAndRecycleEvent(event, message.arg1, message.what);
  1499. }
  1500. case WHAT_STOP_ALL_SPEAKING:
  1501. ttsStop();
  1502. return;
  1503. case WHAT_START_TTS:
  1504. startTts();
  1505. return;
  1506. case WHAT_SHUTDOWN_TTS:
  1507. shutdownTts();
  1508. return;
  1509. case WHAT_SWITCH_TTS:
  1510. if ((mTtsExtendedPref && mTts != null) ||
  1511. (!mTtsExtendedPref && mTtsExtended != null)) {
  1512. shutdownTts();
  1513. startTts();
  1514. return;
  1515. }
  1516. }
  1517. }
  1518. };
  1519. /**
  1520. * Start the TTS service. Starts either TTS or TTS Extended based on the
  1521. * prefs. After initialization has complete, sets up an
  1522. * OnUtteranceCompletedListener, registers earcons, and then sets
  1523. * sTtsInitialized to true.
  1524. */
  1525. private void startTts() {
  1526. mTts = null;
  1527. mTtsExtended = null;
  1528. if (mTtsExtendedPref) {
  1529. mTtsExtendedInitializing = new TextToSpeechBeta(
  1530. sInstance, new TextToSpeechBeta.OnInitListener() {
  1531. @Override
  1532. public void onInit(int status, int version) {
  1533. if (status != TextToSpeechBeta.SUCCESS) {
  1534. Log.e(LOG_TAG, "TTS extended init failed.");
  1535. return;
  1536. }
  1537. mTtsExtended = mTtsExtendedInitializing;
  1538. mTtsExtended.setOnUtteranceCompletedListener(
  1539. new TextToSpeechBeta.OnUtteranceCompletedListener() {
  1540. @Override