Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 04fa2534 authored by Sergey Volnov's avatar Sergey Volnov
Browse files

Change attribution integration points for SpeechRecognizer.

I've deleted the extra attribution for now just to fix the bug asap, but
we need a long-term sync to make sure attribution works properly. For
both cases when the RS app decides to integrate with the new attribution
framework OR decides to ignore it.

Bug: 184963112
Test: atest CtsVoiceRecognitionTestCases
Change-Id: I20bd8174c1a188da6c5af55cdd3bebee8a638d2d
parent 1b2c6656
Loading
Loading
Loading
Loading
+0 −1
Original line number Original line Diff line number Diff line
@@ -58,7 +58,6 @@ oneway interface IRecognitionService {
     * Cancels the speech recognition.
     * Cancels the speech recognition.
     *
     *
     * @param listener to receive callbacks, note that this must be non-null
     * @param listener to receive callbacks, note that this must be non-null
     * @param packageName the package name calling this API
     */
     */
    void cancel(in IRecognitionListener listener, boolean isShutdown);
    void cancel(in IRecognitionListener listener, boolean isShutdown);
}
}
+59 −88
Original line number Original line Diff line number Diff line
@@ -34,7 +34,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder;
import android.os.Message;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.RemoteException;
import android.util.Log;
import android.util.Log;


@@ -66,7 +65,12 @@ public abstract class RecognitionService extends Service {
    private static final String TAG = "RecognitionService";
    private static final String TAG = "RecognitionService";


    /** Debugging flag */
    /** Debugging flag */
    private static final boolean DBG = false;
    private static final boolean DBG = true;

    private static final String RECORD_AUDIO_APP_OP =
            AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO);
    private static final int RECORD_AUDIO_APP_OP_CODE =
            AppOpsManager.permissionToOpCode(Manifest.permission.RECORD_AUDIO);


    /** Binder of the recognition service */
    /** Binder of the recognition service */
    private RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this);
    private RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this);
@@ -97,7 +101,7 @@ public abstract class RecognitionService extends Service {
                    dispatchStopListening((IRecognitionListener) msg.obj);
                    dispatchStopListening((IRecognitionListener) msg.obj);
                    break;
                    break;
                case MSG_CANCEL:
                case MSG_CANCEL:
                    dispatchCancel((IRecognitionListener) msg.obj, msg.arg1 == 1);
                    dispatchCancel((IRecognitionListener) msg.obj);
                    break;
                    break;
                case MSG_RESET:
                case MSG_RESET:
                    dispatchClearCallback();
                    dispatchClearCallback();
@@ -108,19 +112,21 @@ public abstract class RecognitionService extends Service {


    private void dispatchStartListening(Intent intent, final IRecognitionListener listener,
    private void dispatchStartListening(Intent intent, final IRecognitionListener listener,
            @NonNull AttributionSource attributionSource) {
            @NonNull AttributionSource attributionSource) {
        try {
            if (mCurrentCallback == null) {
            if (mCurrentCallback == null) {
            if (DBG) Log.d(TAG, "created new mCurrentCallback, listener = " + listener.asBinder());
                if (DBG) {
                    Log.d(TAG, "created new mCurrentCallback, listener = " + listener.asBinder());
                }
                mCurrentCallback = new Callback(listener, attributionSource);
                mCurrentCallback = new Callback(listener, attributionSource);


                RecognitionService.this.onStartListening(intent, mCurrentCallback);
                RecognitionService.this.onStartListening(intent, mCurrentCallback);
            } else {
            } else {
            try {
                listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
                listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
                Log.i(TAG, "concurrent startListening received - ignoring this call");
            }
        } catch (RemoteException e) {
        } catch (RemoteException e) {
            Log.d(TAG, "onError call from startListening failed");
            Log.d(TAG, "onError call from startListening failed");
        }
        }
            Log.i(TAG, "concurrent startListening received - ignoring this call");
        }
    }
    }


    private void dispatchStopListening(IRecognitionListener listener) {
    private void dispatchStopListening(IRecognitionListener listener) {
@@ -139,16 +145,13 @@ public abstract class RecognitionService extends Service {
        }
        }
    }
    }


    private void dispatchCancel(IRecognitionListener listener, boolean shutDown) {
    private void dispatchCancel(IRecognitionListener listener) {
        if (mCurrentCallback == null) {
        if (mCurrentCallback == null) {
            if (DBG) Log.d(TAG, "cancel called with no preceding startListening - ignoring");
            if (DBG) Log.d(TAG, "cancel called with no preceding startListening - ignoring");
        } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
        } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
            Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
            Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
        } else { // the correct state
        } else { // the correct state
            RecognitionService.this.onCancel(mCurrentCallback);
            RecognitionService.this.onCancel(mCurrentCallback);
            if (shutDown) {
                mCurrentCallback.finishRecordAudioOpAttributionToCallerIfNeeded();
            }
            mCurrentCallback = null;
            mCurrentCallback = null;
            if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
            if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
        }
        }
@@ -172,47 +175,6 @@ public abstract class RecognitionService extends Service {
        }
        }
    }
    }


    /**
     * Checks whether the caller has sufficient permissions
     * 
     * @param listener to send the error message to in case of error
     * @param forDataDelivery If the permission check is for delivering the sensitive data.
     * @param packageName the package name of the caller
     * @param featureId The feature in the package
     * @return {@code true} if the caller has enough permissions, {@code false} otherwise
     */
    private boolean checkPermissions(IRecognitionListener listener, boolean forDataDelivery,
            @NonNull String packageName, @Nullable String featureId) {
        if (DBG) Log.d(TAG, "checkPermissions");

        final int callingUid = Binder.getCallingUid();
        if (callingUid == Process.SYSTEM_UID) {
            // Assuming system has verified permissions of the caller.
            return true;
        }

        if (forDataDelivery) {
            if (PermissionChecker.checkCallingOrSelfPermissionForDataDelivery(this,
                    android.Manifest.permission.RECORD_AUDIO, packageName, featureId,
                    null /*message*/) == PermissionChecker.PERMISSION_GRANTED) {
                return true;
            }
        } else {
            if (PermissionChecker.checkCallingOrSelfPermissionForPreflight(this,
                    android.Manifest.permission.RECORD_AUDIO)
                            == PermissionChecker.PERMISSION_GRANTED) {
                return true;
            }
        }
        try {
            Log.e(TAG, "call for recognition service without RECORD_AUDIO permissions");
            listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
        } catch (RemoteException re) {
            Log.e(TAG, "sending ERROR_INSUFFICIENT_PERMISSIONS message failed", re);
        }
        return false;
    }

    /**
    /**
     * Notifies the service that it should start listening for speech.
     * Notifies the service that it should start listening for speech.
     * 
     * 
@@ -281,7 +243,6 @@ public abstract class RecognitionService extends Service {
         *        single channel audio stream. The sample rate is implementation dependent.
         *        single channel audio stream. The sample rate is implementation dependent.
         */
         */
        public void bufferReceived(byte[] buffer) throws RemoteException {
        public void bufferReceived(byte[] buffer) throws RemoteException {
            startRecordAudioOpAttributionToCallerIfNeeded();
            mListener.onBufferReceived(buffer);
            mListener.onBufferReceived(buffer);
        }
        }


@@ -314,7 +275,6 @@ public abstract class RecognitionService extends Service {
         *        {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
         *        {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
         */
         */
        public void partialResults(Bundle partialResults) throws RemoteException {
        public void partialResults(Bundle partialResults) throws RemoteException {
            startRecordAudioOpAttributionToCallerIfNeeded();
            mListener.onPartialResults(partialResults);
            mListener.onPartialResults(partialResults);
        }
        }


@@ -336,7 +296,6 @@ public abstract class RecognitionService extends Service {
         *        {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
         *        {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
         */
         */
        public void results(Bundle results) throws RemoteException {
        public void results(Bundle results) throws RemoteException {
            startRecordAudioOpAttributionToCallerIfNeeded();
            Message.obtain(mHandler, MSG_RESET).sendToTarget();
            Message.obtain(mHandler, MSG_RESET).sendToTarget();
            mListener.onResults(results);
            mListener.onResults(results);
        }
        }
@@ -366,9 +325,6 @@ public abstract class RecognitionService extends Service {
         * and passing this identity to {@link
         * and passing this identity to {@link
         * android.content.ContextParams.Builder#setNextAttributionSource(AttributionSource)}.
         * android.content.ContextParams.Builder#setNextAttributionSource(AttributionSource)}.
         *
         *
         *
         *
         *
         * @return The permission identity of the calling app.
         * @return The permission identity of the calling app.
         *
         *
         * @see android.content.ContextParams.Builder#setNextAttributionSource(
         * @see android.content.ContextParams.Builder#setNextAttributionSource(
@@ -379,40 +335,55 @@ public abstract class RecognitionService extends Service {
            return mCallingAttributionSource;
            return mCallingAttributionSource;
        }
        }


        private void startRecordAudioOpAttributionToCallerIfNeeded() throws RemoteException {
        boolean maybeStartAttribution() {
            if (!isProxyingRecordAudioToCaller()) {
            if (DBG) {
                final int result = PermissionChecker.checkPermissionAndStartDataDelivery(
                Log.i(TAG, "Starting attribution");
                        RecognitionService.this, Manifest.permission.RECORD_AUDIO,
                        getAttributionContextForCaller().getAttributionSource(),
                        /*message*/ null);
                if (result == PermissionChecker.PERMISSION_GRANTED) {
                    return;
                }
                error(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
            }
            }

            if (DBG && isProxyingRecordAudioToCaller()) {
                Log.i(TAG, "Proxying already in progress, not starting the attribution");
            }
            }


        private @NonNull Context getAttributionContextForCaller() {
            if (!isProxyingRecordAudioToCaller()) {
            if (mAttributionContext == null) {
                mAttributionContext = createContext(new ContextParams.Builder()
                mAttributionContext = createContext(new ContextParams.Builder()
                        .setNextAttributionSource(mCallingAttributionSource)
                        .setNextAttributionSource(mCallingAttributionSource)
                        .build());
                        .build());

                final int result = PermissionChecker.checkPermissionAndStartDataDelivery(
                        RecognitionService.this,
                        Manifest.permission.RECORD_AUDIO,
                        mAttributionContext.getAttributionSource(),
                        /*message*/ null);

                return result == PermissionChecker.PERMISSION_GRANTED;
            }
            }
            return mAttributionContext;
            return false;
        }

        void maybeFinishAttribution() {
            if (DBG) {
                Log.i(TAG, "Finishing attribution");
            }

            if (DBG && !isProxyingRecordAudioToCaller()) {
                Log.i(TAG, "Not proxying currently, not finishing the attribution");
            }
            }


        void finishRecordAudioOpAttributionToCallerIfNeeded() {
            if (isProxyingRecordAudioToCaller()) {
            if (isProxyingRecordAudioToCaller()) {
                final String op = AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO);
                PermissionChecker.finishDataDelivery(
                PermissionChecker.finishDataDelivery(RecognitionService.this,
                        RecognitionService.this,
                        op, getAttributionContextForCaller().getAttributionSource());
                        RECORD_AUDIO_APP_OP,
                        mAttributionContext.getAttributionSource());

                mAttributionContext = null;
            }
            }
        }
        }


        private boolean isProxyingRecordAudioToCaller() {
        private boolean isProxyingRecordAudioToCaller() {
            final int op = AppOpsManager.permissionToOpCode(Manifest.permission.RECORD_AUDIO);
            final AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
            final AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
            return appOpsManager.isProxying(op, getAttributionTag(),
            return appOpsManager.isProxying(
                    RECORD_AUDIO_APP_OP_CODE,
                    getAttributionTag(),
                    mCallingAttributionSource.getUid(),
                    mCallingAttributionSource.getUid(),
                    mCallingAttributionSource.getPackageName());
                    mCallingAttributionSource.getPackageName());
        }
        }
@@ -423,7 +394,7 @@ public abstract class RecognitionService extends Service {
        private final WeakReference<RecognitionService> mServiceRef;
        private final WeakReference<RecognitionService> mServiceRef;


        public RecognitionServiceBinder(RecognitionService service) {
        public RecognitionServiceBinder(RecognitionService service) {
            mServiceRef = new WeakReference<RecognitionService>(service);
            mServiceRef = new WeakReference<>(service);
        }
        }


        @Override
        @Override
@@ -445,8 +416,8 @@ public abstract class RecognitionService extends Service {
            if (DBG) Log.d(TAG, "stopListening called by:" + listener.asBinder());
            if (DBG) Log.d(TAG, "stopListening called by:" + listener.asBinder());
            final RecognitionService service = mServiceRef.get();
            final RecognitionService service = mServiceRef.get();
            if (service != null) {
            if (service != null) {
                service.mHandler.sendMessage(Message.obtain(service.mHandler,
                service.mHandler.sendMessage(
                        MSG_STOP_LISTENING, listener));
                        Message.obtain(service.mHandler, MSG_STOP_LISTENING, listener));
            }
            }
        }
        }


@@ -455,8 +426,8 @@ public abstract class RecognitionService extends Service {
            if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder());
            if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder());
            final RecognitionService service = mServiceRef.get();
            final RecognitionService service = mServiceRef.get();
            if (service != null) {
            if (service != null) {
                service.mHandler.sendMessage(Message.obtain(service.mHandler,
                service.mHandler.sendMessage(
                        MSG_CANCEL, isShutdown ? 1 : 0, 0, listener));
                        Message.obtain(service.mHandler, MSG_CANCEL, listener));
            }
            }
        }
        }