Compare commits
1 Commits
android14
...
audio_outp
Author | SHA1 | Date | |
---|---|---|---|
99a0b13496 |
@ -3,11 +3,9 @@ _scrcpy() {
|
|||||||
local opts="
|
local opts="
|
||||||
--always-on-top
|
--always-on-top
|
||||||
--audio-bit-rate=
|
--audio-bit-rate=
|
||||||
--audio-buffer=
|
|
||||||
--audio-codec=
|
--audio-codec=
|
||||||
--audio-codec-options=
|
--audio-codec-options=
|
||||||
--audio-encoder=
|
--audio-encoder=
|
||||||
--audio-output-buffer=
|
|
||||||
-b --video-bit-rate=
|
-b --video-bit-rate=
|
||||||
--crop=
|
--crop=
|
||||||
-d --select-usb
|
-d --select-usb
|
||||||
@ -117,26 +115,20 @@ _scrcpy() {
|
|||||||
COMPREPLY=($(compgen -W "$("${ADB:-adb}" devices | awk '$2 == "device" {print $1}')" -- ${cur}))
|
COMPREPLY=($(compgen -W "$("${ADB:-adb}" devices | awk '$2 == "device" {print $1}')" -- ${cur}))
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--audio-bit-rate \
|
-b|--video-bit-rate \
|
||||||
|--audio-buffer \
|
|--codec-options \
|
||||||
|-b|--video-bit-rate \
|
|
||||||
|--audio-codec-options \
|
|
||||||
|--audio-encoder \
|
|
||||||
|--audio-output-buffer \
|
|
||||||
|--crop \
|
|--crop \
|
||||||
|--display \
|
|--display \
|
||||||
|--display-buffer \
|
|--display-buffer \
|
||||||
|
|--encoder \
|
||||||
|--max-fps \
|
|--max-fps \
|
||||||
|-m|--max-size \
|
|-m|--max-size \
|
||||||
|-p|--port \
|
|-p|--port \
|
||||||
|--push-target \
|
|--push-target \
|
||||||
|--rotation \
|
|
||||||
|--tunnel-host \
|
|--tunnel-host \
|
||||||
|--tunnel-port \
|
|--tunnel-port \
|
||||||
|--v4l2-buffer \
|
|--v4l2-buffer \
|
||||||
|--v4l2-sink \
|
|--v4l2-sink \
|
||||||
|--video-codec-options \
|
|
||||||
|--video-encoder \
|
|
||||||
|--tcpip \
|
|--tcpip \
|
||||||
|--window-*)
|
|--window-*)
|
||||||
# Option accepting an argument, but nothing to auto-complete
|
# Option accepting an argument, but nothing to auto-complete
|
||||||
|
@ -5,7 +5,7 @@ Comment=Display and control your Android device
|
|||||||
# For some users, the PATH or ADB environment variables are set from the shell
|
# For some users, the PATH or ADB environment variables are set from the shell
|
||||||
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
|
# startup file, like .bashrc or .zshrc… Run an interactive shell to get
|
||||||
# environment correctly initialized.
|
# environment correctly initialized.
|
||||||
Exec=/bin/bash --norc --noprofile -i -c "\"\\$SHELL\" -i -c scrcpy || read -p 'Press Enter to quit...'"
|
Exec=/bin/bash --norc --noprofile -i -c "\"\\$SHELL\" -i -c scrcpy || read -p 'Press any key to quit...'"
|
||||||
Icon=scrcpy
|
Icon=scrcpy
|
||||||
Terminal=true
|
Terminal=true
|
||||||
Type=Application
|
Type=Application
|
||||||
|
@ -10,11 +10,9 @@ local arguments
|
|||||||
arguments=(
|
arguments=(
|
||||||
'--always-on-top[Make scrcpy window always on top \(above other windows\)]'
|
'--always-on-top[Make scrcpy window always on top \(above other windows\)]'
|
||||||
'--audio-bit-rate=[Encode the audio at the given bit-rate]'
|
'--audio-bit-rate=[Encode the audio at the given bit-rate]'
|
||||||
'--audio-buffer=[Configure the audio buffering delay (in milliseconds)]'
|
|
||||||
'--audio-codec=[Select the audio codec]:codec:(opus aac raw)'
|
'--audio-codec=[Select the audio codec]:codec:(opus aac raw)'
|
||||||
'--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]'
|
'--audio-codec-options=[Set a list of comma-separated key\:type=value options for the device audio encoder]'
|
||||||
'--audio-encoder=[Use a specific MediaCodec audio encoder]'
|
'--audio-encoder=[Use a specific MediaCodec audio encoder]'
|
||||||
'--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]'
|
|
||||||
{-b,--video-bit-rate=}'[Encode the video at the given bit-rate]'
|
{-b,--video-bit-rate=}'[Encode the video at the given bit-rate]'
|
||||||
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
|
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
|
||||||
{-d,--select-usb}'[Use USB device]'
|
{-d,--select-usb}'[Use USB device]'
|
||||||
|
@ -59,59 +59,46 @@ public final class AudioCapture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void startWorkaroundAndroid11() {
|
private static void startWorkaroundAndroid11() {
|
||||||
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||||
// Android 11 requires Apps to be at foreground to record audio.
|
// Android 11 requires Apps to be at foreground to record audio.
|
||||||
// Normally, each App has its own user ID, so Android checks whether the requesting App has the user ID that's at the foreground.
|
// Normally, each App has its own user ID, so Android checks whether the requesting App has the user ID that's at the foreground.
|
||||||
// But scrcpy server is NOT an App, it's a Java application started from Android shell, so it has the same user ID (2000) with Android
|
// But scrcpy server is NOT an App, it's a Java application started from Android shell, so it has the same user ID (2000) with Android
|
||||||
// shell ("com.android.shell").
|
// shell ("com.android.shell").
|
||||||
// If there is an Activity from Android shell running at foreground, then the permission system will believe scrcpy is also in the
|
// If there is an Activity from Android shell running at foreground, then the permission system will believe scrcpy is also in the
|
||||||
// foreground.
|
// foreground.
|
||||||
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||||
Intent intent = new Intent(Intent.ACTION_MAIN);
|
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||||
intent.setComponent(new ComponentName(FakeContext.PACKAGE_NAME, "com.android.shell.HeapDumpActivity"));
|
intent.setComponent(new ComponentName(FakeContext.PACKAGE_NAME, "com.android.shell.HeapDumpActivity"));
|
||||||
ServiceManager.getActivityManager().startActivityAsUserWithFeature(intent);
|
ServiceManager.getActivityManager().startActivityAsUserWithFeature(intent);
|
||||||
|
// Wait for activity to start
|
||||||
|
SystemClock.sleep(150);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void stopWorkaroundAndroid11() {
|
private static void stopWorkaroundAndroid11() {
|
||||||
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||||
ServiceManager.getActivityManager().forceStopPackage(FakeContext.PACKAGE_NAME);
|
ServiceManager.getActivityManager().forceStopPackage(FakeContext.PACKAGE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryStartRecording(int attempts, int delayMs) throws AudioCaptureForegroundException {
|
|
||||||
while (attempts-- > 0) {
|
|
||||||
// Wait for activity to start
|
|
||||||
SystemClock.sleep(delayMs);
|
|
||||||
try {
|
|
||||||
startRecording();
|
|
||||||
return; // it worked
|
|
||||||
} catch (UnsupportedOperationException e) {
|
|
||||||
if (attempts == 0) {
|
|
||||||
Ln.e("Failed to start audio capture");
|
|
||||||
Ln.e("On Android 11, audio capture must be started in the foreground, make sure that the device is unlocked when starting " +
|
|
||||||
"scrcpy.");
|
|
||||||
throw new AudioCaptureForegroundException();
|
|
||||||
} else {
|
|
||||||
Ln.d("Failed to start audio capture, retrying...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startRecording() {
|
|
||||||
recorder = createAudioRecord();
|
|
||||||
recorder.startRecording();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() throws AudioCaptureForegroundException {
|
public void start() throws AudioCaptureForegroundException {
|
||||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
|
||||||
startWorkaroundAndroid11();
|
startWorkaroundAndroid11();
|
||||||
try {
|
try {
|
||||||
tryStartRecording(3, 100);
|
recorder = createAudioRecord();
|
||||||
|
recorder.startRecording();
|
||||||
|
} catch (UnsupportedOperationException e) {
|
||||||
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
|
||||||
|
Ln.e("Failed to start audio capture");
|
||||||
|
Ln.e("On Android 11, it is only possible to capture in foreground, make sure that the device is unlocked when starting scrcpy.");
|
||||||
|
throw new AudioCaptureForegroundException();
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
stopWorkaroundAndroid11();
|
stopWorkaroundAndroid11();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
startRecording();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
|
@ -271,22 +271,13 @@ public final class AudioEncoder implements AsyncProcessor {
|
|||||||
try {
|
try {
|
||||||
return MediaCodec.createByCodecName(encoderName);
|
return MediaCodec.createByCodecName(encoderName);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Ln.e("Audio encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildAudioEncoderListMessage());
|
Ln.e("Encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildAudioEncoderListMessage());
|
||||||
throw new ConfigurationException("Unknown encoder: " + encoderName);
|
throw new ConfigurationException("Unknown encoder: " + encoderName);
|
||||||
} catch (IOException e) {
|
|
||||||
Ln.e("Could not create audio encoder '" + encoderName + "' for " + codec.getName() + "\n" + LogUtils.buildAudioEncoderListMessage());
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType());
|
MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType());
|
||||||
Ln.d("Using audio encoder: '" + mediaCodec.getName() + "'");
|
Ln.d("Using audio encoder: '" + mediaCodec.getName() + "'");
|
||||||
return mediaCodec;
|
return mediaCodec;
|
||||||
} catch (IOException | IllegalArgumentException e) {
|
|
||||||
Ln.e("Could not create default audio encoder for " + codec.getName() + "\n" + LogUtils.buildAudioEncoderListMessage());
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class EncoderCallback extends MediaCodec.Callback {
|
private class EncoderCallback extends MediaCodec.Callback {
|
||||||
|
@ -288,7 +288,10 @@ public final class Device {
|
|||||||
boolean allOk = true;
|
boolean allOk = true;
|
||||||
for (long physicalDisplayId : physicalDisplayIds) {
|
for (long physicalDisplayId : physicalDisplayIds) {
|
||||||
IBinder binder = SurfaceControl.getPhysicalDisplayToken(physicalDisplayId);
|
IBinder binder = SurfaceControl.getPhysicalDisplayToken(physicalDisplayId);
|
||||||
allOk &= SurfaceControl.setDisplayPowerMode(binder, mode);
|
boolean ok = SurfaceControl.setDisplayPowerMode(binder, mode);
|
||||||
|
if (!ok) {
|
||||||
|
allOk = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return allOk;
|
return allOk;
|
||||||
}
|
}
|
||||||
|
@ -38,10 +38,4 @@ public final class FakeContext extends ContextWrapper {
|
|||||||
builder.setPackageName(PACKAGE_NAME);
|
builder.setPackageName(PACKAGE_NAME);
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override to be added on SDK upgrade for Android 14
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public int getDeviceId() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -202,22 +202,13 @@ public class ScreenEncoder implements Device.RotationListener {
|
|||||||
try {
|
try {
|
||||||
return MediaCodec.createByCodecName(encoderName);
|
return MediaCodec.createByCodecName(encoderName);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Ln.e("Video encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildVideoEncoderListMessage());
|
Ln.e("Encoder '" + encoderName + "' for " + codec.getName() + " not found\n" + LogUtils.buildVideoEncoderListMessage());
|
||||||
throw new ConfigurationException("Unknown encoder: " + encoderName);
|
throw new ConfigurationException("Unknown encoder: " + encoderName);
|
||||||
} catch (IOException e) {
|
|
||||||
Ln.e("Could not create video encoder '" + encoderName + "' for " + codec.getName() + "\n" + LogUtils.buildVideoEncoderListMessage());
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType());
|
MediaCodec mediaCodec = MediaCodec.createEncoderByType(codec.getMimeType());
|
||||||
Ln.d("Using video encoder: '" + mediaCodec.getName() + "'");
|
Ln.d("Using encoder: '" + mediaCodec.getName() + "'");
|
||||||
return mediaCodec;
|
return mediaCodec;
|
||||||
} catch (IOException | IllegalArgumentException e) {
|
|
||||||
Ln.e("Could not create default video encoder for " + codec.getName() + "\n" + LogUtils.buildVideoEncoderListMessage());
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaFormat createFormat(String videoMimeType, int bitRate, int maxFps, List<CodecOption> codecOptions) {
|
private static MediaFormat createFormat(String videoMimeType, int bitRate, int maxFps, List<CodecOption> codecOptions) {
|
||||||
|
@ -16,9 +16,9 @@ public class ClipboardManager {
|
|||||||
private Method getPrimaryClipMethod;
|
private Method getPrimaryClipMethod;
|
||||||
private Method setPrimaryClipMethod;
|
private Method setPrimaryClipMethod;
|
||||||
private Method addPrimaryClipChangedListener;
|
private Method addPrimaryClipChangedListener;
|
||||||
private int getMethodVersion;
|
private boolean alternativeGetMethod;
|
||||||
private int setMethodVersion;
|
private boolean alternativeSetMethod;
|
||||||
private int addListenerMethodVersion;
|
private boolean alternativeAddListenerMethod;
|
||||||
|
|
||||||
public ClipboardManager(IInterface manager) {
|
public ClipboardManager(IInterface manager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
@ -31,15 +31,9 @@ public class ClipboardManager {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
|
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
|
||||||
getMethodVersion = 0;
|
} catch (NoSuchMethodException e) {
|
||||||
} catch (NoSuchMethodException e1) {
|
|
||||||
try {
|
|
||||||
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class);
|
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class);
|
||||||
getMethodVersion = 1;
|
alternativeGetMethod = true;
|
||||||
} catch (NoSuchMethodException e2) {
|
|
||||||
getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class);
|
|
||||||
getMethodVersion = 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,62 +47,41 @@ public class ClipboardManager {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class);
|
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class);
|
||||||
setMethodVersion = 0;
|
} catch (NoSuchMethodException e) {
|
||||||
} catch (NoSuchMethodException e1) {
|
|
||||||
try {
|
|
||||||
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class);
|
setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class);
|
||||||
setMethodVersion = 1;
|
alternativeSetMethod = true;
|
||||||
} catch (NoSuchMethodException e2) {
|
|
||||||
setPrimaryClipMethod = manager.getClass()
|
|
||||||
.getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class, int.class);
|
|
||||||
setMethodVersion = 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return setPrimaryClipMethod;
|
return setPrimaryClipMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ClipData getPrimaryClip(Method method, int methodVersion, IInterface manager)
|
private static ClipData getPrimaryClip(Method method, boolean alternativeMethod, IInterface manager)
|
||||||
throws InvocationTargetException, IllegalAccessException {
|
throws InvocationTargetException, IllegalAccessException {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME);
|
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME);
|
||||||
}
|
}
|
||||||
|
if (alternativeMethod) {
|
||||||
switch (methodVersion) {
|
|
||||||
case 0:
|
|
||||||
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID);
|
|
||||||
case 1:
|
|
||||||
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
|
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
|
||||||
default:
|
|
||||||
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0);
|
|
||||||
}
|
}
|
||||||
|
return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setPrimaryClip(Method method, int methodVersion, IInterface manager, ClipData clipData)
|
private static void setPrimaryClip(Method method, boolean alternativeMethod, IInterface manager, ClipData clipData)
|
||||||
throws InvocationTargetException, IllegalAccessException {
|
throws InvocationTargetException, IllegalAccessException {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME);
|
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME);
|
||||||
return;
|
} else if (alternativeMethod) {
|
||||||
}
|
|
||||||
|
|
||||||
switch (methodVersion) {
|
|
||||||
case 0:
|
|
||||||
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
|
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
|
||||||
break;
|
} else {
|
||||||
default:
|
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID);
|
||||||
method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CharSequence getText() {
|
public CharSequence getText() {
|
||||||
try {
|
try {
|
||||||
Method method = getGetPrimaryClipMethod();
|
Method method = getGetPrimaryClipMethod();
|
||||||
ClipData clipData = getPrimaryClip(method, getMethodVersion, manager);
|
ClipData clipData = getPrimaryClip(method, alternativeGetMethod, manager);
|
||||||
if (clipData == null || clipData.getItemCount() == 0) {
|
if (clipData == null || clipData.getItemCount() == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -123,7 +96,7 @@ public class ClipboardManager {
|
|||||||
try {
|
try {
|
||||||
Method method = getSetPrimaryClipMethod();
|
Method method = getSetPrimaryClipMethod();
|
||||||
ClipData clipData = ClipData.newPlainText(null, text);
|
ClipData clipData = ClipData.newPlainText(null, text);
|
||||||
setPrimaryClip(method, setMethodVersion, manager, clipData);
|
setPrimaryClip(method, alternativeSetMethod, manager, clipData);
|
||||||
return true;
|
return true;
|
||||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
Ln.e("Could not invoke method", e);
|
Ln.e("Could not invoke method", e);
|
||||||
@ -131,23 +104,14 @@ public class ClipboardManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager,
|
private static void addPrimaryClipChangedListener(Method method, boolean alternativeMethod, IInterface manager,
|
||||||
IOnPrimaryClipChangedListener listener) throws InvocationTargetException, IllegalAccessException {
|
IOnPrimaryClipChangedListener listener) throws InvocationTargetException, IllegalAccessException {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
method.invoke(manager, listener, FakeContext.PACKAGE_NAME);
|
method.invoke(manager, listener, FakeContext.PACKAGE_NAME);
|
||||||
return;
|
} else if (alternativeMethod) {
|
||||||
}
|
|
||||||
|
|
||||||
switch (methodVersion) {
|
|
||||||
case 0:
|
|
||||||
method.invoke(manager, listener, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
|
method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
|
||||||
break;
|
} else {
|
||||||
default:
|
method.invoke(manager, listener, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID);
|
||||||
method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,19 +124,10 @@ public class ClipboardManager {
|
|||||||
try {
|
try {
|
||||||
addPrimaryClipChangedListener = manager.getClass()
|
addPrimaryClipChangedListener = manager.getClass()
|
||||||
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class);
|
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class);
|
||||||
addListenerMethodVersion = 0;
|
} catch (NoSuchMethodException e) {
|
||||||
} catch (NoSuchMethodException e1) {
|
|
||||||
try {
|
|
||||||
addPrimaryClipChangedListener = manager.getClass()
|
addPrimaryClipChangedListener = manager.getClass()
|
||||||
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class,
|
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class, int.class);
|
||||||
int.class);
|
alternativeAddListenerMethod = true;
|
||||||
addListenerMethodVersion = 1;
|
|
||||||
} catch (NoSuchMethodException e2) {
|
|
||||||
addPrimaryClipChangedListener = manager.getClass()
|
|
||||||
.getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class,
|
|
||||||
int.class, int.class);
|
|
||||||
addListenerMethodVersion = 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,7 +137,7 @@ public class ClipboardManager {
|
|||||||
public boolean addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
|
public boolean addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
|
||||||
try {
|
try {
|
||||||
Method method = getAddPrimaryClipChangedListener();
|
Method method = getAddPrimaryClipChangedListener();
|
||||||
addPrimaryClipChangedListener(method, addListenerMethodVersion, manager, listener);
|
addPrimaryClipChangedListener(method, alternativeAddListenerMethod, manager, listener);
|
||||||
return true;
|
return true;
|
||||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
Ln.e("Could not invoke method", e);
|
Ln.e("Could not invoke method", e);
|
||||||
|
Reference in New Issue
Block a user