Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecognitionService: call for recognition service without RECORD_AUDIO permissions; extending RecognitionService

I am trying to extend RecognitionService to try out different Speech to Text services other than given by google. In order to check if SpeechRecognizer initializes correctly dummy implementations are given now. I get "RecognitionService: call for recognition service without RECORD_AUDIO permissions" when below check is done inside RecognitionService#checkPermissions().

   if (PermissionChecker.checkCallingPermissionForDataDelivery(this,
                    android.Manifest.permission.RECORD_AUDIO, packageName, featureId,
                    null /*message*/)
                             == PermissionChecker.PERMISSION_GRANTED) {
                return true;
            } 

Note that checked similar reported issue and I verified that inside my extended service, this permission exists when checked with below.

if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) 

Android manifest file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.texttospeech">
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <queries>
        <package android:name="com.google.android.googlequicksearchbox"/>
    </queries>

    <application
        android:name=".App"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".SampleSpeechRecognizerService"
            android:exported="true"
            android:foregroundServiceType="microphone"
            android:permission="android.permission.RECORD_AUDIO">
            <intent-filter>
                <action android:name="android.speech.RecognitionService" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>
    </application>

</manifest>

MainActivity

package com.example.texttospeech;

import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.os.Bundle;
import android.speech.RecognitionListener;
import android.speech.RecognitionService;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = AppCompatActivity.class.getSimpleName();
    private Intent speechRecognizerIntent;
    public static final int PERMISSION_REQUEST_RECORD_AUDIO = 1;
    private SpeechRecognizer speechRecognizer;
    private EditText editText;
    private ImageView micButton;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        editText = findViewById(R.id.text);
        micButton = findViewById(R.id.button);

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            checkPermission();
        } else {
            configureSpeechListener();
        }

        boolean isSupported = SpeechRecognizer.isRecognitionAvailable(this);

        if (!isSupported) {
            Log.i(TAG, "Device has no Speech support");
        }

        micButton.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
                    speechRecognizer.stopListening();
                }
                if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
                    micButton.setImageResource(R.drawable.ic_mic_black_24dp);
                    speechRecognizer.startListening(speechRecognizerIntent);
                }
                return false;
            }
        });
    }

    private void configureSpeechListener() {
        //speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);

        ComponentName currentRecognitionCmp = null;

        List<ResolveInfo> list = getPackageManager().queryIntentServices(
                new Intent(RecognitionService.SERVICE_INTERFACE), 0);
        for (ResolveInfo info : list) {
            currentRecognitionCmp = new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
        }
        speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this, currentRecognitionCmp);

        speechRecognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());

        speechRecognizer.setRecognitionListener(new SampleSpeechRecognitionListener());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        speechRecognizer.destroy();
    }

    private void checkPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_RECORD_AUDIO);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case PERMISSION_REQUEST_RECORD_AUDIO:
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0 &&
                        grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    configureSpeechListener();
                } else {
                    Toast.makeText(this, "Microphone permission required to proceed", Toast.LENGTH_SHORT).show();
                }
                return;
        }
    }

    private class SampleSpeechRecognitionListener implements RecognitionListener {
        @Override
        public void onReadyForSpeech(Bundle params) {
            Log.i("Sample", "ReadyForSpeech");
        }

        @Override
        public void onBeginningOfSpeech() {
            editText.setText("");
            editText.setHint("Listening...");
            Log.i("Sample", "onBeginningOfSpeech");
        }

        @Override
        public void onRmsChanged(float rmsdB) {

        }

        @Override
        public void onBufferReceived(byte[] buffer) {

        }

        @Override
        public void onEndOfSpeech() {
            Log.i("Sample", "onEndOfSpeech");
        }

        @Override
        public void onError(int error) {
            Log.e("Sample", "Error occured.." + error);
        }

        @Override
        public void onResults(Bundle bundle) {
            Log.i("Sample", "onResults");
            micButton.setImageResource(R.drawable.ic_mic_black_off);
            ArrayList<String> data = bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
            editText.setText(data.get(0));
            Log.i("Sample", data.get(0));
        }

        @Override
        public void onPartialResults(Bundle partialResults) {
            Log.i("Sample", "onPartialResults");
        }

        @Override
        public void onEvent(int eventType, Bundle params) {
            Log.i("Sample", "onEvent");
        }
    }
}

SampleSpeechRecognizerService

package com.example.texttospeech;

import static com.example.texttospeech.App.CHANNEL_ID;

import android.app.Notification;
import android.content.Intent;
import android.os.Bundle;
import android.os.RemoteException;
import android.speech.RecognitionService;
import android.speech.SpeechRecognizer;
import android.util.Log;

import java.util.ArrayList;

public class SampleSpeechRecognizerService extends RecognitionService {

    private RecognitionService.Callback mListener;
    private Bundle mExtras;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("Sample", "Service started");
        startForeground(new Intent(),1,1);
    }


    private int startForeground(Intent intent, int flags, int startId) {
        Notification notification = new Notification.Builder(this, CHANNEL_ID)
                .setContentTitle("Speech Service")
                .setContentText("Speech to Text conversion is ongoing")
                .setSmallIcon(R.drawable.ic_android)
                .build();
        startForeground(1, notification);

        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i("Sample", "Service stopped");
    }

    @Override
    protected void onStartListening(Intent recognizerIntent, Callback listener) {
        mListener = listener;
        Log.i("Sample", "onStartListening");
        mExtras = recognizerIntent.getExtras();
        if (mExtras == null) {
            mExtras = new Bundle();
        }
        onReadyForSpeech(new Bundle());
        onBeginningOfSpeech();
    }

    @Override
    protected void onCancel(Callback listener) {
        Log.i("Sample", "onCancel");
        onResults(new Bundle());
    }

    @Override
    protected void onStopListening(Callback listener) {
        Log.i("Sample", "onStopListening");
        onEndOfSpeech();
    }

    protected void onReadyForSpeech(Bundle bundle) {
        try {
            mListener.readyForSpeech(bundle);
        } catch (RemoteException e) {
            // Ignored
        }
    }

    protected void afterRecording(ArrayList<String> results) {
        Log.i("Sample", "afterRecording");
        for (String item : results) {
            Log.i("RESULT", item);
        }
    }

    protected void onRmsChanged(float rms) {
        try {
            mListener.rmsChanged(rms);
        } catch (RemoteException e) {
            // Ignored
        }
    }

    protected void onResults(Bundle bundle) {
        try {
            mListener.results(bundle);
        } catch (RemoteException e) {
            // Ignored
        }
    }

    protected void onPartialResults(Bundle bundle) {
        try {
            mListener.partialResults(bundle);
        } catch (RemoteException e) {
            // Ignored
        }
    }

    protected void onBeginningOfSpeech() {
        try {
            mListener.beginningOfSpeech();
        } catch (RemoteException e) {
            // Ignored
        }
    }

    protected void onEndOfSpeech() {
        try {
            mListener.endOfSpeech();
        } catch (RemoteException e) {
            // Ignored
        }

        ArrayList<String> results = new ArrayList<>();
        results.add("1");
        results.add("2");
        results.add("3");

        Bundle bundle = new Bundle();
        bundle.putStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION, results);

        afterRecording(results);
    }

    protected void onBufferReceived(byte[] buffer) {
        try {
            mListener.bufferReceived(buffer);
        } catch (RemoteException e) {
            // Ignored
        }
    }
}

I am running on Android 11 in Google Pixel 4XL. As there are privacy restrictions for microphone access in Android 11, ran the extended service as foreground service as well. Still getting same error. Anyone faced this issue with Android 11? Thanks in advance

like image 915
Renil Joy Avatar asked Nov 06 '22 00:11

Renil Joy


1 Answers

As mentioned in the comments above, it was resolved after moved the service to run on a separate process (by specifying service with android:process in manifest)

<application
    android:name=".App"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <service android:name=".SampleSpeechRecognizerService"
        android:exported="true"
        android:foregroundServiceType="microphone"
        android:process=":speechProcess"
        android:permission="android.permission.RECORD_AUDIO">
        <intent-filter>
            <action android:name="android.speech.RecognitionService" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </service>
</application>
like image 79
Renil Joy Avatar answered Nov 15 '22 09:11

Renil Joy