Skip to content

Conversation

@mubashardev
Copy link

Summary

This PR adds a comprehensive Call Recording feature for WhatsApp while staying synchronized with the latest upstream changes.

What's New

🎙️ Call Recording Feature

  • Automatic recording of both voice and video calls (saves as audio)
  • Recording management UI with playback capabilities
  • Settings activity for configuring recording preferences
  • Recordings fragment for browsing and managing recordings
  • Audio player dialog with playback controls

🔄 Upstream Sync

This PR includes all upstream changes from commits 2c96d465 to 89416fe7 (17 commits):

  • Added version 2.26.2.XX support
  • Refactored FMessageWpp JID type detection logic
  • Improved HideSeen and HideReceipt privacy features
  • Refactored CustomToolbar and Unobfuscator
  • Fixed UI thread safety in contact verification
  • Improved activity lifecycle management to prevent memory leaks
  • Added support for Android 16 (Baklava)
  • Updated DelMessageStore database version to 5
  • Various bug fixes and improvements

Files Changed

New Files

  • CallRecordingSettingsActivity.java - Settings UI for call recording
  • CallRecording.java - Core call recording feature implementation
  • RecordingsFragment.java - UI for browsing recordings
  • RecordingsAdapter.java - Adapter for recordings list
  • AudioPlayerDialog.java - Audio playback dialog
  • Recording.java - Model class for recording data
  • strings_recordings.xml - Localization strings
  • Multiple drawable resources for UI elements

Modified Files

  • changelog.txt - Updated with new features and upstream changes
  • MainActivity.java & MainPagerAdapter.java - Added recordings tab
  • MediaFragment.java - Enhanced media handling
  • AndroidManifest.xml - Added permissions and activity declarations
  • Various upstream refactorings merged successfully

Testing

All custom features have been preserved while integrating upstream improvements. The merge was completed successfully with only minor changelog conflicts that were resolved.

Breaking Changes

None. All existing features remain intact.

Replaces

This PR replaces the previously submitted PRs:

Both have been closed in favor of this updated PR with the latest upstream sync.

New features from upstream:
- Contact Blocked Verification
- Enhanced Locked Chats
- Disable Ads
- Improved AntiRevoke with ConversationItemListener
- ContactItemListener & MenuStatusListener refactoring
- Various bug fixes and UI improvements

Preserved fork feature:
- Call Recording (Voice/Video as Audio)
- Merged upstream commits from 2c96d46 to 89416fe
- Added version 2.26.2.XX support
- Refactored FMessageWpp JID type detection
- Improved HideSeen and HideReceipt privacy features
- Refactored CustomToolbar and Unobfuscator
- Fixed UI thread safety and activity lifecycle management
- Added support for Android 16 (Baklava)
- Preserved custom Call Recording feature
- Updated changelog to reflect both upstream and fork changes
Copilot AI review requested due to automatic review settings January 28, 2026 05:52
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a full Call Recording feature (recording + playback/management UI) while syncing a large set of upstream changes.

Changes:

  • Implemented Xposed-based call audio recording and saving to disk.
  • Added in-app recordings browser + audio player dialog, and wired a new “Recordings” tab into the main UI.
  • Updated resources/manifest/CI configuration to support the new feature and upstream updates.

Reviewed changes

Copilot reviewed 36 out of 38 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
gradlew Adds Gradle wrapper script to support consistent builds.
changelog.txt Documents call recording + upstream sync items.
app/src/main/res/xml/fragment_media.xml Adds call recording preferences (enable, path, settings entry).
app/src/main/res/xml/file_paths.xml Adds FileProvider paths definition for sharing recordings.
app/src/main/res/values/strings_recordings.xml Adds new strings for recordings UI + settings.
app/src/main/res/values/strings.xml Adds call recording strings and a “video call screen recording” label/summary.
app/src/main/res/values/arrays.xml Extends supported versions arrays.
app/src/main/res/menu/bottom_nav_menu.xml Adds a bottom-nav item for “Recordings”.
app/src/main/res/layout/item_recording.xml Defines a list item layout for a recording row.
app/src/main/res/layout/fragment_recordings.xml Defines recordings list screen UI (selection bar, sort, empty view).
app/src/main/res/layout/dialog_audio_player.xml Adds audio player dialog UI.
app/src/main/res/layout/activity_call_recording_settings.xml Adds call recording mode selection UI (root vs non-root).
app/src/main/res/drawable/ic_warning.xml New vector drawable used in settings UI.
app/src/main/res/drawable/ic_recording.xml New vector drawable for recordings icon.
app/src/main/res/drawable/ic_play.xml New vector drawable for audio playback.
app/src/main/res/drawable/ic_pause.xml New vector drawable for audio playback.
app/src/main/res/drawable/ic_close.xml New vector drawable for close actions.
app/src/main/res/drawable/ic_check_circle.xml New vector drawable for settings advantages list.
app/src/main/res/drawable/duration_badge_background.xml Adds shape background for duration badge.
app/src/main/res/drawable/dialog_background.xml Adds background shape for audio dialog.
app/src/main/res/drawable/circle_button_background.xml Adds circular background for play/pause button.
app/src/main/java/com/wmods/wppenhacer/xposed/spoofer/HookBL.java Uses FeatureLoader.mApp instead of AndroidAppHelper.currentApplication().
app/src/main/java/com/wmods/wppenhacer/xposed/features/media/CallRecording.java Implements call recording hooks + audio capture + WAV writing.
app/src/main/java/com/wmods/wppenhacer/xposed/features/general/Others.java Wraps sendAudioType in try/catch for safety.
app/src/main/java/com/wmods/wppenhacer/xposed/core/devkit/Unobfuscator.java Adjusts OriginFMessageField discovery logic for broader matching.
app/src/main/java/com/wmods/wppenhacer/xposed/core/FeatureLoader.java Registers CallRecording plugin for loading.
app/src/main/java/com/wmods/wppenhacer/ui/fragments/RecordingsFragment.java Adds recordings browser UI + share/delete/actions.
app/src/main/java/com/wmods/wppenhacer/ui/fragments/MediaFragment.java Wires preference click to open call recording settings activity.
app/src/main/java/com/wmods/wppenhacer/ui/dialogs/AudioPlayerDialog.java Adds in-app audio playback dialog using MediaPlayer.
app/src/main/java/com/wmods/wppenhacer/model/Recording.java Adds recording metadata model (duration, contact lookup, formatting).
app/src/main/java/com/wmods/wppenhacer/adapter/RecordingsAdapter.java Adds adapter for recordings list + selection mode.
app/src/main/java/com/wmods/wppenhacer/adapter/MainPagerAdapter.java Conditionally adds a 6th tab for recordings based on pref.
app/src/main/java/com/wmods/wppenhacer/activities/MainActivity.java Hides recordings nav item when disabled; adds navigation to recordings tab.
app/src/main/java/com/wmods/wppenhacer/activities/CallRecordingSettingsActivity.java Adds settings activity for recording mode + root check.
app/src/main/java/com/wmods/wppenhacer/activities/AboutActivity.java Updates GitHub URL capitalization.
app/src/main/AndroidManifest.xml Registers CallRecordingSettingsActivity and FileProvider.
.gitignore Ignores key_base64.txt.
.github/workflows/android.yml Expands CI branch triggers and improves secret handling for keystore decoding.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +5
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_files"
path="." />
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

This FileProvider paths resource is malformed: the <paths> root is missing xmlns:android, and the child entries should use android:name/android:path (not un-namespaced name/path). As-is, FileProvider will fail to parse this XML and sharing recordings will crash.

Copilot uses AI. Check for mistakes.
Comment on lines +237 to +247
for (String cmd : commands) {
try {
Process process = Runtime.getRuntime().exec(new String[]{"su", "-c", cmd});
int exitCode = process.waitFor();
XposedBridge.log("WaEnhancer: " + cmd + " exit: " + exitCode);
} catch (Exception e) {
XposedBridge.log("WaEnhancer: Root failed: " + e.getMessage());
}
}

permissionGranted = true;
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

permissionGranted is set to true regardless of whether the su/pm grant commands actually succeed (non-zero exit code or exception). That prevents retries and can leave root mode broken for the session; only mark it granted when the required commands succeed.

Suggested change
for (String cmd : commands) {
try {
Process process = Runtime.getRuntime().exec(new String[]{"su", "-c", cmd});
int exitCode = process.waitFor();
XposedBridge.log("WaEnhancer: " + cmd + " exit: " + exitCode);
} catch (Exception e) {
XposedBridge.log("WaEnhancer: Root failed: " + e.getMessage());
}
}
permissionGranted = true;
boolean allSucceeded = true;
for (String cmd : commands) {
try {
Process process = Runtime.getRuntime().exec(new String[]{"su", "-c", cmd});
int exitCode = process.waitFor();
XposedBridge.log("WaEnhancer: " + cmd + " exit: " + exitCode);
if (exitCode != 0) {
allSucceeded = false;
break;
}
} catch (Exception e) {
XposedBridge.log("WaEnhancer: Root failed: " + e.getMessage());
allSucceeded = false;
break;
}
}
if (allSucceeded) {
permissionGranted = true;
}

Copilot uses AI. Check for mistakes.
import java.io.File;
import java.io.RandomAccessFile;
import java.io.IOException;
import java.lang.reflect.Field;
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

java.lang.reflect.Field is imported but not used in this class. Please remove unused imports to avoid lint warnings/failures.

Suggested change
import java.lang.reflect.Field;

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +123
private void loadRecordings() {
allRecordings.clear();

for (File baseDir : baseDirs) {
if (baseDir.exists() && baseDir.isDirectory()) {
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

loadRecordings() performs recursive filesystem traversal and constructs Recording objects (disk I/O + contact lookups) on the main thread. On devices with many files this can cause jank/ANRs; please move the scan/metadata extraction to a background thread and post results back to the UI.

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +87
} catch (IOException e) {
e.printStackTrace();
dismiss();
return;
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

If setDataSource()/prepare() throws, this catch path calls dismiss() and returns before setOnDismissListener(...) is registered, so the partially-created MediaPlayer won’t be released. Ensure you clean up mediaPlayer (and handler callbacks) on this error path too (e.g., call releasePlayer() in the catch/finally).

Copilot uses AI. Check for mistakes.
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_alignParentEnd="true"
android:background="?selectableItemBackgroundBorderless"
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

android:background is set to ?selectableItemBackgroundBorderless here, but this project consistently uses ?attr/selectableItemBackgroundBorderless (and there is no local attr named selectableItemBackgroundBorderless). As written, the attribute reference may resolve to 0 and you’ll lose the expected touch feedback. Use ?attr/selectableItemBackgroundBorderless (or ?android:attr/selectableItemBackgroundBorderless).

Suggested change
android:background="?selectableItemBackgroundBorderless"
android:background="?attr/selectableItemBackgroundBorderless"

Copilot uses AI. Check for mistakes.
duration = (dataSize * 1000L) / byteRate;
} else if (sampleRate > 0) {
// Assume 16-bit mono
duration = (dataSize * 1000L) / (sampleRate * 2);
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

Potential overflow in int multiplication before it is converted to long by use in a numeric context.

Suggested change
duration = (dataSize * 1000L) / (sampleRate * 2);
duration = (dataSize * 1000L) / (sampleRate * 2L);

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +101
DataOutputStream os = new DataOutputStream(process.getOutputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

os.writeBytes("id\n");
os.writeBytes("exit\n");
os.flush();

// Read output
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
rootOutput = sb.toString();

int exitCode = process.waitFor();
Log.d(TAG, "Root check exit code: " + exitCode + ", output: " + rootOutput);

Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

This InputStreamReader is not always closed on method exit.

Suggested change
DataOutputStream os = new DataOutputStream(process.getOutputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
os.writeBytes("id\n");
os.writeBytes("exit\n");
os.flush();
// Read output
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
rootOutput = sb.toString();
int exitCode = process.waitFor();
Log.d(TAG, "Root check exit code: " + exitCode + ", output: " + rootOutput);
try (DataOutputStream os = new DataOutputStream(process.getOutputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
os.writeBytes("id\n");
os.writeBytes("exit\n");
os.flush();
// Read output
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
rootOutput = sb.toString();
}
int exitCode = process.waitFor();
Log.d(TAG, "Root check exit code: " + exitCode + ", output: " + rootOutput);

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +102
DataOutputStream os = new DataOutputStream(process.getOutputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

os.writeBytes("id\n");
os.writeBytes("exit\n");
os.flush();

// Read output
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
rootOutput = sb.toString();

int exitCode = process.waitFor();
Log.d(TAG, "Root check exit code: " + exitCode + ", output: " + rootOutput);

hasRoot = (exitCode == 0 && rootOutput.contains("uid=0"));
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

This DataOutputStream is not always closed on method exit.

Suggested change
DataOutputStream os = new DataOutputStream(process.getOutputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
os.writeBytes("id\n");
os.writeBytes("exit\n");
os.flush();
// Read output
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
rootOutput = sb.toString();
int exitCode = process.waitFor();
Log.d(TAG, "Root check exit code: " + exitCode + ", output: " + rootOutput);
hasRoot = (exitCode == 0 && rootOutput.contains("uid=0"));
try (DataOutputStream os = new DataOutputStream(process.getOutputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
os.writeBytes("id\n");
os.writeBytes("exit\n");
os.flush();
// Read output
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
rootOutput = sb.toString();
int exitCode = process.waitFor();
Log.d(TAG, "Root check exit code: " + exitCode + ", output: " + rootOutput);
hasRoot = (exitCode == 0 && rootOutput.contains("uid=0"));
}

Copilot uses AI. Check for mistakes.
return recordings.size();
}

static class ViewHolder extends RecyclerView.ViewHolder {
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

ViewHolder has the same name as its supertype androidx.recyclerview.widget.RecyclerView$ViewHolder.

Copilot uses AI. Check for mistakes.
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.

1 participant