aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/OboeTester/app/CMakeLists.txt2
-rw-r--r--apps/OboeTester/app/src/main/AndroidManifest.xml4
-rw-r--r--apps/OboeTester/app/src/main/cpp/TestErrorCallback.cpp70
-rw-r--r--apps/OboeTester/app/src/main/cpp/TestErrorCallback.h111
-rw-r--r--apps/OboeTester/app/src/main/cpp/jni-bridge.cpp15
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExtraTestsActivity.java4
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestErrorCallbackActivity.java100
-rw-r--r--apps/OboeTester/app/src/main/res/layout/activity_error_callback.xml35
-rw-r--r--apps/OboeTester/app/src/main/res/layout/activity_extra_tests.xml12
-rw-r--r--apps/OboeTester/app/src/main/res/values/strings.xml4
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">