blob: a6252505fde033a65a6c4d5e6321542795686338 [file] [log] [blame]
/*
* Copyright (C) 2014 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.fmradio;
import android.app.Activity;
import android.app.FragmentManager;
import android.app.Notification;
import android.app.Notification.Builder;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.android.fmradio.FmStation.Station;
import com.android.fmradio.dialogs.FmSaveDialog;
import com.android.fmradio.views.FmVisualizerView;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* This class interact with user, FM recording function.
*/
public class FmRecordActivity extends Activity implements
FmSaveDialog.OnRecordingDialogClickListener {
private static final String TAG = "FmRecordActivity";
private static final String FM_STOP_RECORDING = "fmradio.stop.recording";
private static final String FM_ENTER_RECORD_SCREEN = "fmradio.enter.record.screen";
private static final String TAG_SAVE_RECORDINGD = "SaveRecording";
private static final int MSG_UPDATE_NOTIFICATION = 1000;
private static final int TIME_BASE = 60;
private Context mContext;
private TextView mMintues;
private TextView mSeconds;
private TextView mFrequency;
private View mStationInfoLayout;
private TextView mStationName;
private TextView mRadioText;
private Button mStopRecordButton;
private FmVisualizerView mPlayIndicator;
private FmService mService = null;
private FragmentManager mFragmentManager;
private boolean mIsInBackground = false;
private int mRecordState = FmRecorder.STATE_INVALID;
private int mCurrentStation = FmUtils.DEFAULT_STATION;
private Notification.Builder mNotificationBuilder = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
mContext = getApplicationContext();
mFragmentManager = getFragmentManager();
setContentView(R.layout.fm_record_activity);
mMintues = (TextView) findViewById(R.id.minutes);
mSeconds = (TextView) findViewById(R.id.seconds);
mFrequency = (TextView) findViewById(R.id.frequency);
mStationInfoLayout = findViewById(R.id.station_name_rt);
mStationName = (TextView) findViewById(R.id.station_name);
mRadioText = (TextView) findViewById(R.id.radio_text);
mStopRecordButton = (Button) findViewById(R.id.btn_stop_record);
mStopRecordButton.setEnabled(false);
mStopRecordButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Stop recording and wait service notify stop record state to show dialog
mService.stopRecordingAsync();
}
});
mPlayIndicator = (FmVisualizerView) findViewById(R.id.fm_play_indicator);
if (savedInstanceState != null) {
mCurrentStation = savedInstanceState.getInt(FmStation.CURRENT_STATION);
mRecordState = savedInstanceState.getInt("last_record_state");
} else {
Intent intent = getIntent();
mCurrentStation = intent.getIntExtra(FmStation.CURRENT_STATION,
FmUtils.DEFAULT_STATION);
mRecordState = intent.getIntExtra("last_record_state", FmRecorder.STATE_INVALID);
}
bindService(new Intent(this, FmService.class), mServiceConnection,
Context.BIND_AUTO_CREATE);
updateUi();
}
private void updateUi() {
// TODO it's on UI thread, change to sub thread
ContentResolver resolver = mContext.getContentResolver();
mFrequency.setText("FM " + FmUtils.formatStation(mCurrentStation));
Cursor cursor = null;
try {
cursor = resolver.query(
Station.CONTENT_URI,
FmStation.COLUMNS,
Station.FREQUENCY + "=?",
new String[] { String.valueOf(mCurrentStation) },
null);
if (cursor != null && cursor.moveToFirst()) {
// If the station name does not exist, show program service(PS) instead
String stationName = cursor.getString(cursor.getColumnIndex(Station.STATION_NAME));
if (TextUtils.isEmpty(stationName)) {
stationName = cursor.getString(cursor.getColumnIndex(Station.PROGRAM_SERVICE));
}
String radioText = cursor.getString(cursor.getColumnIndex(Station.RADIO_TEXT));
mStationName.setText(stationName);
mRadioText.setText(radioText);
int id = cursor.getInt(cursor.getColumnIndex(Station._ID));
resolver.registerContentObserver(
ContentUris.withAppendedId(Station.CONTENT_URI, id), false,
mContentObserver);
// If no station name and no radio text, hide the view
if ((!TextUtils.isEmpty(stationName))
|| (!TextUtils.isEmpty(radioText))) {
mStationInfoLayout.setVisibility(View.VISIBLE);
} else {
mStationInfoLayout.setVisibility(View.GONE);
}
Log.d(TAG, "updateUi, frequency = " + mCurrentStation + ", stationName = "
+ stationName + ", radioText = " + radioText);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
private void updateRecordingNotification(long recordTime) {
if (mNotificationBuilder == null) {
Intent intent = new Intent(FM_STOP_RECORDING);
intent.setClass(mContext, FmRecordActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
Bitmap largeIcon = FmUtils.createNotificationLargeIcon(mContext,
FmUtils.formatStation(mCurrentStation));
mNotificationBuilder = new Builder(this)
.setContentText(getText(R.string.record_notification_message))
.setShowWhen(false)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_launcher)
.setLargeIcon(largeIcon)
.addAction(R.drawable.btn_fm_rec_stop_enabled, getText(R.string.stop_record),
pendingIntent);
Intent cIntent = new Intent(FM_ENTER_RECORD_SCREEN);
cIntent.setClass(mContext, FmRecordActivity.class);
cIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent contentPendingIntent = PendingIntent.getActivity(mContext, 0, cIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
mNotificationBuilder.setContentIntent(contentPendingIntent);
}
// Format record time to show on title
Date date = new Date(recordTime);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss", Locale.ENGLISH);
String time = simpleDateFormat.format(date);
mNotificationBuilder.setContentTitle(time);
if (mService != null) {
mService.showRecordingNotification(mNotificationBuilder.build());
}
}
@Override
public void onNewIntent(Intent intent) {
if (intent != null && intent.getAction() != null) {
String action = intent.getAction();
if (FM_STOP_RECORDING.equals(action)) {
// If click stop button in notification, need to stop recording
if (mService != null && !isStopRecording()) {
mService.stopRecordingAsync();
}
} else if (FM_ENTER_RECORD_SCREEN.equals(action)) {
// Just enter record screen, do nothing
}
}
}
@Override
protected void onResume() {
super.onResume();
mIsInBackground = false;
if (null != mService) {
mService.setFmRecordActivityForeground(true);
}
// Show save dialog if record has stopped and never show it before.
if (isStopRecording() && !isSaveDialogShown()) {
showSaveDialog();
}
// Trigger to refreshing timer text if still in record
if (!isStopRecording()) {
mHandler.removeMessages(FmListener.MSGID_REFRESH);
mHandler.sendEmptyMessage(FmListener.MSGID_REFRESH);
}
// Clear notification, it only need show when in background
removeNotification();
}
@Override
protected void onPause() {
super.onPause();
mIsInBackground = true;
if (null != mService) {
mService.setFmRecordActivityForeground(false);
}
// Stop refreshing timer text
mHandler.removeMessages(FmListener.MSGID_REFRESH);
// Show notification when switch to background
showNotification();
}
private void showNotification() {
// If have stopped recording, need not show notification
if (!isStopRecording()) {
mHandler.sendEmptyMessage(MSG_UPDATE_NOTIFICATION);
} else if (isSaveDialogShown()) {
// Only when save dialog is shown and FM radio is back to background,
// it is necessary to update playing notification.
// Otherwise, FmMainActivity will update playing notification.
mService.updatePlayingNotification();
}
}
private void removeNotification() {
mHandler.removeMessages(MSG_UPDATE_NOTIFICATION);
if (mService != null) {
mService.removeNotification();
mService.updatePlayingNotification();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putInt(FmStation.CURRENT_STATION, mCurrentStation);
outState.putInt("last_record_state", mRecordState);
super.onSaveInstanceState(outState);
}
@Override
protected void onDestroy() {
removeNotification();
mHandler.removeCallbacksAndMessages(null);
if (mService != null) {
mService.unregisterFmRadioListener(mFmListener);
}
unbindService(mServiceConnection);
mContext.getContentResolver().unregisterContentObserver(mContentObserver);
super.onDestroy();
}
/**
* Recording dialog click
*
* @param recordingName The new recording name
*/
@Override
public void onRecordingDialogClick(
String recordingName) {
// Happen when activity recreate, such as switch language
if (mIsInBackground) {
return;
}
if (recordingName != null && mService != null) {
mService.saveRecordingAsync(recordingName);
returnResult(recordingName, getString(R.string.toast_record_saved));
} else {
returnResult(null, getString(R.string.toast_record_not_saved));
}
finish();
}
@Override
public void onBackPressed() {
if (mService != null & !isStopRecording()) {
// Stop recording and wait service notify stop record state to show dialog
mService.stopRecordingAsync();
return;
}
super.onBackPressed();
}
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, android.os.IBinder service) {
mService = ((FmService.ServiceBinder) service).getService();
mService.registerFmRadioListener(mFmListener);
mService.setFmRecordActivityForeground(!mIsInBackground);
// 1. If have stopped recording, we need check whether need show save dialog again.
// Because when stop recording in background, we need show it when switch to foreground.
if (isStopRecording()) {
if (!isSaveDialogShown()) {
showSaveDialog();
}
return;
}
// 2. If not start recording, start it directly, this case happen when start this
// activity from main fm activity.
if (!isStartRecording()) {
mService.startRecordingAsync();
}
mPlayIndicator.startAnimation();
mStopRecordButton.setEnabled(true);
mHandler.removeMessages(FmListener.MSGID_REFRESH);
mHandler.sendEmptyMessage(FmListener.MSGID_REFRESH);
};
@Override
public void onServiceDisconnected(android.content.ComponentName name) {
mService = null;
};
};
private String addPaddingForString(long time) {
StringBuilder builder = new StringBuilder();
if (time >= 0 && time < 10) {
builder.append("0");
}
return builder.append(time).toString();
}
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case FmListener.MSGID_REFRESH:
if (mService != null) {
long recordTimeInMillis = mService.getRecordTime();
long recordTimeInSec = recordTimeInMillis / 1000L;
mMintues.setText(addPaddingForString(recordTimeInSec / TIME_BASE));
mSeconds.setText(addPaddingForString(recordTimeInSec % TIME_BASE));
checkStorageSpaceAndStop();
}
mHandler.sendEmptyMessageDelayed(FmListener.MSGID_REFRESH, 1000);
break;
case MSG_UPDATE_NOTIFICATION:
if (mService != null) {
updateRecordingNotification(mService.getRecordTime());
checkStorageSpaceAndStop();
}
mHandler.sendEmptyMessageDelayed(MSG_UPDATE_NOTIFICATION, 1000);
break;
case FmListener.LISTEN_RECORDSTATE_CHANGED:
// State change from STATE_INVALID to STATE_RECORDING mean begin recording
// State change from STATE_RECORDING to STATE_IDLE mean stop recording
int newState = mService.getRecorderState();
Log.d(TAG, "handleMessage, record state changed: newState = " + newState
+ ", mRecordState = " + mRecordState);
if (mRecordState == FmRecorder.STATE_INVALID
&& newState == FmRecorder.STATE_RECORDING) {
mRecordState = FmRecorder.STATE_RECORDING;
} else if (mRecordState == FmRecorder.STATE_RECORDING
&& newState == FmRecorder.STATE_IDLE) {
mRecordState = FmRecorder.STATE_IDLE;
mPlayIndicator.stopAnimation();
showSaveDialog();
}
break;
case FmListener.LISTEN_RECORDERROR:
Bundle bundle = msg.getData();
int errorType = bundle.getInt(FmListener.KEY_RECORDING_ERROR_TYPE);
handleRecordError(errorType);
break;
default:
break;
}
};
};
private void checkStorageSpaceAndStop() {
long recordTimeInMillis = mService.getRecordTime();
long recordTimeInSec = recordTimeInMillis / 1000L;
// Check storage free space
String recordingSdcard = FmUtils.getDefaultStoragePath();
if (!FmUtils.hasEnoughSpace(recordingSdcard)) {
// Need to record more than 1s.
// Avoid calling MediaRecorder.stop() before native record starts.
if (recordTimeInSec >= 1) {
// Insufficient storage
mService.stopRecordingAsync();
Toast.makeText(FmRecordActivity.this,
R.string.toast_sdcard_insufficient_space,
Toast.LENGTH_SHORT).show();
}
}
}
private void handleRecordError(int errorType) {
Log.d(TAG, "handleRecordError, errorType = " + errorType);
String showString = null;
switch (errorType) {
case FmRecorder.ERROR_SDCARD_NOT_PRESENT:
showString = getString(R.string.toast_sdcard_missing);
returnResult(null, showString);
finish();
break;
case FmRecorder.ERROR_SDCARD_INSUFFICIENT_SPACE:
showString = getString(R.string.toast_sdcard_insufficient_space);
returnResult(null, showString);
finish();
break;
case FmRecorder.ERROR_RECORDER_INTERNAL:
showString = getString(R.string.toast_recorder_internal_error);
Toast.makeText(mContext, showString, Toast.LENGTH_SHORT).show();
break;
case FmRecorder.ERROR_SDCARD_WRITE_FAILED:
showString = getString(R.string.toast_recorder_internal_error);
returnResult(null, showString);
finish();
break;
default:
Log.w(TAG, "handleRecordError, invalid record error");
break;
}
}
private void returnResult(String recordName, String resultString) {
Intent intent = new Intent();
intent.putExtra(FmMainActivity.EXTRA_RESULT_STRING, resultString);
if (recordName != null) {
intent.setData(Uri.parse("file://" + FmService.getRecordingSdcard()
+ File.separator + FmRecorder.FM_RECORD_FOLDER + File.separator
+ Uri.encode(recordName) + FmRecorder.RECORDING_FILE_EXTENSION));
}
setResult(RESULT_OK, intent);
}
private final ContentObserver mContentObserver = new ContentObserver(new Handler()) {
public void onChange(boolean selfChange) {
updateUi();
};
};
// Service listener
private final FmListener mFmListener = new FmListener() {
@Override
public void onCallBack(Bundle bundle) {
int flag = bundle.getInt(FmListener.CALLBACK_FLAG);
if (flag == FmListener.MSGID_FM_EXIT) {
mHandler.removeCallbacksAndMessages(null);
}
// remove tag message first, avoid too many same messages in queue.
Message msg = mHandler.obtainMessage(flag);
msg.setData(bundle);
mHandler.removeMessages(flag);
mHandler.sendMessage(msg);
}
};
/**
* Show save record dialog
*/
public void showSaveDialog() {
removeNotification();
if (mIsInBackground) {
Log.d(TAG, "showSaveDialog, activity is in background, show it later");
return;
}
String sdcard = FmService.getRecordingSdcard();
String recordingName = mService.getRecordingName();
String saveName = null;
if (TextUtils.isEmpty(mStationName.getText())) {
saveName = FmRecorder.RECORDING_FILE_PREFIX + "_" + recordingName;
} else {
saveName = FmRecorder.RECORDING_FILE_PREFIX + "_" + mStationName.getText() + "_"
+ recordingName;
}
FmSaveDialog newFragment = new FmSaveDialog(sdcard, recordingName, saveName);
newFragment.show(mFragmentManager, TAG_SAVE_RECORDINGD);
mFragmentManager.executePendingTransactions();
mHandler.removeMessages(FmListener.MSGID_REFRESH);
}
private boolean isStartRecording() {
return mRecordState == FmRecorder.STATE_RECORDING;
}
private boolean isStopRecording() {
return mRecordState == FmRecorder.STATE_IDLE;
}
private boolean isSaveDialogShown() {
FmSaveDialog saveDialog = (FmSaveDialog)
mFragmentManager.findFragmentByTag(TAG_SAVE_RECORDINGD);
return saveDialog != null;
}
}