title: Running speech recognition as foreground service (continuously in Android)
body: <p>I am trying to build an app that continuously listens to what people are talking around the phone, and stores the converted speech-to-text in a file. Also the user knows that the device is in always listening mode.</p>
  
  <p>The below code WORKS! (only to an extent though). It runs the speech recognition continuously:</p>
  
  <ul>
  <li><p>when the app is opened</p></li>
  <li><p>even when the device screen is blank</p></li>
  <li><p>even when I toggle to a different app.</p></li>
  </ul>
  
  <p>However as soon as I press the Home button (center round soft touch button in Android), it stops working. The app does not crashes but it stops receiving audio. </p>
  
  <p>When I click on the Notification card for this app (by pulling from the top of the phone), the app crashes, stating that the "Activity com.truiton.foregroundservice.MainActivity has leaked ServiceConnection android.speech.SpeechRecognizer$Connection@188bc34 that was originally bound here" meaning that the audio device is already bound.</p>
  
  <p>I am testing it on a Nexus 6P phone.</p>
  
  <p>Here is the code:</p>
  
  <pre><code>public class MainActivity extends AppCompatActivity implements OnClickListener {
  
      private static final String TAG = "ForegroundMain";
      private ForegroundService fservice;
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
  
          int PERMISSION_ALL = 1;
          String[] PERMISSIONS = {Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE};
          if(!hasPermissions(this, PERMISSIONS)){
              ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_ALL);
          }
  
          Button startButton = (Button) findViewById(R.id.button1);
          Button stopButton = (Button) findViewById(R.id.button2);
  
          startButton.setOnClickListener(this);
          stopButton.setOnClickListener(this);
      }
  
      public static boolean hasPermissions(Context context, String... permissions) {
          if (android.os.Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.M &amp;&amp; context != null &amp;&amp; permissions != null) {
              for (String permission : permissions) {
                  if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
                      return false;
                  }
              }
          }
          return true;
      }
  
      @Override
      public void onClick(View v) {
          switch (v.getId()) {
              case R.id.button1:
                  fservice = new ForegroundService(this, new ForegroundService.onResultsReady() {
                      @Override
                      public void onResults(ArrayList&lt;String&gt; results) {
                          if (results != null &amp;&amp; results.size() &gt; 0) {
                              Log.d(TAG, "Speech-to-text: " + results.get(0));
                              writeToFile(results.get(0));
                          }
                      }
                  });
                  Intent startIntent = new Intent(MainActivity.this, ForegroundService.class);
                  startIntent.setAction(Constants.ACTION.STARTFOREGROUND_ACTION);
                  startService(startIntent);
                  break;
              case R.id.button2:
                  Intent stopIntent = new Intent(MainActivity.this, ForegroundService.class);
                  stopIntent.setAction(Constants.ACTION.STOPFOREGROUND_ACTION);
                  startService(stopIntent);
                  break;
              default:
                  break;
          }
      }
  
      public void writeToFile(String data) {
          final String directoryPath = Environment.getExternalStorageDirectory() + File.separator + "speechdata";
          File fileDirectory = new File(directoryPath);
  
          // Make sure the path directory exists.
          if(!fileDirectory.exists()) {
              fileDirectory.mkdirs();
          }
  
          final File file = new File(fileDirectory, "data.txt");
          try {
              if (!file.exists()) {
                  file.createNewFile();
              }
              FileOutputStream fOut = new FileOutputStream(file, true);
              OutputStreamWriter myOutWriter = new OutputStreamWriter(fOut);
              myOutWriter.append(data + " /n ");
              myOutWriter.close();
              fOut.flush();
              fOut.close();
          } catch (IOException e) {
              Log.e(TAG, "Exception: File write failed: " + e.toString());
          }
      }
  }
  </code></pre>
  
  <p>and the Foreground Service code calling Speech Recognition is here:</p>
  
  <pre><code>public class ForegroundService extends Service {
      private static final String TAG = "ForegroundService";
  
      protected AudioManager mAudioManager;
      protected SpeechRecognizer mSpeechRecognizer;
      protected Intent mSpeechRecognizerIntent;
  
      protected boolean mIsListening;
      private boolean mIsStreamSolo;
  
      private boolean mMute=true;
      private onResultsReady mListener;
  
      public ForegroundService() {}
  
      public ForegroundService(Context context, onResultsReady listener) {
          try {
              mListener=listener;
          }
          catch(ClassCastException e) {
              Log.e(TAG,e.toString());
          }
          mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
          mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context);
          mSpeechRecognizer.setRecognitionListener(new SpeechRecognitionListener());
          mSpeechRecognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
          mSpeechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                  RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);   mSpeechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,
                  context.getPackageName());
          startListening();
      }
  
      private void listenAgain() {
          if(mIsListening) {
              mIsListening = false;
              mSpeechRecognizer.cancel();
              startListening();
          }
      }
  
      private void startListening() {
          if(!mIsListening) {
              mIsListening = true;
              if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.JELLY_BEAN) {
                  // turn off beep sound
                  if (!mIsStreamSolo &amp;&amp; mMute) {
                      mAudioManager.setStreamMute(AudioManager.STREAM_NOTIFICATION, true);
                      mAudioManager.setStreamMute(AudioManager.STREAM_ALARM, true);
                      mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
                      mAudioManager.setStreamMute(AudioManager.STREAM_RING, true);
                      mAudioManager.setStreamMute(AudioManager.STREAM_SYSTEM, true);
                      mIsStreamSolo = true;
                  }
              }
              mSpeechRecognizer.startListening(mSpeechRecognizerIntent);
          }
      }
  
      public void destroy() {
          mIsListening=false;
          if (!mIsStreamSolo) {
              mAudioManager.setStreamMute(AudioManager.STREAM_NOTIFICATION, false);
              mAudioManager.setStreamMute(AudioManager.STREAM_ALARM, false);
              mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
              mAudioManager.setStreamMute(AudioManager.STREAM_RING, false);
              mAudioManager.setStreamMute(AudioManager.STREAM_SYSTEM, false);
              mIsStreamSolo = true;
          }
          Log.d(TAG, "onDestroy");
          if (mSpeechRecognizer != null) {
              mSpeechRecognizer.stopListening();
              mSpeechRecognizer.cancel();
              mSpeechRecognizer.destroy();
              mSpeechRecognizer=null;
          }
      }
  
      protected class SpeechRecognitionListener implements RecognitionListener {
  
          @Override
          public void onBeginningOfSpeech() {}
  
          @Override
          public void onBufferReceived(byte[] buffer) {}
  
          @Override
          public void onEndOfSpeech() {}
  
          @Override
          public synchronized void onError(int error) {
              Log.d(TAG, "ERROR! ERROR!");
              if(error==SpeechRecognizer.ERROR_RECOGNIZER_BUSY) {
                  if(mListener!=null) {
                      ArrayList&lt;String&gt; errorList=new ArrayList&lt;String&gt;(1);
                      errorList.add("ERROR RECOGNIZER BUSY");
                      if(mListener!=null)
                          mListener.onResults(errorList);
                  }
                  return;
              }
  
              if(error==SpeechRecognizer.ERROR_NO_MATCH) {
                  if(mListener!=null)
                      mListener.onResults(null);
              }
  
              if(error==SpeechRecognizer.ERROR_NETWORK) {
                  ArrayList&lt;String&gt; errorList=new ArrayList&lt;String&gt;(1);
                  errorList.add("STOPPED LISTENING");
                  if(mListener!=null)
                      mListener.onResults(errorList);
              }
              Log.d(TAG, "error = " + error);
              new Handler().postDelayed(new Runnable() {
                  @Override
                  public void run() {
                      listenAgain();
                  }
              },100);
          }
  
          @Override
          public void onEvent(int eventType, Bundle params) {}
  
          @Override
          public void onPartialResults(Bundle partialResults) {}
  
          @Override
          public void onReadyForSpeech(Bundle params) {}
  
          @Override
          public void onResults(Bundle results) {
              if(results!=null &amp;&amp; mListener!=null)
                  mListener.onResults(results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION));
              listenAgain();
          }
  
          @Override
          public void onRmsChanged(float rmsdB) {}
      }
  
      public boolean ismIsListening() { return mIsListening; }
  
  
      public interface onResultsReady {
          public void onResults(ArrayList&lt;String&gt; results);
      }
  
      public void mute(boolean mute) { mMute=mute; }
  
      public boolean isInMuteMode() { return mMute; }
  
      @Override
      public void onCreate() {
          super.onCreate();
      }
  
      @Override
      public int onStartCommand(Intent intent, int flags, int startId) {
          if (intent.getAction().equals(Constants.ACTION.STARTFOREGROUND_ACTION)) {
              Log.i(TAG, "Received Start Foreground Intent ");
              Intent notificationIntent = new Intent(this, MainActivity.class);
              notificationIntent.setAction(Constants.ACTION.MAIN_ACTION);
              notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
              PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
  
              Intent previousIntent = new Intent(this, ForegroundService.class);
              previousIntent.setAction(Constants.ACTION.PREV_ACTION);
              PendingIntent ppreviousIntent = PendingIntent.getService(this, 0,
                      previousIntent, 0);
  
              Bitmap icon = BitmapFactory.decodeResource(getResources(),
                      R.drawable.truiton_short);
  
              Notification notification = new NotificationCompat.Builder(this)
                      .setContentTitle("Truiton Music Player")
                      .setTicker("Truiton Music Player")
                      .setContentText("My Music")
                      .setSmallIcon(R.drawable.ic_launcher)
                      .setLargeIcon(
                              Bitmap.createScaledBitmap(icon, 128, 128, false))
                      .setContentIntent(pendingIntent)
                      .setOngoing(true)
                      .addAction(android.R.drawable.ic_media_previous,
                              "Previous", ppreviousIntent)
                      .build();
              startForeground(Constants.NOTIFICATION_ID.FOREGROUND_SERVICE,
                      notification);
          } else if (intent.getAction().equals(Constants.ACTION.PREV_ACTION)) {
              Log.i(TAG, "Clicked Previous");
          } else if (intent.getAction().equals(Constants.ACTION.STOPFOREGROUND_ACTION)) {
              Log.d(TAG, "In onDestroy");
              stopForeground(true);
              stopSelf();
          }
          return START_STICKY;
      }
  
      @Override
      public void onDestroy() {
          Log.d(TAG, "In onDestroy");
          super.onDestroy();
  
          mIsListening=false;
          if (!mIsStreamSolo) {
              mAudioManager.setStreamMute(AudioManager.STREAM_NOTIFICATION, false);
              mAudioManager.setStreamMute(AudioManager.STREAM_ALARM, false);
              mAudioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
              mAudioManager.setStreamMute(AudioManager.STREAM_RING, false);
              mAudioManager.setStreamMute(AudioManager.STREAM_SYSTEM, false);
              mIsStreamSolo = true;
          }
          Log.d(TAG, "onDestroy");
          if (mSpeechRecognizer != null) {
              mSpeechRecognizer.stopListening();
              mSpeechRecognizer.cancel();
              mSpeechRecognizer.destroy();
              mSpeechRecognizer=null;
          }
      }
  
      @Override
      public IBinder onBind(Intent intent) {
          Log.d(TAG, "In onBind");
          return null;
      }
  }
  </code></pre>