Add files via upload

This commit is contained in:
VinujaHerath
2026-05-03 15:51:23 +05:30
committed by GitHub
parent ab4a384298
commit 0932646e82
34 changed files with 2154 additions and 2 deletions
+55
View File
@@ -0,0 +1,55 @@
plugins {
id("com.android.application")
}
android {
namespace = "io.github.pixelims"
compileSdk = 35
defaultConfig {
applicationId = "io.github.pixelims"
minSdk = 31
targetSdk = 35
versionCode = 1
versionName = "1.0"
}
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles("proguard-rules.pro")
signingConfig = signingConfigs["debug"]
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
buildConfig = true
}
packaging {
resources {
excludes += "**"
}
}
lint {
checkReleaseBuilds = false
}
dependenciesInfo {
includeInApk = false
}
}
dependencies {
compileOnly(project(":stub"))
implementation(libs.shizuku.provider)
implementation(libs.shizuku.api)
implementation(libs.hiddenapibypass)
}
+2
View File
@@ -0,0 +1,2 @@
-keep class io.github.pixelims.** { *; }
-keep class rikka.shizuku.** { *; }
+52
View File
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Needed so we can grant ourselves READ_PHONE_STATE at runtime -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!--
Instrumentation entry point — this is the component that vvb2060's bypass launches.
targetPackage MUST match applicationId exactly.
-->
<instrumentation
android:name=".PrivilegedProcess"
android:targetPackage="${applicationId}" />
<application
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
tools:ignore="MissingApplicationIcon">
<!-- Splash screen is the launcher entry point -->
<activity
android:name=".SplashActivity"
android:exported="true"
android:theme="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:exported="false"
android:label="@string/app_name" />
<!--
Shizuku content provider — authority must match applicationId + ".shizuku".
This is how Shizuku delivers its binder to the app.
Permission restricts who can talk to it (only system/shell).
-->
<provider
android:name=".ShizukuProvider"
android:authorities="${applicationId}.shizuku"
android:exported="true"
android:multiprocess="false"
tools:ignore="ExportedContentProvider" />
</application>
</manifest>
@@ -0,0 +1,45 @@
package io.github.pixelims;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import java.util.Locale;
public class LocaleHelper {
private static final String PREF_NAME = "locale_config";
private static final String KEY_LANGUAGE = "language";
public static void setLocale(Context context, String languageCode) {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.edit().putString(KEY_LANGUAGE, languageCode).apply();
updateResources(context, languageCode);
}
public static String getLanguage(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
if (prefs.contains(KEY_LANGUAGE)) {
return prefs.getString(KEY_LANGUAGE, "en");
}
// Auto-detect: Chinese system → zh, everything else → en
return Locale.getDefault().getLanguage().startsWith("zh") ? "zh" : "en";
}
public static void updateResources(Context context, String languageCode) {
Locale locale = new Locale(languageCode);
Locale.setDefault(locale);
Resources resources = context.getResources();
Configuration config = new Configuration(resources.getConfiguration());
config.setLocale(locale);
context.createConfigurationContext(config);
resources.updateConfiguration(config, resources.getDisplayMetrics());
}
public static String toggleLanguage(Context context) {
String newLang = getLanguage(context).equals("zh") ? "en" : "zh";
setLocale(context, newLang);
return newLang;
}
}
@@ -0,0 +1,354 @@
package io.github.pixelims;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import rikka.shizuku.Shizuku;
/**
* PixelIMS — MainActivity
*
* TurboIMS-style UI wired to vvb2060's bypass engine.
*
* Improvements over TurboIMS:
* - Shows real-time "Config Status" per SIM (Applied / Not Applied)
* - Shows "Persistent Mode" status (from ShizukuProvider.canPersistent())
* - Auto-applies when Shizuku connects (vvb2060 headless behaviour preserved)
* - Manual apply button still available for forced re-apply
* - Language toggle (EN/ZH) from TurboIMS
* - Per-SIM selection (SIM 1, SIM 2, All) from TurboIMS
* - Android version warning for API 36+ builds from TurboIMS
*/
public class MainActivity extends Activity {
private static final String TAG = "PixelIMS.UI";
static final String PREFS_NAME = "pixelims_config";
private TextView tvAndroidVersion;
private TextView tvShizukuStatus;
private TextView tvPersistentMode;
private TextView tvConfigStatus;
private TextView tvPersistentWarning;
private TextView tvSimInfo;
private Button btnSelectSim;
private Button btnSwitchLanguage;
private Button btnApply;
private Switch switchVoLTE;
private Switch switchVoWiFi;
private Switch switchVT;
private Switch switchVoNR;
private Switch switchCrossSIM;
private Switch switchUT;
private Switch switch5GNR;
private SharedPreferences prefs;
private int selectedSubId = -1; // -1 = all SIMs (changed default from TurboIMS)
private final Shizuku.OnBinderReceivedListener binderReceived = () -> {
// Auto-apply on Shizuku connect — vvb2060 headless behaviour
runOnUiThread(() -> {
updateShizukuStatus();
updateStatusInfo();
});
};
private final Shizuku.OnBinderDeadListener binderDead = () ->
runOnUiThread(this::updateShizukuStatus);
@Override
protected void onCreate(Bundle savedInstanceState) {
String language = LocaleHelper.getLanguage(this);
LocaleHelper.updateResources(this, language);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
initViews();
loadPreferences();
updateSimInfo();
updateAndroidVersionInfo();
updateShizukuStatus();
updateStatusInfo();
Shizuku.addBinderReceivedListener(binderReceived);
Shizuku.addBinderDeadListener(binderDead);
}
@Override
protected void onResume() {
super.onResume();
updateShizukuStatus();
updateStatusInfo();
}
@Override
protected void onDestroy() {
super.onDestroy();
Shizuku.removeBinderReceivedListener(binderReceived);
Shizuku.removeBinderDeadListener(binderDead);
}
private void initViews() {
tvAndroidVersion = findViewById(R.id.tv_android_version);
tvShizukuStatus = findViewById(R.id.tv_shizuku_status);
tvPersistentMode = findViewById(R.id.tv_persistent_mode);
tvConfigStatus = findViewById(R.id.tv_config_status);
tvPersistentWarning = findViewById(R.id.tv_persistent_warning);
tvSimInfo = findViewById(R.id.tv_sim_info);
btnSelectSim = findViewById(R.id.btn_select_sim);
btnSwitchLanguage = findViewById(R.id.btn_switch_language);
// Feature switches
switchVoLTE = findViewById(R.id.item_volte).findViewById(R.id.feature_switch);
switchVoWiFi = findViewById(R.id.item_vowifi).findViewById(R.id.feature_switch);
switchVT = findViewById(R.id.item_vt).findViewById(R.id.feature_switch);
switchVoNR = findViewById(R.id.item_vonr).findViewById(R.id.feature_switch);
switchCrossSIM = findViewById(R.id.item_cross_sim).findViewById(R.id.feature_switch);
switchUT = findViewById(R.id.item_ut).findViewById(R.id.feature_switch);
switch5GNR = findViewById(R.id.item_5g_nr).findViewById(R.id.feature_switch);
// Feature titles and descriptions
setFeatureText(R.id.item_volte, R.string.volte, R.string.volte_desc);
setFeatureText(R.id.item_vowifi, R.string.vowifi, R.string.vowifi_desc);
setFeatureText(R.id.item_vt, R.string.vt, R.string.vt_desc);
setFeatureText(R.id.item_vonr, R.string.vonr, R.string.vonr_desc);
setFeatureText(R.id.item_cross_sim, R.string.cross_sim, R.string.cross_sim_desc);
setFeatureText(R.id.item_ut, R.string.ut, R.string.ut_desc);
setFeatureText(R.id.item_5g_nr, R.string._5g_nr, R.string._5g_nr_desc);
btnApply = findViewById(R.id.btn_apply);
btnApply.setOnClickListener(v -> applyConfiguration());
btnSelectSim.setOnClickListener(v -> showSimSelectionDialog());
btnSwitchLanguage.setOnClickListener(v -> {
LocaleHelper.toggleLanguage(this);
recreate();
});
}
private void setFeatureText(int itemId, int titleRes, int descRes) {
View item = findViewById(itemId);
((TextView) item.findViewById(R.id.feature_title)).setText(titleRes);
((TextView) item.findViewById(R.id.feature_desc)).setText(descRes);
}
// -------------------------------------------------------------------------
// Status display (new in PixelIMS, absent from TurboIMS)
// -------------------------------------------------------------------------
private void updateStatusInfo() {
// Persistent mode indicator
try {
boolean persistent = ShizukuProvider.canPersistent(this);
tvPersistentMode.setText(getString(R.string.persistent_mode,
persistent ? getString(R.string.yes) : getString(R.string.no)));
tvPersistentMode.setTextColor(persistent ? 0xFF4CAF50 : 0xFFF57C00);
} catch (Exception e) {
tvPersistentMode.setText(getString(R.string.persistent_mode, "?"));
}
// Config applied indicator
try {
boolean applied = !ShizukuProvider.needOverride(this);
tvConfigStatus.setText(getString(R.string.config_status,
applied ? getString(R.string.config_applied_short)
: getString(R.string.config_not_applied)));
tvConfigStatus.setTextColor(applied ? 0xFF4CAF50 : 0xFFBDBDBD);
} catch (Exception e) {
tvConfigStatus.setText(getString(R.string.config_status, "?"));
}
}
private void updateAndroidVersionInfo() {
tvAndroidVersion.setText(getString(R.string.android_version,
"Android " + Build.VERSION.RELEASE + " (API " + Build.VERSION.SDK_INT + ")"));
tvPersistentWarning.setVisibility(
Build.VERSION.SDK_INT >= 36 ? View.VISIBLE : View.GONE);
}
private void updateShizukuStatus() {
runOnUiThread(() -> {
String statusText;
int statusColor;
if (!Shizuku.pingBinder()) {
statusText = getString(R.string.shizuku_status,
getString(R.string.shizuku_not_running));
statusColor = 0xFFFF0000;
btnApply.setEnabled(false);
} else if (Shizuku.checkSelfPermission() != PackageManager.PERMISSION_GRANTED) {
statusText = getString(R.string.shizuku_status,
getString(R.string.shizuku_no_permission));
statusColor = 0xFFFF9800;
btnApply.setEnabled(false);
requestShizukuPermission();
} else {
statusText = getString(R.string.shizuku_status,
getString(R.string.shizuku_ready));
statusColor = 0xFF4CAF50;
btnApply.setEnabled(true);
}
tvShizukuStatus.setText(statusText);
tvShizukuStatus.setTextColor(statusColor);
});
}
private void requestShizukuPermission() {
if (Shizuku.isPreV11()) {
Toast.makeText(this, R.string.update_shizuku, Toast.LENGTH_LONG).show();
return;
}
Shizuku.requestPermission(0);
}
@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
// Shizuku routes its permission result through onRequestPermissionsResult.
// Refresh status whenever any permission result arrives.
if (Shizuku.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
updateShizukuStatus();
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
// -------------------------------------------------------------------------
// Apply
// -------------------------------------------------------------------------
private void applyConfiguration() {
if (!Shizuku.pingBinder()) {
Toast.makeText(this, R.string.shizuku_not_running_msg, Toast.LENGTH_LONG).show();
return;
}
if (Shizuku.checkSelfPermission() != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, R.string.shizuku_no_permission_msg, Toast.LENGTH_LONG).show();
requestShizukuPermission();
return;
}
savePreferences();
Log.i(TAG, "Manual apply triggered by user.");
// Kick off the bypass — ShizukuProvider.applyNow() → startInstrument()
ShizukuProvider.applyNow(this);
// Instrumentation causes the process to temporarily background.
// Bring UI back and refresh status after 3 seconds.
new Thread(() -> {
try {
Thread.sleep(3000);
runOnUiThread(() -> {
updateStatusInfo();
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
showSuccessDialog();
});
} catch (InterruptedException ignored) {}
}).start();
}
private void showSuccessDialog() {
new AlertDialog.Builder(this)
.setTitle(R.string.config_applied)
.setMessage(R.string.config_success_message)
.setPositiveButton(R.string.go_to_network_settings, (d, w) -> {
try {
startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
} catch (Exception e) {
Toast.makeText(this, "Unable to open network settings",
Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton(R.string.later, null)
.show();
}
// -------------------------------------------------------------------------
// SIM selection
// -------------------------------------------------------------------------
private void showSimSelectionDialog() {
String[] items = {
getString(R.string.sim_1),
getString(R.string.sim_2),
getString(R.string.apply_to_all_sims)
};
int selectedIndex = selectedSubId == 1 ? 0 : selectedSubId == 2 ? 1 : 2;
new AlertDialog.Builder(this)
.setTitle(R.string.select_sim)
.setSingleChoiceItems(items, selectedIndex, (dialog, which) -> {
selectedSubId = which == 0 ? 1 : which == 1 ? 2 : -1;
updateSimInfo();
dialog.dismiss();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void updateSimInfo() {
if (selectedSubId == 1) {
tvSimInfo.setText(R.string.sim_1);
btnApply.setText(R.string.apply_to_sim_1);
} else if (selectedSubId == 2) {
tvSimInfo.setText(R.string.sim_2);
btnApply.setText(R.string.apply_to_sim_2);
} else {
tvSimInfo.setText(R.string.apply_to_all_sims);
btnApply.setText(R.string.apply_to_all);
}
prefs.edit().putInt("selected_subid", selectedSubId).apply();
}
// -------------------------------------------------------------------------
// Preferences
// -------------------------------------------------------------------------
private void loadPreferences() {
switchVoLTE.setChecked(prefs.getBoolean("volte", true));
switchVoWiFi.setChecked(prefs.getBoolean("vowifi", true));
switchVT.setChecked(prefs.getBoolean("vt", true));
switchVoNR.setChecked(prefs.getBoolean("vonr", true));
switchCrossSIM.setChecked(prefs.getBoolean("cross_sim", true));
switchUT.setChecked(prefs.getBoolean("ut", true));
switch5GNR.setChecked(prefs.getBoolean("5g_nr", true));
selectedSubId = prefs.getInt("selected_subid", -1);
}
private void savePreferences() {
prefs.edit()
.putBoolean("volte", switchVoLTE.isChecked())
.putBoolean("vowifi", switchVoWiFi.isChecked())
.putBoolean("vt", switchVT.isChecked())
.putBoolean("vonr", switchVoNR.isChecked())
.putBoolean("cross_sim", switchCrossSIM.isChecked())
.putBoolean("ut", switchUT.isChecked())
.putBoolean("5g_nr", switch5GNR.isChecked())
.apply();
}
@Override
protected void attachBaseContext(Context newBase) {
LocaleHelper.updateResources(newBase, LocaleHelper.getLanguage(newBase));
super.attachBaseContext(newBase);
}
}
@@ -0,0 +1,297 @@
package io.github.pixelims;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.app.IActivityManager;
import android.app.Instrumentation;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.permission.PermissionManager;
import android.system.Os;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.util.Log;
import rikka.shizuku.ShizukuBinderWrapper;
import static rikka.shizuku.ShizukuProvider.METHOD_GET_BINDER;
/**
* PixelIMS — PrivilegedProcess
*
* This is the core bypass engine, based on vvb2060's v3.1 Instrumentation trick.
*
* HOW THE BYPASS WORKS:
* Google's October 2025 patch (CVE-2025-48617) blocked calling overrideConfig()
* directly as the shell user. The fix: we use Android's Instrumentation framework
* to launch this class as a runner. The system sees the call coming from an
* Instrumentation context rather than shell directly, bypassing the restriction.
*
* The pid argument check is a security gate — it ensures this only runs when
* launched by our own ShizukuProvider (same process), not by anything external.
*
* The SDK sandbox path handles Android 14+ where the app may run in an isolated
* sandbox process, requiring a different Binder callback route.
*
* Feature toggles are read from SharedPreferences so the user's choices
* from the UI (TurboIMS-style) are respected.
*/
public class PrivilegedProcess extends Instrumentation {
static final String TAG = "PixelIMS";
static final String PREFS_NAME = "pixelims_config";
@Override
public void onCreate(Bundle arguments) {
var context = getContext();
if (Process.isSdkSandbox()) {
// Android 14+ SDK Sandbox path: we can't call overrideConfig directly
// from the sandbox process. Instead, we pass a Binder back to
// ShizukuProvider which holds the shell delegation and calls back into us.
var extras = makeSandboxExtras(context);
var cr = context.getContentResolver();
cr.call(BuildConfig.APPLICATION_ID + ".shizuku", METHOD_GET_BINDER, null, extras);
} else if (arguments != null && arguments.getInt("pid", 0) == Process.myPid()) {
// Normal path: we were launched by our own ShizukuProvider (pid matches).
// Delegate shell permissions to our UID, apply config, then release.
var binder = ServiceManager.getService(Context.ACTIVITY_SERVICE);
var am = IActivityManager.Stub.asInterface(new ShizukuBinderWrapper(binder));
try {
am.startDelegateShellPermissionIdentity(Os.getuid(), null);
grantReadPhoneState(context);
overrideCarrierConfig(context, false);
am.stopDelegateShellPermissionIdentity();
} catch (RemoteException e) {
Log.e(TAG, "Failed in PrivilegedProcess main path", e);
}
finish(0, new Bundle());
} else {
// Launched by something other than our provider — refuse and exit cleanly.
Log.w(TAG, "PrivilegedProcess: unexpected caller pid, aborting.");
finish(0, new Bundle());
}
}
/**
* SDK Sandbox path: create a Binder that ShizukuProvider can call back on.
* When transact(1) arrives, we apply config while the shell delegation
* is already active in the provider's process.
*/
private Bundle makeSandboxExtras(Context context) {
var binder = new Binder() {
@Override
protected boolean onTransact(int code, @NonNull Parcel data,
Parcel reply, int flags) throws RemoteException {
if (code == 1) {
try {
grantReadPhoneState(context);
overrideCarrierConfig(context, true);
} catch (Exception e) {
Log.e(TAG, "Sandbox callback failed", e);
}
// Delay finish so the config call completes before we die
new Handler(Looper.getMainLooper())
.postDelayed(() -> finish(0, new Bundle()), 1000);
return true;
}
return super.onTransact(code, data, reply, flags);
}
};
var extras = new Bundle();
extras.putBinder("binder", binder);
return extras;
}
/**
* Self-grants READ_PHONE_STATE so getActiveSubscriptionIdList() works.
* We can do this because we have shell identity delegated.
*/
@SuppressLint("MissingPermission")
private static void grantReadPhoneState(Context context) {
try {
var pm = context.getSystemService(PermissionManager.class);
pm.grantRuntimePermission(BuildConfig.APPLICATION_ID,
android.Manifest.permission.READ_PHONE_STATE, Process.myUserHandle());
} catch (Exception e) {
Log.w(TAG, "grantReadPhoneState failed (non-fatal): " + e.getMessage());
}
}
/**
* The main config override method.
* Reads selected_subid from SharedPreferences:
* -1 = all SIMs (default)
* 1 = SIM slot 1 only (index 0 of active subscription list)
* 2 = SIM slot 2 only (index 1 of active subscription list)
*
* Falls back from persistent=true to persistent=false if the system rejects it.
*/
@SuppressLint("MissingPermission")
static void overrideCarrierConfig(Context context, boolean persistent) {
var cm = context.getSystemService(CarrierConfigManager.class);
var sm = context.getSystemService(SubscriptionManager.class);
var values = buildConfigBundle(context);
int[] allSubIds;
try {
allSubIds = sm.getActiveSubscriptionIdList();
} catch (SecurityException e) {
Log.e(TAG, "Could not get subscription list", e);
return;
}
if (allSubIds == null || allSubIds.length == 0) {
Log.w(TAG, "No active subscriptions found.");
return;
}
// Respect the SIM slot selection saved by MainActivity
int selectedSlot = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.getInt("selected_subid", -1);
int[] subIds;
if (selectedSlot == 1 && allSubIds.length >= 1) {
subIds = new int[]{ allSubIds[0] };
Log.i(TAG, "Applying to SIM 1 only (subId=" + allSubIds[0] + ")");
} else if (selectedSlot == 2 && allSubIds.length >= 2) {
subIds = new int[]{ allSubIds[1] };
Log.i(TAG, "Applying to SIM 2 only (subId=" + allSubIds[1] + ")");
} else {
subIds = allSubIds;
Log.i(TAG, "Applying to all " + allSubIds.length + " SIM(s)");
}
for (var subId : subIds) {
values.putInt("vvb2060_config_version", BuildConfig.VERSION_CODE);
try {
cm.overrideConfig(subId, values, persistent);
} catch (SecurityException e) {
Log.w(TAG, "overrideConfig(persistent=" + persistent + ") rejected for subId="
+ subId + ", retrying non-persistent");
if (persistent) {
try {
cm.overrideConfig(subId, values, false);
} catch (SecurityException e2) {
Log.e(TAG, "overrideConfig failed entirely for subId=" + subId, e2);
continue;
}
} else {
continue;
}
}
// Verify it actually took effect
var check = cm.getConfigForSubId(subId, "vvb2060_config_version");
if (check.getInt("vvb2060_config_version", 0) == BuildConfig.VERSION_CODE) {
Log.i(TAG, "✓ Config applied for subId=" + subId + " persistent=" + persistent);
} else {
Log.e(TAG, "✗ Config verification failed for subId=" + subId);
}
}
}
/**
* Builds the PersistableBundle of carrier config keys to override.
*
* Reads user toggle preferences from SharedPreferences (set via MainActivity).
* All features default to enabled (true) if prefs not yet written.
*
* Config keys are sourced from:
* - vvb2060 v3.1 (5G NR thresholds, VoNR, VoLTE, provisioning display)
* - TurboIMS v3.0 (VoWiFi, VT, UT, Cross-SIM, WFC format)
*/
static PersistableBundle buildConfigBundle(Context context) {
// When running as Instrumentation, getContext() returns our app's context
// since targetPackage == applicationId. But to be safe, always resolve
// prefs through the app package's own context using createPackageContext.
SharedPreferences prefs;
try {
Context appContext = context.createPackageContext(
BuildConfig.APPLICATION_ID, Context.CONTEXT_IGNORE_SECURITY);
prefs = appContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
} catch (Exception e) {
// Fallback: use passed context directly (works in non-sandbox cases)
prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
}
boolean enableVoLTE = prefs.getBoolean("volte", true);
boolean enableVoWiFi = prefs.getBoolean("vowifi", true);
boolean enableVT = prefs.getBoolean("vt", true);
boolean enableVoNR = prefs.getBoolean("vonr", true);
boolean enableCrossSIM = prefs.getBoolean("cross_sim", true);
boolean enableUT = prefs.getBoolean("ut", true);
boolean enable5GNR = prefs.getBoolean("5g_nr", true);
var bundle = new PersistableBundle();
// Always show IMS registration status in Settings
bundle.putBoolean(CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, true);
if (enableVoLTE) {
bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL, true);
bundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
bundle.putBoolean(CarrierConfigManager.KEY_HIDE_ENHANCED_4G_LTE_BOOL, false);
bundle.putBoolean(CarrierConfigManager.KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, false);
}
if (enableVoWiFi) {
bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, true);
bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, true);
bundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_WFC_MODE_BOOL, true);
bundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_WFC_ROAMING_MODE_BOOL, true);
bundle.putBoolean(CarrierConfigManager.KEY_SHOW_WIFI_CALLING_ICON_IN_STATUS_BAR_BOOL, true);
bundle.putInt(CarrierConfigManager.KEY_WFC_SPN_FORMAT_IDX_INT, 6);
}
if (enableVT) {
bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VT_AVAILABLE_BOOL, true);
}
if (enableUT) {
bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, true);
}
if (enableCrossSIM) {
bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL, true);
bundle.putBoolean(
CarrierConfigManager.KEY_ENABLE_CROSS_SIM_CALLING_ON_OPPORTUNISTIC_DATA_BOOL,
true);
}
if (enableVoNR) {
bundle.putBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL, true);
bundle.putBoolean(CarrierConfigManager.KEY_VONR_SETTING_VISIBILITY_BOOL, true);
}
if (enable5GNR) {
// Enable both NSA (Non-Standalone, 5G anchored to 4G core) and SA (Standalone, full 5G)
bundle.putIntArray(CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
new int[]{
CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA,
CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA
});
// Signal strength thresholds for 5G NR display bars.
// Without these, the phone has no reference for Dialog's network
// and may show incorrect signal bars even when 5G is connected.
// Values are SSRSRP in dBm. Boundaries: [-140 dBm, -44 dBm]
bundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY,
new int[]{
-128, // SIGNAL_STRENGTH_POOR threshold
-118, // SIGNAL_STRENGTH_MODERATE threshold
-108, // SIGNAL_STRENGTH_GOOD threshold
-98 // SIGNAL_STRENGTH_GREAT threshold
});
}
return bundle;
}
}
@@ -0,0 +1,281 @@
package io.github.pixelims;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.app.UiAutomationConnection;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.system.Os;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.ims.ProvisioningManager;
import android.util.Log;
import com.android.internal.telephony.ITelephony;
import org.lsposed.hiddenapibypass.LSPass;
import rikka.shizuku.Shizuku;
import rikka.shizuku.ShizukuBinderWrapper;
import static io.github.pixelims.PrivilegedProcess.TAG;
/**
* PixelIMS — ShizukuProvider
*
* This is the Shizuku integration layer, based on vvb2060's v3.1 ShizukuProvider.
*
* Responsibilities:
* 1. Receive the Shizuku binder when Shizuku starts (METHOD_SEND_BINDER)
* 2. On binder received: call showVoLTE() then check if config needs applying
* 3. If needed: launch PrivilegedProcess via Instrumentation (the bypass)
* 4. Handle the SDK sandbox path (Android 14+)
* 5. Expose applyNow() for manual apply from MainActivity
*
* Key methods from vvb2060 v3.1:
* - canPersistent(): runtime-detects whether the current Android build allows
* persistent overrideConfig. Probes private methods in CarrierConfigLoader.
* - needOverride(): checks if config is already at current version (fingerprint).
* - showVoLTE(): directly sets IMS provisioning opt-in status via ITelephony.
* Fixes carriers that block VoLTE at the provisioning layer even after config override.
*/
public class ShizukuProvider extends rikka.shizuku.ShizukuProvider {
// Unhide all hidden APIs at static init time — must run before any hidden API call
static {
LSPass.setHiddenApiExemptions("");
}
// Tracks whether we're in SDK sandbox mode (skip auto-apply, use sandbox path)
private boolean isSandboxMode = false;
@Override
public Bundle call(@NonNull String method, String arg, Bundle extras) {
// Only operate in the primary user profile
if (UserHandle.myUserId() != UserHandle.USER_SYSTEM) {
return new Bundle();
}
// Validate caller: must be our sandbox UID, shell, or root
var sdkUid = Process.toSdkSandboxUid(Os.getuid());
var callingUid = Binder.getCallingUid();
if (callingUid != sdkUid
&& callingUid != Process.SHELL_UID
&& callingUid != Process.ROOT_UID) {
return new Bundle();
}
if (METHOD_SEND_BINDER.equals(method)) {
// Shizuku is delivering its binder — register a one-shot listener
Shizuku.addBinderReceivedListener(() -> {
if (!isSandboxMode
&& Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
showVoLTE();
var context = getContext();
if (context != null && needOverride(context)) {
startInstrument(context, canPersistent(context));
}
}
});
} else if (METHOD_GET_BINDER.equals(method) && callingUid == sdkUid && extras != null) {
// SDK Sandbox path: PrivilegedProcess is running in sandbox and sent us a binder.
// We hold shell delegation here and transact back to it.
isSandboxMode = true;
Shizuku.addBinderReceivedListener(() -> {
var binder = extras.getBinder("binder");
if (binder != null
&& Shizuku.checkSelfPermission() == PackageManager.PERMISSION_GRANTED) {
startSandboxDelegate(binder, sdkUid);
}
});
}
return super.call(method, arg, extras);
}
/**
* Called from MainActivity when the user taps "Apply Configuration".
* Forces a re-apply regardless of the version fingerprint.
*/
public static void applyNow(Context context) {
showVoLTE();
startInstrument(context, canPersistent(context));
}
// -------------------------------------------------------------------------
// Instrumentation launcher
// -------------------------------------------------------------------------
/**
* Launches PrivilegedProcess as an Instrumentation runner.
*
* This is the core of the bypass. By routing through startInstrumentation()
* rather than calling overrideConfig() directly from shell, we avoid the
* CVE-2025-48617 restriction that blocks shell from calling overrideConfig.
*
* The pid argument is a security gate — PrivilegedProcess checks that it was
* launched by our own process and refuses to run otherwise.
*
* @param sdkSandbox if true, use INSTR_FLAG_INSTRUMENT_SDK_SANDBOX instead of NO_RESTART
*/
static void startInstrument(Context context, boolean sdkSandbox) {
try {
var binder = ServiceManager.getService(Context.ACTIVITY_SERVICE);
var am = IActivityManager.Stub.asInterface(new ShizukuBinderWrapper(binder));
var name = new ComponentName(context, PrivilegedProcess.class);
int flags = ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS;
if (sdkSandbox) {
flags |= ActivityManager.INSTR_FLAG_INSTRUMENT_SDK_SANDBOX;
} else {
flags |= ActivityManager.INSTR_FLAG_NO_RESTART;
}
// Pass our PID so PrivilegedProcess can verify its caller
var args = new Bundle();
args.putInt("pid", Process.myPid());
var connection = new UiAutomationConnection();
am.startInstrumentation(name, null, flags, args, null, connection, 0, null);
Log.i(TAG, "Instrumentation started (sdkSandbox=" + sdkSandbox + ")");
} catch (Exception e) {
Log.e(TAG, "startInstrument failed", e);
}
}
// -------------------------------------------------------------------------
// SDK Sandbox delegation
// -------------------------------------------------------------------------
private static void startSandboxDelegate(IBinder binder, int sdkUid) {
try {
var activity = ServiceManager.getService(Context.ACTIVITY_SERVICE);
var am = IActivityManager.Stub.asInterface(new ShizukuBinderWrapper(activity));
am.startDelegateShellPermissionIdentity(sdkUid, null);
var data = Parcel.obtain();
binder.transact(1, data, null, 0);
data.recycle();
am.stopDelegateShellPermissionIdentity();
} catch (Exception e) {
Log.e(TAG, "Sandbox delegate failed", e);
}
}
// -------------------------------------------------------------------------
// Version fingerprint check
// -------------------------------------------------------------------------
/**
* Checks if the carrier config is already at the current app version.
* Reads the "vvb2060_config_version" key we stamp into the config.
* If it matches VERSION_CODE, the config is current and we skip re-applying.
*/
@SuppressLint("MissingPermission")
static boolean needOverride(Context context) {
var cm = context.getSystemService(CarrierConfigManager.class);
var sm = context.getSystemService(SubscriptionManager.class);
try {
var list = sm.getActiveSubscriptionInfoList();
if (list == null || list.isEmpty()) return true;
for (var sub : list) {
var bundle = cm.getConfigForSubId(sub.getSubscriptionId(), "vvb2060_config_version");
if (bundle.getInt("vvb2060_config_version", 0) != BuildConfig.VERSION_CODE) {
return true;
}
}
Log.i(TAG, "Config is current, no override needed.");
return false;
} catch (SecurityException e) {
return true;
}
}
// -------------------------------------------------------------------------
// Persistent mode detection (vvb2060 v3.1 innovation)
// -------------------------------------------------------------------------
/**
* Runtime-detects whether persistent overrideConfig is safe on this build.
*
* Probes private methods in com.android.phone's CarrierConfigLoader to
* fingerprint the security patch level:
*
* - If isSystemApp() doesn't exist → old build, persistent is safe → true
* - If isSystemApp() exists but isSdkSandboxUidInternal() doesn't → persistent safe → true
* - If both exist → newest build, persistent is blocked → false
*
* This avoids blindly trying persistent=true and failing, which would
* leave the config in an inconsistent state.
*/
@SuppressLint("PrivateApi")
static boolean canPersistent(Context context) {
try {
var phone = context.createPackageContext("com.android.phone",
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
var clazz = phone.getClassLoader()
.loadClass("com.android.phone.CarrierConfigLoader");
try {
clazz.getDeclaredMethod("isSystemApp");
} catch (NoSuchMethodException e) {
return true; // Old build — persistent is safe
}
clazz.getDeclaredMethod("secureOverrideConfig",
PersistableBundle.class, boolean.class);
try {
clazz.getDeclaredMethod("isSdkSandboxUidInternal", int.class);
return false; // Newest patch — persistent is blocked
} catch (NoSuchMethodException e) {
return true; // Intermediate build — persistent is safe
}
} catch (Exception e) {
return false; // Unknown build — play it safe
}
}
// -------------------------------------------------------------------------
// IMS Provisioning (vvb2060 v3.1 innovation, absent from TurboIMS)
// -------------------------------------------------------------------------
/**
* Directly enables the IMS Voice Opt-In provisioning flag via ITelephony.
*
* Some carriers block VoLTE at the provisioning layer even after the
* CarrierConfig override. This operates at a completely separate layer
* (IMS provisioning, not carrier config), fixing a second potential blocker.
*
* TurboIMS never touched provisioning — this is exclusive to vvb2060 v3.1.
*/
private static void showVoLTE() {
var subId = SubscriptionManager.getDefaultVoiceSubscriptionId();
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) return;
var binder = ServiceManager.getService(Context.TELEPHONY_SERVICE);
var phone = ITelephony.Stub.asInterface(new ShizukuBinderWrapper(binder));
try {
var value = phone.getImsProvisioningInt(subId,
ProvisioningManager.KEY_VOIMS_OPT_IN_STATUS);
if (value == ProvisioningManager.PROVISIONING_VALUE_ENABLED) {
Log.i(TAG, "IMS Voice Opt-In already enabled.");
return;
}
phone.setImsProvisioningInt(subId,
ProvisioningManager.KEY_VOIMS_OPT_IN_STATUS,
ProvisioningManager.PROVISIONING_VALUE_ENABLED);
Log.i(TAG, "IMS Voice Opt-In enabled.");
} catch (RemoteException e) {
Log.w(TAG, "showVoLTE failed (non-fatal): " + e.getMessage());
}
}
}
@@ -0,0 +1,28 @@
package io.github.pixelims;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
public class SplashActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
);
new Handler(Looper.getMainLooper()).postDelayed(() -> {
startActivity(new Intent(this, MainActivity.class));
finish();
}, 1800);
}
}
+233
View File
@@ -0,0 +1,233 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="#F5F5F5">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- ── Header ───────────────────────────────────────────────── -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#1A73E8"
android:padding="24dp"
android:paddingTop="48dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_centerInParent="true">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="⚡ Pixel IMS"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:gravity="center" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="v1.0 · for Pixel"
android:textSize="13sp"
android:textColor="#C5E0FF"
android:gravity="center"
android:layout_marginTop="4dp"
android:fontFamily="monospace" />
</LinearLayout>
<Button
android:id="@+id/btn_switch_language"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:text="@string/switch_language"
android:textSize="12sp"
android:textColor="#FFFFFF"
android:background="@android:color/transparent"
android:minWidth="60dp"
android:minHeight="36dp"
android:paddingHorizontal="12dp" />
</RelativeLayout>
<!-- ── System Info Card ──────────────────────────────────────── -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#FFFFFF"
android:layout_margin="16dp"
android:padding="20dp"
android:elevation="2dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/system_info"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#1A73E8"
android:paddingBottom="12dp" />
<TextView
android:id="@+id/tv_android_version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#424242"
android:paddingBottom="6dp" />
<TextView
android:id="@+id/tv_shizuku_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#424242"
android:paddingBottom="6dp" />
<!-- NEW: Persistent mode indicator (vvb2060 canPersistent) -->
<TextView
android:id="@+id/tv_persistent_mode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#4CAF50"
android:paddingBottom="6dp" />
<!-- NEW: Config applied status (vvb2060 needOverride) -->
<TextView
android:id="@+id/tv_config_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#BDBDBD"
android:paddingBottom="4dp" />
<TextView
android:id="@+id/tv_persistent_warning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/qpr2_warning"
android:textSize="12sp"
android:textColor="#F57C00"
android:background="#FFF3E0"
android:padding="10dp"
android:visibility="gone"
android:layout_marginTop="8dp" />
</LinearLayout>
<!-- ── SIM Card Selection ────────────────────────────────────── -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#FFFFFF"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:padding="20dp"
android:elevation="2dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/sim_card"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#1A73E8"
android:paddingBottom="10dp" />
<TextView
android:id="@+id/tv_sim_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/apply_to_all_sims"
android:textSize="14sp"
android:textColor="#757575"
android:paddingBottom="6dp" />
<Button
android:id="@+id/btn_select_sim"
android:layout_width="match_parent"
android:layout_height="40dp"
android:text="@string/select_sim"
android:textSize="13sp"
android:textColor="#1A73E8"
android:background="@android:color/transparent"
android:gravity="start|center_vertical"
android:paddingStart="0dp"
android:paddingEnd="0dp" />
</LinearLayout>
<!-- ── Features Card ─────────────────────────────────────────── -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#FFFFFF"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:padding="20dp"
android:elevation="2dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/features_config"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#1A73E8"
android:paddingBottom="14dp" />
<include layout="@layout/feature_item" android:id="@+id/item_volte" />
<View style="@style/Divider" />
<include layout="@layout/feature_item" android:id="@+id/item_vowifi" />
<View style="@style/Divider" />
<include layout="@layout/feature_item" android:id="@+id/item_vt" />
<View style="@style/Divider" />
<include layout="@layout/feature_item" android:id="@+id/item_vonr" />
<View style="@style/Divider" />
<include layout="@layout/feature_item" android:id="@+id/item_cross_sim" />
<View style="@style/Divider" />
<include layout="@layout/feature_item" android:id="@+id/item_ut" />
<View style="@style/Divider" />
<include layout="@layout/feature_item" android:id="@+id/item_5g_nr" />
</LinearLayout>
<!-- ── Apply Button ──────────────────────────────────────────── -->
<Button
android:id="@+id/btn_apply"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp"
android:text="@string/apply_to_all"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:background="#1A73E8"
android:enabled="false" />
<!-- ── Tips ──────────────────────────────────────────────────── -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/tips"
android:textSize="12sp"
android:textColor="#9E9E9E"
android:padding="16dp"
android:paddingBottom="36dp" />
</LinearLayout>
</ScrollView>
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#1A73E8">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="⚡"
android:textSize="80sp"
android:gravity="center" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pixel IMS"
android:textSize="32sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:layout_marginTop="16dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="v1.0"
android:textSize="14sp"
android:textColor="#C5E0FF"
android:layout_marginTop="8dp"
android:fontFamily="monospace" />
</LinearLayout>
+37
View File
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingVertical="12dp"
android:gravity="center_vertical">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/feature_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
android:textStyle="bold"
android:textColor="#212121" />
<TextView
android:id="@+id/feature_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="#757575"
android:layout_marginTop="3dp" />
</LinearLayout>
<Switch
android:id="@+id/feature_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true" />
</LinearLayout>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+57
View File
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Pixel IMS</string>
<string name="system_info">系统状态</string>
<string name="android_version">Android: %s</string>
<string name="shizuku_status">Shizuku: %s</string>
<string name="shizuku_ready">✅ 就绪</string>
<string name="shizuku_not_running">❌ 未运行</string>
<string name="shizuku_no_permission">⚠️ 无权限</string>
<string name="persistent_mode">持久模式: %s</string>
<string name="config_status">配置: %s</string>
<string name="config_applied_short">✅ 已应用</string>
<string name="config_not_applied">⬜ 尚未应用</string>
<string name="yes">是</string>
<string name="no">否(仅本次会话)</string>
<string name="qpr2_warning">⚠️ Android 16 QPR2 Beta 3+: 持久配置受限,重启后需重新应用。</string>
<string name="features_config">功能配置</string>
<string name="volte">VoLTE(4G语音)</string>
<string name="volte_desc">启用4G高清语音通话</string>
<string name="vowifi">VoWiFi(WiFi通话)</string>
<string name="vowifi_desc">通过WiFi拨打电话</string>
<string name="vt">VT(视频通话)</string>
<string name="vt_desc">启用视频通话</string>
<string name="vonr">VoNR(5G语音)</string>
<string name="vonr_desc">通过5G进行语音通话</string>
<string name="cross_sim">跨SIM卡通话</string>
<string name="cross_sim_desc">双卡互通</string>
<string name="ut">UT补充服务</string>
<string name="ut_desc">呼叫转移、等待等</string>
<string name="_5g_nr">5G NR(NSA+SA)</string>
<string name="_5g_nr_desc">启用5G(非独立组网+独立组网)</string>
<string name="sim_card">SIM卡</string>
<string name="select_sim">选择SIM卡 →</string>
<string name="sim_1">SIM 1</string>
<string name="sim_2">SIM 2</string>
<string name="apply_to_all_sims">所有SIM卡</string>
<string name="apply_to_sim_1">应用到SIM 1</string>
<string name="apply_to_sim_2">应用到SIM 2</string>
<string name="apply_to_all">应用到全部</string>
<string name="switch_language">English</string>
<string name="go_to_network_settings">打开网络设置</string>
<string name="later">稍后</string>
<string name="shizuku_not_running_msg">Shizuku未运行,请先启动Shizuku。</string>
<string name="shizuku_no_permission_msg">请先授予Shizuku权限。</string>
<string name="config_applied">配置已应用</string>
<string name="config_success_message">配置成功!请前往网络设置开启5G/VoLTE开关。</string>
<string name="update_shizuku">请将Shizuku更新到最新版本。</string>
<string name="tips">提示:\n• 通过无线调试启动Shizuku(无需电脑)\n• 出现提示时授予Shizuku权限\n• "持久模式:是"表示配置重启后保留\n• 若为"否",每次重启后需重新点击应用\n• 双卡设备推荐选择"所有SIM卡"</string>
</resources>
+65
View File
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Pixel IMS</string>
<!-- Header -->
<string name="system_info">System Status</string>
<string name="android_version">Android: %s</string>
<string name="shizuku_status">Shizuku: %s</string>
<string name="shizuku_ready">✅ Ready</string>
<string name="shizuku_not_running">❌ Not Running</string>
<string name="shizuku_no_permission">⚠️ No Permission</string>
<!-- NEW: Status indicators -->
<string name="persistent_mode">Persistent mode: %s</string>
<string name="config_status">Config: %s</string>
<string name="config_applied_short">✅ Applied</string>
<string name="config_not_applied">⬜ Not yet applied</string>
<string name="yes">Yes</string>
<string name="no">No (session-only)</string>
<!-- Warning -->
<string name="qpr2_warning">⚠️ Android 16 QPR2 Beta 3+: persistent config is restricted. Settings will reset on reboot — reopen the app and tap Apply again.</string>
<!-- Features -->
<string name="features_config">Features</string>
<string name="volte">VoLTE (4G Voice)</string>
<string name="volte_desc">Enable 4G HD voice calling</string>
<string name="vowifi">VoWiFi (WiFi Calling)</string>
<string name="vowifi_desc">Enable calls over WiFi</string>
<string name="vt">VT (Video Call)</string>
<string name="vt_desc">Enable video calling</string>
<string name="vonr">VoNR (5G Voice)</string>
<string name="vonr_desc">Enable voice calls over 5G</string>
<string name="cross_sim">Cross-SIM Calling</string>
<string name="cross_sim_desc">Use both SIMs for calls</string>
<string name="ut">UT Supplementary Services</string>
<string name="ut_desc">Call forwarding, waiting, etc.</string>
<string name="_5g_nr">5G NR (NSA + SA)</string>
<string name="_5g_nr_desc">Enable 5G — Non-Standalone and Standalone</string>
<!-- SIM -->
<string name="sim_card">SIM Card</string>
<string name="select_sim">Select SIM →</string>
<string name="sim_1">SIM 1</string>
<string name="sim_2">SIM 2</string>
<string name="apply_to_all_sims">All SIM Cards</string>
<string name="apply_to_sim_1">Apply to SIM 1</string>
<string name="apply_to_sim_2">Apply to SIM 2</string>
<string name="apply_to_all">Apply to All SIMs</string>
<!-- Buttons -->
<string name="switch_language">中文</string>
<string name="go_to_network_settings">Open Network Settings</string>
<string name="later">Later</string>
<!-- Messages -->
<string name="shizuku_not_running_msg">Shizuku is not running. Start Shizuku first.</string>
<string name="shizuku_no_permission_msg">Grant Shizuku permission first.</string>
<string name="config_applied">Configuration Applied</string>
<string name="config_success_message">Config applied! Open Network Settings to enable 5G / VoLTE toggles.</string>
<string name="update_shizuku">Please update Shizuku to the latest version.</string>
<!-- Tips -->
<string name="tips">Tips:\n• Start Shizuku via Wireless Debugging (no PC needed)\n• Grant Shizuku permission when prompted\n• "Persistent mode: Yes" means config survives reboot\n• If No, reopen app and tap Apply after each reboot\n• All SIMs mode recommended for dual-SIM devices</string>
</resources>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Divider">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">1dp</item>
<item name="android:background">#EEEEEE</item>
<item name="android:layout_marginVertical">2dp</item>
</style>
</resources>