• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.fmradio;
18 
19 import android.app.Activity;
20 import android.app.FragmentManager;
21 import android.app.Notification;
22 import android.app.Notification.Builder;
23 import android.app.PendingIntent;
24 import android.content.ComponentName;
25 import android.content.ContentResolver;
26 import android.content.ContentUris;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.ServiceConnection;
30 import android.database.ContentObserver;
31 import android.database.Cursor;
32 import android.graphics.Bitmap;
33 import android.net.Uri;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.Message;
37 import android.text.TextUtils;
38 import android.util.Log;
39 import android.view.View;
40 import android.widget.Button;
41 import android.widget.TextView;
42 import android.widget.Toast;
43 
44 import com.android.fmradio.FmStation.Station;
45 import com.android.fmradio.dialogs.FmSaveDialog;
46 import com.android.fmradio.views.FmVisualizerView;
47 
48 import java.io.File;
49 import java.text.SimpleDateFormat;
50 import java.util.Date;
51 import java.util.Locale;
52 
53 /**
54  * This class interact with user, FM recording function.
55  */
56 public class FmRecordActivity extends Activity implements
57         FmSaveDialog.OnRecordingDialogClickListener {
58     private static final String TAG = "FmRecordActivity";
59 
60     private static final String FM_STOP_RECORDING = "fmradio.stop.recording";
61     private static final String FM_ENTER_RECORD_SCREEN = "fmradio.enter.record.screen";
62     private static final String TAG_SAVE_RECORDINGD = "SaveRecording";
63     private static final int MSG_UPDATE_NOTIFICATION = 1000;
64     private static final int TIME_BASE = 60;
65     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
onCreate(Bundle savedInstanceState)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 
updateUi()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 
updateRecordingNotification(long recordTime)165     private void updateRecordingNotification(long recordTime) {
166         if (mNotificationBuilder == null) {
167             Intent intent = new Intent(FM_STOP_RECORDING);
168             intent.setClass(mContext, FmRecordActivity.class);
169             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);
183 
184             Intent cIntent = new Intent(FM_ENTER_RECORD_SCREEN);
185             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);
190         }
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
onNewIntent(Intent intent)203     public void onNewIntent(Intent intent) {
204         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             }
214         }
215     }
216 
217     @Override
onResume()218     protected void onResume() {
219         super.onResume();
220         mIsInBackground = false;
221         if (null != mService) {
222             mService.setFmRecordActivityForeground(true);
223         }
224         // 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()) {
230             mHandler.removeMessages(FmListener.MSGID_REFRESH);
231             mHandler.sendEmptyMessage(FmListener.MSGID_REFRESH);
232         }
233         // Clear notification, it only need show when in background
234         removeNotification();
235     }
236 
237     @Override
onPause()238     protected void onPause() {
239         super.onPause();
240         mIsInBackground = true;
241         if (null != mService) {
242             mService.setFmRecordActivityForeground(false);
243         }
244         // Stop refreshing timer text
245         mHandler.removeMessages(FmListener.MSGID_REFRESH);
246         // Show notification when switch to background
247         showNotification();
248     }
249 
showNotification()250     private void showNotification() {
251         // If have stopped recording, need not show notification
252         if (!isStopRecording()) {
253             mHandler.sendEmptyMessage(MSG_UPDATE_NOTIFICATION);
254         } 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();
259         }
260     }
261 
removeNotification()262     private void removeNotification() {
263         mHandler.removeMessages(MSG_UPDATE_NOTIFICATION);
264         if (mService != null) {
265             mService.removeNotification();
266             mService.updatePlayingNotification();
267         }
268     }
269 
270     @Override
onSaveInstanceState(Bundle outState)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
onDestroy()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
onRecordingDialogClick( String recordingName)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
onBackPressed()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);
326             mService.setFmRecordActivityForeground(!mIsInBackground);
327             // 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);
342             mHandler.removeMessages(FmListener.MSGID_REFRESH);
343             mHandler.sendEmptyMessage(FmListener.MSGID_REFRESH);
344         };
345 
346         @Override
347         public void onServiceDisconnected(android.content.ComponentName name) {
348             mService = null;
349         };
350     };
351 
addPaddingForString(long time)352     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 
360     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) {
366                         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();
371                     }
372                     mHandler.sendEmptyMessageDelayed(FmListener.MSGID_REFRESH, 1000);
373                     break;
374 
375                 case MSG_UPDATE_NOTIFICATION:
376                     if (mService != null) {
377                         updateRecordingNotification(mService.getRecordTime());
378                         checkStorageSpaceAndStop();
379                     }
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 
checkStorageSpaceAndStop()412     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 
handleRecordError(int errorType)430     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);
436                 returnResult(null, showString);
437                 finish();
438                 break;
439 
440             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);
448                 Toast.makeText(mContext, showString, Toast.LENGTH_SHORT).show();
449                 break;
450 
451             case FmRecorder.ERROR_SDCARD_WRITE_FAILED:
452                 showString = getString(R.string.toast_recorder_internal_error);
453                 returnResult(null, showString);
454                 finish();
455                 break;
456 
457             default:
458                 Log.w(TAG, "handleRecordError, invalid record error");
459                 break;
460         }
461     }
462 
returnResult(String recordName, String resultString)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
469                     + Uri.encode(recordName) + FmRecorder.RECORDING_FILE_EXTENSION));
470         }
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      */
showSaveDialog()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();
508         String saveName = null;
509         if (TextUtils.isEmpty(mStationName.getText())) {
510             saveName = FmRecorder.RECORDING_FILE_PREFIX +  "_" + recordingName;
511         } else {
512             saveName = FmRecorder.RECORDING_FILE_PREFIX + "_" + mStationName.getText() + "_"
513                     + recordingName;
514         }
515         FmSaveDialog newFragment = new FmSaveDialog(sdcard, recordingName, saveName);
516         newFragment.show(mFragmentManager, TAG_SAVE_RECORDINGD);
517         mFragmentManager.executePendingTransactions();
518         mHandler.removeMessages(FmListener.MSGID_REFRESH);
519     }
520 
isStartRecording()521     private boolean isStartRecording() {
522         return mRecordState == FmRecorder.STATE_RECORDING;
523     }
524 
isStopRecording()525     private boolean isStopRecording() {
526         return mRecordState == FmRecorder.STATE_IDLE;
527     }
528 
isSaveDialogShown()529     private boolean isSaveDialogShown() {
530         FmSaveDialog saveDialog = (FmSaveDialog)
531                 mFragmentManager.findFragmentByTag(TAG_SAVE_RECORDINGD);
532         return saveDialog != null;
533     }
534 }
535