Skip to content

Commit b428def

Browse files
committed
fix: our reaction to android security patch Oct 2025
1 parent cc3230b commit b428def

27 files changed

Lines changed: 1359 additions & 674 deletions

.idea/compiler.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/gradle.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CLAUDE.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
**Pixel IMS** is an Android application that enables VoLTE (Voice over LTE) functionality on Google Tensor-powered Pixel devices without requiring root access. The app leverages Android's internal `telephony.ICarrierConfigLoader.overrideConfig()` API through Shizuku to override carrier configurations and enable IMS features.
8+
9+
**Primary carriers supported:**
10+
- LG U+ (Republic of Korea) - 1st tier support
11+
- Various international carriers - 2nd tier (community-reported)
12+
13+
**Target devices:** Google Pixel 6/6a/6 Pro, 7/7a/7 Pro, 8/8 Pro, Pixel Fold (Tensor chipset)
14+
15+
## Build & Development Commands
16+
17+
### Prerequisites
18+
This project requires a **patched android.jar** to compile successfully:
19+
1. Download the [patched android.jar](https://github.com/Reginer/aosp-android-jar/raw/main/android-34/android.jar)
20+
2. Place it under `$ANDROID_SDK/platforms/android-34/`
21+
3. This enables access to hidden Android APIs at compile-time
22+
23+
### Build Commands
24+
```bash
25+
# Build debug APK
26+
./gradlew assembleDebug
27+
28+
# Build release APK
29+
./gradlew assembleRelease
30+
31+
# Install debug build to connected device
32+
./gradlew installDebug
33+
34+
# Run lint checks
35+
ktlint --reporter=checkstyle,output=build/ktlint-report.xml
36+
```
37+
38+
### Testing
39+
Currently, this project does not have automated unit tests. Testing is performed manually by:
40+
- Installing the APK on a Tensor Pixel device
41+
- Verifying Shizuku permission grant
42+
- Toggling VoLTE settings
43+
- Checking IMS registration status
44+
45+
## Architecture
46+
47+
### Core Components
48+
49+
**1. Moder Classes (`Moder.kt`)**
50+
- `Moder`: Base class providing access to hidden Android telephony services via Shizuku
51+
- `carrierConfigLoader`: ICarrierConfigLoader interface for carrier config overrides
52+
- `telephony`: ITelephony interface for telephony operations
53+
- `phoneSubInfo`: IPhoneSubInfo for subscription info
54+
- `sub`: ISub for subscription management
55+
56+
- `CarrierModer`: Device-level operations
57+
- Get active subscriptions
58+
- Check device IMS support
59+
60+
- `SubscriptionModer`: Per-SIM operations
61+
- Override carrier configs (VoLTE, VoWiFi, VoNR, etc.)
62+
- Query current config values
63+
- Restart IMS registration
64+
65+
**2. Shizuku Integration (`Utils.kt`)**
66+
- `checkShizukuPermission()`: Verifies Shizuku service is running and permission is granted
67+
- Shizuku provides ADB-level privileges without root to call hidden system APIs
68+
69+
**3. UI Layer**
70+
- Built with **Jetpack Compose** and Material 3
71+
- `HomeActivity.kt`: Main entry point with bottom navigation
72+
- Navigation structure:
73+
- `/home`: Overview page showing app status
74+
- `/config{subscriptionId}`: Per-SIM configuration page
75+
- `/config{subscriptionId}/dump`: Raw config dump viewer
76+
- `/config{subscriptionId}/edit`: Expert mode editor
77+
78+
**4. Hidden API Access**
79+
- Uses `HiddenApiBypass` library to bypass Android's hidden API restrictions
80+
- AIDL interfaces (`IPrivilegedService.aidl`) for Shizuku service binding
81+
82+
### Key Configuration Keys
83+
The app primarily modifies these `CarrierConfigManager` keys:
84+
- `KEY_CARRIER_VOLTE_AVAILABLE_BOOL`: Enable VoLTE
85+
- `KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL`: Enable VoWiFi
86+
- `KEY_CARRIER_VT_AVAILABLE_BOOL`: Enable Video Telephony
87+
- `KEY_VONR_ENABLED_BOOL`: Enable VoNR (5G voice)
88+
- `KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL`: Enable cross-SIM calling
89+
90+
## Important Development Notes
91+
92+
### Hidden API Bypass
93+
On app startup (`HomeActivity.onCreate`), the app adds hidden API exemptions:
94+
```kotlin
95+
HiddenApiBypass.addHiddenApiExemptions("L") // Classes starting with L
96+
HiddenApiBypass.addHiddenApiExemptions("I") // Interfaces starting with I
97+
```
98+
99+
### Interface Caching
100+
The `Moder` class implements `loadCachedInterface()` to cache Binder interfaces and avoid repeated service lookups, improving performance.
101+
102+
### Subscription Handling
103+
- The app dynamically generates navigation routes based on active SIM subscriptions
104+
- Each SIM gets its own config page with unique subscription ID
105+
- Uses `SubscriptionInfo.uniqueName` extension: `"${displayName} (SIM ${simSlotIndex + 1})"`
106+
107+
### IMS Registration Check
108+
VoLTE status is verified via:
109+
1. `SubscriptionModer.isIMSRegistered`: Programmatic check using `ITelephony.isImsRegistered()`
110+
2. User can also check via dialer codes: `*#*#4636#*#*` → Phone information → IMS Service Status
111+
112+
### Version Updates
113+
The app checks GitHub Releases API for updates:
114+
```kotlin
115+
getLatestAppVersion { latestVersion ->
116+
// Compare with BuildConfig.VERSION_NAME
117+
}
118+
```
119+
120+
## CI/CD
121+
122+
### GitHub Actions Workflows
123+
- **default.yml**: Runs ktlint on PRs, builds debug APK, posts download link as PR comment
124+
- **build-apk.yml**: Reusable workflow for building APKs with signing
125+
- **build-aab.yml**: Builds Android App Bundle for Play Store releases
126+
- **build-debug-apk.yml**: Standalone debug build workflow
127+
128+
### Linting
129+
The project uses **ktlint 0.48.2**. Lint violations will fail CI checks on PRs affecting `app/src/**`.
130+
131+
## Distribution
132+
133+
The app is distributed via:
134+
1. **Google Play Store**: `dev.bluehouse.enablevolte`
135+
2. **GitHub Releases**: Direct APK download
136+
137+
## Troubleshooting Development Issues
138+
139+
### Build Failures
140+
- **Missing hidden API classes**: Ensure patched `android.jar` is installed at `$ANDROID_SDK/platforms/android-34/`
141+
- **Shizuku not working**: Shizuku must be running via ADB or wireless debugging before launching the app
142+
143+
### Common Runtime Issues
144+
- **Shizuku permission denied**: App will prompt for permission; grant "Allow all the time"
145+
- **IMS not registering**: After toggling VoLTE, restart device 2-3 times with 5-minute intervals
146+
- **Post-system update**: VoLTE configuration must be re-applied after Android system updates
147+
148+
## Code Style
149+
150+
Follow standard Kotlin conventions and ktlint rules. Use Jetpack Compose best practices for UI components.

app/build.gradle

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
plugins {
22
id 'com.android.application'
33
id 'org.jetbrains.kotlin.android'
4+
id 'org.jetbrains.kotlin.plugin.compose'
45
}
56

67
android {
78
namespace 'dev.bluehouse.enablevolte'
8-
compileSdk 34
9+
compileSdk 36
910

1011
defaultConfig {
1112
applicationId "dev.bluehouse.enablevolte"
1213
minSdk 29
13-
targetSdk 34
14+
targetSdk 36
1415
versionCode 13
1516
versionName "1.2.8"
1617

@@ -49,17 +50,18 @@ android {
4950
}
5051

5152
dependencies {
52-
debugImplementation 'androidx.compose.ui:ui-tooling:1.6.1'
53-
def compose_runtime_version = "1.4.0-beta01"
53+
implementation 'androidx.test:monitor:1.8.0'
54+
debugImplementation 'androidx.compose.ui:ui-tooling:1.9.3'
55+
def compose_runtime_version = "1.9.3"
5456

55-
implementation 'androidx.core:core-ktx:1.12.0'
56-
implementation 'androidx.appcompat:appcompat:1.6.1'
57-
implementation 'com.google.android.material:material:1.11.0'
58-
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
57+
implementation 'androidx.core:core-ktx:1.17.0'
58+
implementation 'androidx.appcompat:appcompat:1.7.1'
59+
implementation 'com.google.android.material:material:1.13.0'
60+
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
5961

60-
implementation 'androidx.activity:activity-compose:1.8.2'
62+
implementation 'androidx.activity:activity-compose:1.11.0'
6163

62-
implementation "androidx.navigation:navigation-compose:2.7.7"
64+
implementation "androidx.navigation:navigation-compose:2.9.5"
6365

6466
implementation "androidx.compose.runtime:runtime:$compose_runtime_version"
6567
implementation "androidx.compose.runtime:runtime-livedata:$compose_runtime_version"
@@ -68,23 +70,24 @@ dependencies {
6870
implementation "androidx.compose.ui:ui:$compose_version"
6971
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
7072

71-
implementation "androidx.compose.material:material:1.6.1"
72-
implementation "androidx.compose.material3:material3:1.2.0"
73+
implementation "androidx.compose.material:material:1.9.3"
74+
implementation "androidx.compose.material:material-icons-core:1.7.8"
75+
implementation "androidx.compose.material3:material3:1.4.0"
7376

74-
implementation "io.arrow-kt:arrow-core:1.2.0-RC"
75-
implementation "io.arrow-kt:arrow-fx-coroutines:1.2.0-RC"
77+
implementation "io.arrow-kt:arrow-core:2.1.2"
78+
implementation "io.arrow-kt:arrow-fx-coroutines:2.1.2"
7679

77-
implementation "com.google.accompanist:accompanist-systemuicontroller:0.28.0"
80+
implementation "com.google.accompanist:accompanist-systemuicontroller:0.36.0"
7881

7982
def shizuku_version = "13.1.5"
8083
implementation "dev.rikka.shizuku:api:$shizuku_version"
8184
implementation "dev.rikka.shizuku:provider:$shizuku_version"
82-
implementation 'com.anggrayudi:android-hidden-api:30.0'
85+
implementation 'com.anggrayudi:android-hidden-api:35.0'
8386

84-
implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3'
87+
implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:6.1'
8588

8689
implementation "com.github.kittinunf.fuel:fuel:2.3.1"
8790
implementation "com.github.kittinunf.fuel:fuel-android:2.3.1"
8891
implementation "com.github.kittinunf.fuel:fuel-json:2.3.1"
89-
implementation 'net.swiftzer.semver:semver:1.1.2'
92+
implementation 'net.swiftzer.semver:semver:2.1.0'
9093
}

app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3-
xmlns:tools="http://schemas.android.com/tools">
3+
xmlns:tools="http://schemas.android.com/tools"
4+
package="dev.bluehouse.enablevolte">
45

56
<uses-permission android:name="android.permission.INTERNET" />
7+
<instrumentation
8+
android:name="dev.bluehouse.enablevolte.BrokerInstrumentation"
9+
android:targetPackage="dev.bluehouse.enablevolte"/>
610
<application
711
android:allowBackup="true"
812
android:dataExtractionRules="@xml/data_extraction_rules"
@@ -11,7 +15,7 @@
1115
android:label="@string/app_name"
1216
android:supportsRtl="true"
1317
android:theme="@style/Theme.EnableVoLTE"
14-
tools:targetApi="34"
18+
tools:targetApi="36"
1519
android:localeConfig="@xml/locales_config">
1620
<activity
1721
android:name=".HomeActivity"
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package dev.bluehouse.enablevolte
2+
3+
import android.annotation.SuppressLint
4+
import android.app.IActivityManager
5+
import android.app.Instrumentation
6+
import android.content.Context
7+
import android.os.Bundle
8+
import android.system.Os
9+
import android.telephony.CarrierConfigManager
10+
import android.util.Log
11+
import rikka.shizuku.ShizukuBinderWrapper
12+
import rikka.shizuku.SystemServiceHelper
13+
14+
const val TAG = "BrokerInstrumentation"
15+
16+
class BrokerInstrumentation : Instrumentation() {
17+
@SuppressLint("MissingPermission")
18+
private fun applyConfig(
19+
subId: Int,
20+
arguments: Bundle,
21+
) {
22+
Log.i(TAG, "applyConfig")
23+
val am = IActivityManager.Stub.asInterface(ShizukuBinderWrapper(SystemServiceHelper.getSystemService(Context.ACTIVITY_SERVICE)))
24+
am.startDelegateShellPermissionIdentity(Os.getuid(), null)
25+
try {
26+
val configurationManager = this.context.getSystemService(CarrierConfigManager::class.java)
27+
val overrideValues = toPersistableBundle(arguments)
28+
29+
configurationManager.overrideConfig(subId, overrideValues, true)
30+
} finally {
31+
Log.i(TAG, "applyConfig done")
32+
am.stopDelegateShellPermissionIdentity()
33+
}
34+
}
35+
36+
@SuppressLint("MissingPermission")
37+
private fun clearConfig(subId: Int) {
38+
Log.i(TAG, "clearConfig")
39+
val am = IActivityManager.Stub.asInterface(ShizukuBinderWrapper(SystemServiceHelper.getSystemService(Context.ACTIVITY_SERVICE)))
40+
am.startDelegateShellPermissionIdentity(Os.getuid(), null)
41+
try {
42+
val configurationManager = this.context.getSystemService(CarrierConfigManager::class.java)
43+
44+
configurationManager.overrideConfig(subId, null, true)
45+
} finally {
46+
Log.i(TAG, "clearConfig done")
47+
am.stopDelegateShellPermissionIdentity()
48+
}
49+
}
50+
51+
override fun onCreate(arguments: Bundle?) {
52+
super.onCreate(arguments)
53+
54+
if (arguments == null) {
55+
return
56+
}
57+
58+
val clear = arguments.getBoolean("moder_clear")
59+
val subId = arguments.getInt("moder_subId")
60+
61+
try {
62+
if (clear) {
63+
this.clearConfig(subId)
64+
} else {
65+
this.applyConfig(subId, arguments)
66+
}
67+
} finally {
68+
finish(0, Bundle())
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)