Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call recording/processing service! - Android

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)
like image 377
Piotr Sobecki Avatar asked Nov 28 '11 13:11

Piotr Sobecki


People also ask

Can I know if someone is recording my call?

On the lefthand menu, click 'Activity controls'. Scroll down to the 'Voice & Audio activity' section and click that. There you'll find a chronological list of all the voice and audio recordings which will include any recorded without you knowing.

What is the best secret call recording app on Android?

Hoverwatch is the best-hidden call recorder app that can remotely record all the incoming and outgoing calls on the monitored device. You can either download call audio to your computer or listen to them right in the panel. You can view all of the websites visited by the user of the Android-powered target device.

Does Android have a built in call recorder?

On your Android device, open the Phone app . Call recording. Under “Always record,” tap Selected numbers. Turn on Always record selected numbers.


1 Answers

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)

like image 186
Eugene Avatar answered Oct 10 '22 09:10

Eugene