1 /* 2 * Copyright (C) 2015 Google Inc. All Rights Reserved. 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 17 package com.example.android.wearable.speaker; 18 19 import android.Manifest; 20 import android.app.Activity; 21 import android.content.Context; 22 import android.content.pm.PackageManager; 23 import android.content.res.Resources; 24 import android.media.AudioDeviceInfo; 25 import android.media.AudioManager; 26 import android.media.MediaPlayer; 27 import android.os.Build; 28 import android.os.Bundle; 29 import android.os.CountDownTimer; 30 import android.support.v4.app.ActivityCompat; 31 import android.support.v4.content.ContextCompat; 32 import android.support.wear.ambient.AmbientMode; 33 import android.util.Log; 34 import android.view.View; 35 import android.widget.ImageView; 36 import android.widget.ProgressBar; 37 import android.widget.RelativeLayout; 38 import android.widget.Toast; 39 40 import java.util.concurrent.TimeUnit; 41 42 /** 43 * We first get the required permission to use the MIC. If it is granted, then we continue with 44 * the application and present the UI with three icons: a MIC icon (if pressed, user can record up 45 * to 10 seconds), a Play icon (if clicked, it wil playback the recorded audio file) and a music 46 * note icon (if clicked, it plays an MP3 file that is included in the app). 47 */ 48 public class MainActivity extends Activity implements 49 AmbientMode.AmbientCallbackProvider, 50 UIAnimation.UIStateListener, 51 SoundRecorder.OnVoicePlaybackStateChangedListener { 52 53 private static final String TAG = "MainActivity"; 54 private static final int PERMISSIONS_REQUEST_CODE = 100; 55 private static final long COUNT_DOWN_MS = TimeUnit.SECONDS.toMillis(10); 56 private static final long MILLIS_IN_SECOND = TimeUnit.SECONDS.toMillis(1); 57 private static final String VOICE_FILE_NAME = "audiorecord.pcm"; 58 59 private MediaPlayer mMediaPlayer; 60 private AppState mState = AppState.READY; 61 private UIAnimation.UIState mUiState = UIAnimation.UIState.HOME; 62 private SoundRecorder mSoundRecorder; 63 64 private RelativeLayout mOuterCircle; 65 private View mInnerCircle; 66 67 private UIAnimation mUIAnimation; 68 private ProgressBar mProgressBar; 69 private CountDownTimer mCountDownTimer; 70 71 /** 72 * Ambient mode controller attached to this display. Used by Activity to see if it is in 73 * ambient mode. 74 */ 75 private AmbientMode.AmbientController mAmbientController; 76 77 enum AppState { 78 READY, PLAYING_VOICE, PLAYING_MUSIC, RECORDING 79 } 80 81 @Override 82 protected void onCreate(Bundle savedInstanceState) { 83 super.onCreate(savedInstanceState); 84 setContentView(R.layout.main_activity); 85 86 mOuterCircle = findViewById(R.id.outer_circle); 87 mInnerCircle = findViewById(R.id.inner_circle); 88 89 mProgressBar = findViewById(R.id.progress_bar); 90 91 // Enables Ambient mode. 92 mAmbientController = AmbientMode.attachAmbientSupport(this); 93 } 94 95 private void setProgressBar(long progressInMillis) { 96 mProgressBar.setProgress((int) (progressInMillis / MILLIS_IN_SECOND)); 97 } 98 99 @Override 100 public void onUIStateChanged(UIAnimation.UIState state) { 101 Log.d(TAG, "UI State is: " + state); 102 if (mUiState == state) { 103 return; 104 } 105 switch (state) { 106 case MUSIC_UP: 107 mState = AppState.PLAYING_MUSIC; 108 mUiState = state; 109 playMusic(); 110 break; 111 case MIC_UP: 112 mState = AppState.RECORDING; 113 mUiState = state; 114 mSoundRecorder.startRecording(); 115 setProgressBar(COUNT_DOWN_MS); 116 mCountDownTimer = new CountDownTimer(COUNT_DOWN_MS, MILLIS_IN_SECOND) { 117 @Override 118 public void onTick(long millisUntilFinished) { 119 mProgressBar.setVisibility(View.VISIBLE); 120 setProgressBar(millisUntilFinished); 121 Log.d(TAG, "Time Left: " + millisUntilFinished / MILLIS_IN_SECOND); 122 } 123 124 @Override 125 public void onFinish() { 126 mProgressBar.setProgress(0); 127 mProgressBar.setVisibility(View.INVISIBLE); 128 mSoundRecorder.stopRecording(); 129 mUIAnimation.transitionToHome(); 130 mUiState = UIAnimation.UIState.HOME; 131 mState = AppState.READY; 132 mCountDownTimer = null; 133 } 134 }; 135 mCountDownTimer.start(); 136 break; 137 case SOUND_UP: 138 mState = AppState.PLAYING_VOICE; 139 mUiState = state; 140 mSoundRecorder.startPlay(); 141 break; 142 case HOME: 143 switch (mState) { 144 case PLAYING_MUSIC: 145 mState = AppState.READY; 146 mUiState = state; 147 stopMusic(); 148 break; 149 case PLAYING_VOICE: 150 mState = AppState.READY; 151 mUiState = state; 152 mSoundRecorder.stopPlaying(); 153 break; 154 case RECORDING: 155 mState = AppState.READY; 156 mUiState = state; 157 mSoundRecorder.stopRecording(); 158 if (mCountDownTimer != null) { 159 mCountDownTimer.cancel(); 160 mCountDownTimer = null; 161 } 162 mProgressBar.setVisibility(View.INVISIBLE); 163 setProgressBar(COUNT_DOWN_MS); 164 break; 165 } 166 break; 167 } 168 } 169 170 /** 171 * Plays back the MP3 file embedded in the application 172 */ 173 private void playMusic() { 174 if (mMediaPlayer == null) { 175 mMediaPlayer = MediaPlayer.create(this, R.raw.sound); 176 mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 177 @Override 178 public void onCompletion(MediaPlayer mp) { 179 // we need to transition to the READY/Home state 180 Log.d(TAG, "Music Finished"); 181 mUIAnimation.transitionToHome(); 182 } 183 }); 184 } 185 mMediaPlayer.start(); 186 } 187 188 /** 189 * Stops the playback of the MP3 file. 190 */ 191 private void stopMusic() { 192 if (mMediaPlayer != null) { 193 mMediaPlayer.stop(); 194 mMediaPlayer.release(); 195 mMediaPlayer = null; 196 } 197 } 198 199 /** 200 * Checks the permission that this app needs and if it has not been granted, it will 201 * prompt the user to grant it, otherwise it shuts down the app. 202 */ 203 private void checkPermissions() { 204 boolean recordAudioPermissionGranted = 205 ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) 206 == PackageManager.PERMISSION_GRANTED; 207 208 if (recordAudioPermissionGranted) { 209 start(); 210 } else { 211 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.RECORD_AUDIO}, 212 PERMISSIONS_REQUEST_CODE); 213 } 214 215 } 216 217 @Override 218 public void onRequestPermissionsResult(int requestCode, 219 String permissions[], int[] grantResults) { 220 if (requestCode == PERMISSIONS_REQUEST_CODE) { 221 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 222 start(); 223 } else { 224 // Permission has been denied before. At this point we should show a dialog to 225 // user and explain why this permission is needed and direct him to go to the 226 // Permissions settings for the app in the System settings. For this sample, we 227 // simply exit to get to the important part. 228 Toast.makeText(this, R.string.exiting_for_permissions, Toast.LENGTH_LONG).show(); 229 finish(); 230 } 231 } 232 } 233 234 /** 235 * Starts the main flow of the application. 236 */ 237 private void start() { 238 mSoundRecorder = new SoundRecorder(this, VOICE_FILE_NAME, this); 239 int[] thumbResources = new int[] {R.id.mic, R.id.play, R.id.music}; 240 ImageView[] thumbs = new ImageView[3]; 241 for(int i=0; i < 3; i++) { 242 thumbs[i] = (ImageView) findViewById(thumbResources[i]); 243 } 244 View containerView = findViewById(R.id.container); 245 ImageView expandedView = (ImageView) findViewById(R.id.expanded); 246 int animationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); 247 mUIAnimation = new UIAnimation(containerView, thumbs, expandedView, animationDuration, 248 this); 249 } 250 251 @Override 252 protected void onStart() { 253 super.onStart(); 254 if (speakerIsSupported()) { 255 checkPermissions(); 256 } else { 257 mOuterCircle.setOnClickListener(new View.OnClickListener() { 258 @Override 259 public void onClick(View v) { 260 Toast.makeText(MainActivity.this, R.string.no_speaker_supported, 261 Toast.LENGTH_SHORT).show(); 262 } 263 }); 264 } 265 } 266 267 @Override 268 protected void onStop() { 269 if (mSoundRecorder != null) { 270 mSoundRecorder.cleanup(); 271 mSoundRecorder = null; 272 } 273 if (mCountDownTimer != null) { 274 mCountDownTimer.cancel(); 275 } 276 277 if (mMediaPlayer != null) { 278 mMediaPlayer.release(); 279 mMediaPlayer = null; 280 } 281 super.onStop(); 282 } 283 284 @Override 285 public void onPlaybackStopped() { 286 mUIAnimation.transitionToHome(); 287 mUiState = UIAnimation.UIState.HOME; 288 mState = AppState.READY; 289 } 290 291 /** 292 * Determines if the wear device has a built-in speaker and if it is supported. Speaker, even if 293 * physically present, is only supported in Android M+ on a wear device.. 294 */ 295 public final boolean speakerIsSupported() { 296 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 297 PackageManager packageManager = getPackageManager(); 298 // The results from AudioManager.getDevices can't be trusted unless the device 299 // advertises FEATURE_AUDIO_OUTPUT. 300 if (!packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) { 301 return false; 302 } 303 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 304 AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); 305 for (AudioDeviceInfo device : devices) { 306 if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) { 307 return true; 308 } 309 } 310 } 311 return false; 312 } 313 314 @Override 315 public AmbientMode.AmbientCallback getAmbientCallback() { 316 return new MyAmbientCallback(); 317 } 318 319 private class MyAmbientCallback extends AmbientMode.AmbientCallback { 320 /** Prepares the UI for ambient mode. */ 321 @Override 322 public void onEnterAmbient(Bundle ambientDetails) { 323 super.onEnterAmbient(ambientDetails); 324 325 Log.d(TAG, "onEnterAmbient() " + ambientDetails); 326 327 // Changes views to grey scale. 328 Context context = getApplicationContext(); 329 Resources resources = context.getResources(); 330 331 mOuterCircle.setBackgroundColor( 332 ContextCompat.getColor(context, R.color.light_grey)); 333 mInnerCircle.setBackground( 334 ContextCompat.getDrawable(context, R.drawable.grey_circle)); 335 336 mProgressBar.setProgressTintList( 337 resources.getColorStateList(R.color.white, context.getTheme())); 338 mProgressBar.setProgressBackgroundTintList( 339 resources.getColorStateList(R.color.black, context.getTheme())); 340 } 341 342 /** Restores the UI to active (non-ambient) mode. */ 343 @Override 344 public void onExitAmbient() { 345 super.onExitAmbient(); 346 347 Log.d(TAG, "onExitAmbient()"); 348 349 // Changes views to color. 350 Context context = getApplicationContext(); 351 Resources resources = context.getResources(); 352 353 mOuterCircle.setBackgroundColor( 354 ContextCompat.getColor(context, R.color.background_color)); 355 mInnerCircle.setBackground( 356 ContextCompat.getDrawable(context, R.drawable.color_circle)); 357 358 mProgressBar.setProgressTintList( 359 resources.getColorStateList(R.color.progressbar_tint, context.getTheme())); 360 mProgressBar.setProgressBackgroundTintList( 361 resources.getColorStateList( 362 R.color.progressbar_background_tint, context.getTheme())); 363 } 364 } 365 } 366