Home | History | Annotate | Download | only in speaker
      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