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

Posted

技术标签:

【中文标题】RecognitionService:在没有RECORD_AUDIO权限的情况下调用识别服务;扩展识别服务【英文标题】:RecognitionService: call for recognition service without RECORD_AUDIO permissions; extending RecognitionService 【发布时间】:2021-11-10 03:58:20 【问题描述】:

我正在尝试扩展 RecognitionService 以尝试 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;
             

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

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
        
    

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

【问题讨论】:

您在 Android 10 或 12 上遇到过同样的问题吗?我在 Android 11 上遇到过类似的情况,记录在这里:github.com/Kaljurand/K6nele/issues/82 感谢@Kaarel,它在 Android 10 中有效,但在 12 中没有尝试。在 Android 11 中,PermissionChecker#checkCallingPermissionForDataDelivery() 有这样的检查。 if (Binder.getCallingPid() == Process.myPid()) return PERMISSION_HARD_DENIED; 。除非服务转移到不同的进程,否则这种情况是否总是成立?在 Android 10 中, if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) return PERMISSION_DENIED; 。有什么想法吗? 我对 Android 权限模型的内部了解不足,无法说明那里的 PID 检查是否正确。但它在某种程度上与我的发现一致,即将服务从服务的使用者移到一个单独的应用程序中,避免了权限问题。 再次感谢@Kaarel,在将服务移动到单独的进程上运行后,我也可以避免权限问题(通过在清单中使用 android:process 指定服务) 【参考方案1】:

如上面的 cmets 所述,将服务移动到单独的进程上运行后解决(通过在清单中使用 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>

【讨论】:

以上是关于RecognitionService:在没有RECORD_AUDIO权限的情况下调用识别服务;扩展识别服务的主要内容,如果未能解决你的问题,请参考以下文章

如何刷回官方的recovery

刷机大师进去rec选择刷机包显示clockworkmod

红米note3全网通解锁后怎么刷rec怎么刷root

如何解决这个rec ylerview问题?我的代码中有任何错误吗?在应用程序中唱歌后运行时崩溃

为啥rec.area 和rec.perimeter 总是0?

lc-矩形重叠 逆向