Updates Wear Speaker with latest APIs and move AsyncTask to static to
avoid memory leaks.
Bug: 4368433
Test: Manual tests.
Change-Id: Id4d837dddd82efdfa3726e5a759ab49ac47c0eff
diff --git a/wearable/wear/WearSpeakerSample/build.gradle b/wearable/wear/WearSpeakerSample/build.gradle
index 6c111e7..b35d701 100644
--- a/wearable/wear/WearSpeakerSample/build.gradle
+++ b/wearable/wear/WearSpeakerSample/build.gradle
@@ -22,7 +22,7 @@
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.0.1'
+ classpath 'com.android.tools.build:gradle:3.1.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/wearable/wear/WearSpeakerSample/wear/build.gradle b/wearable/wear/WearSpeakerSample/wear/build.gradle
index 19604a1..08c92f4 100644
--- a/wearable/wear/WearSpeakerSample/wear/build.gradle
+++ b/wearable/wear/WearSpeakerSample/wear/build.gradle
@@ -19,7 +19,7 @@
android {
compileSdkVersion 26
- buildToolsVersion '26.0.2'
+ buildToolsVersion '27.0.3'
defaultConfig {
applicationId "com.example.android.wearable.speaker"
@@ -38,11 +38,16 @@
dependencies {
- compile 'com.android.support:wear:27.1.0'
+ implementation 'com.android.support:wear:27.1.1'
+ implementation 'com.android.support:animated-vector-drawable:27.1.1'
+ implementation 'com.android.support:support-media-compat:27.1.1'
- compile 'com.google.android.gms:play-services-wearable:11.8.0'
- compile 'com.android.support:appcompat-v7:27.1.0'
+ implementation 'com.android.support:percent:27.1.1'
- provided 'com.google.android.wearable:wearable:2.3.0'
- compile 'com.google.android.support:wearable:2.3.0'
+ implementation 'com.google.android.gms:play-services-wearable:15.0.1'
+ implementation 'com.android.support:appcompat-v7:27.1.1'
+ implementation 'com.android.support:support-v4:27.1.1'
+
+ compileOnly 'com.google.android.wearable:wearable:2.3.0'
+ implementation 'com.google.android.support:wearable:2.3.0'
}
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/MainActivity.java b/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/MainActivity.java
index e212195..72813b4 100644
--- a/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/MainActivity.java
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/MainActivity.java
@@ -17,7 +17,6 @@
package com.example.android.wearable.speaker;
import android.Manifest;
-import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -28,8 +27,9 @@
import android.os.Bundle;
import android.os.CountDownTimer;
import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.FragmentActivity;
import android.support.v4.content.ContextCompat;
-import android.support.wear.ambient.AmbientMode;
+import android.support.wear.ambient.AmbientModeSupport;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
@@ -45,8 +45,8 @@
* to 10 seconds), a Play icon (if clicked, it wil playback the recorded audio file) and a music
* note icon (if clicked, it plays an MP3 file that is included in the app).
*/
-public class MainActivity extends Activity implements
- AmbientMode.AmbientCallbackProvider,
+public class MainActivity extends FragmentActivity implements
+ AmbientModeSupport.AmbientCallbackProvider,
UIAnimation.UIStateListener,
SoundRecorder.OnVoicePlaybackStateChangedListener {
@@ -72,7 +72,7 @@
* Ambient mode controller attached to this display. Used by Activity to see if it is in
* ambient mode.
*/
- private AmbientMode.AmbientController mAmbientController;
+ private AmbientModeSupport.AmbientController mAmbientController;
enum AppState {
READY, PLAYING_VOICE, PLAYING_MUSIC, RECORDING
@@ -89,7 +89,7 @@
mProgressBar = findViewById(R.id.progress_bar);
// Enables Ambient mode.
- mAmbientController = AmbientMode.attachAmbientSupport(this);
+ mAmbientController = AmbientModeSupport.attach(this);
}
private void setProgressBar(long progressInMillis) {
@@ -239,10 +239,10 @@
int[] thumbResources = new int[] {R.id.mic, R.id.play, R.id.music};
ImageView[] thumbs = new ImageView[3];
for(int i=0; i < 3; i++) {
- thumbs[i] = (ImageView) findViewById(thumbResources[i]);
+ thumbs[i] = findViewById(thumbResources[i]);
}
View containerView = findViewById(R.id.container);
- ImageView expandedView = (ImageView) findViewById(R.id.expanded);
+ ImageView expandedView = findViewById(R.id.expanded);
int animationDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);
mUIAnimation = new UIAnimation(containerView, thumbs, expandedView, animationDuration,
this);
@@ -312,11 +312,11 @@
}
@Override
- public AmbientMode.AmbientCallback getAmbientCallback() {
+ public AmbientModeSupport.AmbientCallback getAmbientCallback() {
return new MyAmbientCallback();
}
- private class MyAmbientCallback extends AmbientMode.AmbientCallback {
+ private class MyAmbientCallback extends AmbientModeSupport.AmbientCallback {
/** Prepares the UI for ambient mode. */
@Override
public void onEnterAmbient(Bundle ambientDetails) {
diff --git a/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/SoundRecorder.java b/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/SoundRecorder.java
index a45bdd2..63604b0 100644
--- a/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/SoundRecorder.java
+++ b/wearable/wear/WearSpeakerSample/wear/src/main/java/com/example/android/wearable/speaker/SoundRecorder.java
@@ -32,6 +32,7 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.lang.ref.WeakReference;
/**
* A helper class to provide methods to record audio input from the MIC to the internal storage
@@ -79,62 +80,7 @@
return;
}
- mRecordingAsyncTask = new AsyncTask<Void, Void, Void>() {
-
- private AudioRecord mAudioRecord;
-
- @Override
- protected void onPreExecute() {
- mState = State.RECORDING;
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
- RECORDING_RATE, CHANNEL_IN, FORMAT, BUFFER_SIZE * 3);
- BufferedOutputStream bufferedOutputStream = null;
- try {
- bufferedOutputStream = new BufferedOutputStream(
- mContext.openFileOutput(mOutputFileName, Context.MODE_PRIVATE));
- byte[] buffer = new byte[BUFFER_SIZE];
- mAudioRecord.startRecording();
- while (!isCancelled()) {
- int read = mAudioRecord.read(buffer, 0, buffer.length);
- bufferedOutputStream.write(buffer, 0, read);
- }
- } catch (IOException | NullPointerException | IndexOutOfBoundsException e) {
- Log.e(TAG, "Failed to record data: " + e);
- } finally {
- if (bufferedOutputStream != null) {
- try {
- bufferedOutputStream.close();
- } catch (IOException e) {
- // ignore
- }
- }
- mAudioRecord.release();
- mAudioRecord = null;
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void aVoid) {
- mState = State.IDLE;
- mRecordingAsyncTask = null;
- }
-
- @Override
- protected void onCancelled() {
- if (mState == State.RECORDING) {
- Log.d(TAG, "Stopping the recording ...");
- mState = State.IDLE;
- } else {
- Log.w(TAG, "Requesting to stop recording while state was not RECORDING");
- }
- mRecordingAsyncTask = null;
- }
- };
+ mRecordingAsyncTask = new RecordAudioAsyncTask(this);
mRecordingAsyncTask.execute();
}
@@ -172,74 +118,9 @@
}
return;
}
- final int intSize = AudioTrack.getMinBufferSize(RECORDING_RATE, CHANNELS_OUT, FORMAT);
+ int intSize = AudioTrack.getMinBufferSize(RECORDING_RATE, CHANNELS_OUT, FORMAT);
- mPlayingAsyncTask = new AsyncTask<Void, Void, Void>() {
-
- private AudioTrack mAudioTrack;
-
- @Override
- protected void onPreExecute() {
- mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
- mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0 /* flags */);
- mState = State.PLAYING;
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- try {
- mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, RECORDING_RATE,
- CHANNELS_OUT, FORMAT, intSize, AudioTrack.MODE_STREAM);
- byte[] buffer = new byte[intSize * 2];
- FileInputStream in = null;
- BufferedInputStream bis = null;
- mAudioTrack.setVolume(AudioTrack.getMaxVolume());
- mAudioTrack.play();
- try {
- in = mContext.openFileInput(mOutputFileName);
- bis = new BufferedInputStream(in);
- int read;
- while (!isCancelled() && (read = bis.read(buffer, 0, buffer.length)) > 0) {
- mAudioTrack.write(buffer, 0, read);
- }
- } catch (IOException e) {
- Log.e(TAG, "Failed to read the sound file into a byte array", e);
- } finally {
- try {
- if (in != null) {
- in.close();
- }
- if (bis != null) {
- bis.close();
- }
- } catch (IOException e) { /* ignore */}
-
- mAudioTrack.release();
- }
- } catch (IllegalStateException e) {
- Log.e(TAG, "Failed to start playback", e);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void aVoid) {
- cleanup();
- }
-
- @Override
- protected void onCancelled() {
- cleanup();
- }
-
- private void cleanup() {
- if (mListener != null) {
- mListener.onPlaybackStopped();
- }
- mState = State.IDLE;
- mPlayingAsyncTask = null;
- }
- };
+ mPlayingAsyncTask = new PlayAudioAsyncTask(this, intSize);
mPlayingAsyncTask.execute();
}
@@ -260,4 +141,176 @@
stopPlaying();
stopRecording();
}
+
+
+ private static class PlayAudioAsyncTask extends AsyncTask<Void, Void, Void> {
+
+ private WeakReference<SoundRecorder> mSoundRecorderWeakReference;
+
+ private AudioTrack mAudioTrack;
+ private int mIntSize;
+
+ PlayAudioAsyncTask(SoundRecorder context, int intSize) {
+ mSoundRecorderWeakReference = new WeakReference<>(context);
+ mIntSize = intSize;
+ }
+
+ @Override
+ protected void onPreExecute() {
+
+ SoundRecorder soundRecorder = mSoundRecorderWeakReference.get();
+
+ if (soundRecorder != null) {
+ soundRecorder.mAudioManager.setStreamVolume(
+ AudioManager.STREAM_MUSIC,
+ soundRecorder.mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC),
+ 0 /* flags */);
+ soundRecorder.mState = State.PLAYING;
+ }
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ SoundRecorder soundRecorder = mSoundRecorderWeakReference.get();
+
+ try {
+ mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, RECORDING_RATE,
+ CHANNELS_OUT, FORMAT, mIntSize, AudioTrack.MODE_STREAM);
+ byte[] buffer = new byte[mIntSize * 2];
+ FileInputStream in = null;
+ BufferedInputStream bis = null;
+ mAudioTrack.setVolume(AudioTrack.getMaxVolume());
+ mAudioTrack.play();
+ try {
+ in = soundRecorder.mContext.openFileInput(soundRecorder.mOutputFileName);
+ bis = new BufferedInputStream(in);
+ int read;
+ while (!isCancelled() && (read = bis.read(buffer, 0, buffer.length)) > 0) {
+ mAudioTrack.write(buffer, 0, read);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to read the sound file into a byte array", e);
+ } finally {
+ try {
+ if (in != null) {
+ in.close();
+ }
+ if (bis != null) {
+ bis.close();
+ }
+ } catch (IOException e) { /* ignore */}
+
+ mAudioTrack.release();
+ }
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Failed to start playback", e);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ cleanup();
+ }
+
+ @Override
+ protected void onCancelled() {
+ cleanup();
+ }
+
+ private void cleanup() {
+ SoundRecorder soundRecorder = mSoundRecorderWeakReference.get();
+
+ if (soundRecorder != null) {
+ if (soundRecorder.mListener != null) {
+ soundRecorder.mListener.onPlaybackStopped();
+ }
+ soundRecorder.mState = State.IDLE;
+ soundRecorder.mPlayingAsyncTask = null;
+ }
+ }
+ }
+
+ private static class RecordAudioAsyncTask extends AsyncTask<Void, Void, Void> {
+
+ private WeakReference<SoundRecorder> mSoundRecorderWeakReference;
+
+ private AudioRecord mAudioRecord;
+
+ RecordAudioAsyncTask(SoundRecorder context) {
+ mSoundRecorderWeakReference = new WeakReference<>(context);
+ }
+
+ @Override
+ protected void onPreExecute() {
+ SoundRecorder soundRecorder = mSoundRecorderWeakReference.get();
+
+ if (soundRecorder != null) {
+ soundRecorder.mState = State.RECORDING;
+ }
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+
+ SoundRecorder soundRecorder = mSoundRecorderWeakReference.get();
+
+ mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
+ RECORDING_RATE, CHANNEL_IN, FORMAT, BUFFER_SIZE * 3);
+
+
+ BufferedOutputStream bufferedOutputStream = null;
+
+ try {
+ bufferedOutputStream = new BufferedOutputStream(
+ soundRecorder.mContext.openFileOutput(
+ soundRecorder.mOutputFileName,
+ Context.MODE_PRIVATE));
+ byte[] buffer = new byte[BUFFER_SIZE];
+ mAudioRecord.startRecording();
+ while (!isCancelled()) {
+ int read = mAudioRecord.read(buffer, 0, buffer.length);
+ bufferedOutputStream.write(buffer, 0, read);
+ }
+ } catch (IOException | NullPointerException | IndexOutOfBoundsException e) {
+ Log.e(TAG, "Failed to record data: " + e);
+ } finally {
+ if (bufferedOutputStream != null) {
+ try {
+ bufferedOutputStream.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ mAudioRecord.release();
+ mAudioRecord = null;
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ SoundRecorder soundRecorder = mSoundRecorderWeakReference.get();
+
+ if (soundRecorder != null) {
+ soundRecorder.mState = State.IDLE;
+ soundRecorder.mRecordingAsyncTask = null;
+ }
+ }
+
+ @Override
+ protected void onCancelled() {
+ SoundRecorder soundRecorder = mSoundRecorderWeakReference.get();
+
+ if (soundRecorder != null) {
+ if (soundRecorder.mState == State.RECORDING) {
+ Log.d(TAG, "Stopping the recording ...");
+ soundRecorder.mState = State.IDLE;
+ } else {
+ Log.w(TAG, "Requesting to stop recording while state was not RECORDING");
+ }
+ soundRecorder.mRecordingAsyncTask = null;
+ }
+ }
+ }
}