diff options
10 files changed, 355 insertions, 2 deletions
diff --git a/apps/OboeTester/app/CMakeLists.txt b/apps/OboeTester/app/CMakeLists.txt index f2a3c3ee..8409dcab 100644 --- a/apps/OboeTester/app/CMakeLists.txt +++ b/apps/OboeTester/app/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") link_directories(${CMAKE_CURRENT_LIST_DIR}/..) -# Increment this number when adding files to OboeTester => 100 +# Increment this number when adding files to OboeTester => 101 # The change in this file will help Android Studio resync # and generate new build files that reference the new code. file(GLOB_RECURSE app_native_sources src/main/cpp/*) diff --git a/apps/OboeTester/app/src/main/AndroidManifest.xml b/apps/OboeTester/app/src/main/AndroidManifest.xml index cd947675..98628186 100644 --- a/apps/OboeTester/app/src/main/AndroidManifest.xml +++ b/apps/OboeTester/app/src/main/AndroidManifest.xml @@ -104,6 +104,10 @@ android:name=".TestPlugLatencyActivity" android:label="@string/title_plug_latency" android:exported="true" /> + <activity + android:name=".TestErrorCallbackActivity" + android:label="@string/title_error_callback" + android:exported="true" /> <service android:name=".MidiTapTester" diff --git a/apps/OboeTester/app/src/main/cpp/TestErrorCallback.cpp b/apps/OboeTester/app/src/main/cpp/TestErrorCallback.cpp new file mode 100644 index 00000000..edf49ef9 --- /dev/null +++ b/apps/OboeTester/app/src/main/cpp/TestErrorCallback.cpp @@ -0,0 +1,70 @@ +/* + * Copyright 2022 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. + */ + +#include <stdlib.h> + +#include "common/OboeDebug.h" +#include "TestErrorCallback.h" + +using namespace oboe; + +oboe::Result TestErrorCallback::open() { + mCallbackMagic = 0; + mDataCallback = std::make_unique<MyDataCallback>(); + mErrorCallback = std::make_unique<MyErrorCallback>(this); + AudioStreamBuilder builder; + oboe::Result result = builder.setSharingMode(oboe::SharingMode::Exclusive) + ->setPerformanceMode(oboe::PerformanceMode::LowLatency) + ->setFormat(oboe::AudioFormat::Float) + ->setChannelCount(kChannelCount) + ->setDataCallback(mDataCallback.get()) + ->setErrorCallback(mErrorCallback.get()) + ->openStream(mStream); + return result; +} + +oboe::Result TestErrorCallback::start() { + return mStream->requestStart(); +} + +oboe::Result TestErrorCallback::stop() { + return mStream->requestStop(); +} + +oboe::Result TestErrorCallback::close() { + return mStream->close(); +} + +int TestErrorCallback::test() { + oboe::Result result = open(); + if (result != oboe::Result::OK) { + return (int) result; + } + return (int) start(); +} + +DataCallbackResult TestErrorCallback::MyDataCallback::onAudioReady( + AudioStream *audioStream, + void *audioData, + int32_t numFrames) { + float *output = (float *) audioData; + // Fill buffer with random numbers to create "white noise". + int numSamples = numFrames * kChannelCount; + for (int i = 0; i < numSamples; i++) { + *output++ = (float)((drand48() - 0.5) * 0.2); + } + return oboe::DataCallbackResult::Continue; +} diff --git a/apps/OboeTester/app/src/main/cpp/TestErrorCallback.h b/apps/OboeTester/app/src/main/cpp/TestErrorCallback.h new file mode 100644 index 00000000..a9ca3c23 --- /dev/null +++ b/apps/OboeTester/app/src/main/cpp/TestErrorCallback.h @@ -0,0 +1,111 @@ +/* + * Copyright 2022 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. + */ + +#ifndef OBOETESTER_TEST_ERROR_CALLBACK_H +#define OBOETESTER_TEST_ERROR_CALLBACK_H + +#include "oboe/Oboe.h" +#include <thread> + +/** + * This code is an experiment to see if we can cause a crash from the ErrorCallback. + */ +class TestErrorCallback { +public: + + oboe::Result open(); + oboe::Result start(); + oboe::Result stop(); + oboe::Result close(); + + int test(); + + int32_t getCallbackMagic() { + return mCallbackMagic.load(); + } + +protected: + + std::atomic<int32_t> mCallbackMagic{0}; + +private: + + void cleanup() { + mErrorCallback.reset(); + } + + class MyDataCallback : public oboe::AudioStreamDataCallback { public: + + oboe::DataCallbackResult onAudioReady( + oboe::AudioStream *audioStream, + void *audioData, + int32_t numFrames) override; + + }; + + class MyErrorCallback : public oboe::AudioStreamErrorCallback { + public: + + MyErrorCallback(TestErrorCallback *parent): mParent(parent) {} + + virtual ~MyErrorCallback() { + // If the delete occurs before onErrorAfterClose() then this bad magic + // value will be seen by the Java test code, causing a failure. + // It is also possible that this code will just cause OboeTester to crash! + mMagic = 0xdeadbeef; + LOGE("%s() called", __func__); + } + + void onErrorBeforeClose(oboe::AudioStream *oboeStream, oboe::Result error) override { + LOGE("%s() - error = %s, parent = %p", + __func__, oboe::convertToText(error), &mParent); + // Trigger a crash by deleting this callback object while in use! + // Do not try this at home. We are just trying to reproduce the crash + // reported in #1603. + std::thread t([this]() { + this->mParent->cleanup(); + LOGE("onErrorBeforeClose called cleanup!"); + }); + t.detach(); + // There is a race condition between the deleting thread and this thread. + // We do not want to add synchronization because the object is getting deleted + // and cannot be relied on. + // So we sleep here to give the deleting thread a chance to win the race. + usleep(10 * 1000); + } + + void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override { + // The callback was probably deleted by now. + LOGE("%s() - error = %s, mMagic = 0x%08X", + __func__, oboe::convertToText(error), mMagic.load()); + mParent->mCallbackMagic = mMagic.load(); + } + + private: + TestErrorCallback *mParent; + // This must match the value in TestErrorCallbackActivity.java + static constexpr int32_t kMagicGood = 0x600DCAFE; + std::atomic<int32_t> mMagic{kMagicGood}; + }; + + std::shared_ptr<oboe::AudioStream> mStream; + std::unique_ptr<MyDataCallback> mDataCallback; + std::unique_ptr<MyErrorCallback> mErrorCallback; + + static constexpr int kChannelCount = 2; +}; + +#endif //OBOETESTER_TEST_ERROR_CALLBACK_H diff --git a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp index 757493c9..3b898a82 100644 --- a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp +++ b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp @@ -26,6 +26,7 @@ #include "oboe/Oboe.h" #include "NativeAudioContext.h" +#include "TestErrorCallback.h" NativeAudioContext engine; @@ -752,4 +753,18 @@ Java_com_mobileer_oboetester_TestAudioActivity_setDefaultAudioValues(JNIEnv *env oboe::DefaultStreamValues::FramesPerBurst = audio_manager_frames_per_burst; } +static TestErrorCallback sTester; + +JNIEXPORT void JNICALL +Java_com_mobileer_oboetester_TestErrorCallbackActivity_testDeleteCrash( + JNIEnv *env, jobject instance) { + sTester.test(); +} + +JNIEXPORT jint JNICALL +Java_com_mobileer_oboetester_TestErrorCallbackActivity_getCallbackMagic( + JNIEnv *env, jobject instance) { + return sTester.getCallbackMagic(); +} + } diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExtraTestsActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExtraTestsActivity.java index 3e0b21e5..557ceb41 100644 --- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExtraTestsActivity.java +++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExtraTestsActivity.java @@ -25,4 +25,8 @@ public class ExtraTestsActivity extends BaseOboeTesterActivity { launchTestActivity(TestPlugLatencyActivity.class); } + public void onLaunchErrorCallbackTest(View view) { + launchTestActivity(TestErrorCallbackActivity.class); + } + } diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestErrorCallbackActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestErrorCallbackActivity.java new file mode 100644 index 00000000..b3b4497d --- /dev/null +++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestErrorCallbackActivity.java @@ -0,0 +1,100 @@ +/* + * Copyright 2022 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.mobileer.oboetester; + +import android.app.Activity; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.View; +import android.widget.TextView; + +public class TestErrorCallbackActivity extends Activity { + + private TextView mStatusDeleteCallback; + // This must match the value in TestErrorCallback.h + private static final int MAGIC_GOOD = 0x600DCAFE; + private MyStreamSniffer mStreamSniffer; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_error_callback); + + mStreamSniffer = new MyStreamSniffer(); + mStatusDeleteCallback = (TextView) findViewById(R.id.text_callback_status); + } + + public void onTestDeleteCrash(View view) { + mStatusDeleteCallback.setText(getString(R.string.plug_or_unplug)); + mStreamSniffer.startStreamSniffer(); + testDeleteCrash(); + } + + + // Periodically query the status of the streams. + protected class MyStreamSniffer { + public static final int SNIFFER_UPDATE_PERIOD_MSEC = 150; + public static final int SNIFFER_UPDATE_DELAY_MSEC = 300; + + private Handler mHandler; + + // Display status info for the stream. + private Runnable runnableCode = new Runnable() { + @Override + public void run() { + int magic = getCallbackMagic(); + updateMagicDisplay(magic); + mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_PERIOD_MSEC); + } + }; + + private void startStreamSniffer() { + stopStreamSniffer(); + mHandler = new Handler(Looper.getMainLooper()); + // Start the initial runnable task by posting through the handler + mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_DELAY_MSEC); + } + + private void stopStreamSniffer() { + if (mHandler != null) { + mHandler.removeCallbacks(runnableCode); + } + } + } + + private void updateMagicDisplay(int magic) { + if (magic != 0) { + String text = getString(R.string.report_magic_pass, MAGIC_GOOD); + if (magic != MAGIC_GOOD) { + text = getString(R.string.report_magic_fail, + magic, MAGIC_GOOD); + } + mStatusDeleteCallback.setText(text); + } + } + + @Override + public void onPause() { + super.onPause(); + mStreamSniffer.stopStreamSniffer(); + } + + private native void testDeleteCrash(); + private native int getCallbackMagic(); + +} diff --git a/apps/OboeTester/app/src/main/res/layout/activity_error_callback.xml b/apps/OboeTester/app/src/main/res/layout/activity_error_callback.xml new file mode 100644 index 00000000..3abc11e9 --- /dev/null +++ b/apps/OboeTester/app/src/main/res/layout/activity_error_callback.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".TestErrorCallbackActivity"> + + <GridLayout + android:id="@+id/buttonGrid" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:columnCount="1"> + + <Button + android:id="@+id/buttonTestDeleteCrash" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_columnWeight="1" + android:layout_gravity="fill" + android:backgroundTint="@color/button_tint" + android:onClick="onTestDeleteCrash" + android:text="Delete Callback" /> + + <TextView + android:id="@+id/text_callback_status" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fontFamily="monospace" + android:gravity="bottom" + android:text="@string/init_status" + /> + +</GridLayout> +</android.support.constraint.ConstraintLayout> diff --git a/apps/OboeTester/app/src/main/res/layout/activity_extra_tests.xml b/apps/OboeTester/app/src/main/res/layout/activity_extra_tests.xml index 14aa80f9..dbd0d37e 100644 --- a/apps/OboeTester/app/src/main/res/layout/activity_extra_tests.xml +++ b/apps/OboeTester/app/src/main/res/layout/activity_extra_tests.xml @@ -41,5 +41,15 @@ android:backgroundTint="@color/button_tint" android:onClick="onLaunchPlugLatencyTest" android:text="Plug Latency" /> - </GridLayout> + + <Button + android:id="@+id/buttonErrorCallback" + android:layout_gravity="fill" + android:layout_width="0dp" + android:layout_columnWeight="1" + android:layout_height="wrap_content" + android:backgroundTint="@color/button_tint" + android:onClick="onLaunchErrorCallbackTest" + android:text="Error Callback" /> +</GridLayout> </android.support.constraint.ConstraintLayout> diff --git a/apps/OboeTester/app/src/main/res/values/strings.xml b/apps/OboeTester/app/src/main/res/values/strings.xml index ae03a606..bf1a6768 100644 --- a/apps/OboeTester/app/src/main/res/values/strings.xml +++ b/apps/OboeTester/app/src/main/res/values/strings.xml @@ -190,6 +190,9 @@ <string name="duration">Duration:</string> <string name="auto_default_status">Status...</string> <string name="device_report">Device Report</string> + <string name="plug_or_unplug">Plug in or Unplug a Headset</string> + <string name="report_magic_pass">PASS, got %X"</string> + <string name="report_magic_fail">FAIL, got %X, expected %X"</string> <string name="log_of_test_results">Log of Test Results</string> <string name="save_file">Save</string> @@ -201,6 +204,7 @@ <string name="test_disconnect_instructions">Disconnect all headsets.\nPress [START]</string> <string name="title_external_tap">External Tap To Tone</string> <string name="title_plug_latency">Plug Latency Test</string> + <string name="title_error_callback">Error Callback Test</string> <string name="analyze">Analyze</string> <string-array name="conversion_qualities"> |