Skip to content

Conversation

@kevin930321
Copy link

This PR implements audio isolation for apps running on a virtual display (--new-display).

When using --audio-source=playback flag, AppMonitor now detects the foreground app on the virtual display and restricts audio capture to its UID, isolating it from other device audio.

New CLI options for compatibility:

  • --audio-ignore-apps: Force global audio capture for specific apps (fixes apps that crash on UID switch).
  • --audio-filter-apps: Ignore specific apps (e.g., Display over other apps) to correctly target the underlying app.

@kevin930321 kevin930321 changed the base branch from master to dev December 26, 2025 11:38
@diffray-bot
Copy link

Changes Summary

This PR implements audio isolation for apps running on virtual displays via the --new-display flag. It introduces AppMonitor functionality to detect foreground apps and restrict audio capture to their UIDs, along with two new CLI options for compatibility. However, there is a critical integrity issue with the screen.c file.

Type: feature|mixed

Components Affected: Audio subsystem (AudioPlaybackCapture, AudioDirectCapture), Virtual display capture (NewDisplayCapture, SurfaceCapture), CLI argument parsing (cli.c, options.h), Server-client communication (server.c, Server.java), System service wrappers (ActivityManager, PackageManager, ServiceManager), Event injection (Controller.java)

Files Changed
File Summary Change Impact
app/src/cli.c Added two new CLI options: --audio-ignore-apps and --audio-filter-apps for controlling app-based audio isolation ✏️ 🟡
app/src/options.h Extended struct scrcpy_options with audio_ignore_apps and audio_filter_apps fields ✏️ 🟢
app/src/options.c Initialized new audio options to NULL in default options ✏️ 🟢
app/src/server.c Added parameter forwarding for audio_ignore_apps and audio_filter_apps to server invocation ✏️ 🟢
app/src/scrcpy.c Propagates audio_ignore_apps and audio_filter_apps from CLI options to server parameters ✏️ 🟢
app/src/screen.c CRITICAL ISSUE: File completely replaced with Java code (Controller.java content) instead of C code ✏️ 🔴
app/src/server.h Added audio_ignore_apps and audio_filter_apps to server_params structure ✏️ 🟢
...rc/main/java/com/genymobile/scrcpy/Options.java Added audioIgnoreApps and audioFilterApps list fields with getter methods and parsing logic ✏️ 🟡
...src/main/java/com/genymobile/scrcpy/Server.java Integrated AudioPlaybackCapture with NewDisplayCapture for audio isolation; added logging for audio source configuration ✏️ 🟡
...nymobile/scrcpy/audio/AudioPlaybackCapture.java Added setTargetUid() method and UID-based audio mixing rule logic using reflection to create AudioPolicy with RULE_MATCH_UID ✏️ 🔴
...a/com/genymobile/scrcpy/control/Controller.java Enhanced key character injection to handle unmapped characters by creating KeyEvent directly ✏️ 🟢
.../genymobile/scrcpy/video/NewDisplayCapture.java Implemented AppMonitor thread to detect foreground apps on virtual display and dynamically update AudioPlaybackCapture UID; added audioIgnoreApps/audioFilterApps filtering ✏️ 🔴
...genymobile/scrcpy/wrappers/ActivityManager.java Added getRunningTasks(int maxNum) method to retrieve foreground app information; minor code cleanup ✏️ 🟡
.../genymobile/scrcpy/wrappers/PackageManager.java NEW: Created PackageManager wrapper using reflection to access IPackageManager service for app UID/info lookups 🟡
.../genymobile/scrcpy/wrappers/ServiceManager.java Added PackageManager singleton instance and getter method; minor comment cleanup ✏️ 🟢
Architecture Impact
  • New Patterns: App monitoring background thread pattern, UID-based audio routing via reflection-based AudioPolicy, Foreground app detection on virtual displays
  • Dependencies: Added: PackageManager wrapper for reflection-based service access
  • Coupling: NewDisplayCapture now tightly coupled to AudioPlaybackCapture for dynamic UID updates. Server.java now must instantiate and configure AudioPlaybackCapture before passing to NewDisplayCapture.
  • Breaking Changes: CRITICAL: app/src/screen.c file has been replaced with Java code - this is a file integrity corruption

Risk Areas: CRITICAL: screen.c file corruption - contains Java code instead of C code, will cause build failure, Reflection-heavy implementation in AudioPlaybackCapture and ActivityManager - relies on private Android APIs that may change across versions, Background AppMonitor thread creation without documented lifecycle guarantees - could lead to race conditions if not carefully managed, Audio UID switching logic may cause audio interruptions during app transitions, PackageManager wrapper uses reflection to access IPackageManager - potential compatibility issues across Android versions, AppMonitor thread does not have exception handling around System.currentTimeMillis() or Thread.sleep() edge cases, Interaction between audioIgnoreApps and audioFilterApps filtering logic could be unclear to users

Suggestions
  • CRITICAL: Fix screen.c file - restore original C code from upstream/dev, do NOT merge until this is resolved
  • Add comprehensive documentation for --audio-ignore-apps and --audio-filter-apps interaction and use cases
  • Add try-catch around AppMonitor thread operations to handle exceptions gracefully
  • Document AppMonitor lifecycle and thread safety guarantees
  • Consider adding version-specific reflection helpers to handle Android API differences in AudioPlaybackCapture
  • Add unit tests for AppMonitor app detection logic with mocked ActivityManager
  • Verify that audio restart triggered by setTargetUid() doesn't cause noticeable artifacts
  • Add logging around UID changes in AudioPlaybackCapture for debugging
  • Consider versioning the reflection-based API calls to handle multiple Android API levels

Full review in progress... | Powered by diffray

Comment on lines +188 to +190
} catch (InterruptedException e) {
// ignore
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - InterruptedException caught without restoring interrupt flag
Agent: spring

Category: quality

Description:
InterruptedException is caught but only contains a comment '// ignore'. The interrupt flag is not restored, which can cause loss of interruption signals.

Suggestion:
Call Thread.currentThread().interrupt() to restore the interrupt flag, or document explicitly why ignoring is safe here (e.g., short sleep for timing purposes)

Why this matters: Unhandled errors cause failures.

Confidence: 72%
Rule: java_do_not_ignore_caught_exceptions
Review ID: 4fa8a08a-4c88-4c8e-acb1-aaf6ba9eb3b8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +232 to +238
if (r == 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
return -1;
}
return read(outDirectBuffer, outBufferInfo);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 HIGH - Potential unbounded recursion in read() method
Agent: bugs

Category: bug

Description:
When reader.read() returns 0, the method recurses after a 10ms sleep. If the reader consistently returns 0, this will cause StackOverflowError.

Suggestion:
Replace recursive call with a while loop with a maximum retry count, or use tail recursion optimization if available

Why this matters: Missing base case causes infinite recursion and StackOverflowError.

Confidence: 78%
Rule: java_recursion_no_base_case
Review ID: 4fa8a08a-4c88-4c8e-acb1-aaf6ba9eb3b8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +199 to +287
while (monitorRunning) {
try {
// Increase limit to 50 to ensure we find tasks even if many background tasks exist
java.util.List<android.app.ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(50);
boolean foundTopApp = false;
if (tasks != null) {
for (android.app.ActivityManager.RunningTaskInfo task : tasks) {
int taskDisplayId = getTaskDisplayId(task);
String paramPkg = task.topActivity != null ? task.topActivity.getPackageName() : "null";

if (audioFilterApps != null && audioFilterApps.contains(paramPkg)) {
continue;
}

if (taskDisplayId == displayId && task.topActivity != null) {
if (!foundTopApp) {
// This is the FIRST task we found for this display -> It is the Top App
foundTopApp = true;
String packageName = paramPkg;
if (!packageName.equals(lastPackageName)) {
Ln.i("App changed on display " + displayId + ": " + packageName);
lastPackageName = packageName;

int newUid = -1;
if (audioIgnoreApps != null && audioIgnoreApps.contains(packageName)) {
Ln.i("App ignored (forcing global audio): " + packageName);
newUid = -1;
} else {
// Resolve UID
try {
android.content.pm.ApplicationInfo info = pm.getApplicationInfo(packageName, 0, 0);
if (info != null) {
newUid = info.uid;
}
} catch (Exception e) {
Ln.e("Could not get UID for " + packageName, e);
}
}

if (newUid != lastUid) {
Ln.i("Updating audio capture target UID: " + newUid + " for package: " + packageName);
audioCapture.setTargetUid(newUid);
lastUid = newUid;
}
}
}
break;
}
}

}

if (!foundTopApp) {
if (!"SYSTEM".equals(lastPackageName)) {
Ln.i("No app detected on display " + displayId + ", switching to Global Audio");
lastPackageName = "SYSTEM";
if (lastUid != -1) {
audioCapture.setTargetUid(-1);
lastUid = -1;
}
}
}
Thread.sleep(500);
} catch (InterruptedException e) {
break;
} catch (Exception e) {
Ln.e("App monitor error", e);
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
// ignored
}
}
}
}, "AppMonitor");
appMonitorThread.start();
}

@android.annotation.SuppressLint("BlockedPrivateApi")
private static int getTaskDisplayId(android.app.ActivityManager.RunningTaskInfo task) {
try {
Class<?> taskInfoClass = Class.forName("android.app.TaskInfo");
java.lang.reflect.Field displayIdField = taskInfoClass.getDeclaredField("displayId");
displayIdField.setAccessible(true);
return displayIdField.getInt(task);
} catch (Exception e) {
Ln.w("Could not get displayId from task", e);
return -1;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 HIGH - Inefficient reflection calls in polling loop
Agent: performance

Category: performance

Description:
The appMonitor thread performs reflection lookups (Class.forName, getDeclaredField, setAccessible) on every poll iteration for each task. The getTaskDisplayId() method at lines 278-287 does full reflection on every call instead of caching the Field reference.

Suggestion:
Cache the reflected Field reference (displayIdField) as a static class field at class initialization, then only call getInt() on each iteration. Example: private static final Field DISPLAY_ID_FIELD = initDisplayIdField();

Why this matters: N+1 queries cause severe performance degradation.

Confidence: 90%
Rule: perf_n_plus_one_queries
Review ID: 4fa8a08a-4c88-4c8e-acb1-aaf6ba9eb3b8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +209 to +223
if (audioFilterApps != null && audioFilterApps.contains(paramPkg)) {
continue;
}

if (taskDisplayId == displayId && task.topActivity != null) {
if (!foundTopApp) {
// This is the FIRST task we found for this display -> It is the Top App
foundTopApp = true;
String packageName = paramPkg;
if (!packageName.equals(lastPackageName)) {
Ln.i("App changed on display " + displayId + ": " + packageName);
lastPackageName = packageName;

int newUid = -1;
if (audioIgnoreApps != null && audioIgnoreApps.contains(packageName)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - List.contains() in polling loop creates O(N*M) complexity
Agent: performance

Category: performance

Description:
audioFilterApps and audioIgnoreApps are declared as List (lines 62-63), causing O(N) lookup per contains() call. With up to 50 tasks checked every 500ms, this creates unnecessary overhead.

Suggestion:
Convert audioFilterApps and audioIgnoreApps from List to Set for O(1) lookup. Modify the constructor and Options class accordingly.

Why this matters: Poor performance degrades user experience.

Confidence: 68%
Rule: perf_quadratic_loops
Review ID: 4fa8a08a-4c88-4c8e-acb1-aaf6ba9eb3b8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +81 to +86
try {
AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usage).build();
addMixRuleMethod.invoke(audioMixingRuleBuilder, ruleMatchAttributeUsageConstant, attributes);
} catch (Exception e) {
// Ignore invalid usages if any
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - Empty catch block with only comment
Agent: quality

Category: quality

Description:
Catch block for Exception in loop (lines 80-87) silently ignores failures when adding audio usage rules. While intentional, lack of logging makes debugging difficult.

Suggestion:
Add debug logging: Ln.d("Skipping usage " + usage + ": " + e.getClass().getSimpleName());

Why this matters: Swallowed errors hide bugs and make debugging difficult.

Confidence: 65%
Rule: java_exception_ignored
Review ID: 4fa8a08a-4c88-4c8e-acb1-aaf6ba9eb3b8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

// IContentProvider provider = providerHolder.provider;
Field providerField = providerHolder.getClass().getDeclaredField("provider");
providerField.setAccessible(true);
return (IContentProvider) providerField.get(providerHolder);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - Reflection field.get() result cast without null check
Agent: security

Category: quality

Description:
providerField.get(providerHolder) is cast to IContentProvider without null checking. Field.get() can return null if the field value is null.

Suggestion:
Add null check before casting: Object provider = providerField.get(providerHolder); return provider != null ? (IContentProvider) provider : null;

Confidence: 65%
Rule: java_validate_parameter_types_before_casting
Review ID: 4fa8a08a-4c88-4c8e-acb1-aaf6ba9eb3b8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

// IContentProvider provider = providerHolder.provider;
Field providerField = providerHolder.getClass().getDeclaredField("provider");
providerField.setAccessible(true);
return (IContentProvider) providerField.get(providerHolder);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - Catch block swallows exception without propagation or error indication
Agent: spring

Category: quality

Description:
ReflectiveOperationException is caught and logged but then silently ignored with a void method. This prevents callers from knowing if the operation failed.

Suggestion:
Either throw a runtime exception to propagate the error, or return a boolean indicating success/failure instead of silently ignoring.

Confidence: 62%
Rule: java_do_not_ignore_caught_exceptions
Review ID: 4fa8a08a-4c88-4c8e-acb1-aaf6ba9eb3b8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines 155 to +158
public void forceStopPackage(String packageName) {
try {
Method method = getForceStopPackageMethod();
method.invoke(manager, packageName, /* userId */ /* UserHandle.USER_CURRENT */ -2);
method.invoke(manager, packageName, -2);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - Catch block swallows exception and returns ambiguous status code
Agent: spring

Category: quality

Description:
Throwable is caught and logged, then 0 is returned. The caller cannot distinguish between a successful activity start and failure.

Suggestion:
Return a distinct error value (e.g., -1) or throw an exception to clearly indicate failure state.

Confidence: 65%
Rule: java_do_not_ignore_caught_exceptions
Review ID: 4fa8a08a-4c88-4c8e-acb1-aaf6ba9eb3b8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines 159 to 161
} catch (Throwable e) {
Ln.e("Could not invoke method", e);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - Catch block swallows exception without error indication to caller
Agent: spring

Category: quality

Description:
Throwable is caught and logged but the method returns void without any indication of failure to the caller.

Suggestion:
Return a boolean value indicating success/failure, or throw a runtime exception to notify callers of the failure.

Confidence: 62%
Rule: java_do_not_ignore_caught_exceptions
Review ID: 4fa8a08a-4c88-4c8e-acb1-aaf6ba9eb3b8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +168 to +171
} catch (Throwable e) {
Ln.e("Could not invoke getTasks", e);
return null;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - Catch block silently returns null on error without clear failure indication
Agent: spring

Category: quality

Description:
Throwable is caught and logged but null is returned, making it impossible for callers to distinguish failure from a legitimate empty result.

Suggestion:
Return Optional or throw an exception to distinguish error state from empty result.

Confidence: 60%
Rule: java_do_not_ignore_caught_exceptions
Review ID: 4fa8a08a-4c88-4c8e-acb1-aaf6ba9eb3b8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

@diffray-bot
Copy link

Review Summary

Free public review - Want AI code reviews on your PRs? Check out diffray.ai

Validated 28 issues: 17 kept, 11 filtered

Issues Found: 17

💬 See 12 individual line comment(s) for details.

📊 7 unique issue type(s) across 17 location(s)

📋 Full issue list (click to expand)

🟠 HIGH - Potential NullPointerException on displayInfo

Agent: bugs

Category: bug

Why this matters: NullPointerException is the most common runtime exception in Java.

File: server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java:126-129

Description: getDisplayInfo() result is used without null check at line 126-129, but the same method call at lines 95-101 has a null check.

Suggestion: Add null check after line 126: if (displayInfo == null) { /* handle error or use defaults */ } before accessing displayInfo methods

Confidence: 88%

Rule: bug_null_pointer_java


🟠 HIGH - Inefficient reflection calls in polling loop

Agent: performance

Category: performance

Why this matters: N+1 queries cause severe performance degradation.

File: server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java:199-287

Description: The appMonitor thread performs reflection lookups (Class.forName, getDeclaredField, setAccessible) on every poll iteration for each task. The getTaskDisplayId() method at lines 278-287 does full reflection on every call instead of caching the Field reference.

Suggestion: Cache the reflected Field reference (displayIdField) as a static class field at class initialization, then only call getInt() on each iteration. Example: private static final Field DISPLAY_ID_FIELD = initDisplayIdField();

Confidence: 90%

Rule: perf_n_plus_one_queries


🟠 HIGH - Potential unbounded recursion in read() method

Agent: bugs

Category: bug

Why this matters: Missing base case causes infinite recursion and StackOverflowError.

File: server/src/main/java/com/genymobile/scrcpy/audio/AudioPlaybackCapture.java:232-238

Description: When reader.read() returns 0, the method recurses after a 10ms sleep. If the reader consistently returns 0, this will cause StackOverflowError.

Suggestion: Replace recursive call with a while loop with a maximum retry count, or use tail recursion optimization if available

Confidence: 78%

Rule: java_recursion_no_base_case


🟡 MEDIUM - InterruptedException caught without restoring interrupt flag (8 occurrences)

Agent: spring

Category: quality

Why this matters: Unhandled errors cause failures.

📍 View all locations
File Description Suggestion Confidence
server/src/main/java/com/genymobile/scrcpy/audio/AudioPlaybackCapture.java:188-190 InterruptedException is caught but only contains a comment '// ignore'. The interrupt flag is not re... Call Thread.currentThread().interrupt() to restore the interrupt flag, or document explicitly why ig... 72%
server/src/main/java/com/genymobile/scrcpy/Server.java:200-202 InterruptedException is caught during shutdown join operations. While the interrupt flag is not rest... Add Thread.currentThread().interrupt() in the catch block for completeness, though the impact is min... 62%
server/src/main/java/com/genymobile/scrcpy/control/Controller.java:168-170 InterruptedException is caught with only '// do nothing'. This is in a timeout wait for display data... Add Thread.currentThread().interrupt() or document that interruption is expected during 1s timeout w... 60%
server/src/main/java/com/genymobile/scrcpy/control/Controller.java:712-714 InterruptedException is caught with only '// do nothing'. Similar pattern to line 168 - timeout wait... Add Thread.currentThread().interrupt() or document that interruption is expected during timeout wait 60%
server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java:85 ReflectiveOperationException is caught and logged but then silently ignored with a void method. This... Either throw a runtime exception to propagate the error, or return a boolean indicating success/fail... 62%
server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java:155-158 Throwable is caught and logged, then 0 is returned. The caller cannot distinguish between a successf... Return a distinct error value (e.g., -1) or throw an exception to clearly indicate failure state. 65%
server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java:159-161 Throwable is caught and logged but the method returns void without any indication of failure to the ... Return a boolean value indicating success/failure, or throw a runtime exception to notify callers of... 62%
server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java:168-171 Throwable is caught and logged but null is returned, making it impossible for callers to distinguish... Return Optional or throw an exception to distinguish error state from empty result. 60%

Rule: java_do_not_ignore_caught_exceptions


🟡 MEDIUM - List.contains() in polling loop creates O(N*M) complexity

Agent: performance

Category: performance

Why this matters: Poor performance degrades user experience.

File: server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java:209-223

Description: audioFilterApps and audioIgnoreApps are declared as List (lines 62-63), causing O(N) lookup per contains() call. With up to 50 tasks checked every 500ms, this creates unnecessary overhead.

Suggestion: Convert audioFilterApps and audioIgnoreApps from List to Set for O(1) lookup. Modify the constructor and Options class accordingly.

Confidence: 68%

Rule: perf_quadratic_loops


🟡 MEDIUM - Empty catch block with only comment (2 occurrences)

Agent: quality

Category: quality

Why this matters: Swallowed errors hide bugs and make debugging difficult.

📍 View all locations
File Description Suggestion Confidence
server/src/main/java/com/genymobile/scrcpy/audio/AudioPlaybackCapture.java:81-86 Catch block for Exception in loop (lines 80-87) silently ignores failures when adding audio usage ru... Add debug logging: Ln.d("Skipping usage " + usage + ": " + e.getClass().getSimpleName()); 65%
server/src/main/java/com/genymobile/scrcpy/audio/AudioPlaybackCapture.java:91-96 Lines 80-87 and 90-97 contain identical try-catch loops - one in if block, one in else block. Could ... Extract the loop adding audio attribute usages into a private helper method: private void addUsageRu... 62%

Rule: java_exception_ignored


🟡 MEDIUM - Reflection invoke results cast without null check (3 occurrences)

Agent: security

Category: quality

📍 View all locations
File Description Suggestion Confidence
server/src/main/java/com/genymobile/scrcpy/wrappers/PackageManager.java:58-60 method.invoke() results at lines 58 and 60 are cast to ApplicationInfo without null checking. While ... Add null check before casting: Object result = method.invoke(...); return result != null ? (Applicat... 65%
server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java:85 providerField.get(providerHolder) is cast to IContentProvider without null checking. Field.get() can... Add null check before casting: Object provider = providerField.get(providerHolder); return provider ... 65%
server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java:35 getDefaultMethod.invoke(null) result is cast to IInterface without null checking. If the underlying ... Add null check: Object result = getDefaultMethod.invoke(null); if (result == null) throw new Asserti... 60%

Rule: java_validate_parameter_types_before_casting


ℹ️ 5 issue(s) outside PR diff (click to expand)

These issues were found in lines not modified in this PR.

🟠 HIGH - Potential NullPointerException on displayInfo

Agent: bugs

Category: bug

Why this matters: NullPointerException is the most common runtime exception in Java.

File: server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java:126-129

Description: getDisplayInfo() result is used without null check at line 126-129, but the same method call at lines 95-101 has a null check.

Suggestion: Add null check after line 126: if (displayInfo == null) { /* handle error or use defaults */ } before accessing displayInfo methods

Confidence: 88%

Rule: bug_null_pointer_java


🟡 MEDIUM - Reflection invoke result cast without null validation

Agent: security

Category: quality

File: server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java:35

Description: getDefaultMethod.invoke(null) result is cast to IInterface without null checking. If the underlying method returns null, this would cause issues.

Suggestion: Add null check: Object result = getDefaultMethod.invoke(null); if (result == null) throw new AssertionError("getDefault() returned null"); IInterface am = (IInterface) result;

Confidence: 60%

Rule: java_validate_parameter_types_before_casting


🔵 LOW - InterruptedException caught without restoring interrupt flag (3 occurrences)

Agent: spring

Category: quality

Why this matters: Unhandled errors cause failures.

📍 View all locations
File Description Suggestion Confidence
server/src/main/java/com/genymobile/scrcpy/Server.java:200-202 InterruptedException is caught during shutdown join operations. While the interrupt flag is not rest... Add Thread.currentThread().interrupt() in the catch block for completeness, though the impact is min... 62%
server/src/main/java/com/genymobile/scrcpy/control/Controller.java:168-170 InterruptedException is caught with only '// do nothing'. This is in a timeout wait for display data... Add Thread.currentThread().interrupt() or document that interruption is expected during 1s timeout w... 60%
server/src/main/java/com/genymobile/scrcpy/control/Controller.java:712-714 InterruptedException is caught with only '// do nothing'. Similar pattern to line 168 - timeout wait... Add Thread.currentThread().interrupt() or document that interruption is expected during timeout wait 60%

Rule: java_do_not_ignore_caught_exceptions



Review ID: 4fa8a08a-4c88-4c8e-acb1-aaf6ba9eb3b8
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants