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

Commit b09f7797 authored by Matt Garnes's avatar Matt Garnes Committed by Steve Kondik
Browse files

Add broadcast and query API for AudioSource.HOTWORD.

- When the AudioSource.HOTWORD input becomes active or is released, send
  a Broadcast with the package name and the new state of the audio input
  to any applications that hold CAPTURE_AUDIO_OUTPUT.
- Store the package name of the application that controls the hOTWORD
  input or set it to null if the input is not in use.
- Add a new method to AudioService to retrieve the package name of the
  application that currently controls the HOTWORD input.

Change-Id: I2f11888f3711d23b6287a4de7b81d361734a8f3b
parent 66ed04e9
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line
@@ -2677,6 +2677,45 @@ public class Intent implements Parcelable, Cloneable {
    public static final String ACTION_GET_RESTRICTION_ENTRIES =
            "android.intent.action.GET_RESTRICTION_ENTRIES";

    /**
     * <p>Broadcast Action: The state of the HOTWORD audio input has changed.:</p>
     * <ul>
     *   <li><em>state</em> - A String value indicating the state of the input.
     *   {@link #EXTRA_HOTWORD_INPUT_STATE}. The value will be one of:
     *   {@link android.media.AudioRecord#RECORDSTATE_RECORDING} or
     *   {@link android.media.AudioRecord#RECORDSTATE_STOPPED}.
     *   </li>
     *   <li><em>package</em> - A String value indicating the package name of the application
     *   that currently holds the HOTWORD input.
     *   {@link #EXTRA_CURRENT_PACKAGE_NAME}
     *   </li>
     * </ul>
     *
     * <p class="note">This is a protected intent that can only be sent
     * by the system. It can only be received by packages that hold
     * {@link android.Manifest.permission#CAPTURE_AUDIO_HOTWORD}.
     *
     * @hide
     */
    //@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_HOTWORD_INPUT_CHANGED
            = "com.cyanogenmod.intent.action.HOTWORD_INPUT_CHANGED";

    /**
     * @hide
     * Activity to challenge the user for a PIN that was configured when setting up
     * restrictions. Restrictions include blocking of apps and preventing certain user operations,
     * controlled by {@link android.os.UserManager#setUserRestrictions(Bundle).
     * Launch the activity using
     * {@link android.app.Activity#startActivityForResult(Intent, int)} and check if the
     * result is {@link android.app.Activity#RESULT_OK} for a successful response to the
     * challenge.<p/>
     * Before launching this activity, make sure that there is a PIN in effect, by calling
     * {@link android.os.UserManager#hasRestrictionsChallenge()}.
     */
    public static final String ACTION_RESTRICTIONS_CHALLENGE =
            "android.intent.action.RESTRICTIONS_CHALLENGE";

    /**
     * Sent the first time a user is starting, to allow system apps to
     * perform one time initialization.  (This will not be seen by third
@@ -3881,6 +3920,15 @@ public class Intent implements Parcelable, Cloneable {
     */
    public static final String EXTRA_THEME_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";

    /**
     * Extra for {@link #ACTION_HOTWORD_INPUT_CHANGED} that provides the state of
     * the input when the broadcast action was sent.
     * @hide
     */
    public static final String EXTRA_HOTWORD_INPUT_STATE =
            "com.cyanogenmod.intent.extra.HOTWORD_INPUT_STATE";


    // ---------------------------------------------------------------------
    // ---------------------------------------------------------------------
    // Intent flags (see mFlags variable).
+19 −0
Original line number Diff line number Diff line
@@ -903,6 +903,11 @@ public class AudioRecord
                mRecordingState = RECORDSTATE_RECORDING;
            }
        }

        if (getRecordingState() == RECORDSTATE_RECORDING &&
                getAudioSource() == MediaRecorder.AudioSource.HOTWORD) {
            handleHotwordInput(true);
        }
    }

    /**
@@ -946,6 +951,10 @@ public class AudioRecord
            native_stop();
            mRecordingState = RECORDSTATE_STOPPED;
        }

        if (getAudioSource() == MediaRecorder.AudioSource.HOTWORD) {
            handleHotwordInput(false);
        }
    }

    private final IBinder mICallBack = new Binder();
@@ -962,6 +971,16 @@ public class AudioRecord
        }
    }

    private void handleHotwordInput(boolean listening) {
        final IBinder b = ServiceManager.getService(android.content.Context.AUDIO_SERVICE);
        final IAudioService ias = IAudioService.Stub.asInterface(b);
        try {
            ias.handleHotwordInput(listening);
        } catch (RemoteException e) {
            Log.e(TAG, "Error talking to AudioService when handling hotword input.", e);
        }
    }

    //---------------------------------------------------------
    // Audio data supply
    //--------------------
+5 −0
Original line number Diff line number Diff line
@@ -227,4 +227,9 @@ interface IAudioService {
    void addMediaPlayerAndUpdateRemoteController(String packageName);

    void removeMediaPlayerAndUpdateRemoteController(String packageName);

    void handleHotwordInput(boolean listening);

    String getCurrentHotwordInputPackageName();

}
+55 −1
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.AudioPort;
import android.media.AudioRecord;
import android.media.AudioRoutesInfo;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioRoutesObserver;
@@ -405,6 +406,15 @@ public class AudioService extends IAudioService.Stub {
     * @see System#MUTE_STREAMS_AFFECTED */
    private int mMuteAffectedStreams;

    /** @see #handleHotwordInput **/
    private Object mHotwordInputLock = new Object();

    /** The package name of the application that is
     * currently using the HOTWORD input.
     */
    // protected by mHotwordInputLock
    private String mHotwordAudioInputPackage;

    /**
     * NOTE: setVibrateSetting(), getVibrateSetting(), shouldVibrate() are deprecated.
     * mVibrateSetting is just maintained during deprecation period but vibration policy is
@@ -1601,6 +1611,46 @@ public class AudioService extends IAudioService.Stub {
        sendVolumeUpdate(streamType, oldIndex, index, flags);
    }

    /**
     * Retrieve the package name of the application that currently controls
     * the {@link android.media.MediaRecorder.AudioSource#HOTWORD} input.
     * @return The package name of the application that controls the input
     * or null if no package currently controls it.
     */
    public String getCurrentHotwordInputPackageName() {
        return mHotwordAudioInputPackage;
    }

    /**
     * Handle the change of state of the HOTWORD input.
     *
     * When the {@link android.media.MediaRecorder.AudioSource#HOTWORD} input is
     * in use, send a broadcast to alert the new state and store the name of the current
     * package that controls the input in mHotwordAudioInputPackage.
     * @param listening Whether the input is being listened to.
     */
    public void handleHotwordInput(boolean listening) {
        synchronized (mHotwordInputLock) {
            Intent broadcastIntent = new Intent(Intent.ACTION_HOTWORD_INPUT_CHANGED);
            String[] packages = mContext.getPackageManager().getPackagesForUid(
                    Binder.getCallingUid());
            if (packages.length > 0) {
                if (listening) {
                    mHotwordAudioInputPackage = packages[0];
                }
                broadcastIntent.putExtra(Intent.EXTRA_CURRENT_PACKAGE_NAME, packages[0]);
            }
            broadcastIntent.putExtra(Intent.EXTRA_HOTWORD_INPUT_STATE,
                                     listening ? AudioRecord.RECORDSTATE_RECORDING :
                                     AudioRecord.RECORDSTATE_STOPPED);
            // Set the currently listening package to null if listening has stopped.
            if (!listening) {
                mHotwordAudioInputPackage = null;
            }
            sendBroadcastToAll(broadcastIntent, Manifest.permission.CAPTURE_AUDIO_HOTWORD);
        }
    }

    /** @see AudioManager#forceVolumeControlStream(int) */
    public void forceVolumeControlStream(int streamType, IBinder cb) {
        synchronized(mForceControlStreamLock) {
@@ -1653,11 +1703,15 @@ public class AudioService extends IAudioService.Stub {
    }

    private void sendBroadcastToAll(Intent intent) {
        sendBroadcastToAll(intent, null);
    }

    private void sendBroadcastToAll(Intent intent, String receiverPermission) {
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
        final long ident = Binder.clearCallingIdentity();
        try {
            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
            mContext.sendBroadcastAsUser(intent, UserHandle.ALL, receiverPermission);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }