summaryrefslogtreecommitdiffstats
path: root/FMRecord
diff options
context:
space:
mode:
authorVenkateshwarlu Domakonda <Venkateshwarlu@codeaurora.org>2013-07-27 12:25:16 +0530
committerVenkateshwarlu Domakonda <Venkateshwarlu@codeaurora.org>2013-07-29 15:54:38 +0530
commitfa22ae13579b10609608ff000411500e3f51e1a7 (patch)
treea8bdd2665b2025614b15ffade192036d9d4bd80f /FMRecord
parent87af28254f474c33171085272d3615c7dd2b4e3f (diff)
downloadandroid_hardware_qcom_fm-fa22ae13579b10609608ff000411500e3f51e1a7.tar.gz
android_hardware_qcom_fm-fa22ae13579b10609608ff000411500e3f51e1a7.tar.bz2
android_hardware_qcom_fm-fa22ae13579b10609608ff000411500e3f51e1a7.zip
FM: New mechanism to handle FM recording
- FM Radio is running with system process. - System process does not have permission to access SDCard. - Handle the FM recording in another application. Change-Id: Ie321143b4a2601f2c669292154510eef8cbf7940 CRs-Fixed: 515621
Diffstat (limited to 'FMRecord')
-rw-r--r--FMRecord/Android.mk14
-rw-r--r--FMRecord/AndroidManifest.xml47
-rw-r--r--FMRecord/res/values/strings.xml49
-rw-r--r--FMRecord/src/com/codeaurora/fmrecording/FMRecordingReceiver.java68
-rw-r--r--FMRecord/src/com/codeaurora/fmrecording/FMRecordingService.java476
5 files changed, 654 insertions, 0 deletions
diff --git a/FMRecord/Android.mk b/FMRecord/Android.mk
new file mode 100644
index 0000000..00837f0
--- /dev/null
+++ b/FMRecord/Android.mk
@@ -0,0 +1,14 @@
+ifeq ($(call is-vendor-board-platform,QCOM),true)
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src/com/codeaurora/fmrecording/)
+
+LOCAL_PACKAGE_NAME := FMRecord
+LOCAL_CERTIFICATE := platform
+LOCAL_PROGUARD_ENABLED := disabled
+include $(BUILD_PACKAGE)
+
+endif
diff --git a/FMRecord/AndroidManifest.xml b/FMRecord/AndroidManifest.xml
new file mode 100644
index 0000000..beb0cf7
--- /dev/null
+++ b/FMRecord/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.codeaurora.fmrecording" >
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+
+<application>
+
+<receiver android:name="FMRecordingReceiver">
+ <intent-filter>
+ <action android:name="codeaurora.intent.action.FM" />
+ </intent-filter>
+</receiver>
+
+<service android:name="com.codeaurora.fmrecording.FMRecordingService"></service>
+</application>
+</manifest>
diff --git a/FMRecord/res/values/strings.xml b/FMRecord/res/values/strings.xml
new file mode 100644
index 0000000..59f14ad
--- /dev/null
+++ b/FMRecord/res/values/strings.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2009-2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- alert to the user that USB storage must be available before using FM recording [CHAR LIMIT=NONE] -->
+ <string name="no_storage">Mount SD card before start recording.</string>
+ <!-- alert to the user that the USB storage is being disk-checked [CHAR LIMIT=30] -->
+ <string name="preparing_sd">Preparing SD card</string>
+ <!-- alert to the user that the FM fails to read or write the USB storage. [CHAR LIMIT=NONE] -->
+ <string name="access_sd_fail">Couldn\'t access SD card.</string>
+ <!-- Low-memory dialog message [CHAR LIMT=NONE] -->
+ <string name="spaceIsLow_content">Your SD card storage is running out of space. Change the quality setting or delete some images or other files.</string>
+ <!-- The messsage shown when FM record reaches size limit. -->
+ <string name="FMRecording_reach_size_limit">Size limit reached.</string>
+ <!-- the name under which recordings will be visible in the media database is formatted like this -->
+ <string name="audio_db_title_format"><xliff:g id="format">yyyy-MM-dd HH:mm:ss</xliff:g></string>
+ <!-- all recordings will show up in the media database with this 'artist' name -->
+ <string name="audio_db_artist_name">My FM recordings</string>
+ <!-- all recordings will show up in the media database with this 'album' name -->
+ <string name="audio_db_album_name">FM recordings</string>
+ <!-- all recordings will show up in the media database in a playlist with this name -->
+ <string name="audio_db_playlist_name">FM recordings</string>
+ <string name="save_record_file">FM Recorded file saved to "<xliff:g id="record_file">%1$s</xliff:g>"</string>
+</resources>
diff --git a/FMRecord/src/com/codeaurora/fmrecording/FMRecordingReceiver.java b/FMRecord/src/com/codeaurora/fmrecording/FMRecordingReceiver.java
new file mode 100644
index 0000000..9389f10
--- /dev/null
+++ b/FMRecord/src/com/codeaurora/fmrecording/FMRecordingReceiver.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.codeaurora.fmrecording;
+
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.content.pm.PackageManager;
+import android.content.Context;
+import android.content.ComponentName;
+import android.util.Log;
+import java.lang.String;
+
+
+public class FMRecordingReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "FMRecordingReceiver";
+ public static final String ACTION_FM =
+ "codeaurora.intent.action.FM";
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.d(TAG, "Received intent: " + action);
+ if((action != null) && action.equals(ACTION_FM)) {
+ Log.d(TAG, "FM intent received");
+ Intent in = new Intent();
+ in.putExtras(intent);
+ in.setClass(context, FMRecordingService.class);
+ int state = intent.getIntExtra("state", 0);
+ boolean startService = true;
+
+ if (state == 1) {
+ Log.d(TAG, "FM ON intent received");
+ startService = true;
+ context.startService(in);
+ } else if(state == 0){
+ Log.d(TAG, "FM OFF intent received");
+ startService = false;
+ context.stopService(in);
+ }
+ }
+ }
+}
diff --git a/FMRecord/src/com/codeaurora/fmrecording/FMRecordingService.java b/FMRecord/src/com/codeaurora/fmrecording/FMRecordingService.java
new file mode 100644
index 0000000..2b0d00a
--- /dev/null
+++ b/FMRecord/src/com/codeaurora/fmrecording/FMRecordingService.java
@@ -0,0 +1,476 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of The Linux Foundation nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.codeaurora.fmrecording;
+
+import java.util.*;
+import android.app.Service;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
+import android.media.AudioManager;
+import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.media.AudioSystem;
+import android.media.MediaRecorder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.util.Log;
+import java.util.Date;
+import java.text.SimpleDateFormat;
+import android.provider.MediaStore;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import java.io.File;
+import android.widget.Toast;
+import android.os.UserHandle;
+import android.net.Uri;
+import android.content.res.Resources;
+import android.os.StatFs;
+
+public class FMRecordingService extends Service {
+ private static final String TAG = "FMRecordingService";
+ private BroadcastReceiver mFmRecordingReceiver = null;
+ private BroadcastReceiver mSdcardUnmountReceiver = null;
+ public static final long UNAVAILABLE = -1L;
+ public static final long PREPARING = -2L;
+ public static final long UNKNOWN_SIZE = -3L;
+ public static final long LOW_STORAGE_THRESHOLD = 50000000;
+ private long mStorageSpace;
+ private boolean mFmRecordingOn = false;
+ public static final String ACTION_FM_RECORDING =
+ "codeaurora.intent.action.FM_Recording";
+ public static final String ACTION_FM_RECORDING_STATUS =
+ "codeaurora.intent.action.FM.Recording.Status";
+
+ private File mSampleFile = null;
+ private MediaRecorder mRecorder = null;
+ long mSampleStart = 0;
+ static final int START = 1;
+ static final int STOP = 0;
+
+ public void onCreate() {
+
+ super.onCreate();
+ Log.d(TAG, "FMRecording Service onCreate");
+ registerRecordingListner();
+ registerExternalStorageListener();
+ }
+
+ public int onStartCommand(Intent intent, int flags, int startId) {
+
+ Log.d(TAG, "FMRecording Service onCreate");
+ return START_NOT_STICKY;
+ }
+
+ public void onDestroy() {
+ Log.d(TAG, "FMRecording Service onDestroy");
+ if (mFmRecordingOn == true) {
+ Log.d(TAG, "Still recording on progress, Stoping it");
+ stopRecord();
+ }
+ unregisterBroadCastReceiver(mFmRecordingReceiver);
+ unregisterBroadCastReceiver(mSdcardUnmountReceiver);
+ super.onDestroy();
+ }
+
+ public IBinder onBind(Intent intent) {
+ Log.v(TAG, "FMRecording Service onBind");
+ return null;
+ }
+
+ private void unregisterBroadCastReceiver(BroadcastReceiver myreceiver) {
+
+ if (myreceiver != null) {
+ unregisterReceiver(myreceiver);
+ myreceiver = null;
+ }
+ }
+ /**
+ * Registers an intent to listen for ACTION_MEDIA_UNMOUNTED notifications.
+ * The intent will call closeExternalStorageFiles() if the external media
+ * is going to be ejected, so applications can clean up.
+ */
+ private void registerExternalStorageListener() {
+ if (mSdcardUnmountReceiver == null) {
+ mSdcardUnmountReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if ((action.equals(Intent.ACTION_MEDIA_UNMOUNTED))
+ || (action.equals(Intent.ACTION_MEDIA_EJECT))) {
+
+ Log.d(TAG, "ACTION_MEDIA_UNMOUNTED Intent received");
+ if (mFmRecordingOn == true) {
+ try {
+ stopRecord();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ };
+ IntentFilter iFilter = new IntentFilter();
+ iFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+ iFilter.addAction(Intent.ACTION_MEDIA_EJECT);
+ iFilter.addDataScheme("file");
+ registerReceiver(mSdcardUnmountReceiver, iFilter);
+ }
+ }
+
+ private static long getAvailableSpace() {
+ String state = Environment.getExternalStorageState();
+ Log.d(TAG, "External storage state=" + state);
+ if (Environment.MEDIA_CHECKING.equals(state)) {
+ return PREPARING;
+ }
+ if (!Environment.MEDIA_MOUNTED.equals(state)) {
+ return UNAVAILABLE;
+ }
+
+ try {
+ File sampleDir = Environment.getExternalStorageDirectory();
+ StatFs stat = new StatFs(sampleDir.getAbsolutePath());
+ return stat.getAvailableBlocks() * (long) stat.getBlockSize();
+ } catch (Exception e) {
+ Log.i(TAG, "Fail to access external storage", e);
+ }
+ return UNKNOWN_SIZE;
+ }
+
+ private boolean updateAndShowStorageHint() {
+ mStorageSpace = getAvailableSpace();
+ return showStorageHint();
+ }
+
+ private boolean showStorageHint() {
+ String errorMessage = null;
+ if (mStorageSpace == UNAVAILABLE) {
+ errorMessage = getString(R.string.no_storage);
+ } else if (mStorageSpace == PREPARING) {
+ errorMessage = getString(R.string.preparing_sd);
+ } else if (mStorageSpace == UNKNOWN_SIZE) {
+ errorMessage = getString(R.string.access_sd_fail);
+ } else if (mStorageSpace < LOW_STORAGE_THRESHOLD) {
+ errorMessage = getString(R.string.spaceIsLow_content);
+ }
+
+ if (errorMessage != null) {
+ Toast.makeText(this, errorMessage,
+ Toast.LENGTH_LONG).show();
+ return false;
+ }
+ return true;
+ }
+
+ private void sendRecordingStatusIntent(int status) {
+ Intent intent = new Intent(ACTION_FM_RECORDING_STATUS);
+ intent.putExtra("state", status);
+ Log.d(TAG, "posting intent for FM Recording status as = " +status);
+ getApplicationContext().sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+ private boolean startRecord() {
+
+ Log.d(TAG, "Enter startRecord");
+ if (mRecorder != null) { /* Stop existing recording if any */
+ Log.d(TAG, "Stopping existing record");
+ try {
+ mRecorder.stop();
+ mRecorder.reset();
+ mRecorder.release();
+ mRecorder = null;
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+ }
+ if (!updateAndShowStorageHint())
+ return false;
+ long maxFileSize = mStorageSpace - LOW_STORAGE_THRESHOLD;
+ mRecorder = new MediaRecorder();
+ try {
+ mRecorder.setMaxFileSize(maxFileSize);
+ } catch (RuntimeException exception) {
+
+ }
+ mSampleFile = null;
+ File sampleDir = Environment.getExternalStorageDirectory();
+
+ try {
+ mSampleFile = File.createTempFile("FMRecording", ".3gpp", sampleDir);
+ } catch (IOException e) {
+ Log.e(TAG, "Not able to access SD Card");
+ Toast.makeText(this, "Not able to access SD Card", Toast.LENGTH_SHORT).show();
+ }
+ try {
+ Log.d(TAG, "AudioSource.FM_RX" +MediaRecorder.AudioSource.FM_RX);
+ mRecorder.setAudioSource(MediaRecorder.AudioSource.FM_RX);
+ mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+ mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+ } catch (RuntimeException exception) {
+ Log.d(TAG, "RuntimeException while settings");
+ mRecorder.reset();
+ mRecorder.release();
+ mRecorder = null;
+ return false;
+ }
+ Log.d(TAG, "setOutputFile");
+ mRecorder.setOutputFile(mSampleFile.getAbsolutePath());
+ try {
+ mRecorder.prepare();
+ Log.d(TAG, "start");
+ mRecorder.start();
+ } catch (IOException e) {
+ Log.d(TAG, "IOException while start");
+ mRecorder.reset();
+ mRecorder.release();
+ mRecorder = null;
+ return false;
+ } catch (RuntimeException e) {
+ Log.d(TAG, "RuntimeException while start");
+ mRecorder.reset();
+ mRecorder.release();
+ mRecorder = null;
+ return false;
+ }
+ mFmRecordingOn = true;
+ Log.d(TAG, "mSampleFile.getAbsolutePath() " +mSampleFile.getAbsolutePath());
+ mRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
+ public void onInfo(MediaRecorder mr, int what, int extra) {
+ if ((what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) ||
+ (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED)) {
+ if (mFmRecordingOn) {
+ Log.d(TAG, "Maximum file size/duration reached, stopping the recording");
+ stopRecord();
+ }
+ // Show the toast.
+ Toast.makeText(FMRecordingService.this, R.string.FMRecording_reach_size_limit,
+ Toast.LENGTH_LONG).show();
+ }
+ }
+ // from MediaRecorder.OnErrorListener
+ public void onError(MediaRecorder mr, int what, int extra) {
+ Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
+ if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
+ // We may have run out of space on the sdcard.
+ if (mFmRecordingOn) {
+ stopRecord();
+ }
+ updateAndShowStorageHint();
+ }
+ }
+ });
+ mSampleStart = System.currentTimeMillis();
+ sendRecordingStatusIntent(START);
+ return true;
+ }
+
+ private void stopRecord() {
+ Log.d(TAG, "Enter stopRecord");
+ mFmRecordingOn = false;
+ if (mRecorder == null)
+ return;
+ try {
+ mRecorder.stop();
+ mRecorder.reset();
+ mRecorder.release();
+ mRecorder = null;
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+
+ sendRecordingStatusIntent(STOP);
+ saveFile();
+ }
+
+ private void saveFile() {
+ int sampleLength = (int)((System.currentTimeMillis() - mSampleStart)/1000 );
+ Log.d(TAG, "Enter saveFile");
+ if (sampleLength == 0)
+ return;
+ String state = Environment.getExternalStorageState();
+ Log.d(TAG, "storage state is " + state);
+
+ if (Environment.MEDIA_MOUNTED.equals(state)) {
+ try {
+ this.addToMediaDB(mSampleFile);
+ Toast.makeText(this,getString(R.string.save_record_file,
+ mSampleFile.getAbsolutePath( )),
+ Toast.LENGTH_LONG).show();
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+ Log.e(TAG, "SD card must have removed during recording. ");
+ Toast.makeText(this, "Recording aborted", Toast.LENGTH_SHORT).show();
+ }
+ return;
+ }
+
+ /*
+ * Adds file and returns content uri.
+ */
+ private Uri addToMediaDB(File file) {
+ Log.d(TAG, "In addToMediaDB");
+ Resources res = getResources();
+ ContentValues cv = new ContentValues();
+ long current = System.currentTimeMillis();
+ long modDate = file.lastModified();
+ Date date = new Date(current);
+ SimpleDateFormat formatter = new SimpleDateFormat(
+ res.getString(R.string.audio_db_title_format));
+ String title = formatter.format(date);
+
+ // Lets label the recorded audio file as NON-MUSIC so that the file
+ // won't be displayed automatically, except for in the playlist.
+ cv.put(MediaStore.Audio.Media.IS_MUSIC, "1");
+ cv.put(MediaStore.Audio.Media.TITLE, title);
+ cv.put(MediaStore.Audio.Media.DATA, file.getAbsolutePath());
+ cv.put(MediaStore.Audio.Media.DATE_ADDED, (int) (current / 1000));
+ cv.put(MediaStore.Audio.Media.DATE_MODIFIED, (int) (modDate / 1000));
+ cv.put(MediaStore.Audio.Media.MIME_TYPE, "AUDIO_AAC_MP4");
+ cv.put(MediaStore.Audio.Media.ARTIST,
+ res.getString(R.string.audio_db_artist_name));
+ cv.put(MediaStore.Audio.Media.ALBUM,
+ res.getString(R.string.audio_db_album_name));
+ Log.d(TAG, "Inserting audio record: " + cv.toString());
+ ContentResolver resolver = getContentResolver();
+ Uri base = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ Log.d(TAG, "ContentURI: " + base);
+ Uri result = resolver.insert(base, cv);
+ if (result == null) {
+ Toast.makeText(this, "Unable to save recorded audio", Toast.LENGTH_SHORT).show();
+ return null;
+ }
+ if (getPlaylistId(res) == -1) {
+ createPlaylist(res, resolver);
+ }
+ int audioId = Integer.valueOf(result.getLastPathSegment());
+ addToPlaylist(resolver, audioId, getPlaylistId(res));
+
+ // Notify those applications such as Music listening to the
+ // scanner events that a recorded audio file just created.
+ sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result));
+ return result;
+ }
+
+ private int getPlaylistId(Resources res) {
+ Uri uri = MediaStore.Audio.Playlists.getContentUri("external");
+ final String[] ids = new String[] { MediaStore.Audio.Playlists._ID };
+ final String where = MediaStore.Audio.Playlists.NAME + "=?";
+ final String[] args = new String[] { res.getString(R.string.audio_db_playlist_name) };
+ Cursor cursor = query(uri, ids, where, args, null);
+ if (cursor == null) {
+ Log.v(TAG, "query returns null");
+ }
+ int id = -1;
+ if (cursor != null) {
+ cursor.moveToFirst();
+ if (!cursor.isAfterLast()) {
+ id = cursor.getInt(0);
+ }
+ cursor.close();
+ }
+ return id;
+ }
+
+ private Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ try {
+ ContentResolver resolver = getContentResolver();
+ if (resolver == null) {
+ return null;
+ }
+ return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
+ } catch (UnsupportedOperationException ex) {
+ return null;
+ }
+ }
+
+ private Uri createPlaylist(Resources res, ContentResolver resolver) {
+ ContentValues cv = new ContentValues();
+ cv.put(MediaStore.Audio.Playlists.NAME, res.getString(R.string.audio_db_playlist_name));
+ Uri uri = resolver.insert(MediaStore.Audio.Playlists.getContentUri("external"), cv);
+ if (uri == null) {
+ Toast.makeText(this, "Unable to save recorded audio", Toast.LENGTH_SHORT).show();
+ }
+ return uri;
+ }
+
+ private void addToPlaylist(ContentResolver resolver, int audioId, long playlistId) {
+ String[] cols = new String[] {
+ "count(*)"
+ };
+ Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
+ Cursor cur = resolver.query(uri, cols, null, null, null);
+ final int base;
+ if (cur != null) {
+ cur.moveToFirst();
+ base = cur.getInt(0);
+ cur.close();
+ } else {
+ base = 0;
+ }
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(base + audioId));
+ values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, audioId);
+ resolver.insert(uri, values);
+ }
+
+ private void registerRecordingListner() {
+ if (mFmRecordingReceiver == null) {
+ mFmRecordingReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "Received intent " +intent);
+ String action = intent.getAction();
+ Log.d(TAG, " action = " +action);
+ if (action.equals(ACTION_FM_RECORDING)) {
+ int state = intent.getIntExtra("state", STOP);
+ Log.d(TAG, "ACTION_FM_RECORDING Intent received" +state);
+ if (state == START) {
+ Log.d(TAG, "Recording start");
+ startRecord();
+ } else if (state == STOP) {
+ Log.d(TAG, "Stop recording");
+ stopRecord();
+ }
+ }
+ }
+ };
+ IntentFilter iFilter = new IntentFilter();
+ iFilter.addAction(ACTION_FM_RECORDING);
+ registerReceiver(mFmRecordingReceiver, iFilter);
+ }
+ }
+
+}