blob: 67ae31416bf1aac2fc18d962745d2dbcdd72a184 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.bugreport;
import static com.android.car.bugreport.BugReportService.MAX_PROGRESS_VALUE;
import android.Manifest;
import android.app.Activity;
import android.car.Car;
import android.car.CarNotConnectedException;
import android.car.drivingstate.CarDrivingStateEvent;
import android.car.drivingstate.CarDrivingStateManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.UserManager;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Date;
import java.util.Random;
/**
* Activity that shows two types of dialogs: starting a new bug report and current status of already
* in progress bug report.
*
* <p>If there is no in-progress bug report, it starts recording voice message. After clicking
* submit button it initiates {@link BugReportService}.
*
* <p>If bug report is in-progress, it shows a progress bar.
*/
public class BugReportActivity extends Activity {
private static final String TAG = BugReportActivity.class.getSimpleName();
/** Starts silent (no audio message recording) bugreporting. */
private static final String ACTION_START_SILENT =
"com.android.car.bugreport.action.START_SILENT";
/** This is deprecated action. Please start SILENT bugreport using {@link BugReportService}. */
private static final String ACTION_ADD_AUDIO =
"com.android.car.bugreport.action.ADD_AUDIO";
private static final int VOICE_MESSAGE_MAX_DURATION_MILLIS = 60 * 1000;
private static final int AUDIO_PERMISSIONS_REQUEST_ID = 1;
private static final String EXTRA_BUGREPORT_ID = "bugreport-id";
/**
* NOTE: mRecorder related messages are cleared when the activity finishes.
*/
private final Handler mHandler = new Handler(Looper.getMainLooper());
/** Look up string length, e.g. [ABCDEF]. */
static final int LOOKUP_STRING_LENGTH = 6;
private TextView mInProgressTitleText;
private ProgressBar mProgressBar;
private TextView mProgressText;
private TextView mAddAudioText;
private VoiceRecordingView mVoiceRecordingView;
private View mVoiceRecordingFinishedView;
private View mSubmitBugReportLayout;
private View mInProgressLayout;
private View mShowBugReportsButton;
private Button mSubmitButton;
private boolean mBound;
/** Audio message recording process started (including waiting for permission). */
private boolean mAudioRecordingStarted;
/** Audio recording using MIC is running (permission given). */
private boolean mAudioRecordingIsRunning;
private boolean mIsNewBugReport;
private boolean mIsOnActivityStartedWithBugReportServiceBoundCalled;
private boolean mIsSubmitButtonClicked;
private BugReportService mService;
private MediaRecorder mRecorder;
private MetaBugReport mMetaBugReport;
private File mAudioFile;
private Car mCar;
private CarDrivingStateManager mDrivingStateManager;
private AudioManager mAudioManager;
private AudioFocusRequest mLastAudioFocusRequest;
private Config mConfig;
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
BugReportService.ServiceBinder binder = (BugReportService.ServiceBinder) service;
mService = binder.getService();
mBound = true;
onActivityStartedWithBugReportServiceBound();
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
// called when service connection breaks unexpectedly.
mBound = false;
}
};
/**
* Builds an intent that starts {@link BugReportActivity} to add audio message to the existing
* bug report.
*/
static Intent buildAddAudioIntent(Context context, MetaBugReport bug) {
Intent addAudioIntent = new Intent(context, BugReportActivity.class);
addAudioIntent.setAction(ACTION_ADD_AUDIO);
addAudioIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
addAudioIntent.putExtra(EXTRA_BUGREPORT_ID, bug.getId());
return addAudioIntent;
}
@Override
public void onCreate(Bundle savedInstanceState) {
Preconditions.checkState(Config.isBugReportEnabled(), "BugReport is disabled.");
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
// Bind to BugReportService.
Intent intent = new Intent(this, BugReportService.class);
bindService(intent, mConnection, BIND_AUTO_CREATE);
}
@Override
protected void onStart() {
super.onStart();
if (mBound) {
onActivityStartedWithBugReportServiceBound();
}
}
@Override
protected void onStop() {
super.onStop();
// If SUBMIT button is clicked, cancelling audio has been taken care of.
if (!mIsSubmitButtonClicked) {
cancelAudioMessageRecording();
}
if (mBound) {
mService.removeBugReportProgressListener();
}
// Reset variables for the next onStart().
mAudioRecordingStarted = false;
mAudioRecordingIsRunning = false;
mIsSubmitButtonClicked = false;
mIsOnActivityStartedWithBugReportServiceBoundCalled = false;
mMetaBugReport = null;
mAudioFile = null;
}
@Override
public void onDestroy() {
if (mRecorder != null) {
mHandler.removeCallbacksAndMessages(/* token= */ mRecorder);
}
if (mBound) {
unbindService(mConnection);
mBound = false;
}
if (mCar != null && mCar.isConnected()) {
mCar.disconnect();
mCar = null;
}
super.onDestroy();
}
private void onCarDrivingStateChanged(CarDrivingStateEvent event) {
if (mShowBugReportsButton == null) {
Log.w(TAG, "Cannot handle driving state change, UI is not ready");
return;
}
// When adding audio message to the existing bugreport, do not show "Show Bug Reports"
// button, users either should explicitly Submit or Cancel.
if (mAudioRecordingStarted && !mIsNewBugReport) {
mShowBugReportsButton.setVisibility(View.GONE);
return;
}
if (event.eventValue == CarDrivingStateEvent.DRIVING_STATE_PARKED
|| event.eventValue == CarDrivingStateEvent.DRIVING_STATE_IDLING) {
mShowBugReportsButton.setVisibility(View.VISIBLE);
} else {
mShowBugReportsButton.setVisibility(View.GONE);
}
}
private void onProgressChanged(float progress) {
int progressValue = (int) progress;
mProgressBar.setProgress(progressValue);
mProgressText.setText(progressValue + "%");
if (progressValue == MAX_PROGRESS_VALUE) {
mInProgressTitleText.setText(R.string.bugreport_dialog_in_progress_title_finished);
}
}
private void prepareUi() {
if (mSubmitBugReportLayout != null) {
return;
}
setContentView(R.layout.bug_report_activity);
// Connect to the services here, because they are used only when showing the dialog.
// We need to minimize system state change when performing SILENT bug report.
mConfig = new Config();
mConfig.start();
mCar = Car.createCar(this, /* handler= */ null,
Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT, this::onCarLifecycleChanged);
mInProgressTitleText = findViewById(R.id.in_progress_title_text);
mProgressBar = findViewById(R.id.progress_bar);
mProgressText = findViewById(R.id.progress_text);
mAddAudioText = findViewById(R.id.bug_report_add_audio_to_existing);
mVoiceRecordingView = findViewById(R.id.voice_recording_view);
mVoiceRecordingFinishedView = findViewById(R.id.voice_recording_finished_text_view);
mSubmitBugReportLayout = findViewById(R.id.submit_bug_report_layout);
mInProgressLayout = findViewById(R.id.in_progress_layout);
mShowBugReportsButton = findViewById(R.id.button_show_bugreports);
mSubmitButton = findViewById(R.id.button_submit);
mShowBugReportsButton.setOnClickListener(this::buttonShowBugReportsClick);
mSubmitButton.setOnClickListener(this::buttonSubmitClick);
findViewById(R.id.button_cancel).setOnClickListener(this::buttonCancelClick);
findViewById(R.id.button_close).setOnClickListener(this::buttonCancelClick);
if (mIsNewBugReport) {
mSubmitButton.setText(R.string.bugreport_dialog_submit);
} else {
mSubmitButton.setText(mConfig.getAutoUpload()
? R.string.bugreport_dialog_upload : R.string.bugreport_dialog_save);
}
}
private void onCarLifecycleChanged(Car car, boolean ready) {
if (!ready) {
mDrivingStateManager = null;
mCar = null;
Log.d(TAG, "Car service is not ready, ignoring");
// If car service is not ready for this activity, just ignore it - as it's only
// used to control UX restrictions.
return;
}
try {
mDrivingStateManager = (CarDrivingStateManager) car.getCarManager(
Car.CAR_DRIVING_STATE_SERVICE);
mDrivingStateManager.registerListener(
BugReportActivity.this::onCarDrivingStateChanged);
// Call onCarDrivingStateChanged(), because it's not called when Car is connected.
onCarDrivingStateChanged(mDrivingStateManager.getCurrentCarDrivingState());
} catch (CarNotConnectedException e) {
Log.w(TAG, "Failed to get CarDrivingStateManager", e);
}
}
private void showInProgressUi() {
mSubmitBugReportLayout.setVisibility(View.GONE);
mInProgressLayout.setVisibility(View.VISIBLE);
mInProgressTitleText.setText(R.string.bugreport_dialog_in_progress_title);
onProgressChanged(mService.getBugReportProgress());
}
private void showSubmitBugReportUi(boolean isRecording) {
mSubmitBugReportLayout.setVisibility(View.VISIBLE);
mInProgressLayout.setVisibility(View.GONE);
if (isRecording) {
mVoiceRecordingFinishedView.setVisibility(View.GONE);
mVoiceRecordingView.setVisibility(View.VISIBLE);
} else {
mVoiceRecordingFinishedView.setVisibility(View.VISIBLE);
mVoiceRecordingView.setVisibility(View.GONE);
}
// NOTE: mShowBugReportsButton visibility is also handled in #onCarDrivingStateChanged().
mShowBugReportsButton.setVisibility(View.GONE);
if (mDrivingStateManager != null) {
try {
onCarDrivingStateChanged(mDrivingStateManager.getCurrentCarDrivingState());
} catch (CarNotConnectedException e) {
Log.e(TAG, "Failed to get current driving state.", e);
}
}
}
/**
* Initializes MetaBugReport in a local DB and starts audio recording.
*
* <p>This method expected to be called when the activity is started and bound to the service.
*/
private void onActivityStartedWithBugReportServiceBound() {
if (mIsOnActivityStartedWithBugReportServiceBoundCalled) {
return;
}
mIsOnActivityStartedWithBugReportServiceBoundCalled = true;
if (mService.isCollectingBugReport()) {
Log.i(TAG, "Bug report is already being collected.");
mService.setBugReportProgressListener(this::onProgressChanged);
prepareUi();
showInProgressUi();
return;
}
if (ACTION_START_SILENT.equals(getIntent().getAction())) {
Log.i(TAG, "Starting a silent bugreport.");
MetaBugReport bugReport = createBugReport(this, MetaBugReport.TYPE_SILENT);
startBugReportCollection(bugReport);
finish();
return;
}
// Close the notification shade and other dialogs when showing BugReportActivity dialog.
sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
if (ACTION_ADD_AUDIO.equals(getIntent().getAction())) {
addAudioToExistingBugReport(
getIntent().getIntExtra(EXTRA_BUGREPORT_ID, /* defaultValue= */ -1));
return;
}
Log.i(TAG, "Starting an interactive bugreport.");
createNewBugReportWithAudioMessage();
}
private void addAudioToExistingBugReport(int bugreportId) {
MetaBugReport bug = BugStorageUtils.findBugReport(this, bugreportId).orElseThrow(
() -> new RuntimeException("Failed to find bug report with id " + bugreportId));
Log.i(TAG, "Adding audio to the existing bugreport " + bug.getTimestamp());
if (bug.getStatus() != Status.STATUS_AUDIO_PENDING.getValue()) {
Log.e(TAG, "Failed to add audio, bad status, expected "
+ Status.STATUS_AUDIO_PENDING.getValue() + ", got " + bug.getStatus());
finish();
}
File audioFile;
try {
audioFile = File.createTempFile("audio", "mp3", getCacheDir());
} catch (IOException e) {
throw new RuntimeException("failed to create temp audio file");
}
startAudioMessageRecording(/* isNewBugReport= */ false, bug, audioFile);
}
private void createNewBugReportWithAudioMessage() {
MetaBugReport bug = createBugReport(this, MetaBugReport.TYPE_INTERACTIVE);
startAudioMessageRecording(
/* isNewBugReport= */ true,
bug,
FileUtils.getFileWithSuffix(this, bug.getTimestamp(), "-message.3gp"));
}
/** Shows a dialog UI and starts recording audio message. */
private void startAudioMessageRecording(
boolean isNewBugReport, MetaBugReport bug, File audioFile) {
if (mAudioRecordingStarted) {
Log.i(TAG, "Audio message recording is already started.");
return;
}
mAudioRecordingStarted = true;
mAudioManager = getSystemService(AudioManager.class);
mIsNewBugReport = isNewBugReport;
mMetaBugReport = bug;
mAudioFile = audioFile;
prepareUi();
showSubmitBugReportUi(/* isRecording= */ true);
if (isNewBugReport) {
mAddAudioText.setVisibility(View.GONE);
} else {
mAddAudioText.setVisibility(View.VISIBLE);
mAddAudioText.setText(String.format(
getString(R.string.bugreport_dialog_add_audio_to_existing),
mMetaBugReport.getTimestamp()));
}
if (!hasRecordPermissions()) {
requestRecordPermissions();
} else {
startRecordingWithPermission();
}
}
/**
* Cancels bugreporting by stopping audio recording and deleting temp files.
*/
private void cancelAudioMessageRecording() {
// If audio recording is not running, most likely there were permission issues,
// so leave the bugreport as is without cancelling it.
if (!mAudioRecordingIsRunning) {
Log.w(TAG, "Cannot cancel, audio recording is not running.");
return;
}
stopAudioRecording();
if (mIsNewBugReport) {
// The app creates a temp dir only for new INTERACTIVE bugreports.
File tempDir = FileUtils.getTempDir(this, mMetaBugReport.getTimestamp());
new DeleteFilesAndDirectoriesAsyncTask().execute(tempDir);
} else {
BugStorageUtils.deleteBugReportFiles(this, mMetaBugReport.getId());
new DeleteFilesAndDirectoriesAsyncTask().execute(mAudioFile);
}
BugStorageUtils.setBugReportStatus(
this, mMetaBugReport, Status.STATUS_USER_CANCELLED, "");
Log.i(TAG, "Bug report " + mMetaBugReport.getTimestamp() + " is cancelled");
mAudioRecordingStarted = false;
mAudioRecordingIsRunning = false;
}
private void buttonCancelClick(View view) {
finish();
}
private void buttonSubmitClick(View view) {
stopAudioRecording();
mIsSubmitButtonClicked = true;
if (mIsNewBugReport) {
Log.i(TAG, "Starting bugreport service.");
startBugReportCollection(mMetaBugReport);
} else {
Log.i(TAG, "Adding audio file to the bugreport " + mMetaBugReport.getTimestamp());
new AddAudioToBugReportAsyncTask(this, mConfig, mMetaBugReport, mAudioFile).execute();
}
setResult(Activity.RESULT_OK);
finish();
}
/** Starts the {@link BugReportService} to collect bug report. */
private void startBugReportCollection(MetaBugReport bug) {
Bundle bundle = new Bundle();
bundle.putParcelable(BugReportService.EXTRA_META_BUG_REPORT, bug);
Intent intent = new Intent(this, BugReportService.class);
intent.putExtras(bundle);
startForegroundService(intent);
}
/**
* Starts {@link BugReportInfoActivity} and finishes current activity, so it won't be running
* in the background and closing {@link BugReportInfoActivity} will not open the current
* activity again.
*/
private void buttonShowBugReportsClick(View view) {
// First cancel the audio recording, then delete the bug report from database.
cancelAudioMessageRecording();
// Delete the bugreport from database, otherwise pressing "Show Bugreports" button will
// create unnecessary cancelled bugreports.
if (mMetaBugReport != null) {
BugStorageUtils.completeDeleteBugReport(this, mMetaBugReport.getId());
}
Intent intent = new Intent(this, BugReportInfoActivity.class);
startActivity(intent);
finish();
}
private void requestRecordPermissions() {
requestPermissions(
new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_PERMISSIONS_REQUEST_ID);
}
private boolean hasRecordPermissions() {
return checkSelfPermission(Manifest.permission.RECORD_AUDIO)
== PackageManager.PERMISSION_GRANTED;
}
@Override
public void onRequestPermissionsResult(
int requestCode, String[] permissions, int[] grantResults) {
if (requestCode != AUDIO_PERMISSIONS_REQUEST_ID) {
return;
}
for (int i = 0; i < grantResults.length; i++) {
if (Manifest.permission.RECORD_AUDIO.equals(permissions[i])
&& grantResults[i] == PackageManager.PERMISSION_GRANTED) {
// Start recording from UI thread, otherwise when MediaRecord#start() fails,
// stack trace gets confusing.
mHandler.post(this::startRecordingWithPermission);
return;
}
}
handleNoPermission(permissions);
}
private void handleNoPermission(String[] permissions) {
String text = this.getText(R.string.toast_permissions_denied) + " : "
+ Arrays.toString(permissions);
Log.w(TAG, text);
Toast.makeText(this, text, Toast.LENGTH_LONG).show();
if (mIsNewBugReport) {
BugStorageUtils.setBugReportStatus(this, mMetaBugReport,
Status.STATUS_USER_CANCELLED, text);
} else {
BugStorageUtils.setBugReportStatus(this, mMetaBugReport,
Status.STATUS_AUDIO_PENDING, text);
}
finish();
}
private void startRecordingWithPermission() {
Log.i(TAG, "Started voice recording, and saving audio to " + mAudioFile);
mLastAudioFocusRequest = new AudioFocusRequest.Builder(
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
.setOnAudioFocusChangeListener(focusChange ->
Log.d(TAG, "AudioManager focus change " + focusChange))
.setAudioAttributes(new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build())
.setAcceptsDelayedFocusGain(true)
.build();
int focusGranted = mAudioManager.requestAudioFocus(mLastAudioFocusRequest);
// NOTE: We will record even if the audio focus was not granted.
Log.d(TAG,
"AudioFocus granted " + (focusGranted == AudioManager.AUDIOFOCUS_REQUEST_GRANTED));
mRecorder = new MediaRecorder();
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mRecorder.setOnInfoListener((MediaRecorder recorder, int what, int extra) ->
Log.i(TAG, "OnMediaRecorderInfo: what=" + what + ", extra=" + extra));
mRecorder.setOnErrorListener((MediaRecorder recorder, int what, int extra) ->
Log.i(TAG, "OnMediaRecorderError: what=" + what + ", extra=" + extra));
mRecorder.setOutputFile(mAudioFile);
try {
mRecorder.prepare();
} catch (IOException e) {
Log.e(TAG, "Failed on MediaRecorder#prepare(), filename: " + mAudioFile, e);
finish();
return;
}
mRecorder.start();
mVoiceRecordingView.setRecorder(mRecorder);
mAudioRecordingIsRunning = true;
// Messages with token mRecorder are cleared when the activity finishes or recording stops.
mHandler.postDelayed(() -> {
Log.i(TAG, "Timed out while recording voice message, cancelling.");
stopAudioRecording();
showSubmitBugReportUi(/* isRecording= */ false);
}, /* token= */ mRecorder, VOICE_MESSAGE_MAX_DURATION_MILLIS);
}
private void stopAudioRecording() {
if (mRecorder != null) {
Log.i(TAG, "Recording ended, stopping the MediaRecorder.");
mHandler.removeCallbacksAndMessages(/* token= */ mRecorder);
try {
mRecorder.stop();
} catch (RuntimeException e) {
// Sometimes MediaRecorder doesn't start and stopping it throws an error.
// We just log these cases, no need to crash the app.
Log.w(TAG, "Couldn't stop media recorder", e);
}
mRecorder.release();
mRecorder = null;
}
if (mLastAudioFocusRequest != null) {
int focusAbandoned = mAudioManager.abandonAudioFocusRequest(mLastAudioFocusRequest);
Log.d(TAG, "Audio focus abandoned "
+ (focusAbandoned == AudioManager.AUDIOFOCUS_REQUEST_GRANTED));
mLastAudioFocusRequest = null;
}
mVoiceRecordingView.setRecorder(null);
}
private static String getCurrentUserName(Context context) {
UserManager um = UserManager.get(context);
return um.getUserName();
}
/**
* Creates a {@link MetaBugReport} and saves it in a local sqlite database.
*
* @param context an Android context.
* @param type bug report type, {@link MetaBugReport.BugReportType}.
*/
static MetaBugReport createBugReport(Context context, int type) {
String timestamp = MetaBugReport.toBugReportTimestamp(new Date());
String username = getCurrentUserName(context);
String title = BugReportTitleGenerator.generateBugReportTitle(timestamp, username);
return BugStorageUtils.createBugReport(context, title, timestamp, username, type);
}
/** A helper class to generate bugreport title. */
private static final class BugReportTitleGenerator {
/** Contains easily readable characters. */
private static final char[] CHARS_FOR_RANDOM_GENERATOR =
new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P',
'R', 'S', 'T', 'U', 'W', 'X', 'Y', 'Z'};
/**
* Generates a bugreport title from given timestamp and username.
*
* <p>Example: "[A45E8] Feedback from user Driver at 2019-09-21_12:00:00"
*/
static String generateBugReportTitle(String timestamp, String username) {
// Lookup string is used to search a bug in Buganizer (see b/130915969).
String lookupString = generateRandomString(LOOKUP_STRING_LENGTH);
return "[" + lookupString + "] Feedback from user " + username + " at " + timestamp;
}
private static String generateRandomString(int length) {
Random random = new Random();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++) {
int randomIndex = random.nextInt(CHARS_FOR_RANDOM_GENERATOR.length);
builder.append(CHARS_FOR_RANDOM_GENERATOR[randomIndex]);
}
return builder.toString();
}
}
/** AsyncTask that recursively deletes files and directories. */
private static class DeleteFilesAndDirectoriesAsyncTask extends AsyncTask<File, Void, Void> {
@Override
protected Void doInBackground(File... files) {
for (File file : files) {
Log.i(TAG, "Deleting " + file.getAbsolutePath());
if (file.isFile()) {
file.delete();
} else {
FileUtils.deleteDirectory(file);
}
}
return null;
}
}
/**
* AsyncTask that moves audio file to the system user's {@link FileUtils#getPendingDir} and
* sets status to either STATUS_UPLOAD_PENDING or STATUS_PENDING_USER_ACTION.
*/
private static class AddAudioToBugReportAsyncTask extends AsyncTask<Void, Void, Void> {
private final Context mContext;
private final Config mConfig;
private final File mAudioFile;
private final MetaBugReport mOriginalBug;
AddAudioToBugReportAsyncTask(
Context context, Config config, MetaBugReport bug, File audioFile) {
mContext = context;
mConfig = config;
mOriginalBug = bug;
mAudioFile = audioFile;
}
@Override
protected Void doInBackground(Void... voids) {
String audioFileName = FileUtils.getAudioFileName(
MetaBugReport.toBugReportTimestamp(new Date()), mOriginalBug);
MetaBugReport bug = BugStorageUtils.update(mContext,
mOriginalBug.toBuilder().setAudioFileName(audioFileName).build());
try (OutputStream out = BugStorageUtils.openAudioMessageFileToWrite(mContext, bug);
InputStream input = new FileInputStream(mAudioFile)) {
ByteStreams.copy(input, out);
} catch (IOException e) {
BugStorageUtils.setBugReportStatus(mContext, bug,
com.android.car.bugreport.Status.STATUS_WRITE_FAILED,
"Failed to write audio to bug report");
Log.e(TAG, "Failed to write audio to bug report", e);
return null;
}
if (mConfig.getAutoUpload()) {
BugStorageUtils.setBugReportStatus(mContext, bug,
com.android.car.bugreport.Status.STATUS_UPLOAD_PENDING, "");
} else {
BugStorageUtils.setBugReportStatus(mContext, bug,
com.android.car.bugreport.Status.STATUS_PENDING_USER_ACTION, "");
BugReportService.showBugReportFinishedNotification(mContext, bug);
}
mAudioFile.delete();
return null;
}
}
}