blob: a6252505fde033a65a6c4d5e6321542795686338 [file] [log] [blame]
Benson Huanga8b6afc2014-11-20 15:42:26 +08001/*
2 * Copyright (C) 2014 The Android Open Source Project
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
17package com.android.fmradio;
18
19import android.app.Activity;
20import android.app.FragmentManager;
21import android.app.Notification;
22import android.app.Notification.Builder;
23import android.app.PendingIntent;
24import android.content.ComponentName;
25import android.content.ContentResolver;
26import android.content.ContentUris;
27import android.content.Context;
28import android.content.Intent;
29import android.content.ServiceConnection;
30import android.database.ContentObserver;
31import android.database.Cursor;
32import android.graphics.Bitmap;
33import android.net.Uri;
34import android.os.Bundle;
35import android.os.Handler;
36import android.os.Message;
37import android.text.TextUtils;
38import android.util.Log;
39import android.view.View;
40import android.widget.Button;
41import android.widget.TextView;
42import android.widget.Toast;
43
44import com.android.fmradio.FmStation.Station;
45import com.android.fmradio.dialogs.FmSaveDialog;
46import com.android.fmradio.views.FmVisualizerView;
47
48import java.io.File;
49import java.text.SimpleDateFormat;
50import java.util.Date;
51import java.util.Locale;
52
53/**
54 * This class interact with user, FM recording function.
55 */
56public class FmRecordActivity extends Activity implements
57 FmSaveDialog.OnRecordingDialogClickListener {
Benson Huang83032432014-12-25 16:35:27 +080058 private static final String TAG = "FmRecordActivity";
Benson Huanga8b6afc2014-11-20 15:42:26 +080059
Benson Huang83032432014-12-25 16:35:27 +080060 private static final String FM_STOP_RECORDING = "fmradio.stop.recording";
61 private static final String FM_ENTER_RECORD_SCREEN = "fmradio.enter.record.screen";
Benson Huanga8b6afc2014-11-20 15:42:26 +080062 private static final String TAG_SAVE_RECORDINGD = "SaveRecording";
63 private static final int MSG_UPDATE_NOTIFICATION = 1000;
Benson Huangec7335d2014-12-24 14:58:24 +080064 private static final int TIME_BASE = 60;
Benson Huanga8b6afc2014-11-20 15:42:26 +080065 private Context mContext;
66 private TextView mMintues;
67 private TextView mSeconds;
68 private TextView mFrequency;
69 private View mStationInfoLayout;
70 private TextView mStationName;
71 private TextView mRadioText;
72 private Button mStopRecordButton;
73 private FmVisualizerView mPlayIndicator;
74 private FmService mService = null;
75 private FragmentManager mFragmentManager;
76 private boolean mIsInBackground = false;
77 private int mRecordState = FmRecorder.STATE_INVALID;
78 private int mCurrentStation = FmUtils.DEFAULT_STATION;
79 private Notification.Builder mNotificationBuilder = null;
80
81 @Override
82 protected void onCreate(Bundle savedInstanceState) {
83 super.onCreate(savedInstanceState);
84 Log.d(TAG, "onCreate");
85 mContext = getApplicationContext();
86 mFragmentManager = getFragmentManager();
87 setContentView(R.layout.fm_record_activity);
88
89 mMintues = (TextView) findViewById(R.id.minutes);
90 mSeconds = (TextView) findViewById(R.id.seconds);
91
92 mFrequency = (TextView) findViewById(R.id.frequency);
93 mStationInfoLayout = findViewById(R.id.station_name_rt);
94 mStationName = (TextView) findViewById(R.id.station_name);
95 mRadioText = (TextView) findViewById(R.id.radio_text);
96
97 mStopRecordButton = (Button) findViewById(R.id.btn_stop_record);
98 mStopRecordButton.setEnabled(false);
99 mStopRecordButton.setOnClickListener(new View.OnClickListener() {
100 @Override
101 public void onClick(View v) {
102 // Stop recording and wait service notify stop record state to show dialog
103 mService.stopRecordingAsync();
104 }
105 });
106
107 mPlayIndicator = (FmVisualizerView) findViewById(R.id.fm_play_indicator);
108
109 if (savedInstanceState != null) {
110 mCurrentStation = savedInstanceState.getInt(FmStation.CURRENT_STATION);
111 mRecordState = savedInstanceState.getInt("last_record_state");
112 } else {
113 Intent intent = getIntent();
114 mCurrentStation = intent.getIntExtra(FmStation.CURRENT_STATION,
115 FmUtils.DEFAULT_STATION);
116 mRecordState = intent.getIntExtra("last_record_state", FmRecorder.STATE_INVALID);
117 }
118 bindService(new Intent(this, FmService.class), mServiceConnection,
119 Context.BIND_AUTO_CREATE);
120 updateUi();
121 }
122
123 private void updateUi() {
124 // TODO it's on UI thread, change to sub thread
125 ContentResolver resolver = mContext.getContentResolver();
126 mFrequency.setText("FM " + FmUtils.formatStation(mCurrentStation));
127 Cursor cursor = null;
128 try {
129 cursor = resolver.query(
130 Station.CONTENT_URI,
131 FmStation.COLUMNS,
132 Station.FREQUENCY + "=?",
133 new String[] { String.valueOf(mCurrentStation) },
134 null);
135 if (cursor != null && cursor.moveToFirst()) {
136 // If the station name does not exist, show program service(PS) instead
137 String stationName = cursor.getString(cursor.getColumnIndex(Station.STATION_NAME));
138 if (TextUtils.isEmpty(stationName)) {
139 stationName = cursor.getString(cursor.getColumnIndex(Station.PROGRAM_SERVICE));
140 }
141 String radioText = cursor.getString(cursor.getColumnIndex(Station.RADIO_TEXT));
142 mStationName.setText(stationName);
143 mRadioText.setText(radioText);
144 int id = cursor.getInt(cursor.getColumnIndex(Station._ID));
145 resolver.registerContentObserver(
146 ContentUris.withAppendedId(Station.CONTENT_URI, id), false,
147 mContentObserver);
148 // If no station name and no radio text, hide the view
149 if ((!TextUtils.isEmpty(stationName))
150 || (!TextUtils.isEmpty(radioText))) {
151 mStationInfoLayout.setVisibility(View.VISIBLE);
152 } else {
153 mStationInfoLayout.setVisibility(View.GONE);
154 }
155 Log.d(TAG, "updateUi, frequency = " + mCurrentStation + ", stationName = "
156 + stationName + ", radioText = " + radioText);
157 }
158 } finally {
159 if (cursor != null) {
160 cursor.close();
161 }
162 }
163 }
164
Benson Huangff42d622014-12-15 17:31:17 +0800165 private void updateRecordingNotification(long recordTime) {
Benson Huanga8b6afc2014-11-20 15:42:26 +0800166 if (mNotificationBuilder == null) {
Benson Huang83032432014-12-25 16:35:27 +0800167 Intent intent = new Intent(FM_STOP_RECORDING);
Benson Huanga8b6afc2014-11-20 15:42:26 +0800168 intent.setClass(mContext, FmRecordActivity.class);
Benson Huanga8b6afc2014-11-20 15:42:26 +0800169 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
170 PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
171 PendingIntent.FLAG_UPDATE_CURRENT);
172
173 Bitmap largeIcon = FmUtils.createNotificationLargeIcon(mContext,
174 FmUtils.formatStation(mCurrentStation));
175 mNotificationBuilder = new Builder(this)
176 .setContentText(getText(R.string.record_notification_message))
177 .setShowWhen(false)
178 .setAutoCancel(true)
179 .setSmallIcon(R.drawable.ic_launcher)
180 .setLargeIcon(largeIcon)
181 .addAction(R.drawable.btn_fm_rec_stop_enabled, getText(R.string.stop_record),
182 pendingIntent);
Benson Huangff42d622014-12-15 17:31:17 +0800183
Benson Huang83032432014-12-25 16:35:27 +0800184 Intent cIntent = new Intent(FM_ENTER_RECORD_SCREEN);
Benson Huangff42d622014-12-15 17:31:17 +0800185 cIntent.setClass(mContext, FmRecordActivity.class);
186 cIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
187 PendingIntent contentPendingIntent = PendingIntent.getActivity(mContext, 0, cIntent,
188 PendingIntent.FLAG_UPDATE_CURRENT);
189 mNotificationBuilder.setContentIntent(contentPendingIntent);
Benson Huanga8b6afc2014-11-20 15:42:26 +0800190 }
191 // Format record time to show on title
192 Date date = new Date(recordTime);
193 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss", Locale.ENGLISH);
194 String time = simpleDateFormat.format(date);
195
196 mNotificationBuilder.setContentTitle(time);
197 if (mService != null) {
198 mService.showRecordingNotification(mNotificationBuilder.build());
199 }
200 }
201
202 @Override
203 public void onNewIntent(Intent intent) {
Benson Huang83032432014-12-25 16:35:27 +0800204 if (intent != null && intent.getAction() != null) {
205 String action = intent.getAction();
206 if (FM_STOP_RECORDING.equals(action)) {
207 // If click stop button in notification, need to stop recording
208 if (mService != null && !isStopRecording()) {
209 mService.stopRecordingAsync();
210 }
211 } else if (FM_ENTER_RECORD_SCREEN.equals(action)) {
212 // Just enter record screen, do nothing
213 }
Benson Huanga8b6afc2014-11-20 15:42:26 +0800214 }
215 }
216
217 @Override
218 protected void onResume() {
219 super.onResume();
220 mIsInBackground = false;
Benson Huang3dfd9a92014-12-22 11:09:27 +0800221 if (null != mService) {
222 mService.setFmRecordActivityForeground(true);
223 }
Benson Huanga8b6afc2014-11-20 15:42:26 +0800224 // Show save dialog if record has stopped and never show it before.
225 if (isStopRecording() && !isSaveDialogShown()) {
226 showSaveDialog();
227 }
228 // Trigger to refreshing timer text if still in record
229 if (!isStopRecording()) {
Benson Huangec7335d2014-12-24 14:58:24 +0800230 mHandler.removeMessages(FmListener.MSGID_REFRESH);
Benson Huanga8b6afc2014-11-20 15:42:26 +0800231 mHandler.sendEmptyMessage(FmListener.MSGID_REFRESH);
232 }
233 // Clear notification, it only need show when in background
234 removeNotification();
235 }
236
237 @Override
238 protected void onPause() {
239 super.onPause();
240 mIsInBackground = true;
Benson Huang3dfd9a92014-12-22 11:09:27 +0800241 if (null != mService) {
242 mService.setFmRecordActivityForeground(false);
243 }
Benson Huanga8b6afc2014-11-20 15:42:26 +0800244 // Stop refreshing timer text
245 mHandler.removeMessages(FmListener.MSGID_REFRESH);
246 // Show notification when switch to background
247 showNotification();
248 }
249
250 private void showNotification() {
251 // If have stopped recording, need not show notification
252 if (!isStopRecording()) {
253 mHandler.sendEmptyMessage(MSG_UPDATE_NOTIFICATION);
Benson Huang59668c12015-01-14 10:35:41 +0800254 } else if (isSaveDialogShown()) {
255 // Only when save dialog is shown and FM radio is back to background,
256 // it is necessary to update playing notification.
257 // Otherwise, FmMainActivity will update playing notification.
258 mService.updatePlayingNotification();
Benson Huanga8b6afc2014-11-20 15:42:26 +0800259 }
260 }
261
262 private void removeNotification() {
263 mHandler.removeMessages(MSG_UPDATE_NOTIFICATION);
264 if (mService != null) {
265 mService.removeNotification();
Benson Huang3cf7f5a2015-01-14 10:55:45 +0800266 mService.updatePlayingNotification();
Benson Huanga8b6afc2014-11-20 15:42:26 +0800267 }
268 }
269
270 @Override
271 protected void onSaveInstanceState(Bundle outState) {
272 outState.putInt(FmStation.CURRENT_STATION, mCurrentStation);
273 outState.putInt("last_record_state", mRecordState);
274 super.onSaveInstanceState(outState);
275 }
276
277 @Override
278 protected void onDestroy() {
279 removeNotification();
280 mHandler.removeCallbacksAndMessages(null);
281 if (mService != null) {
282 mService.unregisterFmRadioListener(mFmListener);
283 }
284 unbindService(mServiceConnection);
285 mContext.getContentResolver().unregisterContentObserver(mContentObserver);
286 super.onDestroy();
287 }
288
289 /**
290 * Recording dialog click
291 *
292 * @param recordingName The new recording name
293 */
294 @Override
295 public void onRecordingDialogClick(
296 String recordingName) {
297 // Happen when activity recreate, such as switch language
298 if (mIsInBackground) {
299 return;
300 }
301
302 if (recordingName != null && mService != null) {
303 mService.saveRecordingAsync(recordingName);
304 returnResult(recordingName, getString(R.string.toast_record_saved));
305 } else {
306 returnResult(null, getString(R.string.toast_record_not_saved));
307 }
308 finish();
309 }
310
311 @Override
312 public void onBackPressed() {
313 if (mService != null & !isStopRecording()) {
314 // Stop recording and wait service notify stop record state to show dialog
315 mService.stopRecordingAsync();
316 return;
317 }
318 super.onBackPressed();
319 }
320
321 private final ServiceConnection mServiceConnection = new ServiceConnection() {
322 @Override
323 public void onServiceConnected(ComponentName name, android.os.IBinder service) {
324 mService = ((FmService.ServiceBinder) service).getService();
325 mService.registerFmRadioListener(mFmListener);
Benson Huang3dfd9a92014-12-22 11:09:27 +0800326 mService.setFmRecordActivityForeground(!mIsInBackground);
Benson Huanga8b6afc2014-11-20 15:42:26 +0800327 // 1. If have stopped recording, we need check whether need show save dialog again.
328 // Because when stop recording in background, we need show it when switch to foreground.
329 if (isStopRecording()) {
330 if (!isSaveDialogShown()) {
331 showSaveDialog();
332 }
333 return;
334 }
335 // 2. If not start recording, start it directly, this case happen when start this
336 // activity from main fm activity.
337 if (!isStartRecording()) {
338 mService.startRecordingAsync();
339 }
340 mPlayIndicator.startAnimation();
341 mStopRecordButton.setEnabled(true);
Benson Huangec7335d2014-12-24 14:58:24 +0800342 mHandler.removeMessages(FmListener.MSGID_REFRESH);
Benson Huanga8b6afc2014-11-20 15:42:26 +0800343 mHandler.sendEmptyMessage(FmListener.MSGID_REFRESH);
344 };
345
346 @Override
347 public void onServiceDisconnected(android.content.ComponentName name) {
348 mService = null;
349 };
350 };
351
Benson Huangec7335d2014-12-24 14:58:24 +0800352 private String addPaddingForString(long time) {
353 StringBuilder builder = new StringBuilder();
354 if (time >= 0 && time < 10) {
355 builder.append("0");
356 }
357 return builder.append(time).toString();
358 }
359
Benson Huanga8b6afc2014-11-20 15:42:26 +0800360 private final Handler mHandler = new Handler() {
361 @Override
362 public void handleMessage(Message msg) {
363 switch (msg.what) {
364 case FmListener.MSGID_REFRESH:
365 if (mService != null) {
Benson Huang3cf7f5a2015-01-14 10:55:45 +0800366 long recordTimeInMillis = mService.getRecordTime();
367 long recordTimeInSec = recordTimeInMillis / 1000L;
368 mMintues.setText(addPaddingForString(recordTimeInSec / TIME_BASE));
369 mSeconds.setText(addPaddingForString(recordTimeInSec % TIME_BASE));
370 checkStorageSpaceAndStop();
Benson Huanga8b6afc2014-11-20 15:42:26 +0800371 }
372 mHandler.sendEmptyMessageDelayed(FmListener.MSGID_REFRESH, 1000);
373 break;
374
375 case MSG_UPDATE_NOTIFICATION:
376 if (mService != null) {
Benson Huangff42d622014-12-15 17:31:17 +0800377 updateRecordingNotification(mService.getRecordTime());
Benson Huang3cf7f5a2015-01-14 10:55:45 +0800378 checkStorageSpaceAndStop();
Benson Huanga8b6afc2014-11-20 15:42:26 +0800379 }
380 mHandler.sendEmptyMessageDelayed(MSG_UPDATE_NOTIFICATION, 1000);
381 break;
382
383 case FmListener.LISTEN_RECORDSTATE_CHANGED:
384 // State change from STATE_INVALID to STATE_RECORDING mean begin recording
385 // State change from STATE_RECORDING to STATE_IDLE mean stop recording
386 int newState = mService.getRecorderState();
387 Log.d(TAG, "handleMessage, record state changed: newState = " + newState
388 + ", mRecordState = " + mRecordState);
389 if (mRecordState == FmRecorder.STATE_INVALID
390 && newState == FmRecorder.STATE_RECORDING) {
391 mRecordState = FmRecorder.STATE_RECORDING;
392 } else if (mRecordState == FmRecorder.STATE_RECORDING
393 && newState == FmRecorder.STATE_IDLE) {
394 mRecordState = FmRecorder.STATE_IDLE;
395 mPlayIndicator.stopAnimation();
396 showSaveDialog();
397 }
398 break;
399
400 case FmListener.LISTEN_RECORDERROR:
401 Bundle bundle = msg.getData();
402 int errorType = bundle.getInt(FmListener.KEY_RECORDING_ERROR_TYPE);
403 handleRecordError(errorType);
404 break;
405
406 default:
407 break;
408 }
409 };
410 };
411
Benson Huang3cf7f5a2015-01-14 10:55:45 +0800412 private void checkStorageSpaceAndStop() {
413 long recordTimeInMillis = mService.getRecordTime();
414 long recordTimeInSec = recordTimeInMillis / 1000L;
415 // Check storage free space
416 String recordingSdcard = FmUtils.getDefaultStoragePath();
417 if (!FmUtils.hasEnoughSpace(recordingSdcard)) {
418 // Need to record more than 1s.
419 // Avoid calling MediaRecorder.stop() before native record starts.
420 if (recordTimeInSec >= 1) {
421 // Insufficient storage
422 mService.stopRecordingAsync();
423 Toast.makeText(FmRecordActivity.this,
424 R.string.toast_sdcard_insufficient_space,
425 Toast.LENGTH_SHORT).show();
426 }
427 }
428 }
429
Benson Huanga8b6afc2014-11-20 15:42:26 +0800430 private void handleRecordError(int errorType) {
431 Log.d(TAG, "handleRecordError, errorType = " + errorType);
432 String showString = null;
433 switch (errorType) {
434 case FmRecorder.ERROR_SDCARD_NOT_PRESENT:
435 showString = getString(R.string.toast_sdcard_missing);
Benson Huangbdcdbb32014-12-03 19:34:40 +0800436 returnResult(null, showString);
437 finish();
438 break;
439
Benson Huanga8b6afc2014-11-20 15:42:26 +0800440 case FmRecorder.ERROR_SDCARD_INSUFFICIENT_SPACE:
441 showString = getString(R.string.toast_sdcard_insufficient_space);
442 returnResult(null, showString);
443 finish();
444 break;
445
446 case FmRecorder.ERROR_RECORDER_INTERNAL:
447 showString = getString(R.string.toast_recorder_internal_error);
Benson Huanga8b6afc2014-11-20 15:42:26 +0800448 Toast.makeText(mContext, showString, Toast.LENGTH_SHORT).show();
449 break;
450
Benson Huang57c15182014-12-22 10:56:31 +0800451 case FmRecorder.ERROR_SDCARD_WRITE_FAILED:
452 showString = getString(R.string.toast_recorder_internal_error);
453 returnResult(null, showString);
454 finish();
455 break;
456
Benson Huanga8b6afc2014-11-20 15:42:26 +0800457 default:
458 Log.w(TAG, "handleRecordError, invalid record error");
459 break;
460 }
461 }
462
463 private void returnResult(String recordName, String resultString) {
464 Intent intent = new Intent();
465 intent.putExtra(FmMainActivity.EXTRA_RESULT_STRING, resultString);
466 if (recordName != null) {
467 intent.setData(Uri.parse("file://" + FmService.getRecordingSdcard()
468 + File.separator + FmRecorder.FM_RECORD_FOLDER + File.separator
Benson Huang19175352014-12-26 16:11:23 +0800469 + Uri.encode(recordName) + FmRecorder.RECORDING_FILE_EXTENSION));
Benson Huanga8b6afc2014-11-20 15:42:26 +0800470 }
471 setResult(RESULT_OK, intent);
472 }
473
474 private final ContentObserver mContentObserver = new ContentObserver(new Handler()) {
475 public void onChange(boolean selfChange) {
476 updateUi();
477 };
478 };
479
480 // Service listener
481 private final FmListener mFmListener = new FmListener() {
482 @Override
483 public void onCallBack(Bundle bundle) {
484 int flag = bundle.getInt(FmListener.CALLBACK_FLAG);
485 if (flag == FmListener.MSGID_FM_EXIT) {
486 mHandler.removeCallbacksAndMessages(null);
487 }
488
489 // remove tag message first, avoid too many same messages in queue.
490 Message msg = mHandler.obtainMessage(flag);
491 msg.setData(bundle);
492 mHandler.removeMessages(flag);
493 mHandler.sendMessage(msg);
494 }
495 };
496
497 /**
498 * Show save record dialog
499 */
500 public void showSaveDialog() {
501 removeNotification();
502 if (mIsInBackground) {
503 Log.d(TAG, "showSaveDialog, activity is in background, show it later");
504 return;
505 }
506 String sdcard = FmService.getRecordingSdcard();
507 String recordingName = mService.getRecordingName();
Benson Huang31fe68d2015-01-19 11:46:27 +0800508 String saveName = null;
Benson Huangdc76ad02014-12-17 18:33:04 +0800509 if (TextUtils.isEmpty(mStationName.getText())) {
Benson Huang31fe68d2015-01-19 11:46:27 +0800510 saveName = FmRecorder.RECORDING_FILE_PREFIX + "_" + recordingName;
Benson Huangdc76ad02014-12-17 18:33:04 +0800511 } else {
Benson Huang31fe68d2015-01-19 11:46:27 +0800512 saveName = FmRecorder.RECORDING_FILE_PREFIX + "_" + mStationName.getText() + "_"
Benson Huangdc76ad02014-12-17 18:33:04 +0800513 + recordingName;
514 }
Benson Huang31fe68d2015-01-19 11:46:27 +0800515 FmSaveDialog newFragment = new FmSaveDialog(sdcard, recordingName, saveName);
Benson Huanga8b6afc2014-11-20 15:42:26 +0800516 newFragment.show(mFragmentManager, TAG_SAVE_RECORDINGD);
517 mFragmentManager.executePendingTransactions();
518 mHandler.removeMessages(FmListener.MSGID_REFRESH);
519 }
520
521 private boolean isStartRecording() {
522 return mRecordState == FmRecorder.STATE_RECORDING;
523 }
524
525 private boolean isStopRecording() {
526 return mRecordState == FmRecorder.STATE_IDLE;
527 }
528
529 private boolean isSaveDialogShown() {
530 FmSaveDialog saveDialog = (FmSaveDialog)
531 mFragmentManager.findFragmentByTag(TAG_SAVE_RECORDINGD);
532 return saveDialog != null;
533 }
534}