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.content.Context; 21 import android.content.pm.PackageManager; 22 import android.media.AudioDeviceInfo; 23 import android.media.AudioManager; 24 import android.media.MediaPlayer; 25 import android.os.Build; 26 import android.os.Bundle; 27 import android.os.CountDownTimer; 28 import android.support.v4.app.ActivityCompat; 29 import android.support.v4.content.ContextCompat; 30 import android.support.wearable.activity.WearableActivity; 31 import android.util.Log; 32 import android.view.View; 33 import android.widget.ImageView; 34 import android.widget.ProgressBar; 35 import android.widget.Toast; 36 37 import java.util.concurrent.TimeUnit; 38 39 /** 40 * We first get the required permission to use the MIC. If it is granted, then we continue with 41 * the application and present the UI with three icons: a MIC icon (if pressed, user can record up 42 * to 10 seconds), a Play icon (if clicked, it wil playback the recorded audio file) and a music 43 * note icon (if clicked, it plays an MP3 file that is included in the app). 44 */ 45 public class MainActivity extends WearableActivity implements UIAnimation.UIStateListener, 46 SoundRecorder.OnVoicePlaybackStateChangedListener { 47 48 private static final String TAG = "MainActivity"; 49 private static final int PERMISSIONS_REQUEST_CODE = 100; 50 private static final long COUNT_DOWN_MS = TimeUnit.SECONDS.toMillis(10); 51 private static final long MILLIS_IN_SECOND = TimeUnit.SECONDS.toMillis(1); 52 private static final String VOICE_FILE_NAME = "audiorecord.pcm"; 53 private MediaPlayer mMediaPlayer; 54 private AppState mState = AppState.READY; 55 private UIAnimation.UIState mUiState = UIAnimation.UIState.HOME; 56 private SoundRecorder mSoundRecorder; 57 58 private UIAnimation mUIAnimation; 59 private ProgressBar mProgressBar; 60 private CountDownTimer mCountDownTimer; 61 62 enum AppState { 63 READY, PLAYING_VOICE, PLAYING_MUSIC, RECORDING 64 } 65 66 @Override onCreate(Bundle savedInstanceState)67 protected void onCreate(Bundle savedInstanceState) { 68 super.onCreate(savedInstanceState); 69 setContentView(R.layout.main_activity); 70 mProgressBar = (ProgressBar) findViewById(R.id.progress); 71 mProgressBar.setMax((int) (COUNT_DOWN_MS / MILLIS_IN_SECOND)); 72 setAmbientEnabled(); 73 } 74 setProgressBar(long progressInMillis)75 private void setProgressBar(long progressInMillis) { 76 mProgressBar.setProgress((int) (progressInMillis / MILLIS_IN_SECOND)); 77 } 78 79 @Override onUIStateChanged(UIAnimation.UIState state)80 public void onUIStateChanged(UIAnimation.UIState state) { 81 Log.d(TAG, "UI State is: " + state); 82 if (mUiState == state) { 83 return; 84 } 85 switch (state) { 86 case MUSIC_UP: 87 mState = AppState.PLAYING_MUSIC; 88 mUiState = state; 89 playMusic(); 90 break; 91 case MIC_UP: 92 mState = AppState.RECORDING; 93 mUiState = state; 94 mSoundRecorder.startRecording(); 95 setProgressBar(COUNT_DOWN_MS); 96 mCountDownTimer = new CountDownTimer(COUNT_DOWN_MS, MILLIS_IN_SECOND) { 97 @Override 98 public void onTick(long millisUntilFinished) { 99 mProgressBar.setVisibility(View.VISIBLE); 100 setProgressBar(millisUntilFinished); 101 Log.d(TAG, "Time Left: " + millisUntilFinished / MILLIS_IN_SECOND); 102 } 103 104 @Override 105 public void onFinish() { 106 mProgressBar.setProgress(0); 107 mProgressBar.setVisibility(View.INVISIBLE); 108 mSoundRecorder.stopRecording(); 109 mUIAnimation.transitionToHome(); 110 mUiState = UIAnimation.UIState.HOME; 111 mState = AppState.READY; 112 mCountDownTimer = null; 113 } 114 }; 115 mCountDownTimer.start(); 116 break; 117 case SOUND_UP: 118 mState = AppState.PLAYING_VOICE; 119 mUiState = state; 120 mSoundRecorder.startPlay(); 121 break; 122 case HOME: 123 switch (mState) { 124 case PLAYING_MUSIC: 125 mState = AppState.READY; 126 mUiState = state; 127 stopMusic(); 128 break; 129 case PLAYING_VOICE: 130 mState = AppState.READY; 131 mUiState = state; 132 mSoundRecorder.stopPlaying(); 133 break; 134 case RECORDING: 135 mState = AppState.READY; 136 mUiState = state; 137 mSoundRecorder.stopRecording(); 138 if (mCountDownTimer != null) { 139 mCountDownTimer.cancel(); 140 mCountDownTimer = null; 141 } 142 mProgressBar.setVisibility(View.INVISIBLE); 143 setProgressBar(COUNT_DOWN_MS); 144 break; 145 } 146 break; 147 } 148 } 149 150 /** 151 * Plays back the MP3 file embedded in the application 152 */ playMusic()153 private void playMusic() { 154 if (mMediaPlayer == null) { 155 mMediaPlayer = MediaPlayer.create(this, R.raw.sound); 156 mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 157 @Override 158 public void onCompletion(MediaPlayer mp) { 159 // we need to transition to the READY/Home state 160 Log.d(TAG, "Music Finished"); 161 mUIAnimation.transitionToHome(); 162 } 163 }); 164 } 165 mMediaPlayer.start(); 166 } 167 168 /** 169 * Stops the playback of the MP3 file. 170 */ stopMusic()171 private void stopMusic() { 172 if (mMediaPlayer != null) { 173 mMediaPlayer.stop(); 174 mMediaPlayer.release(); 175 mMediaPlayer = null; 176 } 177 } 178 179 /** 180 * Checks the permission that this app needs and if it has not been granted, it will 181 * prompt the user to grant it, otherwise it shuts down the app. 182 */ checkPermissions()183 private void checkPermissions() { 184 boolean recordAudioPermissionGranted = 185 ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) 186 == PackageManager.PERMISSION_GRANTED; 187 188 if (recordAudioPermissionGranted) { 189 start(); 190 } else { 191 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.RECORD_AUDIO}, 192 PERMISSIONS_REQUEST_CODE); 193 } 194 195 } 196 197 @Override onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)198 public void onRequestPermissionsResult(int requestCode, 199 String permissions[], int[] grantResults) { 200 if (requestCode == PERMISSIONS_REQUEST_CODE) { 201 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 202 start(); 203 } else { 204 // Permission has been denied before. At this point we should show a dialog to 205 // user and explain why this permission is needed and direct him to go to the 206 // Permissions settings for the app in the System settings. For this sample, we 207 // simply exit to get to the important part. 208 Toast.makeText(this, R.string.exiting_for_permissions, Toast.LENGTH_LONG).show(); 209 finish(); 210 } 211 } 212 } 213 214 /** 215 * Starts the main flow of the application. 216 */ start()217 private void start() { 218 mSoundRecorder = new SoundRecorder(this, VOICE_FILE_NAME, this); 219 int[] thumbResources = new int[] {R.id.mic, R.id.play, R.id.music}; 220 ImageView[] thumbs = new ImageView[3]; 221 for(int i=0; i < 3; i++) { 222 thumbs[i] = (ImageView) findViewById(thumbResources[i]); 223 } 224 View containerView = findViewById(R.id.container); 225 ImageView expandedView = (ImageView) findViewById(R.id.expanded); 226 int animationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); 227 mUIAnimation = new UIAnimation(containerView, thumbs, expandedView, animationDuration, 228 this); 229 } 230 231 @Override onStart()232 protected void onStart() { 233 super.onStart(); 234 if (speakerIsSupported()) { 235 checkPermissions(); 236 } else { 237 findViewById(R.id.container2).setOnClickListener(new View.OnClickListener() { 238 @Override 239 public void onClick(View v) { 240 Toast.makeText(MainActivity.this, R.string.no_speaker_supported, 241 Toast.LENGTH_SHORT).show(); 242 } 243 }); 244 } 245 } 246 247 @Override onStop()248 protected void onStop() { 249 if (mSoundRecorder != null) { 250 mSoundRecorder.cleanup(); 251 mSoundRecorder = null; 252 } 253 if (mCountDownTimer != null) { 254 mCountDownTimer.cancel(); 255 } 256 257 if (mMediaPlayer != null) { 258 mMediaPlayer.release(); 259 mMediaPlayer = null; 260 } 261 super.onStop(); 262 } 263 264 @Override onPlaybackStopped()265 public void onPlaybackStopped() { 266 mUIAnimation.transitionToHome(); 267 mUiState = UIAnimation.UIState.HOME; 268 mState = AppState.READY; 269 } 270 271 /** 272 * Determines if the wear device has a built-in speaker and if it is supported. Speaker, even if 273 * physically present, is only supported in Android M+ on a wear device.. 274 */ speakerIsSupported()275 public final boolean speakerIsSupported() { 276 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 277 PackageManager packageManager = getPackageManager(); 278 // The results from AudioManager.getDevices can't be trusted unless the device 279 // advertises FEATURE_AUDIO_OUTPUT. 280 if (!packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) { 281 return false; 282 } 283 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 284 AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); 285 for (AudioDeviceInfo device : devices) { 286 if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) { 287 return true; 288 } 289 } 290 } 291 return false; 292 } 293 } 294