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