Skip to content

Commit

Permalink
Use reflection to create AudioRecord
Browse files Browse the repository at this point in the history
  • Loading branch information
yume-chan committed Mar 26, 2023
1 parent 2fff9b9 commit 92e7bcb
Show file tree
Hide file tree
Showing 6 changed files with 657 additions and 1 deletion.
6 changes: 6 additions & 0 deletions server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ dependencies {
}

apply from: "$project.rootDir/config/android-checkstyle.gradle"

android {
lintOptions {
baseline file("lint-baseline.xml")
}
}
48 changes: 48 additions & 0 deletions server/lint-baseline.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 7.4.0" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0)" variant="fatal" version="7.4.0">

<issue
id="BlockedPrivateApi"
message="Reflective access to withPackageName is forbidden when targeting API 33 and above"
errorLine1=" Method withPackageNameMethod = AttributionSource.class.getDeclaredMethod(&quot;withPackageName&quot;,"
errorLine2=" ^">
<location
file="src/main/java/com/genymobile/scrcpy/wrappers/AudioRecordWrapper.java"
line="231"
column="52"/>
</issue>

<issue
id="BlockedPrivateApi"
message="Reflective access to asScopedParcelState is forbidden when targeting API 33 and above"
errorLine1=" Method asScopedParcelStateMethod = AttributionSource.class.getDeclaredMethod(&quot;asScopedParcelState&quot;);"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/com/genymobile/scrcpy/wrappers/AudioRecordWrapper.java"
line="240"
column="52"/>
</issue>

<issue
id="BlockedPrivateApi"
message="Reflective access to mPrivacySensitive is forbidden when targeting API 33 and above"
errorLine1=" Field mPrivacySensitiveField = AudioRecord.Builder.class.getDeclaredField(&quot;mPrivacySensitive&quot;);"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/com/genymobile/scrcpy/wrappers/AudioRecordWrapper.java"
line="326"
column="44"/>
</issue>

<issue
id="BlockedPrivateApi"
message="Reflective access to mContext is forbidden when targeting API 33 and above"
errorLine1=" Field mContextField = AudioRecord.Builder.class.getDeclaredField(&quot;mContext&quot;);"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/com/genymobile/scrcpy/wrappers/AudioRecordWrapper.java"
line="364"
column="39"/>
</issue>

</issues>
8 changes: 7 additions & 1 deletion server/src/main/java/com/genymobile/scrcpy/AudioCapture.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.genymobile.scrcpy;

import com.genymobile.scrcpy.wrappers.AudioRecordWrapper;
import com.genymobile.scrcpy.wrappers.ServiceManager;

import android.annotation.SuppressLint;
Expand Down Expand Up @@ -55,7 +56,12 @@ private static AudioRecord createAudioRecord() {
int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, FORMAT);
// This buffer size does not impact latency
builder.setBufferSizeInBytes(8 * minBufferSize);
return builder.build();

try {
return builder.build();
} catch (Exception e) {
return AudioRecordWrapper.build(builder);
}
}

private static void startWorkaroundAndroid11() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.genymobile.scrcpy.wrappers;

import com.genymobile.scrcpy.Ln;

import android.media.AudioAttributes;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;

public class AudioAttributesWrapper {
private static Method getCapturePresetMethod;
private static Method setCapturePresetMethod;
private static Method getTagsMethod;

private static Method getGetCapturePresetMethod() throws NoSuchMethodException {
if (getCapturePresetMethod == null) {
getCapturePresetMethod = AudioAttributes.class.getMethod("getCapturePreset");
}
return getCapturePresetMethod;
}

public static int getCapturePreset(AudioAttributes audioAttributes)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException {
try {
return (int) getGetCapturePresetMethod().invoke(audioAttributes);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException e) {
Ln.e("Failed to invoke AudioAttributes.getCapturePreset()", e);
throw e;
}
}

private static Method getSetCapturePresetMethod() throws NoSuchMethodException {
if (setCapturePresetMethod == null) {
setCapturePresetMethod = AudioAttributes.class.getMethod("setCapturePreset", int.class);
}
return setCapturePresetMethod;
}

public static void setCapturePreset(AudioAttributes audioAttributes, int preset)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException {
try {
getSetCapturePresetMethod().invoke(audioAttributes, preset);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException e) {
Ln.e("Failed to invoke AudioAttributes.setCapturePreset()", e);
throw e;
}
}

private static Method getGetTagsMethod() throws NoSuchMethodException {
if (getTagsMethod == null) {
getTagsMethod = AudioAttributes.class.getMethod("getTags");
}
return getTagsMethod;
}

@SuppressWarnings("unchecked")
public static Set<String> getTags(AudioAttributes audioAttributes)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException {
try {
return (Set<String>) getGetTagsMethod().invoke(audioAttributes);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException e) {
Ln.e("Failed to invoke AudioAttributes.getTags()", e);
throw e;
}
}

public static class Builder {
private static Method setInternalCapturePresetMethod;
private static Method setPrivacySensitiveMethod;
private static Method addTagMethod;

private static Method getSetInternalCapturePresetMethod() throws NoSuchMethodException {
if (setInternalCapturePresetMethod == null) {
setInternalCapturePresetMethod = AudioAttributes.Builder.class.getMethod("setInternalCapturePreset",
int.class);
}
return setInternalCapturePresetMethod;
}

public static void setInternalCapturePreset(AudioAttributes.Builder builder, int preset)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,
NoSuchMethodException {
try {
getSetInternalCapturePresetMethod().invoke(builder, preset);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException e) {
Ln.e("Failed to invoke AudioAttributes.Builder.setInternalCapturePreset()", e);
throw e;
}
}

private static Method getSetPrivacySensitiveMethod() throws NoSuchMethodException {
if (setPrivacySensitiveMethod == null) {
setPrivacySensitiveMethod = AudioAttributes.Builder.class.getMethod("setPrivacySensitive",
boolean.class);
}
return setPrivacySensitiveMethod;
}

public static void setPrivacySensitive(AudioAttributes.Builder builder, boolean privacySensitive)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,
NoSuchMethodException {
try {
getSetPrivacySensitiveMethod().invoke(builder, privacySensitive);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException e) {
Ln.e("Failed to invoke AudioAttributes.Builder.setPrivacySensitive()", e);
throw e;
}
}

private static Method getAddTagMethod() throws NoSuchMethodException {
if (addTagMethod == null) {
addTagMethod = AudioAttributes.Builder.class.getMethod("addTag", String.class);
}
return addTagMethod;
}

public static void addTag(AudioAttributes.Builder builder, String tag)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,
NoSuchMethodException {
try {
getAddTagMethod().invoke(builder, tag);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException e) {
Ln.e("Failed to invoke AudioAttributes.Builder.addTag()", e);
throw e;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.genymobile.scrcpy.wrappers;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import com.genymobile.scrcpy.Ln;

import android.media.AudioFormat;

public class AudioFormatWrapper {
public final static int AUDIO_FORMAT_HAS_PROPERTY_NONE = 0x0;
public final static int AUDIO_FORMAT_HAS_PROPERTY_ENCODING = 0x1 << 0;
public final static int AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE = 0x1 << 1;
public final static int AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK = 0x1 << 2;
public final static int AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK = 0x1 << 3;

private static Method getPropertySetMaskMethod;
private static Method channelCountFromInChannelMaskMethod;
private static Method getBytesPerSampleMethod;

private static Method getGetPropertySetMaskMethod() throws NoSuchMethodException {
if (getPropertySetMaskMethod == null) {
getPropertySetMaskMethod = AudioFormat.class.getMethod("getPropertySetMask");
}
return getPropertySetMaskMethod;
}

public static int getPropertySetMask(AudioFormat audioFormat)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,
NoSuchMethodException {
try {
return (int) getGetPropertySetMaskMethod().invoke(audioFormat);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException e) {
Ln.e("Failed to invoke AudioFormat.getPropertySetMask()", e);
throw e;
}
}

private static Method getChannelCountFromInChannelMaskMethod() throws NoSuchMethodException {
if (channelCountFromInChannelMaskMethod == null) {
channelCountFromInChannelMaskMethod = AudioFormat.class.getMethod("channelCountFromInChannelMask",
int.class);
}
return channelCountFromInChannelMaskMethod;
}

public static int channelCountFromInChannelMask(int channelMask)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,
NoSuchMethodException {
try {
return (int) getChannelCountFromInChannelMaskMethod().invoke(null, channelMask);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException e) {
Ln.e("Failed to invoke AudioFormat.channelCountFromInChannelMask()", e);
throw e;
}
}

private static Method getGetBytesPerSampleMethod() throws NoSuchMethodException {
if (getBytesPerSampleMethod == null) {
getBytesPerSampleMethod = AudioFormat.class.getMethod("getBytesPerSample", int.class);
}
return getBytesPerSampleMethod;
}

public static int getBytesPerSample(AudioFormat format, int encoding)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException,
NoSuchMethodException {
try {
return (int) getGetBytesPerSampleMethod().invoke(format, encoding);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException e) {
Ln.e("Failed to invoke AudioFormat.getBytesPerSample()", e);
throw e;
}
}
}
Loading

0 comments on commit 92e7bcb

Please sign in to comment.