/documentation/RockLockTutorial/StepByStep/src/com/marvin/rocklock/RockLockActivity.java

http://eyes-free.googlecode.com/ · Java · 403 lines · 315 code · 49 blank · 39 comment · 43 complexity · 9321548f4e8d30a3904a66b74bd69718 MD5 · raw file

  1. /*
  2. * Copyright (C) 2010 Google Inc.
  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.marvin.rocklock;
  17. import com.google.marvin.widget.GestureOverlay;
  18. import com.google.marvin.widget.GestureOverlay.Gesture;
  19. import com.google.marvin.widget.GestureOverlay.GestureListener;
  20. import android.app.Activity;
  21. import android.content.BroadcastReceiver;
  22. import android.content.Context;
  23. import android.content.Intent;
  24. import android.content.IntentFilter;
  25. import android.os.Bundle;
  26. import android.os.Vibrator;
  27. import android.speech.tts.TextToSpeech;
  28. import android.speech.tts.TextToSpeech.OnInitListener;
  29. import android.telephony.PhoneStateListener;
  30. import android.telephony.TelephonyManager;
  31. import android.view.KeyEvent;
  32. import android.view.View;
  33. import android.view.WindowManager;
  34. import android.view.View.OnClickListener;
  35. import android.view.accessibility.AccessibilityEvent;
  36. import android.widget.Button;
  37. import android.widget.FrameLayout;
  38. import android.widget.TextView;
  39. import java.text.SimpleDateFormat;
  40. import java.util.Calendar;
  41. /**
  42. * The main Rock Lock application that runs as an alternate lock screen which
  43. * enables the user to use stroke gestures to play music. If there is no lock
  44. * pattern, Rock Lock will replace the lock screen entirely; dismissing Rock
  45. * Lock will unlock the phone. If there is a lock pattern, Rock Lock will put up
  46. * the default pattern locked screen when the user dismisses Rock Lock.
  47. *
  48. * @author clchen@google.com (Charles L. Chen)
  49. */
  50. public class RockLockActivity extends Activity {
  51. public static final String EXTRA_STARTED_BY_SERVICE = "STARTED_BY_SERVICE";
  52. public static final String TICK_EARCON = "[TICK]";
  53. private static final long[] VIBE_PATTERN = {
  54. 0, 10, 70, 80
  55. };
  56. private boolean poked = false;
  57. private FrameLayout contentFrame;
  58. private Button unlockButton;
  59. private MusicPlayer mp;
  60. private boolean isSeeking = false;
  61. private boolean seekingStopped = true;
  62. private GestureOverlay gestureOverlay;
  63. private AnimationLayer uiAnimation;
  64. private Vibrator vibe;
  65. private TextToSpeech tts;
  66. private TextView dateText;
  67. private TextView statusText;
  68. private TextView infoText;
  69. // Catch media button events so that controls from plugged in headsets and
  70. // BlueTooth headsets will work.
  71. //
  72. // Note that this only works if there are NO other apps that are trying to
  73. // consume the media button events and aborting the broadcasts; otherwise,
  74. // whether it works or not is a function of the order in which the
  75. // broadcasts are sent.
  76. private BroadcastReceiver mediaButtonReceiver = new BroadcastReceiver() {
  77. @Override
  78. public void onReceive(Context ctx, Intent data) {
  79. this.abortBroadcast();
  80. KeyEvent event = data.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
  81. int keyCode = event.getKeyCode();
  82. if (event.getAction() == KeyEvent.ACTION_DOWN) {
  83. if ((keyCode == KeyEvent.KEYCODE_HEADSETHOOK)
  84. || (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)) {
  85. mp.togglePlayPause();
  86. updateDisplayText(null, null, false);
  87. } else if (keyCode == KeyEvent.KEYCODE_MEDIA_NEXT) {
  88. mp.nextTrack();
  89. updateDisplayText(null, null, false);
  90. } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS) {
  91. mp.prevTrack();
  92. updateDisplayText(null, null, false);
  93. }
  94. }
  95. }
  96. };
  97. // Don't send any accessibility events since this is a fully self voicing
  98. // app.
  99. @Override
  100. public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent evt) {
  101. return true;
  102. }
  103. /** Called when the activity is first created. */
  104. @Override
  105. public void onCreate(Bundle savedInstanceState) {
  106. super.onCreate(savedInstanceState);
  107. // Start the service in case it is not already running
  108. startService(new Intent(this, ScreenOnHandlerService.class));
  109. requestWindowFeature(android.view.Window.FEATURE_NO_TITLE);
  110. getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
  111. setContentView(R.layout.main);
  112. mp = new MusicPlayer(this);
  113. final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
  114. tm.listen(new PhoneStateListener() {
  115. @Override
  116. public void onCallStateChanged(int state, String incomingNumber) {
  117. // If the phone is not idle, immediately quit and let the
  118. // default lock screen handle it.
  119. if (state != TelephonyManager.CALL_STATE_IDLE) {
  120. finish();
  121. return;
  122. }
  123. }
  124. }, PhoneStateListener.LISTEN_CALL_STATE);
  125. unlockButton = (Button) findViewById(R.id.unlockButton);
  126. unlockButton.setOnClickListener(new OnClickListener() {
  127. @Override
  128. public void onClick(View arg0) {
  129. dismissSlideUnlockScreen();
  130. }
  131. });
  132. IntentFilter filter = new IntentFilter();
  133. filter.addAction(Intent.ACTION_MEDIA_BUTTON);
  134. filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
  135. registerReceiver(mediaButtonReceiver, filter);
  136. vibe = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
  137. uiAnimation = new AnimationLayer(this);
  138. gestureOverlay = new GestureOverlay(this, new GestureListener() {
  139. @Override
  140. public void onGestureChange(int g) {
  141. isSeeking = false;
  142. vibe.vibrate(VIBE_PATTERN, -1);
  143. tts.playEarcon(TICK_EARCON, TextToSpeech.QUEUE_FLUSH, null);
  144. uiAnimation.setDirection(g);
  145. switch (g) {
  146. case Gesture.UPLEFT:
  147. updateDisplayText(getString(R.string.previous_artist), mp
  148. .getPrevArtistName(), true);
  149. break;
  150. case Gesture.UP:
  151. updateDisplayText(getString(R.string.previous_album),
  152. mp.getPrevAlbumName(), true);
  153. break;
  154. case Gesture.UPRIGHT:
  155. updateDisplayText(getString(R.string.next_artist), mp.getNextArtistName(),
  156. true);
  157. break;
  158. case Gesture.LEFT:
  159. updateDisplayText(getString(R.string.previous_track),
  160. mp.getPrevTrackName(), true);
  161. break;
  162. case Gesture.CENTER:
  163. if (mp.isPlaying()) {
  164. updateDisplayText(getString(R.string.pause), mp.getCurrentSongInfo(),
  165. true);
  166. } else {
  167. updateDisplayText(getString(R.string.play), mp.getCurrentSongInfo(),
  168. true);
  169. }
  170. break;
  171. case Gesture.RIGHT:
  172. updateDisplayText(getString(R.string.next_track), mp.getNextTrackName(),
  173. true);
  174. break;
  175. case Gesture.DOWNLEFT:
  176. if (seekingStopped) {
  177. updateDisplayText(getString(R.string.rewind), mp.getCurrentSongInfo(),
  178. false);
  179. isSeeking = true;
  180. new Thread(new Seeker(-1)).start();
  181. }
  182. break;
  183. case Gesture.DOWN:
  184. updateDisplayText(getString(R.string.next_album), mp.getNextAlbumName(),
  185. true);
  186. break;
  187. case Gesture.DOWNRIGHT:
  188. if (seekingStopped) {
  189. updateDisplayText(getString(R.string.fast_forward), mp
  190. .getCurrentSongInfo(), false);
  191. isSeeking = true;
  192. new Thread(new Seeker(1)).start();
  193. }
  194. break;
  195. }
  196. }
  197. @Override
  198. public void onGestureFinish(int g) {
  199. isSeeking = false;
  200. vibe.vibrate(VIBE_PATTERN, -1);
  201. tts.playEarcon(TICK_EARCON, TextToSpeech.QUEUE_FLUSH, null);
  202. uiAnimation.setDirection(-1);
  203. switch (g) {
  204. case Gesture.UPLEFT:
  205. mp.prevArtist();
  206. break;
  207. case Gesture.UP:
  208. mp.prevAlbum();
  209. break;
  210. case Gesture.UPRIGHT:
  211. mp.nextArtist();
  212. break;
  213. case Gesture.LEFT:
  214. mp.prevTrack();
  215. break;
  216. case Gesture.CENTER:
  217. mp.togglePlayPause();
  218. break;
  219. case Gesture.RIGHT:
  220. mp.nextTrack();
  221. break;
  222. case Gesture.DOWN:
  223. mp.nextAlbum();
  224. break;
  225. }
  226. updateDisplayText(null, null, false);
  227. }
  228. @Override
  229. public void onGestureStart(int g) {
  230. poked = true;
  231. isSeeking = false;
  232. vibe.vibrate(VIBE_PATTERN, -1);
  233. tts.playEarcon(TICK_EARCON, TextToSpeech.QUEUE_FLUSH, null);
  234. }
  235. });
  236. contentFrame = (FrameLayout) findViewById(R.id.contentFrame);
  237. View textLayer = this.getLayoutInflater().inflate(R.layout.textlayer, null);
  238. dateText = (TextView) textLayer.findViewById(R.id.dateText);
  239. statusText = (TextView) textLayer.findViewById(R.id.statusText);
  240. infoText = (TextView) textLayer.findViewById(R.id.infoText);
  241. contentFrame.addView(uiAnimation);
  242. contentFrame.addView(textLayer);
  243. contentFrame.addView(gestureOverlay);
  244. final RockLockActivity self = this;
  245. tts = new TextToSpeech(this, new OnInitListener() {
  246. @Override
  247. public void onInit(int arg0) {
  248. tts.addEarcon(TICK_EARCON, self.getPackageName(), R.raw.tick_snd);
  249. tts.playEarcon(TICK_EARCON, TextToSpeech.QUEUE_FLUSH, null);
  250. }
  251. });
  252. }
  253. @Override
  254. public void onResume() {
  255. super.onResume();
  256. poked = false;
  257. Calendar cal = Calendar.getInstance();
  258. int day = cal.get(Calendar.DAY_OF_MONTH);
  259. SimpleDateFormat monthFormat = new SimpleDateFormat("MMMM");
  260. String monthStr = monthFormat.format(cal.getTime());
  261. int year = cal.get(Calendar.YEAR);
  262. dateText.setText(monthStr + " " + Integer.toString(day) + ", " + year);
  263. new Thread(new PokeWatcher()).start();
  264. }
  265. @Override
  266. public boolean onKeyDown(int keyCode, KeyEvent event) {
  267. if (keyCode == KeyEvent.KEYCODE_BACK) {
  268. dismissSlideUnlockScreen();
  269. return true;
  270. }
  271. if (keyCode == KeyEvent.KEYCODE_MENU) {
  272. mp.stop();
  273. int songPickerType = mp.cycleSongPicker();
  274. int songPickerTextResId = R.string.tagged_music_playlist;
  275. if (songPickerType == MusicPlayer.ROCKLOCK_PLAYLIST) {
  276. songPickerTextResId = R.string.rock_lock_playlist;
  277. }
  278. updateDisplayText(getString(R.string.app_name), getString(songPickerTextResId), true);
  279. return true;
  280. }
  281. return super.onKeyDown(keyCode, event);
  282. }
  283. @Override
  284. public void onDestroy() {
  285. super.onDestroy();
  286. poked = true;
  287. mp.stop();
  288. tts.shutdown();
  289. unregisterReceiver(mediaButtonReceiver);
  290. }
  291. public void updateDisplayText(String status, String info, boolean speak) {
  292. if ((status == null) || (info == null)) {
  293. if (mp.isPlaying()) {
  294. statusText.setText(R.string.playing);
  295. infoText.setText(mp.getCurrentSongInfo());
  296. } else {
  297. statusText.setText(R.string.app_name);
  298. infoText.setText("The lock that rocks!");
  299. }
  300. return;
  301. }
  302. statusText.setText(status);
  303. infoText.setText(info);
  304. if (speak) {
  305. tts.speak(info, TextToSpeech.QUEUE_FLUSH, null);
  306. }
  307. }
  308. private class Seeker implements Runnable {
  309. private int seekMode = 0;
  310. public Seeker(int seekDirection) {
  311. seekMode = seekDirection;
  312. }
  313. @Override
  314. public void run() {
  315. while (isSeeking) {
  316. try {
  317. Thread.sleep(100);
  318. } catch (InterruptedException e) {
  319. e.printStackTrace();
  320. }
  321. if (seekMode == 1) {
  322. mp.seekForward();
  323. } else if (seekMode == -1) {
  324. mp.seekBackward();
  325. }
  326. }
  327. seekingStopped = true;
  328. }
  329. }
  330. private class PokeWatcher implements Runnable {
  331. @Override
  332. public void run() {
  333. try {
  334. Thread.sleep(5000);
  335. } catch (InterruptedException e) {
  336. e.printStackTrace();
  337. }
  338. if (!poked && (mp != null) && !mp.isPlaying()) {
  339. finish();
  340. }
  341. }
  342. }
  343. private void dismissSlideUnlockScreen() {
  344. getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
  345. // finish() must be called in another thread or else the addFlags
  346. // call in the previous line will not take effect.
  347. new Thread(new Runnable() {
  348. @Override
  349. public void run() {
  350. finish();
  351. }
  352. }).start();
  353. }
  354. }