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

Commit 07a97da5 authored by Andrew Chant's avatar Andrew Chant
Browse files

ALSA jack detection support

Adds support for ALSA jack detection for USB.
Spawns a new thread for ALSA jack detection on device
insert.  If the device doesn't support ALSA jack detection,
the thread terminates.

Test: UAC2 audio accessory and a kernel
which supports USB ALSA Jack detection, switching between
speaker and USB works perfectly with plug/unplug at jack.

Bug: 68337205
Bug: 70632415
Change-Id: I1800660ad4d2341f19ce7be6d6b01f81a7f2d1a6
parent 54b15e98
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ cc_library_static {
        "com_android_server_tv_TvUinputBridge.cpp",
        "com_android_server_tv_TvInputHal.cpp",
        "com_android_server_vr_VrManagerService.cpp",
        "com_android_server_UsbAlsaJackDetector.cpp",
        "com_android_server_UsbDeviceManager.cpp",
        "com_android_server_UsbDescriptorParser.cpp",
        "com_android_server_UsbMidiDevice.cpp",
@@ -96,6 +97,7 @@ cc_defaults {
        "libgui",
        "libusbhost",
        "libsuspend",
        "libtinyalsa",
        "libEGL",
        "libGLESv2",
        "libnetutils",
+152 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define LOG_TAG "UsbAlsaJackDetectorJNI"
#include "utils/Log.h"

#include "jni.h"
#include <nativehelper/JNIHelp.h>
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/Log.h"

#include <stdio.h>
#include <string.h>
#include <asm/byteorder.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <tinyalsa/asoundlib.h>

#define DRIVER_NAME "/dev/usb_accessory"

#define USB_IN_JACK_NAME "USB in Jack"
#define USB_OUT_JACK_NAME "USB out Jack"

namespace android
{

static jboolean is_jack_connected(jint card, const char* control) {
  struct mixer* card_mixer = mixer_open(card);
  if (card_mixer == NULL) {
    return true;
  }
  struct mixer_ctl* ctl = mixer_get_ctl_by_name(card_mixer, control);
  if (!ctl) {
    return true;
  }
  mixer_ctl_update(ctl);
  int val = mixer_ctl_get_value(ctl, 0);
  ALOGI("JACK %s - value %d\n", control, val);
  mixer_close(card_mixer);

  return val != 0;
}

static jboolean android_server_UsbAlsaJackDetector_hasJackDetect(JNIEnv* /* env */,
                                                                 jobject /* thiz */,
                                                                 jint card)
{
    struct mixer* card_mixer = mixer_open(card);
    if (card_mixer == NULL) {
        return false;
    }

    jboolean has_jack = false;
    if ((mixer_get_ctl_by_name(card_mixer, USB_IN_JACK_NAME) != NULL) ||
            (mixer_get_ctl_by_name(card_mixer, USB_OUT_JACK_NAME) != NULL)) {
        has_jack = true;
    }
    mixer_close(card_mixer);
    return has_jack;
}


static jboolean android_server_UsbAlsaJackDetector_inputJackConnected(JNIEnv* /* env */,
                                                                      jobject /* thiz */,
                                                                      jint card)
{
    return is_jack_connected(card, USB_IN_JACK_NAME);
}


static jboolean android_server_UsbAlsaJackDetector_outputJackConnected(JNIEnv* /* env */,
                                                                       jobject /* thiz */,
                                                                       jint card)
{
    return is_jack_connected(card, USB_OUT_JACK_NAME);
}

static void android_server_UsbAlsaJackDetector_jackDetect(JNIEnv* env,
                                                                                                        jobject thiz,
                                                                                                        jint card) {
    jclass jdclass = env->GetObjectClass(thiz);
    jmethodID method_jackDetectCallback = env->GetMethodID(jdclass, "jackDetectCallback", "()Z");
    if (method_jackDetectCallback == NULL) {
        ALOGE("Can't find jackDetectCallback");
        return;
    }

    struct mixer* m = mixer_open(card);
    if (!m) {
        ALOGE("Jack detect unable to open mixer\n");
        return;
    }
    mixer_subscribe_events(m, 1);
    do {

        // Wait for a mixer event.  Retry if interrupted, exit on error.
        int retval;
        do {
            retval = mixer_wait_event(m, -1);
        } while (retval == -EINTR);
        if (retval < 0) {
            break;
        }
        mixer_consume_event(m);
    } while (env->CallBooleanMethod(thiz, method_jackDetectCallback));

    mixer_close(m);
    return;
}

static const JNINativeMethod method_table[] = {
    { "nativeHasJackDetect", "(I)Z", (void*)android_server_UsbAlsaJackDetector_hasJackDetect },
    { "nativeInputJackConnected",     "(I)Z",
            (void*)android_server_UsbAlsaJackDetector_inputJackConnected },
    { "nativeOutputJackConnected",    "(I)Z",
            (void*)android_server_UsbAlsaJackDetector_outputJackConnected },
    { "nativeJackDetect", "(I)Z", (void*)android_server_UsbAlsaJackDetector_jackDetect },
};

int register_android_server_UsbAlsaJackDetector(JNIEnv *env)
{
    jclass clazz = env->FindClass("com/android/server/usb/UsbAlsaJackDetector");
    if (clazz == NULL) {
        ALOGE("Can't find com/android/server/usb/UsbAlsaJackDetector");
        return -1;
    }

    if (!jniRegisterNativeMethods(env, "com/android/server/usb/UsbAlsaJackDetector",
            method_table, NELEM(method_table))) {
      ALOGE("Can't register UsbAlsaJackDetector native methods");
      return -1;
    }

    return 0;
}

}
+2 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_storage_AppFuse(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_UsbAlsaJackDetector(JNIEnv* env);
int register_android_server_UsbDeviceManager(JNIEnv* env);
int register_android_server_UsbMidiDevice(JNIEnv* env);
int register_android_server_UsbHostManager(JNIEnv* env);
@@ -81,6 +82,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
    register_android_server_AlarmManagerService(env);
    register_android_server_UsbDeviceManager(env);
    register_android_server_UsbMidiDevice(env);
    register_android_server_UsbAlsaJackDetector(env);
    register_android_server_UsbHostManager(env);
    register_android_server_vr_VrManagerService(env);
    register_android_server_VibratorService(env);
+115 −2
Original line number Diff line number Diff line
@@ -17,6 +17,9 @@
package com.android.server.usb;

import android.annotation.NonNull;
import android.media.AudioSystem;
import android.media.IAudioService;
import android.os.RemoteException;
import android.service.usb.UsbAlsaDeviceProto;
import android.util.Slog;

@@ -32,20 +35,26 @@ public final class UsbAlsaDevice {

    private final int mCardNum;
    private final int mDeviceNum;
    private final String mDeviceAddress;
    private final boolean mHasOutput;
    private final boolean mHasInput;

    private final boolean mIsInputHeadset;
    private final boolean mIsOutputHeadset;

    private final String mDeviceAddress;
    private boolean mSelected = false;
    private int mOutputState;
    private int mInputState;
    private UsbAlsaJackDetector mJackDetector;
    private IAudioService mAudioService;

    private String mDeviceName = "";
    private String mDeviceDescription = "";

    public UsbAlsaDevice(int card, int device, String deviceAddress,
    public UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress,
            boolean hasOutput, boolean hasInput,
            boolean isInputHeadset, boolean isOutputHeadset) {
        mAudioService = audioService;
        mCardNum = card;
        mDeviceNum = device;
        mDeviceAddress = deviceAddress;
@@ -116,6 +125,110 @@ public final class UsbAlsaDevice {
        return mIsOutputHeadset;
    }

    /**
     * @returns true if input jack is detected or jack detection is not supported.
     */
    private synchronized boolean isInputJackConnected() {
        if (mJackDetector == null) {
            return true;  // If jack detect isn't supported, say it's connected.
        }
        return mJackDetector.isInputJackConnected();
    }

    /**
     * @returns true if input jack is detected or jack detection is not supported.
     */
    private synchronized boolean isOutputJackConnected() {
        if (mJackDetector == null) {
            return true;  // if jack detect isn't supported, say it's connected.
        }
        return mJackDetector.isOutputJackConnected();
    }

    /** Begins a jack-detection thread. */
    private synchronized void startJackDetect() {
        // If no jack detect capabilities exist, mJackDetector will be null.
        mJackDetector = UsbAlsaJackDetector.startJackDetect(this);
    }

    /** Stops a jack-detection thread. */
    private synchronized void stopJackDetect() {
        if (mJackDetector != null) {
            mJackDetector.pleaseStop();
        }
        mJackDetector = null;
    }

    /** Start using this device as the selected USB Audio Device. */
    public synchronized void start() {
        mSelected = true;
        mInputState = 0;
        mOutputState = 0;
        startJackDetect();
        updateWiredDeviceConnectionState(true);
    }

    /** Stop using this device as the selected USB Audio Device. */
    public synchronized void stop() {
        stopJackDetect();
        updateWiredDeviceConnectionState(false);
        mSelected = false;
    }

    /** Updates AudioService with the connection state of the alsaDevice.
     *  Checks ALSA Jack state for inputs and outputs before reporting.
     */
    public synchronized void updateWiredDeviceConnectionState(boolean enable) {
        if (!mSelected) {
            Slog.e(TAG, "updateWiredDeviceConnectionState on unselected AlsaDevice!");
            return;
        }
        String alsaCardDeviceString = getAlsaCardDeviceString();
        if (alsaCardDeviceString == null) {
            return;
        }
        try {
            // Output Device
            if (mHasOutput) {
                int device = mIsOutputHeadset
                        ? AudioSystem.DEVICE_OUT_USB_HEADSET
                        : AudioSystem.DEVICE_OUT_USB_DEVICE;
                if (DEBUG) {
                    Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device)
                            + " addr:" + alsaCardDeviceString
                            + " name:" + mDeviceName);
                }
                boolean connected = isOutputJackConnected();
                Slog.i(TAG, "OUTPUT JACK connected: " + connected);
                int outputState = (enable && connected) ? 1 : 0;
                if (outputState != mOutputState) {
                    mOutputState = outputState;
                    mAudioService.setWiredDeviceConnectionState(device, outputState,
                                                                alsaCardDeviceString,
                                                                mDeviceName, TAG);
                }
            }

            // Input Device
            if (mHasInput) {
                int device = mIsInputHeadset ? AudioSystem.DEVICE_IN_USB_HEADSET
                        : AudioSystem.DEVICE_IN_USB_DEVICE;
                boolean connected = isInputJackConnected();
                Slog.i(TAG, "INPUT JACK connected: " + connected);
                int inputState = (enable && connected) ? 1 : 0;
                if (inputState != mInputState) {
                    mInputState = inputState;
                    mAudioService.setWiredDeviceConnectionState(
                            device, inputState, alsaCardDeviceString,
                            mDeviceName, TAG);
                }
            }
        } catch (RemoteException e) {
            Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
        }
    }


    /**
     * @Override
     * @returns a string representation of the object.
+97 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.usb;

/**
 * Detects and reports ALSA jack state and events.
 */
public final class UsbAlsaJackDetector implements Runnable {
    private static final String TAG = "UsbAlsaJackDetector";

    private static native boolean nativeHasJackDetect(int card);
    private native boolean nativeJackDetect(int card);
    private native boolean nativeOutputJackConnected(int card);
    private native boolean nativeInputJackConnected(int card);

    private boolean mStopJackDetect = false;
    private UsbAlsaDevice mAlsaDevice;

    /* use startJackDetect to create a UsbAlsaJackDetector */
    private UsbAlsaJackDetector(UsbAlsaDevice device) {
        mAlsaDevice = device;
    }

    /** If jack detection is detected on the given Alsa Device,
     * create and return a UsbAlsaJackDetector which will update wired device state
     * each time a jack detection event is registered.
     *
     * @returns UsbAlsaJackDetector if jack detect is supported, or null.
     */
    public static UsbAlsaJackDetector startJackDetect(UsbAlsaDevice device) {
        if (!nativeHasJackDetect(device.getCardNum())) {
            return null;
        }
        UsbAlsaJackDetector jackDetector = new UsbAlsaJackDetector(device);

        // This thread will exit once the USB device disappears.
        // It can also be convinced to stop with pleaseStop().
        new Thread(jackDetector, "USB jack detect thread").start();
        return jackDetector;
    }

    public boolean isInputJackConnected() {
        return nativeInputJackConnected(mAlsaDevice.getCardNum());
    }

    public boolean isOutputJackConnected() {
        return nativeOutputJackConnected(mAlsaDevice.getCardNum());
    }

    /**
     * Stop the jack detect thread from calling back into UsbAlsaDevice.
     * This doesn't force the thread to stop (which is deprecated in java and dangerous due to
     * locking issues), but will cause the thread to exit at the next safe opportunity.
     */
    public void pleaseStop() {
        synchronized (this) {
            mStopJackDetect = true;
        }
    }

    /**
     * Called by nativeJackDetect each time a jack detect event is reported.
     * @return false when the jackDetect thread should stop.  true otherwise.
     */
    public boolean jackDetectCallback() {
        synchronized (this) {
            if (mStopJackDetect) {
                return false;
            }
            mAlsaDevice.updateWiredDeviceConnectionState(true);
        }
        return true;
    }

    /**
     * This will call jackDetectCallback each time it detects a jack detect event.
     * If jackDetectCallback returns false, this function will return.
     */
    public void run() {
        nativeJackDetect(mAlsaDevice.getCardNum());
    }
}
Loading