Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Android CI

on:
push:
branches: [ "master" ]
branches: [ "master", "feature/*" ]
jobs:
build:
permissions: write-all
Expand All @@ -19,13 +19,15 @@ jobs:

- name: Write key
if: github.event_name != 'pull_request'
env:
KEY_STORE: ${{ secrets.KEY_STORE }}
run: |
if [ ! -z "${{ secrets.KEY_STORE }}" ]; then
if [ ! -z "$KEY_STORE" ]; then
echo androidStorePassword='${{ secrets.KEY_STORE_PASSWORD }}' >> gradle.properties
echo androidKeyAlias='${{ secrets.ALIAS }}' >> gradle.properties
echo androidKeyPassword='${{ secrets.KEY_PASSWORD }}' >> gradle.properties
echo androidStoreFile='key.jks' >> gradle.properties
echo ${{ secrets.KEY_STORE }} | base64 --decode > key.jks
echo "$KEY_STORE" | base64 --decode > key.jks
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The environment variable KEY_STORE is now wrapped in quotes when echoed to base64 decode. However, the original code had ${{ secrets.KEY_STORE }} directly without quotes. If the KEY_STORE secret contains spaces or special characters, the quoted version is correct. But verify that this change doesn't break the base64 decoding if the secret value itself contains quotes or if the shell interprets them incorrectly.

Suggested change
echo "$KEY_STORE" | base64 --decode > key.jks
printf '%s' "$KEY_STORE" | base64 --decode > key.jks

Copilot uses AI. Check for mistakes.
fi

- name: Grant execute permission for gradlew
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
.cxx
local.properties
key.jks
key_base64.txt
15 changes: 15 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@
android:name=".activities.TextEditorActivity"
android:theme="@style/AppTheme" />

<activity
android:name=".activities.CallRecordingSettingsActivity"
android:theme="@style/Theme.Material3.DynamicColors.DayNight.NoActionBar"
android:parentActivityName=".activities.MainActivity" />

<activity
android:name=".activities.ForceStartActivity"
android:excludeFromRecents="true"
Expand Down Expand Up @@ -115,6 +120,16 @@
android:exported="true"
tools:ignore="ExportedService" />

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

<provider
android:name=".xposed.bridge.providers.HookProvider"
android:authorities="${applicationId}.hookprovider"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
binding.btnGithub.setOnClickListener(view -> {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://github.com/Dev4Mod/waenhancer"));
intent.setData(Uri.parse("https://github.com/Dev4Mod/WaEnhancer"));
startActivity(intent);
});
binding.btnDonate.setOnClickListener(view -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.wmods.wppenhacer.activities;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.MenuItem;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceManager;

import com.google.android.material.appbar.MaterialToolbar;
import com.wmods.wppenhacer.R;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;

public class CallRecordingSettingsActivity extends AppCompatActivity {

private static final String TAG = "WaEnhancer";
private SharedPreferences prefs;
private RadioGroup radioGroupMode;
private RadioButton radioRoot;
private RadioButton radioNonRoot;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_call_recording_settings);

MaterialToolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.call_recording_settings);
}

prefs = PreferenceManager.getDefaultSharedPreferences(this);

radioGroupMode = findViewById(R.id.radio_group_mode);
radioRoot = findViewById(R.id.radio_root);
radioNonRoot = findViewById(R.id.radio_non_root);

// Load saved preference
boolean useRoot = prefs.getBoolean("call_recording_use_root", false);
Log.d(TAG, "Loaded call_recording_use_root: " + useRoot);

if (useRoot) {
radioRoot.setChecked(true);
} else {
radioNonRoot.setChecked(true);
}

// Direct click listeners on radio buttons
radioRoot.setOnClickListener(v -> {
Log.d(TAG, "Root mode clicked");
radioRoot.setChecked(true);
radioNonRoot.setChecked(false);
Toast.makeText(this, "Checking root access...", Toast.LENGTH_SHORT).show();
checkRootAccess();
});

radioNonRoot.setOnClickListener(v -> {
Log.d(TAG, "Non-root mode clicked");
radioNonRoot.setChecked(true);
radioRoot.setChecked(false);
boolean saved = prefs.edit().putBoolean("call_recording_use_root", false).commit();
Log.d(TAG, "Saved non-root preference: " + saved);
Comment on lines +70 to +71
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

Using commit() instead of apply() for SharedPreferences writes on the main thread will block the UI until the write completes. Since this is triggered by user interaction in the UI, it could cause noticeable lag, especially on slower devices. Consider using apply() for asynchronous writes, or move the preference save to a background thread.

Suggested change
boolean saved = prefs.edit().putBoolean("call_recording_use_root", false).commit();
Log.d(TAG, "Saved non-root preference: " + saved);
prefs.edit().putBoolean("call_recording_use_root", false).apply();
Log.d(TAG, "Requested save of non-root preference using apply()");

Copilot uses AI. Check for mistakes.
Toast.makeText(this, R.string.non_root_mode_enabled, Toast.LENGTH_SHORT).show();
});
}

private void checkRootAccess() {
new Thread(() -> {
boolean hasRoot = false;
String rootOutput = "";

try {
Log.d(TAG, "Executing su command...");
Process process = Runtime.getRuntime().exec("su");
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"));
} catch (Exception e) {
Log.e(TAG, "Root check exception: " + e.getMessage());
hasRoot = false;
}

final boolean rootGranted = hasRoot;
final String output = rootOutput;

Comment on lines +80 to +110
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The BufferedReader is created but never explicitly closed. While the try-with-resources pattern is used for the cursor below, it's not used here. This could lead to resource leaks. The DataOutputStream is also not closed. Consider wrapping these streams in try-with-resources or ensuring they're closed in a finally block.

Suggested change
try {
Log.d(TAG, "Executing su command...");
Process process = Runtime.getRuntime().exec("su");
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"));
} catch (Exception e) {
Log.e(TAG, "Root check exception: " + e.getMessage());
hasRoot = false;
}
final boolean rootGranted = hasRoot;
final String output = rootOutput;
Process process = null;
try {
Log.d(TAG, "Executing su command...");
process = Runtime.getRuntime().exec("su");
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"));
}
} catch (Exception e) {
Log.e(TAG, "Root check exception: " + e.getMessage());
hasRoot = false;
} finally {
if (process != null) {
process.destroy();
}
}
final boolean rootGranted = hasRoot;
final String output = rootOutput;

Copilot uses AI. Check for mistakes.
runOnUiThread(() -> {
if (rootGranted) {
boolean saved = prefs.edit().putBoolean("call_recording_use_root", true).commit();
Log.d(TAG, "Root granted, saved preference: " + saved);
Toast.makeText(this, R.string.root_access_granted, Toast.LENGTH_SHORT).show();
} else {
boolean saved = prefs.edit().putBoolean("call_recording_use_root", false).commit();
Log.d(TAG, "Root denied, saved preference: " + saved + ", output: " + output);
Comment on lines +113 to +118
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

Using commit() instead of apply() blocks the UI thread while writing to disk. These SharedPreferences updates are happening on the UI thread (via runOnUiThread) and could cause frame drops or UI lag. Since immediate confirmation of the write isn't necessary for correctness here, use apply() for better performance.

Suggested change
boolean saved = prefs.edit().putBoolean("call_recording_use_root", true).commit();
Log.d(TAG, "Root granted, saved preference: " + saved);
Toast.makeText(this, R.string.root_access_granted, Toast.LENGTH_SHORT).show();
} else {
boolean saved = prefs.edit().putBoolean("call_recording_use_root", false).commit();
Log.d(TAG, "Root denied, saved preference: " + saved + ", output: " + output);
prefs.edit().putBoolean("call_recording_use_root", true).apply();
Log.d(TAG, "Root granted, preference update requested.");
Toast.makeText(this, R.string.root_access_granted, Toast.LENGTH_SHORT).show();
} else {
prefs.edit().putBoolean("call_recording_use_root", false).apply();
Log.d(TAG, "Root denied, preference update requested, output: " + output);

Copilot uses AI. Check for mistakes.
radioNonRoot.setChecked(true);
Toast.makeText(this, R.string.root_access_denied, Toast.LENGTH_LONG).show();
}
});
Comment on lines +111 to +122
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The root check spawns a new thread but doesn't maintain a reference to it or provide any way to cancel it. If the user navigates away from this activity before the check completes, the callback will still attempt to update the UI, which could cause issues. Consider canceling the thread in onDestroy() or checking if the activity is still valid before running UI updates.

Suggested change
runOnUiThread(() -> {
if (rootGranted) {
boolean saved = prefs.edit().putBoolean("call_recording_use_root", true).commit();
Log.d(TAG, "Root granted, saved preference: " + saved);
Toast.makeText(this, R.string.root_access_granted, Toast.LENGTH_SHORT).show();
} else {
boolean saved = prefs.edit().putBoolean("call_recording_use_root", false).commit();
Log.d(TAG, "Root denied, saved preference: " + saved + ", output: " + output);
radioNonRoot.setChecked(true);
Toast.makeText(this, R.string.root_access_denied, Toast.LENGTH_LONG).show();
}
});
if (!isFinishing() && !isDestroyed()) {
runOnUiThread(() -> {
if (isFinishing() || isDestroyed()) {
return;
}
if (rootGranted) {
boolean saved = prefs.edit().putBoolean("call_recording_use_root", true).commit();
Log.d(TAG, "Root granted, saved preference: " + saved);
Toast.makeText(this, R.string.root_access_granted, Toast.LENGTH_SHORT).show();
} else {
boolean saved = prefs.edit().putBoolean("call_recording_use_root", false).commit();
Log.d(TAG, "Root denied, saved preference: " + saved + ", output: " + output);
radioNonRoot.setChecked(true);
Toast.makeText(this, R.string.root_access_denied, Toast.LENGTH_LONG).show();
}
});
}

Copilot uses AI. Check for mistakes.
}).start();
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ protected void onCreate(Bundle savedInstanceState) {
MainPagerAdapter pagerAdapter = new MainPagerAdapter(this);
binding.viewPager.setAdapter(pagerAdapter);

var prefs = androidx.preference.PreferenceManager.getDefaultSharedPreferences(this);
if (!prefs.getBoolean("call_recording_enable", false)) {
binding.navView.getMenu().findItem(R.id.navigation_recordings).setVisible(false);
}
Comment on lines +46 to +49
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

Similar to the adapter issue, the navigation menu item visibility is set once in onCreate based on the preference value. If the preference changes while the activity is running, the menu won't update. This creates an inconsistency where the navigation item visibility doesn't match the actual feature state. Consider observing preference changes and updating the menu visibility dynamically.

Copilot uses AI. Check for mistakes.
binding.viewPager.setPageTransformer(new DepthPageTransformer());

binding.navView.setOnItemSelectedListener(new NavigationBarView.OnItemSelectedListener() {
Expand Down Expand Up @@ -70,6 +74,10 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) {
binding.viewPager.setCurrentItem(4, true);
yield true;
}
case R.id.navigation_recordings -> {
binding.viewPager.setCurrentItem(5);
yield true;
}
default -> false;
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

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

The switch statement uses yield expressions but the default case returns false instead of yielding false. While this works, it's inconsistent with the other cases and could be confusing. For consistency and clarity, the default case should use yield false; to match the pattern of the other cases.

Suggested change
default -> false;
default -> {
yield false;
}

Copilot uses AI. Check for mistakes.
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.PreferenceManager;
import androidx.viewpager2.adapter.FragmentStateAdapter;

import com.wmods.wppenhacer.ui.fragments.CustomizationFragment;
import com.wmods.wppenhacer.ui.fragments.GeneralFragment;
import com.wmods.wppenhacer.ui.fragments.HomeFragment;
import com.wmods.wppenhacer.ui.fragments.MediaFragment;
import com.wmods.wppenhacer.ui.fragments.PrivacyFragment;
import com.wmods.wppenhacer.ui.fragments.RecordingsFragment;

public class MainPagerAdapter extends FragmentStateAdapter {

private final boolean isRecordingEnabled;

public MainPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
var prefs = PreferenceManager.getDefaultSharedPreferences(fragmentActivity);
isRecordingEnabled = prefs.getBoolean("call_recording_enable", false);
}

@NonNull
Expand All @@ -25,12 +31,13 @@ public Fragment createFragment(int position) {
case 1 -> new PrivacyFragment();
case 3 -> new MediaFragment();
case 4 -> new CustomizationFragment();
case 5 -> new RecordingsFragment();
default -> new HomeFragment();
};
}

@Override
public int getItemCount() {
return 5; // Number of fragments
return isRecordingEnabled ? 6 : 5;
}
}
Loading