diff --git a/build+install.sh b/build+install.sh index 068baf1..e02f1c1 100755 --- a/build+install.sh +++ b/build+install.sh @@ -7,6 +7,8 @@ JAVA_PKG="im.angry.openeuicc.bridge" APKTOOL_TREE="eazyeuicc" OUT_DIR="out" SRC_ROOT="src" +ORIG_APK="eazyeuicc.apk" +APP_JAR="deps/eazyeuicc.jar" BAKSMALI_JAR="tools/baksmali.jar" # ============================================================================ @@ -45,6 +47,51 @@ command -v keytool >/dev/null || { echo "ERROR: keytool not found"; exit 1; } [[ -d "$APKTOOL_TREE" ]] || { echo "ERROR: apktool tree missing: $APKTOOL_TREE"; exit 1; } [[ -d "$SRC_PKG_DIR" ]] || { echo "ERROR: source package dir missing: $SRC_PKG_DIR"; exit 1; } +# Ensure we have a complete classpath jar containing ALL dexes (kotlin, coroutines, lpac_jni, etc.) +rebuild_cp_jar() { + local apk="$1" + local dexdir="deps/_dex" + local mergedir="deps/_merge" + rm -rf "$dexdir" "$mergedir" + mkdir -p "$dexdir" "$mergedir" deps + + echo "[dex] extracting classes*.dex from $apk" + unzip -q -j "$apk" 'classes*.dex' -d "$dexdir" + + local any=0 + for d in "$dexdir"/classes*.dex; do + [[ -f "$d" ]] || continue + any=1 + local j="${d%.dex}.jar" + echo "[dex2jar] $d -> $j" + d2j-dex2jar "$d" -o "$j" >/dev/null + unzip -q -o "$j" -d "$mergedir" + done + [[ $any -eq 1 ]] || { echo "ERROR: no classes*.dex found in $apk"; exit 1; } + + (cd "$mergedir" && jar cf ../eazyeuicc.jar .) + rm -rf "$dexdir" "$mergedir" +} + +# (Re)build classpath jar if missing or incomplete +need_rebuild=0 +if [[ ! -f "$APP_JAR" ]]; then + need_rebuild=1 +else + jar tf "$APP_JAR" | grep -q '^kotlin/' || need_rebuild=1 + jar tf "$APP_JAR" | grep -q '^kotlinx/coroutines/' || need_rebuild=1 + jar tf "$APP_JAR" | grep -q '^net/typeblog/lpac_jni/' || need_rebuild=1 +fi +if [[ $need_rebuild -eq 1 ]]; then + [[ -f "$ORIG_APK" ]] || { echo "ERROR: ORIG_APK not found: $ORIG_APK"; exit 1; } + rebuild_cp_jar "$ORIG_APK" +fi + +# Verify again (fail fast with helpful messages) +jar tf "$APP_JAR" | grep -q '^kotlin/' || { echo "ERROR: Kotlin stdlib missing in $APP_JAR"; exit 1; } +jar tf "$APP_JAR" | grep -q '^kotlinx/coroutines/' || { echo "ERROR: kotlinx coroutines missing in $APP_JAR"; exit 1; } +jar tf "$APP_JAR" | grep -q '^net/typeblog/lpac_jni/' || { echo "ERROR: net.typeblog.lpac_jni missing in $APP_JAR"; exit 1; } + # Gather sources JAVA_SOURCES=() while IFS= read -r -d '' f; do @@ -58,12 +105,20 @@ rm -rf "$OUT_DIR" mkdir -p "$OUT_DIR"/{classes,dex,smali} # Compile -javac --release 23 -cp "$ANDROID_JAR" \ +CP="$ANDROID_JAR:$APP_JAR" +javac \ + -source 23 -target 23 \ + -classpath "$CP" \ + -Xlint:-options \ + -encoding UTF-8 \ -d "$OUT_DIR/classes" \ "${JAVA_SOURCES[@]}" + # DEX -"$BT/d8" --lib "$ANDROID_JAR" \ +"$BT/d8" \ + --lib "$ANDROID_JAR" \ + --classpath "$APP_JAR" \ --output "$OUT_DIR/dex" \ $(find "$OUT_DIR/classes" -type f -name '*.class') diff --git a/deps/eazyeuicc.jar b/deps/eazyeuicc.jar new file mode 100644 index 0000000..590a941 Binary files /dev/null and b/deps/eazyeuicc.jar differ diff --git a/src/im/angry/openeuicc/bridge/LpaBridgeProvider.java b/src/im/angry/openeuicc/bridge/LpaBridgeProvider.java index 88d8e37..8c59eb6 100644 --- a/src/im/angry/openeuicc/bridge/LpaBridgeProvider.java +++ b/src/im/angry/openeuicc/bridge/LpaBridgeProvider.java @@ -1,20 +1,44 @@ package im.angry.openeuicc.bridge; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.HashMap; + +import kotlin.coroutines.Continuation; +import kotlin.coroutines.CoroutineContext; +import kotlin.coroutines.EmptyCoroutineContext; +import kotlin.jvm.functions.Function2; +import kotlinx.coroutines.BuildersKt; +import kotlinx.coroutines.CoroutineScope; + import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.HashMap; +import im.angry.openeuicc.OpenEuiccApplication; +import im.angry.openeuicc.core.EuiccChannel; +import im.angry.openeuicc.core.EuiccChannelManager; +import im.angry.openeuicc.util.LPAUtilsKt; +import im.angry.openeuicc.di.AppContainer; +import net.typeblog.lpac_jni.LocalProfileInfo; public class LpaBridgeProvider extends ContentProvider { public static final String AUTHORITY = "lpa.bridge"; + private AppContainer appContainer; + + @Override + public boolean onCreate() + { + appContainer = ((OpenEuiccApplication)getContext().getApplicationContext()).getAppContainer(); + return true; + } + @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { @@ -28,16 +52,26 @@ public class LpaBridgeProvider extends ContentProvider } else { - final Map args = getArgsFromUri(uri); - - switch (path) + try { - case "test": - rows = handleTest(args); - break; - default: - rows = error("unknown_path"); - break; + final Map args = getArgsFromUri(uri); + + switch (path) + { + case "ping": + rows = handlePing(args); + break; + case "profiles": + rows = handleGetProfiles(args); + break; + default: + rows = error("unknown_path"); + break; + } + } + catch (Exception ex) + { + rows = error(ex.getMessage()); } } @@ -46,9 +80,6 @@ public class LpaBridgeProvider extends ContentProvider // region Mandatory Overrides - @Override - public boolean onCreate() { return true; } - @Override public Uri insert(Uri uri, ContentValues values) { return null; } @@ -65,15 +96,74 @@ public class LpaBridgeProvider extends ContentProvider // region Handlers - private MatrixCursor handleTest(Map args) + private MatrixCursor handlePing(Map args) { + return row("ping", "pong"); + } + + private MatrixCursor handleGetProfiles(Map args) throws Exception + { + List profiles = withEuiccChannel( + args, + (channel, _) -> channel.getLpa().getProfiles() + ); + + if (profiles == null || profiles.isEmpty()) + return empty(); + + var rows = new MatrixCursor(new String[] + { + "iccid", + "state", + "name", + "nickname" + }); + + for (LocalProfileInfo profile : profiles) + { + rows.addRow(new Object[] { + profile.getIccid(), + profile.getState().toString(), // TODO: replace by LPAUtilsKt.isEnabled(profile)? + profile.getName(), + LPAUtilsKt.getDisplayName(profile) + }); + } + + return rows; + } + + // endregion + + // region LPA Helpers + + @SuppressWarnings("unchecked") + private T withEuiccChannel(Map args, Function2, ?> operation) throws Exception + { + final String slotIdArg = "slotId"; + final String portIdArg = "portId"; + var slotId = new int[1]; var portId = new int[1]; - var error = requireSlotAndPort(args, slotId, portId); - if (error != null) return error; + if (!tryGetInt(args, slotIdArg, slotId)) + throw new Exception("missing_arg_" + slotIdArg); - return row("ok", "true"); + if (!tryGetInt(args, portIdArg, portId)) + throw new Exception("missing_arg_" + portIdArg); + + EuiccChannelManager channelManager = appContainer.getEuiccChannelManager(); + + return (T)BuildersKt.runBlocking( + EmptyCoroutineContext.INSTANCE, + (scope, continuation) -> { + return channelManager.withEuiccChannel( + slotId[0], + portId[0], + operation, + continuation + ); + } + ); } // endregion @@ -121,20 +211,6 @@ public class LpaBridgeProvider extends ContentProvider } } - private MatrixCursor requireSlotAndPort(Map args, int[] slotIdOut, int[] portIdOut) - { - final String slotIdArg = "slotId"; - final String portIdArg = "portId"; - - if (!tryGetInt(args, slotIdArg, slotIdOut)) - return error("missing_arg_" + slotIdArg); - - if (!tryGetInt(args, portIdArg, portIdOut)) - return error("missing_arg_" + portIdArg); - - return null; - } - // endregion // region Row Helpers