Merge "SoundTrigger DeviceState" into udc-dev am: 8e471cfef6
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/22505323 Change-Id: I24fbadb5ddae293300aa3d24f85a8fc44c2be54d Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -0,0 +1,261 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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.soundtrigger;
|
||||||
|
|
||||||
|
import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_DISABLED;
|
||||||
|
import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
|
||||||
|
import static android.os.PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
|
||||||
|
|
||||||
|
import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState;
|
||||||
|
import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState.*;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
|
import androidx.test.filters.FlakyTest;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
import com.android.server.utils.EventLogger;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public final class DeviceStateHandlerTest {
|
||||||
|
private final long CONFIRM_NO_EVENT_WAIT_MS = 1000;
|
||||||
|
// A wait substantially less than the duration we delay phone notifications by
|
||||||
|
private final long PHONE_DELAY_BRIEF_WAIT_MS =
|
||||||
|
DeviceStateHandler.CALL_INACTIVE_MSG_DELAY_MS / 4;
|
||||||
|
|
||||||
|
private DeviceStateHandler mHandler;
|
||||||
|
private DeviceStateHandler.DeviceStateListener mDeviceStateCallback;
|
||||||
|
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private SoundTriggerDeviceState mState;
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private CountDownLatch mLatch;
|
||||||
|
|
||||||
|
private EventLogger mEventLogger;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
// Reset the state prior to each test
|
||||||
|
mEventLogger = new EventLogger(256, "test logger");
|
||||||
|
synchronized (mLock) {
|
||||||
|
mLatch = new CountDownLatch(1);
|
||||||
|
}
|
||||||
|
mDeviceStateCallback =
|
||||||
|
(SoundTriggerDeviceState state) -> {
|
||||||
|
synchronized (mLock) {
|
||||||
|
mState = state;
|
||||||
|
mLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mHandler = new DeviceStateHandler(Runnable::run, mEventLogger);
|
||||||
|
mHandler.onPhoneCallStateChanged(false);
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
|
||||||
|
mHandler.registerListener(mDeviceStateCallback);
|
||||||
|
try {
|
||||||
|
waitAndAssertState(ENABLE);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitAndAssertState(SoundTriggerDeviceState state) throws InterruptedException {
|
||||||
|
CountDownLatch latch;
|
||||||
|
synchronized (mLock) {
|
||||||
|
latch = mLatch;
|
||||||
|
}
|
||||||
|
latch.await();
|
||||||
|
synchronized (mLock) {
|
||||||
|
assertThat(mState).isEqualTo(state);
|
||||||
|
mLatch = new CountDownLatch(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitToConfirmNoEventReceived() throws InterruptedException {
|
||||||
|
CountDownLatch latch;
|
||||||
|
synchronized (mLock) {
|
||||||
|
latch = mLatch;
|
||||||
|
}
|
||||||
|
// Check that we time out
|
||||||
|
assertThat(latch.await(CONFIRM_NO_EVENT_WAIT_MS, TimeUnit.MILLISECONDS)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onPowerModeChangedCritical_receiveStateChange() throws Exception {
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
|
||||||
|
waitAndAssertState(CRITICAL);
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
|
||||||
|
waitAndAssertState(ENABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onPowerModeChangedDisabled_receiveStateChange() throws Exception {
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
|
||||||
|
waitAndAssertState(DISABLE);
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
|
||||||
|
waitAndAssertState(ENABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onPowerModeChangedMultiple_receiveStateChange() throws Exception {
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
|
||||||
|
waitAndAssertState(DISABLE);
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
|
||||||
|
waitAndAssertState(CRITICAL);
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
|
||||||
|
waitAndAssertState(DISABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onPowerModeSameState_noStateChange() throws Exception {
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
|
||||||
|
waitAndAssertState(DISABLE);
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
|
||||||
|
waitToConfirmNoEventReceived();
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
|
||||||
|
waitAndAssertState(CRITICAL);
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
|
||||||
|
waitToConfirmNoEventReceived();
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
|
||||||
|
waitAndAssertState(ENABLE);
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
|
||||||
|
waitToConfirmNoEventReceived();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onPhoneCall_receiveStateChange() throws Exception {
|
||||||
|
mHandler.onPhoneCallStateChanged(true);
|
||||||
|
waitAndAssertState(DISABLE);
|
||||||
|
mHandler.onPhoneCallStateChanged(false);
|
||||||
|
waitAndAssertState(ENABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onPhoneCall_receiveStateChangeIsDelayed() throws Exception {
|
||||||
|
mHandler.onPhoneCallStateChanged(true);
|
||||||
|
waitAndAssertState(DISABLE);
|
||||||
|
long beforeTime = SystemClock.uptimeMillis();
|
||||||
|
mHandler.onPhoneCallStateChanged(false);
|
||||||
|
waitAndAssertState(ENABLE);
|
||||||
|
long afterTime = SystemClock.uptimeMillis();
|
||||||
|
assertThat(afterTime - beforeTime).isAtLeast(DeviceStateHandler.CALL_INACTIVE_MSG_DELAY_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onPhoneCallEnterExitEnter_receiveNoStateChange() throws Exception {
|
||||||
|
mHandler.onPhoneCallStateChanged(true);
|
||||||
|
waitAndAssertState(DISABLE);
|
||||||
|
mHandler.onPhoneCallStateChanged(false);
|
||||||
|
SystemClock.sleep(PHONE_DELAY_BRIEF_WAIT_MS);
|
||||||
|
mHandler.onPhoneCallStateChanged(true);
|
||||||
|
waitToConfirmNoEventReceived();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onBatteryCallbackDuringPhoneWait_receiveStateChangeDelayed() throws Exception {
|
||||||
|
mHandler.onPhoneCallStateChanged(true);
|
||||||
|
waitAndAssertState(DISABLE);
|
||||||
|
mHandler.onPhoneCallStateChanged(false);
|
||||||
|
SystemClock.sleep(PHONE_DELAY_BRIEF_WAIT_MS);
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
|
||||||
|
waitAndAssertState(CRITICAL);
|
||||||
|
// Ensure we don't get an ENABLE event after
|
||||||
|
waitToConfirmNoEventReceived();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onBatteryChangeWhenInPhoneCall_receiveNoStateChange() throws Exception {
|
||||||
|
mHandler.onPhoneCallStateChanged(true);
|
||||||
|
waitAndAssertState(DISABLE);
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
|
||||||
|
waitToConfirmNoEventReceived();
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
|
||||||
|
waitToConfirmNoEventReceived();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenBatteryCriticalChangeDuringCallAfterPhoneCall_receiveCriticalStateChange()
|
||||||
|
throws Exception {
|
||||||
|
mHandler.onPhoneCallStateChanged(true);
|
||||||
|
waitAndAssertState(DISABLE);
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
|
||||||
|
waitToConfirmNoEventReceived();
|
||||||
|
mHandler.onPhoneCallStateChanged(false);
|
||||||
|
waitAndAssertState(CRITICAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenBatteryDisableDuringCallAfterPhoneCallBatteryEnable_receiveStateChange()
|
||||||
|
throws Exception {
|
||||||
|
mHandler.onPhoneCallStateChanged(true);
|
||||||
|
waitAndAssertState(DISABLE);
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
|
||||||
|
waitToConfirmNoEventReceived();
|
||||||
|
mHandler.onPhoneCallStateChanged(false);
|
||||||
|
waitToConfirmNoEventReceived();
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
|
||||||
|
waitAndAssertState(CRITICAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenPhoneCallDuringBatteryDisable_receiveNoStateChange() throws Exception {
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
|
||||||
|
waitAndAssertState(DISABLE);
|
||||||
|
mHandler.onPhoneCallStateChanged(true);
|
||||||
|
waitToConfirmNoEventReceived();
|
||||||
|
mHandler.onPhoneCallStateChanged(false);
|
||||||
|
waitToConfirmNoEventReceived();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenPhoneCallDuringBatteryCritical_receiveStateChange() throws Exception {
|
||||||
|
mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
|
||||||
|
waitAndAssertState(CRITICAL);
|
||||||
|
mHandler.onPhoneCallStateChanged(true);
|
||||||
|
waitAndAssertState(DISABLE);
|
||||||
|
mHandler.onPhoneCallStateChanged(false);
|
||||||
|
waitAndAssertState(CRITICAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test could be flaky, but we want to verify that we only delay notification if
|
||||||
|
// we are exiting a call, NOT if we are entering a call.
|
||||||
|
@FlakyTest
|
||||||
|
@Test
|
||||||
|
public void whenPhoneCallReceived_receiveStateChangeFast() throws Exception {
|
||||||
|
mHandler.onPhoneCallStateChanged(true);
|
||||||
|
CountDownLatch latch;
|
||||||
|
synchronized (mLock) {
|
||||||
|
latch = mLatch;
|
||||||
|
}
|
||||||
|
assertThat(latch.await(PHONE_DELAY_BRIEF_WAIT_MS, TimeUnit.MILLISECONDS)).isTrue();
|
||||||
|
synchronized (mLock) {
|
||||||
|
assertThat(mState).isEqualTo(DISABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2023 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.soundtrigger;
|
||||||
|
|
||||||
|
import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_DISABLED;
|
||||||
|
import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
|
||||||
|
import static android.os.PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
import com.android.server.utils.EventLogger;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages device state events which require pausing SoundTrigger recognition
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public class DeviceStateHandler implements PhoneCallStateHandler.Callback {
|
||||||
|
|
||||||
|
public static final long CALL_INACTIVE_MSG_DELAY_MS = 1000;
|
||||||
|
|
||||||
|
public interface DeviceStateListener {
|
||||||
|
void onSoundTriggerDeviceStateUpdate(SoundTriggerDeviceState state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SoundTriggerDeviceState {
|
||||||
|
DISABLE, // The device state requires all SoundTrigger sessions are disabled
|
||||||
|
CRITICAL, // The device state requires all non-critical SoundTrigger sessions are disabled
|
||||||
|
ENABLE // The device state permits all SoundTrigger sessions
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
|
||||||
|
private final EventLogger mEventLogger;
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
SoundTriggerDeviceState mSoundTriggerDeviceState = SoundTriggerDeviceState.ENABLE;
|
||||||
|
|
||||||
|
// Individual components of the SoundTriggerDeviceState
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private int mSoundTriggerPowerSaveMode = SOUND_TRIGGER_MODE_ALL_ENABLED;
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private boolean mIsPhoneCallOngoing = false;
|
||||||
|
|
||||||
|
// There can only be one pending notify at any given time.
|
||||||
|
// If any phone state change comes in between, we will cancel the previous pending
|
||||||
|
// task.
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private NotificationTask mPhoneStateChangePendingNotify = null;
|
||||||
|
|
||||||
|
private Set<DeviceStateListener> mCallbackSet = ConcurrentHashMap.newKeySet(4);
|
||||||
|
|
||||||
|
private final Executor mDelayedNotificationExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
|
private final Executor mCallbackExecutor;
|
||||||
|
|
||||||
|
public void onPowerModeChanged(int soundTriggerPowerSaveMode) {
|
||||||
|
mEventLogger.enqueue(new SoundTriggerPowerEvent(soundTriggerPowerSaveMode));
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (soundTriggerPowerSaveMode == mSoundTriggerPowerSaveMode) {
|
||||||
|
// No state change, nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mSoundTriggerPowerSaveMode = soundTriggerPowerSaveMode;
|
||||||
|
evaluateStateChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPhoneCallStateChanged(boolean isInPhoneCall) {
|
||||||
|
mEventLogger.enqueue(new PhoneCallEvent(isInPhoneCall));
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mIsPhoneCallOngoing == isInPhoneCall) {
|
||||||
|
// no change, nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Clear any pending notification
|
||||||
|
if (mPhoneStateChangePendingNotify != null) {
|
||||||
|
mPhoneStateChangePendingNotify.cancel();
|
||||||
|
mPhoneStateChangePendingNotify = null;
|
||||||
|
}
|
||||||
|
mIsPhoneCallOngoing = isInPhoneCall;
|
||||||
|
if (!mIsPhoneCallOngoing) {
|
||||||
|
// State has changed from call to no call, delay notification
|
||||||
|
mPhoneStateChangePendingNotify = new NotificationTask(
|
||||||
|
new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mPhoneStateChangePendingNotify != null &&
|
||||||
|
mPhoneStateChangePendingNotify.runnableEquals(this)) {
|
||||||
|
|
||||||
|
mPhoneStateChangePendingNotify = null;
|
||||||
|
evaluateStateChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CALL_INACTIVE_MSG_DELAY_MS);
|
||||||
|
mDelayedNotificationExecutor.execute(mPhoneStateChangePendingNotify);
|
||||||
|
} else {
|
||||||
|
evaluateStateChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Note, we expect initial callbacks immediately following construction */
|
||||||
|
public DeviceStateHandler(Executor callbackExecutor, EventLogger eventLogger) {
|
||||||
|
mCallbackExecutor = Objects.requireNonNull(callbackExecutor);
|
||||||
|
mEventLogger = Objects.requireNonNull(eventLogger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SoundTriggerDeviceState getDeviceState() {
|
||||||
|
synchronized (mLock) {
|
||||||
|
return mSoundTriggerDeviceState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerListener(DeviceStateListener callback) {
|
||||||
|
final var state = getDeviceState();
|
||||||
|
mCallbackExecutor.execute(
|
||||||
|
() -> callback.onSoundTriggerDeviceStateUpdate(state));
|
||||||
|
mCallbackSet.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterListener(DeviceStateListener callback) {
|
||||||
|
mCallbackSet.remove(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump(PrintWriter pw) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
pw.println("DeviceState: " + mSoundTriggerDeviceState.name());
|
||||||
|
pw.println("PhoneState: " + mIsPhoneCallOngoing);
|
||||||
|
pw.println("PowerSaveMode: " + mSoundTriggerPowerSaveMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private void evaluateStateChange() {
|
||||||
|
// We should wait until any pending delays are complete to update.
|
||||||
|
// We will eventually get called by the notification task, or something which
|
||||||
|
// cancels it.
|
||||||
|
// Additionally, if there isn't a state change, there is nothing to update.
|
||||||
|
SoundTriggerDeviceState newState = computeState();
|
||||||
|
if (mPhoneStateChangePendingNotify != null || mSoundTriggerDeviceState == newState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mSoundTriggerDeviceState = newState;
|
||||||
|
mEventLogger.enqueue(new DeviceStateEvent(mSoundTriggerDeviceState));
|
||||||
|
final var state = mSoundTriggerDeviceState;
|
||||||
|
for (var callback : mCallbackSet) {
|
||||||
|
mCallbackExecutor.execute(
|
||||||
|
() -> callback.onSoundTriggerDeviceStateUpdate(state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private SoundTriggerDeviceState computeState() {
|
||||||
|
if (mIsPhoneCallOngoing) {
|
||||||
|
return SoundTriggerDeviceState.DISABLE;
|
||||||
|
}
|
||||||
|
return switch (mSoundTriggerPowerSaveMode) {
|
||||||
|
case SOUND_TRIGGER_MODE_ALL_ENABLED -> SoundTriggerDeviceState.ENABLE;
|
||||||
|
case SOUND_TRIGGER_MODE_CRITICAL_ONLY -> SoundTriggerDeviceState.CRITICAL;
|
||||||
|
case SOUND_TRIGGER_MODE_ALL_DISABLED -> SoundTriggerDeviceState.DISABLE;
|
||||||
|
default -> throw new IllegalStateException(
|
||||||
|
"Received unexpected power state code" + mSoundTriggerPowerSaveMode);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One-shot, cancellable task which runs after a delay. Run must only be called once, from a
|
||||||
|
* single thread. Cancel can be called from any other thread.
|
||||||
|
*/
|
||||||
|
private static class NotificationTask implements Runnable {
|
||||||
|
private final Runnable mRunnable;
|
||||||
|
private final long mWaitInMillis;
|
||||||
|
|
||||||
|
private final CountDownLatch mCancelLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
NotificationTask(Runnable r, long waitInMillis) {
|
||||||
|
mRunnable = r;
|
||||||
|
mWaitInMillis = waitInMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel() {
|
||||||
|
mCancelLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for determining task equality.
|
||||||
|
boolean runnableEquals(Runnable runnable) {
|
||||||
|
return mRunnable == runnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
if (!mCancelLatch.await(mWaitInMillis, TimeUnit.MILLISECONDS)) {
|
||||||
|
mRunnable.run();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new AssertionError("Unexpected InterruptedException", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PhoneCallEvent extends EventLogger.Event {
|
||||||
|
final boolean mIsInPhoneCall;
|
||||||
|
|
||||||
|
PhoneCallEvent(boolean isInPhoneCall) {
|
||||||
|
mIsInPhoneCall = isInPhoneCall;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String eventToString() {
|
||||||
|
return "PhoneCallChange - inPhoneCall: " + mIsInPhoneCall;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SoundTriggerPowerEvent extends EventLogger.Event {
|
||||||
|
final int mSoundTriggerPowerState;
|
||||||
|
|
||||||
|
SoundTriggerPowerEvent(int soundTriggerPowerState) {
|
||||||
|
mSoundTriggerPowerState = soundTriggerPowerState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String eventToString() {
|
||||||
|
return "SoundTriggerPowerChange: " + stateToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String stateToString() {
|
||||||
|
return switch (mSoundTriggerPowerState) {
|
||||||
|
case SOUND_TRIGGER_MODE_ALL_ENABLED -> "All enabled";
|
||||||
|
case SOUND_TRIGGER_MODE_CRITICAL_ONLY -> "Critical only";
|
||||||
|
case SOUND_TRIGGER_MODE_ALL_DISABLED -> "All disabled";
|
||||||
|
default -> "Unknown power state: " + mSoundTriggerPowerState;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DeviceStateEvent extends EventLogger.Event {
|
||||||
|
final SoundTriggerDeviceState mSoundTriggerDeviceState;
|
||||||
|
|
||||||
|
DeviceStateEvent(SoundTriggerDeviceState soundTriggerDeviceState) {
|
||||||
|
mSoundTriggerDeviceState = soundTriggerDeviceState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String eventToString() {
|
||||||
|
return "DeviceStateChange: " + mSoundTriggerDeviceState.name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2023 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.soundtrigger;
|
||||||
|
|
||||||
|
import android.telephony.Annotation;
|
||||||
|
import android.telephony.SubscriptionInfo;
|
||||||
|
import android.telephony.SubscriptionManager;
|
||||||
|
import android.telephony.TelephonyCallback;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
|
import android.util.Slog;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles monitoring telephony call state across active subscriptions.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public class PhoneCallStateHandler {
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
void onPhoneCallStateChanged(boolean isInPhoneCall);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
|
||||||
|
// Actually never contended due to executor.
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private final List<MyCallStateListener> mListenerList = new ArrayList<>();
|
||||||
|
|
||||||
|
private final AtomicBoolean mIsPhoneCallOngoing = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
private final SubscriptionManager mSubscriptionManager;
|
||||||
|
private final TelephonyManager mTelephonyManager;
|
||||||
|
private final Callback mCallback;
|
||||||
|
|
||||||
|
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
|
public PhoneCallStateHandler(
|
||||||
|
SubscriptionManager subscriptionManager,
|
||||||
|
TelephonyManager telephonyManager,
|
||||||
|
Callback callback) {
|
||||||
|
mSubscriptionManager = Objects.requireNonNull(subscriptionManager);
|
||||||
|
mTelephonyManager = Objects.requireNonNull(telephonyManager);
|
||||||
|
mCallback = Objects.requireNonNull(callback);
|
||||||
|
mSubscriptionManager.addOnSubscriptionsChangedListener(
|
||||||
|
mExecutor,
|
||||||
|
new SubscriptionManager.OnSubscriptionsChangedListener() {
|
||||||
|
@Override
|
||||||
|
public void onSubscriptionsChanged() {
|
||||||
|
updateTelephonyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAddListenerFailed() {
|
||||||
|
Slog.wtf(
|
||||||
|
"SoundTriggerPhoneCallStateHandler",
|
||||||
|
"Failed to add a telephony listener");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class MyCallStateListener extends TelephonyCallback
|
||||||
|
implements TelephonyCallback.CallStateListener {
|
||||||
|
|
||||||
|
final TelephonyManager mTelephonyManagerForSubId;
|
||||||
|
|
||||||
|
// Manager corresponding to the sub-id
|
||||||
|
MyCallStateListener(TelephonyManager telephonyManager) {
|
||||||
|
mTelephonyManagerForSubId = telephonyManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanup() {
|
||||||
|
mExecutor.execute(() -> mTelephonyManagerForSubId.unregisterTelephonyCallback(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCallStateChanged(int unused) {
|
||||||
|
updateCallStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Compute the current call status, and dispatch callback if it has changed. */
|
||||||
|
private void updateCallStatus() {
|
||||||
|
boolean callStatus = checkCallStatus();
|
||||||
|
if (mIsPhoneCallOngoing.compareAndSet(!callStatus, callStatus)) {
|
||||||
|
mCallback.onPhoneCallStateChanged(callStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronously query the current telephony call state across all subscriptions
|
||||||
|
*
|
||||||
|
* @return - {@code true} if in call, {@code false} if not in call.
|
||||||
|
*/
|
||||||
|
private boolean checkCallStatus() {
|
||||||
|
List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList();
|
||||||
|
if (infoList == null) return false;
|
||||||
|
return infoList.stream()
|
||||||
|
.filter(s -> (s.getSubscriptionId() != SubscriptionManager.INVALID_SUBSCRIPTION_ID))
|
||||||
|
.anyMatch(s -> isCallOngoingFromState(
|
||||||
|
mTelephonyManager
|
||||||
|
.createForSubscriptionId(s.getSubscriptionId())
|
||||||
|
.getCallStateForSubscription()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTelephonyListeners() {
|
||||||
|
synchronized (mLock) {
|
||||||
|
for (var listener : mListenerList) {
|
||||||
|
listener.cleanup();
|
||||||
|
}
|
||||||
|
mListenerList.clear();
|
||||||
|
List<SubscriptionInfo> infoList = mSubscriptionManager.getActiveSubscriptionInfoList();
|
||||||
|
if (infoList == null) return;
|
||||||
|
infoList.stream()
|
||||||
|
.filter(s -> s.getSubscriptionId()
|
||||||
|
!= SubscriptionManager.INVALID_SUBSCRIPTION_ID)
|
||||||
|
.map(s -> mTelephonyManager.createForSubscriptionId(s.getSubscriptionId()))
|
||||||
|
.forEach(manager -> {
|
||||||
|
synchronized (mLock) {
|
||||||
|
var listener = new MyCallStateListener(manager);
|
||||||
|
mListenerList.add(listener);
|
||||||
|
manager.registerTelephonyCallback(mExecutor, listener);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isCallOngoingFromState(@Annotation.CallState int callState) {
|
||||||
|
return switch (callState) {
|
||||||
|
case TelephonyManager.CALL_STATE_IDLE, TelephonyManager.CALL_STATE_RINGING -> false;
|
||||||
|
case TelephonyManager.CALL_STATE_OFFHOOK -> true;
|
||||||
|
default -> throw new IllegalStateException(
|
||||||
|
"Received unexpected call state from Telephony Manager: " + callState);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,15 +16,12 @@
|
|||||||
|
|
||||||
package com.android.server.soundtrigger;
|
package com.android.server.soundtrigger;
|
||||||
|
|
||||||
|
import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState;
|
||||||
import static com.android.server.soundtrigger.SoundTriggerEvent.SessionEvent.Type;
|
import static com.android.server.soundtrigger.SoundTriggerEvent.SessionEvent.Type;
|
||||||
import static com.android.server.utils.EventLogger.Event.ALOGW;
|
import static com.android.server.utils.EventLogger.Event.ALOGW;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.hardware.soundtrigger.IRecognitionStatusCallback;
|
import android.hardware.soundtrigger.IRecognitionStatusCallback;
|
||||||
import android.hardware.soundtrigger.ModelParams;
|
import android.hardware.soundtrigger.ModelParams;
|
||||||
import android.hardware.soundtrigger.SoundTrigger;
|
import android.hardware.soundtrigger.SoundTrigger;
|
||||||
@@ -45,11 +42,7 @@ import android.os.DeadObjectException;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.PowerManager;
|
|
||||||
import android.os.PowerManager.SoundTriggerPowerSaveMode;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.telephony.PhoneStateListener;
|
|
||||||
import android.telephony.TelephonyManager;
|
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
@@ -99,37 +92,20 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
|||||||
private SoundTriggerModule mModule;
|
private SoundTriggerModule mModule;
|
||||||
private final Object mLock = new Object();
|
private final Object mLock = new Object();
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final TelephonyManager mTelephonyManager;
|
|
||||||
private final PhoneStateListener mPhoneStateListener;
|
|
||||||
private final PowerManager mPowerManager;
|
|
||||||
|
|
||||||
// The SoundTriggerManager layer handles multiple recognition models of type generic and
|
// The SoundTriggerManager layer handles multiple recognition models of type generic and
|
||||||
// keyphrase. We store the ModelData here in a hashmap.
|
// keyphrase. We store the ModelData here in a hashmap.
|
||||||
private final HashMap<UUID, ModelData> mModelDataMap;
|
private final HashMap<UUID, ModelData> mModelDataMap = new HashMap<>();
|
||||||
|
|
||||||
// An index of keyphrase sound models so that we can reach them easily. We support indexing
|
// An index of keyphrase sound models so that we can reach them easily. We support indexing
|
||||||
// keyphrase sound models with a keyphrase ID. Sound model with the same keyphrase ID will
|
// keyphrase sound models with a keyphrase ID. Sound model with the same keyphrase ID will
|
||||||
// replace an existing model, thus there is a 1:1 mapping from keyphrase ID to a voice
|
// replace an existing model, thus there is a 1:1 mapping from keyphrase ID to a voice
|
||||||
// sound model.
|
// sound model.
|
||||||
private HashMap<Integer, UUID> mKeyphraseUuidMap;
|
private final HashMap<Integer, UUID> mKeyphraseUuidMap = new HashMap<>();
|
||||||
|
|
||||||
private boolean mCallActive = false;
|
|
||||||
private @SoundTriggerPowerSaveMode int mSoundTriggerPowerSaveMode =
|
|
||||||
PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
|
|
||||||
|
|
||||||
// Whether ANY recognition (keyphrase or generic) has been requested.
|
// Whether ANY recognition (keyphrase or generic) has been requested.
|
||||||
private boolean mRecognitionRequested = false;
|
private boolean mRecognitionRequested = false;
|
||||||
|
|
||||||
private PowerSaveModeListener mPowerSaveModeListener;
|
|
||||||
|
|
||||||
|
|
||||||
// Handler to process call state changes will delay to allow time for the audio
|
|
||||||
// and sound trigger HALs to process the end of call notifications
|
|
||||||
// before we re enable pending recognition requests.
|
|
||||||
private final Handler mHandler;
|
|
||||||
private static final int MSG_CALL_STATE_CHANGED = 0;
|
|
||||||
private static final int CALL_INACTIVE_MSG_DELAY_MS = 1000;
|
|
||||||
|
|
||||||
// TODO(b/269366605) Temporary solution to query correct moduleProperties
|
// TODO(b/269366605) Temporary solution to query correct moduleProperties
|
||||||
private final int mModuleId;
|
private final int mModuleId;
|
||||||
private final Function<SoundTrigger.StatusListener, SoundTriggerModule> mModuleProvider;
|
private final Function<SoundTrigger.StatusListener, SoundTriggerModule> mModuleProvider;
|
||||||
@@ -139,16 +115,15 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
|||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private boolean mIsDetached = false;
|
private boolean mIsDetached = false;
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private SoundTriggerDeviceState mDeviceState = SoundTriggerDeviceState.DISABLE;
|
||||||
|
|
||||||
SoundTriggerHelper(Context context, EventLogger eventLogger,
|
SoundTriggerHelper(Context context, EventLogger eventLogger,
|
||||||
@NonNull Function<SoundTrigger.StatusListener, SoundTriggerModule> moduleProvider,
|
@NonNull Function<SoundTrigger.StatusListener, SoundTriggerModule> moduleProvider,
|
||||||
int moduleId,
|
int moduleId,
|
||||||
@NonNull Supplier<List<ModuleProperties>> modulePropertiesProvider) {
|
@NonNull Supplier<List<ModuleProperties>> modulePropertiesProvider) {
|
||||||
mModuleId = moduleId;
|
mModuleId = moduleId;
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
|
||||||
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
|
||||||
mModelDataMap = new HashMap<UUID, ModelData>();
|
|
||||||
mKeyphraseUuidMap = new HashMap<Integer, UUID>();
|
|
||||||
mModuleProvider = moduleProvider;
|
mModuleProvider = moduleProvider;
|
||||||
mEventLogger = eventLogger;
|
mEventLogger = eventLogger;
|
||||||
mModulePropertiesProvider = modulePropertiesProvider;
|
mModulePropertiesProvider = modulePropertiesProvider;
|
||||||
@@ -157,31 +132,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
|||||||
} else {
|
} else {
|
||||||
mModule = mModuleProvider.apply(this);
|
mModule = mModuleProvider.apply(this);
|
||||||
}
|
}
|
||||||
Looper looper = Looper.myLooper();
|
|
||||||
if (looper == null) {
|
|
||||||
looper = Looper.getMainLooper();
|
|
||||||
}
|
|
||||||
mPhoneStateListener = new MyCallStateListener(looper);
|
|
||||||
if (looper != null) {
|
|
||||||
mHandler = new Handler(looper) {
|
|
||||||
@Override
|
|
||||||
public void handleMessage(Message msg) {
|
|
||||||
switch (msg.what) {
|
|
||||||
case MSG_CALL_STATE_CHANGED:
|
|
||||||
synchronized (mLock) {
|
|
||||||
onCallStateChangedLocked(
|
|
||||||
TelephonyManager.CALL_STATE_OFFHOOK == msg.arg1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Slog.e(TAG, "unknown message in handler:" + msg.what);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
mHandler = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -373,7 +323,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
|||||||
modelData.setSoundModel(soundModel);
|
modelData.setSoundModel(soundModel);
|
||||||
|
|
||||||
if (!isRecognitionAllowedByDeviceState(modelData)) {
|
if (!isRecognitionAllowedByDeviceState(modelData)) {
|
||||||
initializeDeviceStateListeners();
|
|
||||||
return STATUS_OK;
|
return STATUS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,11 +446,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
|||||||
modelData.setLoaded();
|
modelData.setLoaded();
|
||||||
modelData.clearCallback();
|
modelData.clearCallback();
|
||||||
modelData.setRecognitionConfig(null);
|
modelData.setRecognitionConfig(null);
|
||||||
|
|
||||||
if (!computeRecognitionRequestedLocked()) {
|
|
||||||
internalClearGlobalStateLocked();
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -638,6 +582,17 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onDeviceStateChanged(SoundTriggerDeviceState state) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mIsDetached || mDeviceState == state) {
|
||||||
|
// Nothing to update
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mDeviceState = state;
|
||||||
|
updateAllRecognitionsLocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int getGenericModelState(UUID modelId) {
|
public int getGenericModelState(UUID modelId) {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
MetricsLogger.count(mContext, "sth_get_generic_model_state", 1);
|
MetricsLogger.count(mContext, "sth_get_generic_model_state", 1);
|
||||||
@@ -880,25 +835,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onCallStateChangedLocked(boolean callActive) {
|
|
||||||
if (mCallActive == callActive) {
|
|
||||||
// We consider multiple call states as being active
|
|
||||||
// so we check if something really changed or not here.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mCallActive = callActive;
|
|
||||||
updateAllRecognitionsLocked();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onPowerSaveModeChangedLocked(
|
|
||||||
@SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode) {
|
|
||||||
if (mSoundTriggerPowerSaveMode == soundTriggerPowerSaveMode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mSoundTriggerPowerSaveMode = soundTriggerPowerSaveMode;
|
|
||||||
updateAllRecognitionsLocked();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onModelUnloadedLocked(int modelHandle) {
|
private void onModelUnloadedLocked(int modelHandle) {
|
||||||
ModelData modelData = getModelDataForLocked(modelHandle);
|
ModelData modelData = getModelDataForLocked(modelHandle);
|
||||||
if (modelData != null) {
|
if (modelData != null) {
|
||||||
@@ -1011,10 +947,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
status = startRecognitionLocked(model, notifyClientOnError);
|
status = startRecognitionLocked(model, notifyClientOnError);
|
||||||
// Initialize power save, call active state monitoring logic.
|
|
||||||
if (status == STATUS_OK) {
|
|
||||||
initializeDeviceStateListeners();
|
|
||||||
}
|
|
||||||
return status;
|
return status;
|
||||||
} else {
|
} else {
|
||||||
return stopRecognitionLocked(model, notifyClientOnError);
|
return stopRecognitionLocked(model, notifyClientOnError);
|
||||||
@@ -1040,7 +972,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
internalClearModelStateLocked();
|
internalClearModelStateLocked();
|
||||||
internalClearGlobalStateLocked();
|
|
||||||
if (mModule != null) {
|
if (mModule != null) {
|
||||||
mModule.detach();
|
mModule.detach();
|
||||||
try {
|
try {
|
||||||
@@ -1054,24 +985,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// internalClearGlobalStateLocked() cleans up the telephony and power save listeners.
|
|
||||||
private void internalClearGlobalStateLocked() {
|
|
||||||
// Unregister from call state changes.
|
|
||||||
final long token = Binder.clearCallingIdentity();
|
|
||||||
try {
|
|
||||||
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
|
|
||||||
} finally {
|
|
||||||
Binder.restoreCallingIdentity(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unregister from power save mode changes.
|
|
||||||
if (mPowerSaveModeListener != null) {
|
|
||||||
mContext.unregisterReceiver(mPowerSaveModeListener);
|
|
||||||
mPowerSaveModeListener = null;
|
|
||||||
}
|
|
||||||
mRecognitionRequested = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clears state for all models (generic and keyphrase).
|
// Clears state for all models (generic and keyphrase).
|
||||||
private void internalClearModelStateLocked() {
|
private void internalClearModelStateLocked() {
|
||||||
for (ModelData modelData : mModelDataMap.values()) {
|
for (ModelData modelData : mModelDataMap.values()) {
|
||||||
@@ -1079,67 +992,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyCallStateListener extends PhoneStateListener {
|
|
||||||
MyCallStateListener(@NonNull Looper looper) {
|
|
||||||
super(Objects.requireNonNull(looper));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCallStateChanged(int state, String arg1) {
|
|
||||||
|
|
||||||
if (mHandler != null) {
|
|
||||||
synchronized (mLock) {
|
|
||||||
mHandler.removeMessages(MSG_CALL_STATE_CHANGED);
|
|
||||||
Message msg = mHandler.obtainMessage(MSG_CALL_STATE_CHANGED, state, 0);
|
|
||||||
mHandler.sendMessageDelayed(
|
|
||||||
msg, (TelephonyManager.CALL_STATE_OFFHOOK == state) ? 0
|
|
||||||
: CALL_INACTIVE_MSG_DELAY_MS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PowerSaveModeListener extends BroadcastReceiver {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
@SoundTriggerPowerSaveMode int soundTriggerPowerSaveMode =
|
|
||||||
mPowerManager.getSoundTriggerPowerSaveMode();
|
|
||||||
synchronized (mLock) {
|
|
||||||
onPowerSaveModeChangedLocked(soundTriggerPowerSaveMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeDeviceStateListeners() {
|
|
||||||
if (mRecognitionRequested) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final long token = Binder.clearCallingIdentity();
|
|
||||||
try {
|
|
||||||
// Get the current call state synchronously for the first recognition.
|
|
||||||
mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
|
|
||||||
|
|
||||||
// Register for call state changes when the first call to start recognition occurs.
|
|
||||||
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
|
|
||||||
|
|
||||||
// Register for power saver mode changes when the first call to start recognition
|
|
||||||
// occurs.
|
|
||||||
if (mPowerSaveModeListener == null) {
|
|
||||||
mPowerSaveModeListener = new PowerSaveModeListener();
|
|
||||||
mContext.registerReceiver(mPowerSaveModeListener,
|
|
||||||
new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
|
|
||||||
}
|
|
||||||
mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode();
|
|
||||||
|
|
||||||
mRecognitionRequested = true;
|
|
||||||
} finally {
|
|
||||||
Binder.restoreCallingIdentity(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops and unloads all models. This is intended as a clean-up call with the expectation that
|
* Stops and unloads all models. This is intended as a clean-up call with the expectation that
|
||||||
* this instance is not used after.
|
* this instance is not used after.
|
||||||
@@ -1153,7 +1005,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
|||||||
forceStopAndUnloadModelLocked(model, null);
|
forceStopAndUnloadModelLocked(model, null);
|
||||||
}
|
}
|
||||||
mModelDataMap.clear();
|
mModelDataMap.clear();
|
||||||
internalClearGlobalStateLocked();
|
|
||||||
if (mModule != null) {
|
if (mModule != null) {
|
||||||
mModule.detach();
|
mModule.detach();
|
||||||
mModule = null;
|
mModule = null;
|
||||||
@@ -1305,28 +1156,14 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
|||||||
* @param modelData Model data to be used for recognition
|
* @param modelData Model data to be used for recognition
|
||||||
* @return True if recognition is allowed to run at this time. False if not.
|
* @return True if recognition is allowed to run at this time. False if not.
|
||||||
*/
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
private boolean isRecognitionAllowedByDeviceState(ModelData modelData) {
|
private boolean isRecognitionAllowedByDeviceState(ModelData modelData) {
|
||||||
// if mRecognitionRequested is false, call and power state listeners are not registered so
|
return switch (mDeviceState) {
|
||||||
// we read current state directly from services
|
case DISABLE -> false;
|
||||||
if (!mRecognitionRequested) {
|
case CRITICAL -> modelData.shouldRunInBatterySaverMode();
|
||||||
mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
|
case ENABLE -> true;
|
||||||
mSoundTriggerPowerSaveMode = mPowerManager.getSoundTriggerPowerSaveMode();
|
default -> throw new AssertionError("Enum changed between compile and runtime");
|
||||||
}
|
};
|
||||||
|
|
||||||
return !mCallActive && isRecognitionAllowedByPowerState(
|
|
||||||
modelData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to validate if a recognition should run based on the current power state
|
|
||||||
*
|
|
||||||
* @param modelData Model data to be used for recognition
|
|
||||||
* @return True if device state allows recognition to run, false if not.
|
|
||||||
*/
|
|
||||||
private boolean isRecognitionAllowedByPowerState(ModelData modelData) {
|
|
||||||
return mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED
|
|
||||||
|| (mSoundTriggerPowerSaveMode == PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY
|
|
||||||
&& modelData.shouldRunInBatterySaverMode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A single routine that implements the start recognition logic for both generic and keyphrase
|
// A single routine that implements the start recognition logic for both generic and keyphrase
|
||||||
|
|||||||
@@ -35,14 +35,18 @@ import static com.android.server.soundtrigger.SoundTriggerEvent.SessionEvent.Typ
|
|||||||
import static com.android.server.utils.EventLogger.Event.ALOGW;
|
import static com.android.server.utils.EventLogger.Event.ALOGW;
|
||||||
|
|
||||||
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
|
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
|
||||||
|
import static com.android.server.soundtrigger.DeviceStateHandler.DeviceStateListener;
|
||||||
|
import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.app.ActivityThread;
|
import android.app.ActivityThread;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.content.PermissionChecker;
|
import android.content.PermissionChecker;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
@@ -85,6 +89,8 @@ import android.os.ServiceSpecificException;
|
|||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
import android.telephony.SubscriptionManager;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
@@ -112,6 +118,8 @@ import java.util.Objects;
|
|||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@@ -131,8 +139,8 @@ public class SoundTriggerService extends SystemService {
|
|||||||
private static final boolean DEBUG = true;
|
private static final boolean DEBUG = true;
|
||||||
private static final int SESSION_MAX_EVENT_SIZE = 128;
|
private static final int SESSION_MAX_EVENT_SIZE = 128;
|
||||||
|
|
||||||
final Context mContext;
|
private final Context mContext;
|
||||||
private Object mLock;
|
private final Object mLock = new Object();
|
||||||
private final SoundTriggerServiceStub mServiceStub;
|
private final SoundTriggerServiceStub mServiceStub;
|
||||||
private final LocalSoundTriggerService mLocalSoundTriggerService;
|
private final LocalSoundTriggerService mLocalSoundTriggerService;
|
||||||
|
|
||||||
@@ -140,6 +148,7 @@ public class SoundTriggerService extends SystemService {
|
|||||||
private SoundTriggerDbHelper mDbHelper;
|
private SoundTriggerDbHelper mDbHelper;
|
||||||
|
|
||||||
private final EventLogger mServiceEventLogger = new EventLogger(256, "Service");
|
private final EventLogger mServiceEventLogger = new EventLogger(256, "Service");
|
||||||
|
private final EventLogger mDeviceEventLogger = new EventLogger(256, "Device Event");
|
||||||
|
|
||||||
private final Set<EventLogger> mSessionEventLoggers = ConcurrentHashMap.newKeySet(4);
|
private final Set<EventLogger> mSessionEventLoggers = ConcurrentHashMap.newKeySet(4);
|
||||||
private final Deque<EventLogger> mDetachedSessionEventLoggers = new LinkedBlockingDeque<>(4);
|
private final Deque<EventLogger> mDetachedSessionEventLoggers = new LinkedBlockingDeque<>(4);
|
||||||
@@ -223,13 +232,18 @@ public class SoundTriggerService extends SystemService {
|
|||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private final ArrayMap<String, NumOps> mNumOpsPerPackage = new ArrayMap<>();
|
private final ArrayMap<String, NumOps> mNumOpsPerPackage = new ArrayMap<>();
|
||||||
|
|
||||||
|
private final DeviceStateHandler mDeviceStateHandler;
|
||||||
|
private final Executor mDeviceStateHandlerExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
private PhoneCallStateHandler mPhoneCallStateHandler;
|
||||||
|
|
||||||
public SoundTriggerService(Context context) {
|
public SoundTriggerService(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mServiceStub = new SoundTriggerServiceStub();
|
mServiceStub = new SoundTriggerServiceStub();
|
||||||
mLocalSoundTriggerService = new LocalSoundTriggerService(context);
|
mLocalSoundTriggerService = new LocalSoundTriggerService(context);
|
||||||
mLock = new Object();
|
|
||||||
mSoundModelStatTracker = new SoundModelStatTracker();
|
mSoundModelStatTracker = new SoundModelStatTracker();
|
||||||
|
mDeviceStateHandler = new DeviceStateHandler(mDeviceStateHandlerExecutor,
|
||||||
|
mDeviceEventLogger);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -243,6 +257,29 @@ public class SoundTriggerService extends SystemService {
|
|||||||
Slog.d(TAG, "onBootPhase: " + phase + " : " + isSafeMode());
|
Slog.d(TAG, "onBootPhase: " + phase + " : " + isSafeMode());
|
||||||
if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
|
if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
|
||||||
mDbHelper = new SoundTriggerDbHelper(mContext);
|
mDbHelper = new SoundTriggerDbHelper(mContext);
|
||||||
|
final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
|
||||||
|
// Hook up power state listener
|
||||||
|
mContext.registerReceiver(
|
||||||
|
new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED
|
||||||
|
.equals(intent.getAction())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mDeviceStateHandler.onPowerModeChanged(
|
||||||
|
powerManager.getSoundTriggerPowerSaveMode());
|
||||||
|
}
|
||||||
|
}, new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
|
||||||
|
// Initialize the initial power state
|
||||||
|
// Do so after registering the listener so we ensure that we don't drop any events
|
||||||
|
mDeviceStateHandler.onPowerModeChanged(powerManager.getSoundTriggerPowerSaveMode());
|
||||||
|
|
||||||
|
// PhoneCallStateHandler initializes the original call state
|
||||||
|
mPhoneCallStateHandler = new PhoneCallStateHandler(
|
||||||
|
mContext.getSystemService(SubscriptionManager.class),
|
||||||
|
mContext.getSystemService(TelephonyManager.class),
|
||||||
|
mDeviceStateHandler);
|
||||||
}
|
}
|
||||||
mMiddlewareService = ISoundTriggerMiddlewareService.Stub.asInterface(
|
mMiddlewareService = ISoundTriggerMiddlewareService.Stub.asInterface(
|
||||||
ServiceManager.waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE));
|
ServiceManager.waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE));
|
||||||
@@ -380,6 +417,9 @@ public class SoundTriggerService extends SystemService {
|
|||||||
// Event loggers
|
// Event loggers
|
||||||
pw.println("##Service-Wide logs:");
|
pw.println("##Service-Wide logs:");
|
||||||
mServiceEventLogger.dump(pw, /* indent = */ " ");
|
mServiceEventLogger.dump(pw, /* indent = */ " ");
|
||||||
|
pw.println("\n##Device state logs:");
|
||||||
|
mDeviceStateHandler.dump(pw);
|
||||||
|
mDeviceEventLogger.dump(pw, /* indent = */ " ");
|
||||||
|
|
||||||
pw.println("\n##Active Session dumps:\n");
|
pw.println("\n##Active Session dumps:\n");
|
||||||
for (var sessionLogger : mSessionEventLoggers) {
|
for (var sessionLogger : mSessionEventLoggers) {
|
||||||
@@ -403,6 +443,7 @@ public class SoundTriggerService extends SystemService {
|
|||||||
|
|
||||||
class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
|
class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
|
||||||
private final SoundTriggerHelper mSoundTriggerHelper;
|
private final SoundTriggerHelper mSoundTriggerHelper;
|
||||||
|
private final DeviceStateListener mListener;
|
||||||
// Used to detect client death.
|
// Used to detect client death.
|
||||||
private final IBinder mClient;
|
private final IBinder mClient;
|
||||||
private final Identity mOriginatorIdentity;
|
private final Identity mOriginatorIdentity;
|
||||||
@@ -424,6 +465,9 @@ public class SoundTriggerService extends SystemService {
|
|||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
clientDied();
|
clientDied();
|
||||||
}
|
}
|
||||||
|
mListener = (SoundTriggerDeviceState state)
|
||||||
|
-> mSoundTriggerHelper.onDeviceStateChanged(state);
|
||||||
|
mDeviceStateHandler.registerListener(mListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -874,6 +918,7 @@ public class SoundTriggerService extends SystemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void detach() {
|
private void detach() {
|
||||||
|
mDeviceStateHandler.unregisterListener(mListener);
|
||||||
mSoundTriggerHelper.detach();
|
mSoundTriggerHelper.detach();
|
||||||
detachSessionLogger(mEventLogger);
|
detachSessionLogger(mEventLogger);
|
||||||
}
|
}
|
||||||
@@ -890,7 +935,8 @@ public class SoundTriggerService extends SystemService {
|
|||||||
private void enforceDetectionPermissions(ComponentName detectionService) {
|
private void enforceDetectionPermissions(ComponentName detectionService) {
|
||||||
PackageManager packageManager = mContext.getPackageManager();
|
PackageManager packageManager = mContext.getPackageManager();
|
||||||
String packageName = detectionService.getPackageName();
|
String packageName = detectionService.getPackageName();
|
||||||
if (packageManager.checkPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName)
|
if (packageManager.checkPermission(
|
||||||
|
Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName)
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
throw new SecurityException(detectionService.getPackageName() + " does not have"
|
throw new SecurityException(detectionService.getPackageName() + " does not have"
|
||||||
+ " permission " + Manifest.permission.CAPTURE_AUDIO_HOTWORD);
|
+ " permission " + Manifest.permission.CAPTURE_AUDIO_HOTWORD);
|
||||||
@@ -1576,6 +1622,7 @@ public class SoundTriggerService extends SystemService {
|
|||||||
private final @NonNull IBinder mClient;
|
private final @NonNull IBinder mClient;
|
||||||
private final EventLogger mEventLogger;
|
private final EventLogger mEventLogger;
|
||||||
private final Identity mOriginatorIdentity;
|
private final Identity mOriginatorIdentity;
|
||||||
|
private final @NonNull DeviceStateListener mListener;
|
||||||
|
|
||||||
private final SparseArray<UUID> mModelUuid = new SparseArray<>(1);
|
private final SparseArray<UUID> mModelUuid = new SparseArray<>(1);
|
||||||
|
|
||||||
@@ -1594,6 +1641,9 @@ public class SoundTriggerService extends SystemService {
|
|||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
clientDied();
|
clientDied();
|
||||||
}
|
}
|
||||||
|
mListener = (SoundTriggerDeviceState state)
|
||||||
|
-> mSoundTriggerHelper.onDeviceStateChanged(state);
|
||||||
|
mDeviceStateHandler.registerListener(mListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1662,6 +1712,7 @@ public class SoundTriggerService extends SystemService {
|
|||||||
private void detachInternal() {
|
private void detachInternal() {
|
||||||
mEventLogger.enqueue(new SessionEvent(Type.DETACH, null));
|
mEventLogger.enqueue(new SessionEvent(Type.DETACH, null));
|
||||||
detachSessionLogger(mEventLogger);
|
detachSessionLogger(mEventLogger);
|
||||||
|
mDeviceStateHandler.unregisterListener(mListener);
|
||||||
mSoundTriggerHelper.detach();
|
mSoundTriggerHelper.detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user