| /* |
| * 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; |
| } |
| } |