Home | History | Annotate | Download | only in fmradio
      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
     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 
    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
    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
    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
    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 
    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 
    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
    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);
    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 
    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 
    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 
    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 
    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      */
    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 
    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 }
    535