Merge changes I8c36f5af,I800bc352 am: 9acfdcf214 am: 8b93d78c01 am: 9daeb3e9ad am: 5d85c9f569

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1826714

Change-Id: I21b1896f6be97725179c9edb6c70484f937b02a0
This commit is contained in:
Treehugger Robot
2021-09-16 00:34:27 +00:00
committed by Automerger Merge Worker
8 changed files with 255 additions and 49 deletions

View File

@@ -14,8 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
#include <stdio.h> #include <stdio.h>
#include <unordered_set>
//#define LOG_NDEBUG 0 //#define LOG_NDEBUG 0
#define LOG_TAG "AudioEffects-JNI" #define LOG_TAG "AudioEffects-JNI"
@@ -61,21 +61,14 @@ static fields_t fields;
struct effect_callback_cookie { struct effect_callback_cookie {
jclass audioEffect_class; // AudioEffect class jclass audioEffect_class; // AudioEffect class
jobject audioEffect_ref; // AudioEffect object instance jobject audioEffect_ref; // AudioEffect object instance
}; bool busy;
Condition cond;
// ----------------------------------------------------------------------------
class AudioEffectJniStorage {
public:
effect_callback_cookie mCallbackData;
AudioEffectJniStorage() {
}
~AudioEffectJniStorage() {
}
}; };
// ----------------------------------------------------------------------------
struct AudioEffectJniStorage {
effect_callback_cookie mCallbackData{};
};
jint AudioEffectJni::translateNativeErrorToJava(int code) { jint AudioEffectJni::translateNativeErrorToJava(int code) {
switch(code) { switch(code) {
@@ -104,6 +97,7 @@ jint AudioEffectJni::translateNativeErrorToJava(int code) {
} }
static Mutex sLock; static Mutex sLock;
static std::unordered_set<effect_callback_cookie*> sAudioEffectCallBackCookies;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
static void effectCallback(int event, void* user, void *info) { static void effectCallback(int event, void* user, void *info) {
@@ -124,7 +118,13 @@ static void effectCallback(int event, void* user, void *info) {
ALOGW("effectCallback error user %p, env %p", user, env); ALOGW("effectCallback error user %p, env %p", user, env);
return; return;
} }
{
Mutex::Autolock l(sLock);
if (sAudioEffectCallBackCookies.count(callbackInfo) == 0) {
return;
}
callbackInfo->busy = true;
}
ALOGV("effectCallback: callbackInfo %p, audioEffect_ref %p audioEffect_class %p", ALOGV("effectCallback: callbackInfo %p, audioEffect_ref %p audioEffect_class %p",
callbackInfo, callbackInfo,
callbackInfo->audioEffect_ref, callbackInfo->audioEffect_ref,
@@ -191,6 +191,11 @@ effectCallback_Exit:
env->ExceptionDescribe(); env->ExceptionDescribe();
env->ExceptionClear(); env->ExceptionClear();
} }
{
Mutex::Autolock l(sLock);
callbackInfo->busy = false;
callbackInfo->cond.broadcast();
}
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@@ -401,6 +406,10 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t
setAudioEffect(env, thiz, lpAudioEffect); setAudioEffect(env, thiz, lpAudioEffect);
} }
{
Mutex::Autolock l(sLock);
sAudioEffectCallBackCookies.insert(&lpJniStorage->mCallbackData);
}
env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage); env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage);
return (jint) AUDIOEFFECT_SUCCESS; return (jint) AUDIOEFFECT_SUCCESS;
@@ -432,6 +441,7 @@ setup_failure:
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#define CALLBACK_COND_WAIT_TIMEOUT_MS 1000
static void android_media_AudioEffect_native_release(JNIEnv *env, jobject thiz) { static void android_media_AudioEffect_native_release(JNIEnv *env, jobject thiz) {
sp<AudioEffect> lpAudioEffect = setAudioEffect(env, thiz, 0); sp<AudioEffect> lpAudioEffect = setAudioEffect(env, thiz, 0);
if (lpAudioEffect == 0) { if (lpAudioEffect == 0) {
@@ -447,7 +457,17 @@ static void android_media_AudioEffect_native_release(JNIEnv *env, jobject thiz)
env->SetLongField(thiz, fields.fidJniData, 0); env->SetLongField(thiz, fields.fidJniData, 0);
if (lpJniStorage) { if (lpJniStorage) {
ALOGV("deleting pJniStorage: %p\n", lpJniStorage); Mutex::Autolock l(sLock);
effect_callback_cookie *lpCookie = &lpJniStorage->mCallbackData;
ALOGV("deleting lpJniStorage: %p\n", lpJniStorage);
sAudioEffectCallBackCookies.erase(lpCookie);
while (lpCookie->busy) {
if (lpCookie->cond.waitRelative(sLock,
milliseconds(CALLBACK_COND_WAIT_TIMEOUT_MS)) !=
NO_ERROR) {
break;
}
}
env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioEffect_class); env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioEffect_class);
env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioEffect_ref); env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioEffect_ref);
delete lpJniStorage; delete lpJniStorage;

View File

@@ -15,6 +15,7 @@
*/ */
#include <stdio.h> #include <stdio.h>
#include <unordered_set>
//#define LOG_NDEBUG 0 //#define LOG_NDEBUG 0
#define LOG_TAG "visualizers-JNI" #define LOG_TAG "visualizers-JNI"
@@ -68,6 +69,12 @@ struct visualizer_callback_cookie {
jclass visualizer_class; // Visualizer class jclass visualizer_class; // Visualizer class
jobject visualizer_ref; // Visualizer object instance jobject visualizer_ref; // Visualizer object instance
// 'busy_count' and 'cond' together with 'sLock' are used to serialize
// concurrent access to the callback cookie from 'setup'/'release'
// and the callback.
int busy_count;
Condition cond;
// Lazily allocated arrays used to hold callback data provided to java // Lazily allocated arrays used to hold callback data provided to java
// applications. These arrays are allocated during the first callback and // applications. These arrays are allocated during the first callback and
// reallocated when the size of the callback data changes. Allocating on // reallocated when the size of the callback data changes. Allocating on
@@ -75,14 +82,12 @@ struct visualizer_callback_cookie {
// reference to the provided data (they need to make a copy if they want to // reference to the provided data (they need to make a copy if they want to
// hold onto outside of the callback scope), but it avoids GC thrash caused // hold onto outside of the callback scope), but it avoids GC thrash caused
// by constantly allocating and releasing arrays to hold callback data. // by constantly allocating and releasing arrays to hold callback data.
// 'callback_data_lock' must never be held at the same time with 'sLock'.
Mutex callback_data_lock; Mutex callback_data_lock;
jbyteArray waveform_data; jbyteArray waveform_data;
jbyteArray fft_data; jbyteArray fft_data;
visualizer_callback_cookie() { // Assumes use of default initialization by the client.
waveform_data = NULL;
fft_data = NULL;
}
~visualizer_callback_cookie() { ~visualizer_callback_cookie() {
cleanupBuffers(); cleanupBuffers();
@@ -107,15 +112,8 @@ struct visualizer_callback_cookie {
}; };
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
class VisualizerJniStorage { struct VisualizerJniStorage {
public: visualizer_callback_cookie mCallbackData{};
visualizer_callback_cookie mCallbackData;
VisualizerJniStorage() {
}
~VisualizerJniStorage() {
}
}; };
@@ -141,6 +139,7 @@ static jint translateError(int code) {
} }
static Mutex sLock; static Mutex sLock;
static std::unordered_set<visualizer_callback_cookie*> sVisualizerCallBackCookies;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
static void ensureArraySize(JNIEnv *env, jbyteArray *array, uint32_t size) { static void ensureArraySize(JNIEnv *env, jbyteArray *array, uint32_t size) {
@@ -178,11 +177,19 @@ static void captureCallback(void* user,
return; return;
} }
{
Mutex::Autolock l(sLock);
if (sVisualizerCallBackCookies.count(callbackInfo) == 0) {
return;
}
callbackInfo->busy_count++;
}
ALOGV("captureCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p", ALOGV("captureCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p",
callbackInfo, callbackInfo,
callbackInfo->visualizer_ref, callbackInfo->visualizer_ref,
callbackInfo->visualizer_class); callbackInfo->visualizer_class);
{
AutoMutex lock(&callbackInfo->callback_data_lock); AutoMutex lock(&callbackInfo->callback_data_lock);
if (waveformSize != 0 && waveform != NULL) { if (waveformSize != 0 && waveform != NULL) {
@@ -224,11 +231,17 @@ static void captureCallback(void* user,
jArray); jArray);
} }
} }
} // callback_data_lock scope
if (env->ExceptionCheck()) { if (env->ExceptionCheck()) {
env->ExceptionDescribe(); env->ExceptionDescribe();
env->ExceptionClear(); env->ExceptionClear();
} }
{
Mutex::Autolock l(sLock);
callbackInfo->busy_count--;
callbackInfo->cond.broadcast();
}
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@@ -337,16 +350,41 @@ static void android_media_visualizer_effect_callback(int32_t event,
void *info) { void *info) {
if ((event == AudioEffect::EVENT_ERROR) && if ((event == AudioEffect::EVENT_ERROR) &&
(*((status_t*)info) == DEAD_OBJECT)) { (*((status_t*)info) == DEAD_OBJECT)) {
VisualizerJniStorage* lpJniStorage = (VisualizerJniStorage*)user; visualizer_callback_cookie* callbackInfo =
visualizer_callback_cookie* callbackInfo = &lpJniStorage->mCallbackData; (visualizer_callback_cookie *)user;
JNIEnv *env = AndroidRuntime::getJNIEnv(); JNIEnv *env = AndroidRuntime::getJNIEnv();
if (!user || !env) {
ALOGW("effectCallback error user %p, env %p", user, env);
return;
}
{
Mutex::Autolock l(sLock);
if (sVisualizerCallBackCookies.count(callbackInfo) == 0) {
return;
}
callbackInfo->busy_count++;
}
ALOGV("effectCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p",
callbackInfo,
callbackInfo->visualizer_ref,
callbackInfo->visualizer_class);
env->CallStaticVoidMethod( env->CallStaticVoidMethod(
callbackInfo->visualizer_class, callbackInfo->visualizer_class,
fields.midPostNativeEvent, fields.midPostNativeEvent,
callbackInfo->visualizer_ref, callbackInfo->visualizer_ref,
NATIVE_EVENT_SERVER_DIED, NATIVE_EVENT_SERVER_DIED,
0, NULL); 0, NULL);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
{
Mutex::Autolock l(sLock);
callbackInfo->busy_count--;
callbackInfo->cond.broadcast();
}
} }
} }
@@ -396,7 +434,7 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th
} }
lpVisualizer->set(0, lpVisualizer->set(0,
android_media_visualizer_effect_callback, android_media_visualizer_effect_callback,
lpJniStorage, &lpJniStorage->mCallbackData,
(audio_session_t) sessionId); (audio_session_t) sessionId);
lStatus = translateError(lpVisualizer->initCheck()); lStatus = translateError(lpVisualizer->initCheck());
@@ -417,6 +455,10 @@ android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_th
setVisualizer(env, thiz, lpVisualizer); setVisualizer(env, thiz, lpVisualizer);
{
Mutex::Autolock l(sLock);
sVisualizerCallBackCookies.insert(&lpJniStorage->mCallbackData);
}
env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage); env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage);
return VISUALIZER_SUCCESS; return VISUALIZER_SUCCESS;
@@ -439,13 +481,15 @@ setup_failure:
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#define CALLBACK_COND_WAIT_TIMEOUT_MS 1000
static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) { static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz) {
{ //limit scope so that lpVisualizer is deleted before JNI storage data. {
sp<Visualizer> lpVisualizer = setVisualizer(env, thiz, 0); sp<Visualizer> lpVisualizer = setVisualizer(env, thiz, 0);
if (lpVisualizer == 0) { if (lpVisualizer == 0) {
return; return;
} }
lpVisualizer->release(); lpVisualizer->release();
// Visualizer can still can be held by AudioEffect::EffectClient
} }
// delete the JNI data // delete the JNI data
VisualizerJniStorage* lpJniStorage = VisualizerJniStorage* lpJniStorage =
@@ -456,9 +500,22 @@ static void android_media_visualizer_native_release(JNIEnv *env, jobject thiz)
env->SetLongField(thiz, fields.fidJniData, 0); env->SetLongField(thiz, fields.fidJniData, 0);
if (lpJniStorage) { if (lpJniStorage) {
{
Mutex::Autolock l(sLock);
visualizer_callback_cookie *lpCookie = &lpJniStorage->mCallbackData;
ALOGV("deleting pJniStorage: %p\n", lpJniStorage); ALOGV("deleting pJniStorage: %p\n", lpJniStorage);
sVisualizerCallBackCookies.erase(lpCookie);
while (lpCookie->busy_count > 0) {
if (lpCookie->cond.waitRelative(sLock,
milliseconds(CALLBACK_COND_WAIT_TIMEOUT_MS)) !=
NO_ERROR) {
break;
}
}
ALOG_ASSERT(lpCookie->busy_count == 0, "Unbalanced busy_count inc/dec");
env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_class); env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_class);
env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_ref); env->DeleteGlobalRef(lpJniStorage->mCallbackData.visualizer_ref);
} // sLock scope
delete lpJniStorage; delete lpJniStorage;
} }
} }
@@ -714,4 +771,3 @@ int register_android_media_visualizer(JNIEnv *env)
{ {
return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
} }

View File

@@ -13,6 +13,10 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<!--
Make sure to enable access to the mic in settings and run:
adb shell am compat enable ALLOW_TEST_API_ACCESS com.android.effectstest
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.effectstest"> package="com.android.effectstest">

View File

@@ -187,6 +187,11 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:scaleType="fitXY"/> android:scaleType="fitXY"/>
<Button android:id="@+id/hammer_on_release_bug"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="@string/hammer_on_release_bug_name">
</Button>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@@ -175,6 +175,11 @@
</LinearLayout> </LinearLayout>
<Button android:id="@+id/hammer_on_release_bug"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:text="@string/hammer_on_release_bug_name">
</Button>
<ImageView <ImageView
android:src="@android:drawable/divider_horizontal_dark" android:src="@android:drawable/divider_horizontal_dark"
android:layout_width="fill_parent" android:layout_width="fill_parent"

View File

@@ -37,4 +37,6 @@
<string name="send_level_name">Send Level</string> <string name="send_level_name">Send Level</string>
<!-- Toggles use of a multi-threaded client for an effect [CHAR LIMIT=24] --> <!-- Toggles use of a multi-threaded client for an effect [CHAR LIMIT=24] -->
<string name="effect_multithreaded">Multithreaded Use</string> <string name="effect_multithreaded">Multithreaded Use</string>
<!-- Runs a stress test for a bug related to simultaneous release of multiple effect instances [CHAR LIMIT=24] -->
<string name="hammer_on_release_bug_name">Hammer on release()</string>
</resources> </resources>

View File

@@ -17,29 +17,24 @@
package com.android.effectstest; package com.android.effectstest;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.media.audiofx.AudioEffect;
import android.content.Intent; import android.media.audiofx.BassBoost;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu;
import android.view.View.OnClickListener;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.View.OnClickListener;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.ToggleButton;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.CompoundButton.OnCheckedChangeListener;
import java.nio.ByteOrder; import android.widget.EditText;
import java.nio.ByteBuffer; import android.widget.SeekBar;
import java.util.HashMap; import android.widget.TextView;
import java.util.Map; import android.widget.ToggleButton;
import android.media.audiofx.BassBoost; import java.nio.ByteBuffer;
import android.media.audiofx.AudioEffect; import java.nio.ByteOrder;
import java.util.HashMap;
public class BassBoostTest extends Activity implements OnCheckedChangeListener { public class BassBoostTest extends Activity implements OnCheckedChangeListener {
@@ -78,6 +73,9 @@ public class BassBoostTest extends Activity implements OnCheckedChangeListener {
mReleaseButton = (ToggleButton)findViewById(R.id.bbReleaseButton); mReleaseButton = (ToggleButton)findViewById(R.id.bbReleaseButton);
mOnOffButton = (ToggleButton)findViewById(R.id.bassboostOnOff); mOnOffButton = (ToggleButton)findViewById(R.id.bassboostOnOff);
final Button hammerReleaseTest = (Button) findViewById(R.id.hammer_on_release_bug);
hammerReleaseTest.setEnabled(false);
getEffect(sSession); getEffect(sSession);
if (mBassBoost != null) { if (mBassBoost != null) {
@@ -93,6 +91,14 @@ public class BassBoostTest extends Activity implements OnCheckedChangeListener {
mStrength = new BassBoostParam(mBassBoost, 0, 1000, seekBar, textView); mStrength = new BassBoostParam(mBassBoost, 0, 1000, seekBar, textView);
seekBar.setOnSeekBarChangeListener(mStrength); seekBar.setOnSeekBarChangeListener(mStrength);
mStrength.setEnabled(mBassBoost.getStrengthSupported()); mStrength.setEnabled(mBassBoost.getStrengthSupported());
hammerReleaseTest.setEnabled(true);
hammerReleaseTest.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
runHammerReleaseTest(hammerReleaseTest);
}
});
} }
} }
@@ -273,4 +279,52 @@ public class BassBoostTest extends Activity implements OnCheckedChangeListener {
} }
} }
// Stress-tests releasing of AudioEffect by doing repeated creation
// and subsequent releasing. Also forces emission of callbacks from
// the AudioFlinger by setting a control status listener. Since all
// effect instances are bound to the same session, the AF will
// notify them about the change in their status. This can reveal racy
// behavior w.r.t. releasing.
class HammerReleaseTest extends Thread {
private static final int NUM_EFFECTS = 10;
private static final int NUM_ITERATIONS = 100;
private final int mSession;
private final Runnable mOnComplete;
HammerReleaseTest(int session, Runnable onComplete) {
mSession = session;
mOnComplete = onComplete;
}
@Override
public void run() {
Log.w(TAG, "HammerReleaseTest started");
BassBoost[] effects = new BassBoost[NUM_EFFECTS];
for (int i = 0; i < NUM_ITERATIONS; i++) {
for (int j = 0; j < NUM_EFFECTS; j++) {
effects[j] = new BassBoost(0, mSession);
effects[j].setControlStatusListener(mEffectListener);
yield();
}
for (int j = NUM_EFFECTS - 1; j >= 0; j--) {
Log.w(TAG, "HammerReleaseTest releasing effect " + (Object) effects[j]);
effects[j].release();
effects[j] = null;
yield();
}
}
Log.w(TAG, "HammerReleaseTest ended");
runOnUiThread(mOnComplete);
}
}
private void runHammerReleaseTest(Button controlButton) {
controlButton.setEnabled(false);
HammerReleaseTest thread = new HammerReleaseTest(sSession,
() -> {
controlButton.setEnabled(true);
});
thread.start();
}
} }

View File

@@ -17,6 +17,7 @@
package com.android.effectstest; package com.android.effectstest;
import android.app.Activity; import android.app.Activity;
import android.media.audiofx.Visualizer;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
@@ -24,6 +25,8 @@ import android.os.Message;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText; import android.widget.EditText;
@@ -74,11 +77,22 @@ public class VisualizerTest extends Activity implements OnCheckedChangeListener
mCallbackOn = false; mCallbackOn = false;
mCallbackButton.setChecked(mCallbackOn); mCallbackButton.setChecked(mCallbackOn);
final Button hammerReleaseTest = (Button) findViewById(R.id.hammer_on_release_bug);
hammerReleaseTest.setEnabled(false);
mMultithreadedButton.setOnCheckedChangeListener(this); mMultithreadedButton.setOnCheckedChangeListener(this);
if (getEffect(sSession) != null) { if (getEffect(sSession) != null) {
mReleaseButton.setOnCheckedChangeListener(this); mReleaseButton.setOnCheckedChangeListener(this);
mOnOffButton.setOnCheckedChangeListener(this); mOnOffButton.setOnCheckedChangeListener(this);
mCallbackButton.setOnCheckedChangeListener(this); mCallbackButton.setOnCheckedChangeListener(this);
hammerReleaseTest.setEnabled(true);
hammerReleaseTest.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
runHammerReleaseTest(hammerReleaseTest);
}
});
} }
} }
@@ -214,4 +228,50 @@ public class VisualizerTest extends Activity implements OnCheckedChangeListener
} }
} }
// Stress-tests releasing of AudioEffect by doing repeated creation
// and subsequent releasing. Unlike a similar class in BassBoostTest,
// this one doesn't sets a control status listener because Visualizer
// doesn't inherit from AudioEffect and doesn't implement this method
// by itself.
class HammerReleaseTest extends Thread {
private static final int NUM_EFFECTS = 10;
private static final int NUM_ITERATIONS = 100;
private final int mSession;
private final Runnable mOnComplete;
HammerReleaseTest(int session, Runnable onComplete) {
mSession = session;
mOnComplete = onComplete;
}
@Override
public void run() {
Log.w(TAG, "HammerReleaseTest started");
Visualizer[] effects = new Visualizer[NUM_EFFECTS];
for (int i = 0; i < NUM_ITERATIONS; i++) {
for (int j = 0; j < NUM_EFFECTS; j++) {
effects[j] = new Visualizer(mSession);
yield();
}
for (int j = NUM_EFFECTS - 1; j >= 0; j--) {
Log.w(TAG, "HammerReleaseTest releasing effect " + (Object) effects[j]);
effects[j].release();
effects[j] = null;
yield();
}
}
Log.w(TAG, "HammerReleaseTest ended");
runOnUiThread(mOnComplete);
}
}
private void runHammerReleaseTest(Button controlButton) {
controlButton.setEnabled(false);
HammerReleaseTest thread = new HammerReleaseTest(sSession,
() -> {
controlButton.setEnabled(true);
});
thread.start();
}
} }