diff options
3 files changed, 157 insertions, 17 deletions
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java index 212f81f72b24..086ebc99eff7 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java @@ -27,8 +27,6 @@ import android.os.SystemProperties; import android.util.Log; import java.util.Objects; -import java.util.Timer; -import java.util.TimerTask; /** * An {@link ISoundTriggerHw2} decorator that would enforce deadlines on all calls and reboot the @@ -38,14 +36,12 @@ public class SoundTriggerHw2Watchdog implements ISoundTriggerHw2 { private static final long TIMEOUT_MS = 3000; private static final String TAG = "SoundTriggerHw2Watchdog"; - private final @NonNull - ISoundTriggerHw2 mUnderlying; - private final @NonNull - Timer mTimer; + private final @NonNull ISoundTriggerHw2 mUnderlying; + private final @NonNull UptimeTimer mTimer; public SoundTriggerHw2Watchdog(@NonNull ISoundTriggerHw2 underlying) { mUnderlying = Objects.requireNonNull(underlying); - mTimer = new Timer("SoundTriggerHw2Watchdog"); + mTimer = new UptimeTimer("SoundTriggerHw2Watchdog"); } @Override @@ -149,21 +145,16 @@ public class SoundTriggerHw2Watchdog implements ISoundTriggerHw2 { } private class Watchdog implements AutoCloseable { - private final @NonNull - TimerTask mTask; + private final @NonNull UptimeTimer.Task mTask; // This exception is used merely for capturing a stack trace at the time of creation. private final @NonNull Exception mException = new Exception(); Watchdog() { - mTask = new TimerTask() { - @Override - public void run() { - Log.e(TAG, "HAL deadline expired. Rebooting.", mException); - rebootHal(); - } - }; - mTimer.schedule(mTask, TIMEOUT_MS); + mTask = mTimer.createTask(() -> { + Log.e(TAG, "HAL deadline expired. Rebooting.", mException); + rebootHal(); + }, TIMEOUT_MS); } @Override diff --git a/services/core/java/com/android/server/soundtrigger_middleware/UptimeTimer.java b/services/core/java/com/android/server/soundtrigger_middleware/UptimeTimer.java new file mode 100644 index 000000000000..bfcc7d840662 --- /dev/null +++ b/services/core/java/com/android/server/soundtrigger_middleware/UptimeTimer.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 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_middleware; + +import android.annotation.NonNull; +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * A simple timer, similar to java.util.Timer, but using the "uptime clock". + * + * Example usage: + * UptimeTimer timer = new UptimeTimer("TimerThread"); + * UptimeTimer.Task task = timer.createTask(() -> { ... }, 100); + * ... + * // optionally, some time later: + * task.cancel(); + */ +class UptimeTimer { + private Handler mHandler = null; + + interface Task { + void cancel(); + } + + UptimeTimer(String threadName) { + new Thread(this::threadFunc, threadName).start(); + synchronized (this) { + while (mHandler == null) { + try { + wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + } + + Task createTask(@NonNull Runnable runnable, long uptimeMs) { + TaskImpl task = new TaskImpl(runnable); + mHandler.postDelayed(task, uptimeMs); + return task; + } + + private void threadFunc() { + Looper.prepare(); + synchronized (this) { + mHandler = new Handler(Looper.myLooper()); + notifyAll(); + } + Looper.loop(); + } + + private static class TaskImpl implements Task, Runnable { + private AtomicReference<Runnable> mRunnable = new AtomicReference<>(); + + TaskImpl(@NonNull Runnable runnable) { + mRunnable.set(runnable); + } + + @Override + public void cancel() { + mRunnable.set(null); + } + + @Override + public void run() { + Runnable runnable = mRunnable.get(); + if (runnable != null) { + runnable.run(); + } + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/UptimeTimerTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/UptimeTimerTest.java new file mode 100644 index 000000000000..38297bf624b7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/UptimeTimerTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021 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_middleware; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.atomic.AtomicBoolean; + +@RunWith(AndroidJUnit4.class) +public class UptimeTimerTest { + private static final String TAG = "UptimeTimerTest"; + + @Test + public void testBasic() throws InterruptedException { + AtomicBoolean taskRan = new AtomicBoolean(false); + UptimeTimer timer = new UptimeTimer("TestTimer"); + timer.createTask(() -> taskRan.set(true), 100); + Thread.sleep(50); + boolean before = taskRan.get(); + Thread.sleep(100); + boolean after = taskRan.get(); + assertFalse(before); + assertTrue(after); + } + + @Test + public void testCancel() throws InterruptedException { + AtomicBoolean taskRan = new AtomicBoolean(false); + UptimeTimer timer = new UptimeTimer("TestTimer"); + UptimeTimer.Task task = timer.createTask(() -> taskRan.set(true), 100); + Thread.sleep(50); + boolean before = taskRan.get(); + task.cancel(); + Thread.sleep(100); + boolean after = taskRan.get(); + assertFalse(before); + assertFalse(after); + } +} |