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