RecognitionService:调用没有RECORD_AUDIO权限的识别服务;扩展识别服务

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

我正在尝试扩展 RecognitionService 以尝试不同的 Speech to Text 服务,而不是 google 提供的服务。为了检查 SpeechRecognizer 是否正确初始化,现在给出了虚拟实现。当在 RecognitionService#checkPermissions().

中完成以下检查时,我得到“RecognitionService:调用没有 RECORD_AUDIO 权限的识别服务”
   if (PermissionChecker.checkCallingPermissionForDataDelivery(this,
                    android.Manifest.permission.RECORD_AUDIO, packageName, featureId,
                    null /*message*/)
                             == PermissionChecker.PERMISSION_GRANTED) {
                return true;
            } 

请注意,已检查 报告了问题,并且我验证了在我的扩展服务中,使用以下检查时存在此权限。

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

Android 清单文件:

<?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>

主要活动

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
        }
    }
}

我 运行 Android 11 Google Pixel 4XL。由于在 Android 11 中有 privacy restrictions 用于麦克风访问,因此 运行 扩展服务也作为前台服务。仍然遇到同样的错误。 Android 11 有人遇到过这个问题吗?提前致谢

如上面的评论所述,在将服务移动到单独进程的 运行 后解决(通过在清单中使用 android:process 指定服务)

<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>