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权限的情况下调用识别服务;扩展识别服务的主要内容,如果未能解决你的问题,请参考以下文章
如何解决这个rec ylerview问题?我的代码中有任何错误吗?在应用程序中唱歌后运行时崩溃