Call recording/processing service! - Android

13,232

Solution 1

I've test your code with small modifications on my Samsung Galaxy S2. It woks pretty good without exceptions. Call was recorded successfully using the service. The one issue that i found is that service was started twice (Broadcast Receiver was started twice and TelephonyManager.PhoneStateListner called twice with state OFFHOOK) I have to change constant in the method setAudioSource from VOICE_CALL to VOICE_UPLINK. It looks like Java enum definition and enum in C header are different. Call was recorded for both sides (UPLINK and DOWNLINK)

Solution 2

Try using

mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION);

instead of

mRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL);

Solution 3

The android documentation states that this exception will be thrown if stop() is called before start().

I would add a check to make sure that your recording flag is set before you call the stop() method. This will probably get rid of your error.

However, for some reason start() is never being called, possibly because of your database code, and that is your real issue.

Solution 4

Pejter,

I had a similar requirement when I tried to port to Android an application I developed for Symbian phones.

After some research I came to the sad conclusion that this is probably impossible on most (maybe even on all) Android phones available today.

Despite having the necessary API since Android 1.6, most Android phones do not support this either due to hardware architecture or firmware implementation. Basically, the problem is that the main phone processor doesn't have access to the baseband audio stream. It usually does have access to the local microphone, so on some phones it's possible to record the local user and possibly a faint signal of the remote user that reaches the microphone through acoustic coupling between the earpiece and the mic. This wasn't good enough for my application's requirements, so I still don't have an Android version.

I know I'm not solving your problem (and it maybe even unrelated to the exception you get), but maybe it will save you from wasting your time.

For more information you can take a look at the following:

  1. http://code.google.com/p/android/issues/detail?id=2117

  2. https://groups.google.com/d/topic/android-developers/AbU85mtDgQw/discussion

Share:
13,232
Piotr Sobecki
Author by

Piotr Sobecki

Updated on June 12, 2022

Comments

  • Piotr Sobecki
    Piotr Sobecki almost 2 years

    Hello,
    Im working on a solution for Android that will record calls (both out and incomming) and will further process recorded data (at end point of my application, no audio-file data will be hold on phone memory). I have implemented BroadcastReceiver with PhoneStateListener.LISTEN_CALL_STATE that starts a recording service if state is CALL_STATE_OFFHOOK. Then in service I start a new thread that attempts recording a call, and another BroadcastReceiver with PhoneStateListener.LISTEN_CALL_STATE, that calls a method to stop recording if phone state is changed to TelephonyManager.CALL_STATE_IDLE.

    The audio file created is empty. Exception is thrown when recorder.stop() method in my service is called. Where is an error? What could i do better?

    First (standalone) BroadcastReceiver:

        package com.piotr.callerrecogniser;
    
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.content.SharedPreferences;
    import android.telephony.PhoneStateListener;
    import android.telephony.TelephonyManager;
    
    public class CallReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            MyPhoneStateListener phoneListener = new MyPhoneStateListener(context);
            TelephonyManager telephony = (TelephonyManager) context
                    .getSystemService(Context.TELEPHONY_SERVICE);
            telephony.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);
        }
        class MyPhoneStateListener extends PhoneStateListener {
            public static final String tag = "CallerRecogniser - CallReceiver";
            private Context context;
            MyPhoneStateListener(Context c) {
                super();
                context = c;
            }
            public static final int NOTIFICATION_ID_RECEIVED = 0x1221;
            @Override
            public void onCallStateChanged(int state, String incomingNumber) {
                SharedPreferences preferences = context.getSharedPreferences("CallReceiver", Context.MODE_PRIVATE);
                switch (state) {
                case TelephonyManager.CALL_STATE_IDLE:
                    break;
                    //If call is answered, run recording service. Also pass "phone_number" variable with incomingNumber to shared prefs, so service will be able to access that via shared prefs.
                case TelephonyManager.CALL_STATE_OFFHOOK: // The call is answered
                    String phone_number = preferences.getString("phone_number",null);
                     Intent serv = new Intent(context,
                     CallRecordingService.class);
                     serv.putExtra("number", phone_number);
                     context.startService(serv);
                    break;          
                    //If phone is ringing, save phone_number. This is done because incomingNumber is not saved on CALL_STATE_OFFHOOK
                case TelephonyManager.CALL_STATE_RINGING:
                    SharedPreferences.Editor editor = preferences.edit();
                    editor.putString("phone_number", incomingNumber);
                    editor.commit();
                    break;
                }
            }
    
        }
    }
    

    Service:

    package com.piotr.callerrecogniser;
    
    import android.app.Notification;
    import android.app.NotificationManager;
    import android.app.PendingIntent;
    import android.app.Service;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.media.MediaRecorder;
    import android.os.Environment;
    import android.os.Handler;
    import android.os.IBinder;
    import android.os.Looper;
    import android.telephony.PhoneStateListener;
    import android.telephony.TelephonyManager;
    
    public class CallRecordingService extends Service implements Runnable {
        CallerRecogniserDB database = new CallerRecogniserDB(this);
    
        String phoneNumber;
        MediaRecorder recorder;
        private final Handler mHandler = new Handler();
        private final BroadcastReceiver myCallStateReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                MyPhoneStateListener phoneListener = new MyPhoneStateListener(
                        context);
                TelephonyManager telephony = (TelephonyManager) context
                        .getSystemService(Context.TELEPHONY_SERVICE);
                telephony.listen(phoneListener,
                        PhoneStateListener.LISTEN_CALL_STATE);
            }
    
            class MyPhoneStateListener extends PhoneStateListener {
                private Context context;
    
                MyPhoneStateListener(Context c) {
                    super();
                    context = c;
                }
    
                @Override
                public void onCallStateChanged(int state, String incomingNumber) {
                    switch (state) {
                    case TelephonyManager.CALL_STATE_IDLE:
                        if (recording) {
                            NotificationManager mNotificationManager = (NotificationManager) context
                                    .getSystemService(Context.NOTIFICATION_SERVICE);
                            Notification not;
    
                            not = new Notification(R.drawable.ic_launcher,
                                    "Phone call being processed",
                                    System.currentTimeMillis());
                            Intent notIntent = new Intent();
                            PendingIntent contentIntent = PendingIntent
                                    .getActivity(context, 0, notIntent, 0);
                            not.setLatestEventInfo(context, "CallRecordingService",
                                    "Notification from idle",
                                    contentIntent);
                            mNotificationManager.notify(NOTIFICATION_ID_RECEIVED,
                                    not);
    
                            stopRecording();
                        }
                        break;
                    }
                }
    
            }
        };
    
        private static boolean recording = false;
    
        private String INCOMING_CALL_ACTION = "android.intent.action.PHONE_STATE";
    
        @Override
        public void onCreate() {
            // TODO Auto-generated method stub
            super.onCreate();
    
            IntentFilter intentToReceiveFilter = new IntentFilter();
            intentToReceiveFilter.addAction(INCOMING_CALL_ACTION);
            this.registerReceiver(myCallStateReceiver, intentToReceiveFilter, null,
                    mHandler);
    
            Thread aThread = new Thread(this);
            aThread.start();
    
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            // TODO Auto-generated method stub
            super.onStart(intent, startId);
            phoneNumber = intent.getExtras().getString("number");
    
            return START_STICKY;
        }
    
        @Override
        public IBinder onBind(Intent arg0) {
            // TODO Auto-generated method stub
            return null;
        }
    
        public static final int NOTIFICATION_ID_RECEIVED = 0x1221;
    
        @Override
        public void run() {
            Looper.myLooper();
            Looper.prepare();
            // TODO Auto-generated method stub
    
    
            database.open();
            String[] numbers = database.getData(CallerRecogniserDB.KEY_NUMBER);
            int index = -1;
    
            outside_for: for (int i = 0; i < numbers.length; i++) {
                String[] splitted = numbers[i].split(",");
                for (String nu : splitted) {
                    if (nu.equals(phoneNumber)) {
                        index = i;
                        break outside_for;
                    }
                }
            }
    
            database.close();
    
            if (index >= 0) { // Phone number is in a database and it's state==0 =>
                                // it has no data recorded
                // Notification that call is recorded
    
                recorder = new MediaRecorder();
                recorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL);
                recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
                recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    
                recorder.setOutputFile(Environment.getExternalStorageDirectory()
                        .getAbsolutePath() + "/" + phoneNumber + ".3gp");
                try {
                    recorder.prepare();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                recording = true;
                recorder.start();
    
    
            } 
        }
    
        void stopRecording() {
    
            // Then clean up with when it hangs up:
            recorder.stop();
            recorder.release();
            recording=false;
        }
    }
    

    Permissions:

    <uses-permission android:name="android.permission.READ_CONTACTS" />
        <uses-permission android:name="android.permission.READ_PHONE_STATE" />
        <uses-permission android:name="android.permission.RECORD_AUDIO"/>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    

    Exception output:

    11-28 14:28:44.385: E/MediaRecorder(22438): stop called in an invalid state: 8
    11-28 14:28:44.400: D/AndroidRuntime(22438): Shutting down VM
    11-28 14:28:44.425: W/dalvikvm(22438): threadid=1: thread exiting with uncaught exception (group=0x4001e578)
    11-28 14:28:44.435: E/AndroidRuntime(22438): FATAL EXCEPTION: main
    11-28 14:28:44.435: E/AndroidRuntime(22438): java.lang.IllegalStateException
    11-28 14:28:44.435: E/AndroidRuntime(22438):    at android.media.MediaRecorder.stop(Native Method)
    11-28 14:28:44.435: E/AndroidRuntime(22438):    at com.piotr.callerrecogniser.CallRecordingService.stopRecording(CallRecordingService.java:159)
    11-28 14:28:44.435: E/AndroidRuntime(22438):    at com.piotr.callerrecogniser.CallRecordingService$1$MyPhoneStateListener.onCallStateChanged(CallRecordingService.java:65)
    11-28 14:28:44.435: E/AndroidRuntime(22438):    at android.telephony.PhoneStateListener$2.handleMessage(PhoneStateListener.java:369)
    11-28 14:28:44.435: E/AndroidRuntime(22438):    at android.os.Handler.dispatchMessage(Handler.java:99)
    11-28 14:28:44.435: E/AndroidRuntime(22438):    at android.os.Looper.loop(Looper.java:123)
    11-28 14:28:44.435: E/AndroidRuntime(22438):    at android.app.ActivityThread.main(ActivityThread.java:3691)
    11-28 14:28:44.435: E/AndroidRuntime(22438):    at java.lang.reflect.Method.invokeNative(Native Method)
    11-28 14:28:44.435: E/AndroidRuntime(22438):    at java.lang.reflect.Method.invoke(Method.java:507)
    11-28 14:28:44.435: E/AndroidRuntime(22438):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:847)
    11-28 14:28:44.435: E/AndroidRuntime(22438):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:605)
    11-28 14:28:44.435: E/AndroidRuntime(22438):    at dalvik.system.NativeStart.main(Native Method)
    
  • Piotr Sobecki
    Piotr Sobecki over 12 years
    I forgot to mention, that it creates an empty audio file, so I presume that the recorder.start() method is reached. Further more boolean flag recording is set to true only in that place before recorder.start(). So this method is called most likely.
  • Piotr Sobecki
    Piotr Sobecki over 12 years
    media.tumblr.com/tumblr_los755Kzxe1qf51e1.jpg <br> If the situation is changed on the subject please notify me, I'll contribute to this thread too if I will manage to find a solution.
  • Piotr Sobecki
    Piotr Sobecki about 12 years
    amazing, works great! thanks a lot! too bad Ive already changed engineering thesis subject :)