title: Call recording/processing service! - Android
body: <p><strong>Hello,</strong><br>
  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.</p>
  
  <p>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? </p>
  
  <p>First (standalone) BroadcastReceiver:</p>
  
  <pre><code>    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;
              }
          }
  
      }
  }
  </code></pre>
  
  <p>Service:</p>
  
  <pre><code>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 &lt; 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 &gt;= 0) { // Phone number is in a database and it's state==0 =&gt;
                              // 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;
      }
  }
  </code></pre>
  
  <p>Permissions:</p>
  
  <pre><code>&lt;uses-permission android:name="android.permission.READ_CONTACTS" /&gt;
      &lt;uses-permission android:name="android.permission.READ_PHONE_STATE" /&gt;
      &lt;uses-permission android:name="android.permission.RECORD_AUDIO"/&gt;
      &lt;uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /&gt;
  </code></pre>
  
  <p>Exception output:</p>
  
  <pre><code>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)
  </code></pre>