Add profile management endpoints

This commit is contained in:
Laiteux
2025-10-29 23:24:32 +04:00
parent e42171aaa2
commit 0a08ddfe40

View File

@@ -7,6 +7,8 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import java.net.URLDecoder;
import java.nio.charset.*;
import kotlin.coroutines.Continuation; import kotlin.coroutines.Continuation;
import kotlin.coroutines.CoroutineContext; import kotlin.coroutines.CoroutineContext;
@@ -22,14 +24,17 @@ import android.database.MatrixCursor;
import android.net.Uri; import android.net.Uri;
import im.angry.openeuicc.OpenEuiccApplication; import im.angry.openeuicc.OpenEuiccApplication;
import im.angry.openeuicc.core.DefaultEuiccChannelManager;
import im.angry.openeuicc.core.EuiccChannel; import im.angry.openeuicc.core.EuiccChannel;
import im.angry.openeuicc.core.EuiccChannelManager; import im.angry.openeuicc.core.EuiccChannelManager;
import im.angry.openeuicc.util.LPAUtilsKt; import im.angry.openeuicc.core.DefaultEuiccChannelManager;
import im.angry.openeuicc.util.UiccCardInfoCompat; import im.angry.openeuicc.util.UiccCardInfoCompat;
import im.angry.openeuicc.util.UiccPortInfoCompat; import im.angry.openeuicc.util.UiccPortInfoCompat;
import im.angry.openeuicc.util.UiccPortInfoCompat;
import im.angry.openeuicc.util.LPAUtilsKt;
import im.angry.openeuicc.util.ActivationCode;
import im.angry.openeuicc.di.AppContainer; import im.angry.openeuicc.di.AppContainer;
import net.typeblog.lpac_jni.LocalProfileInfo; import net.typeblog.lpac_jni.LocalProfileInfo;
import net.typeblog.lpac_jni.ProfileDownloadCallback;
public class LpaBridgeProvider extends ContentProvider public class LpaBridgeProvider extends ContentProvider
{ {
@@ -48,6 +53,7 @@ public class LpaBridgeProvider extends ContentProvider
MatrixCursor rows; MatrixCursor rows;
final String path = uri.getLastPathSegment(); final String path = uri.getLastPathSegment();
final Map<String, String> args = getArgsFromUri(uri);
if (path == null) if (path == null)
{ {
@@ -57,8 +63,6 @@ public class LpaBridgeProvider extends ContentProvider
{ {
try try
{ {
final Map<String, String> args = getArgsFromUri(uri);
switch (path) switch (path)
{ {
case "ping": case "ping":
@@ -70,6 +74,27 @@ public class LpaBridgeProvider extends ContentProvider
case "profiles": case "profiles":
rows = handleGetProfiles(args); rows = handleGetProfiles(args);
break; break;
case "activeProfile":
rows = handleGetActiveProfile(args);
break;
case "downloadProfile":
rows = handleDownloadProfile(args);
break;
case "deleteProfile":
rows = handleDeleteProfile(args);
break;
case "enableProfile":
rows = handleEnableProfile(args);
break;
case "disableProfile":
rows = handleDisableProfile(args);
break;
case "disableActiveProfile":
rows = handleDisableActiveProfile(args);
break;
case "switchProfile":
rows = handleSwitchProfile(args);
break;
default: default:
rows = error("unknown_path"); rows = error("unknown_path");
break; break;
@@ -114,9 +139,6 @@ public class LpaBridgeProvider extends ContentProvider
var getUiccCardsMethod = DefaultEuiccChannelManager.class.getDeclaredMethod("getUiccCards"); var getUiccCardsMethod = DefaultEuiccChannelManager.class.getDeclaredMethod("getUiccCards");
getUiccCardsMethod.setAccessible(true); getUiccCardsMethod.setAccessible(true);
var findEuiccChannelByPortMethod = DefaultEuiccChannelManager.class.getDeclaredMethod("findEuiccChannelByPort", int.class, int.class, Continuation.class);
findEuiccChannelByPortMethod.setAccessible(true);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
var cards = (Collection<UiccCardInfoCompat>) getUiccCardsMethod.invoke(euiccChannelManager); var cards = (Collection<UiccCardInfoCompat>) getUiccCardsMethod.invoke(euiccChannelManager);
@@ -133,22 +155,7 @@ public class LpaBridgeProvider extends ContentProvider
int slotId = card.getPhysicalSlotIndex(); int slotId = card.getPhysicalSlotIndex();
int portId = port.getPortIndex(); int portId = port.getPortIndex();
@SuppressWarnings("unchecked") var euiccChannel = findEuiccChannel(euiccChannelManager, slotId, portId);
var euiccChannel = (EuiccChannel) BuildersKt.runBlocking
(
EmptyCoroutineContext.INSTANCE,
(_, continuation) ->
{
try
{
return findEuiccChannelByPortMethod.invoke(euiccChannelManager, slotId, portId, continuation);
}
catch (Exception ex)
{
return null;
}
}
);
if (euiccChannel != null) if (euiccChannel != null)
{ {
@@ -192,10 +199,200 @@ public class LpaBridgeProvider extends ContentProvider
return rows; return rows;
} }
private MatrixCursor handleGetActiveProfile(Map<String, String> args) throws Exception
{
List<LocalProfileInfo> profiles = withEuiccChannel
(
args,
(channel, _) -> channel.getLpa().getProfiles()
);
var enabledProfile = LPAUtilsKt.getEnabled(profiles);
if (enabledProfile == null)
return empty();
return row("iccid", enabledProfile.getIccid());
}
private MatrixCursor handleDownloadProfile(Map<String, String> args) throws Exception
{
String[] address = new String[1];
String[] matchingId = { args.get("matchingId") };
String[] confirmationCode = { args.get("confirmationCode") };
String imei = args.get("imei");
String[] activationCodeArg = new String[1];
if (tryGetArgAsString(args, "activationCode", activationCodeArg))
{
var activationCode = ActivationCode.Companion.fromString(activationCodeArg[0]);
address[0] = activationCode.getAddress();
matchingId[0] = activationCode.getMatchingId();
if (activationCode.getConfirmationCodeRequired())
if (!tryGetArgAsString(args, "confirmationCode", confirmationCode))
return missingArgError("confirmationCode");
}
else if (!tryGetArgAsString(args, "address", address))
return missingArgError("activationCode_or_address");
withEuiccChannel
(
args,
(channel, _) ->
{
channel.getLpa().downloadProfile
(
address[0],
matchingId[0],
imei,
confirmationCode[0],
new ProfileDownloadCallback()
{
@Override
public void onStateUpdate(ProfileDownloadCallback.DownloadState state)
{
// ignored
// TODO: callbackUrl?
}
}
);
return null;
}
);
return success();
}
private MatrixCursor handleDeleteProfile(Map<String, String> args) throws Exception
{
String[] iccid = new String[1];
if (!tryGetArgAsString(args, "iccid", iccid))
return missingArgError("iccid");
boolean success = withEuiccChannel
(
args,
(channel, _) -> channel.getLpa().deleteProfile(iccid[0])
);
return success(success);
}
private MatrixCursor handleEnableProfile(Map<String, String> args) throws Exception
{
String[] iccid = new String[1];
boolean[] refresh = new boolean[1];
if (!tryGetArgAsString(args, "iccid", iccid))
return missingArgError("iccid");
if (!tryGetArgAsBoolean(args, "refresh", refresh))
refresh[0] = true;
boolean success = withEuiccChannel
(
args,
(channel, _) -> channel.getLpa().enableProfile(iccid[0], refresh[0])
);
return success(success);
}
private MatrixCursor handleDisableProfile(Map<String, String> args) throws Exception
{
String[] iccid = new String[1];
boolean[] refresh = new boolean[1];
if (!tryGetArgAsString(args, "iccid", iccid))
return missingArgError("iccid");
if (!tryGetArgAsBoolean(args, "refresh", refresh))
refresh[0] = true;
boolean success = withEuiccChannel
(
args,
(channel, _) -> channel.getLpa().disableProfile(iccid[0], refresh[0])
);
return success(success);
}
private MatrixCursor handleDisableActiveProfile(Map<String, String> args) throws Exception
{
boolean[] refresh = new boolean[1];
if (!tryGetArgAsBoolean(args, "refresh", refresh))
refresh[0] = true;
String iccid = withEuiccChannel
(
args,
(channel, _) -> LPAUtilsKt.disableActiveProfileKeepIccId(channel.getLpa(), refresh[0])
);
if (iccid == null)
return success(false);
return row("iccid", iccid);
}
private MatrixCursor handleSwitchProfile(Map<String, String> args) throws Exception
{
String[] iccid = new String[1];
boolean[] enable = new boolean[1];
boolean[] refresh = new boolean[1];
if (!tryGetArgAsString(args, "iccid", iccid))
return missingArgError("iccid");
if (!tryGetArgAsBoolean(args, "enable", enable))
enable[0] = true;
if (!tryGetArgAsBoolean(args, "refresh", refresh))
refresh[0] = true;
boolean success = withEuiccChannel
(
args,
(channel, _) -> LPAUtilsKt.switchProfile(channel.getLpa(), iccid[0], enable[0], refresh[0])
);
return success(success);
}
// endregion // endregion
// region LPA Helpers // region LPA Helpers
@SuppressWarnings("unchecked")
private EuiccChannel findEuiccChannel(DefaultEuiccChannelManager euiccChannelManager, int slotId, int portId) throws Exception
{
var findEuiccChannelByPortMethod = DefaultEuiccChannelManager.class.getDeclaredMethod("findEuiccChannelByPort", int.class, int.class, Continuation.class);
findEuiccChannelByPortMethod.setAccessible(true);
return (EuiccChannel) BuildersKt.runBlocking
(
EmptyCoroutineContext.INSTANCE,
(_, continuation) ->
{
try
{
return findEuiccChannelByPortMethod.invoke(euiccChannelManager, slotId, portId, continuation);
}
catch (Exception ex)
{
return null;
}
}
);
}
private <T> T withEuiccChannel(Map<String, String> args, Function2<EuiccChannel, Continuation<? super T>, ?> operation) throws Exception private <T> T withEuiccChannel(Map<String, String> args, Function2<EuiccChannel, Continuation<? super T>, ?> operation) throws Exception
{ {
var slotId = new int[1]; var slotId = new int[1];
@@ -213,16 +410,7 @@ public class LpaBridgeProvider extends ContentProvider
return (T) BuildersKt.runBlocking return (T) BuildersKt.runBlocking
( (
EmptyCoroutineContext.INSTANCE, EmptyCoroutineContext.INSTANCE,
(scope, continuation) -> (_, continuation) -> euiccChannelManager.withEuiccChannel(slotId, portId, operation, continuation)
{
return euiccChannelManager.withEuiccChannel
(
slotId,
portId,
operation,
continuation
);
}
); );
} }
@@ -230,20 +418,31 @@ public class LpaBridgeProvider extends ContentProvider
// region Arg Helpers // region Arg Helpers
// TODO: decode?
private static Map<String, String> getArgsFromUri(Uri uri) private static Map<String, String> getArgsFromUri(Uri uri)
{ {
var args = new HashMap<String, String>(); var args = new HashMap<String, String>();
for (String name : uri.getQueryParameterNames()) for (String name : uri.getQueryParameterNames())
{ {
args.put(name, uri.getQueryParameter(name)); args.put(name, URLDecoder.decode(uri.getQueryParameter(name), StandardCharsets.UTF_8));
} }
return args; return args;
} }
private static boolean tryGet(Map<String, String> args, String key, String[] out) private void requireSlotAndPort(Map<String, String> args, int[] slotIdOut, int[] portIdOut) throws Exception
{
final String slotIdArg = "slotId";
final String portIdArg = "portId";
if (!tryGetArgAsInt(args, slotIdArg, slotIdOut))
throw new Exception("missing_arg_" + slotIdArg);
if (!tryGetArgAsInt(args, portIdArg, portIdOut))
throw new Exception("missing_arg_" + portIdArg);
}
private static boolean tryGetArgAsString(Map<String, String> args, String key, String[] out)
{ {
String arg = args.get(key); String arg = args.get(key);
@@ -254,11 +453,11 @@ public class LpaBridgeProvider extends ContentProvider
return true; return true;
} }
private static boolean tryGetInt(Map<String, String> args, String key, int[] out) private static boolean tryGetArgAsInt(Map<String, String> args, String key, int[] out)
{ {
String[] arg = new String[1]; String[] arg = new String[1];
if (!tryGet(args, key, arg)) if (!tryGetArgAsString(args, key, arg))
return false; return false;
try try
@@ -272,32 +471,50 @@ public class LpaBridgeProvider extends ContentProvider
} }
} }
private void requireSlotAndPort(Map<String, String> args, int[] slotIdOut, int[] portIdOut) throws Exception private static boolean tryGetArgAsBoolean(Map<String, String> args, String key, boolean[] out)
{ {
final String slotIdArg = "slotId"; String[] arg = new String[1];
final String portIdArg = "portId";
if (!tryGetInt(args, slotIdArg, slotIdOut)) if (!tryGetArgAsString(args, key, arg))
throw new Exception("missing_arg_" + slotIdArg); return false;
if (!tryGetInt(args, portIdArg, portIdOut)) out[0] = arg[0].equals("1")
throw new Exception("missing_arg_" + portIdArg); || arg[0].toLowerCase().startsWith("y")
|| arg[0].equalsIgnoreCase("on")
|| arg[0].equalsIgnoreCase("true");
return true;
} }
// endregion // endregion
// region Row Helpers // region Row Helpers
private static MatrixCursor rows(String[] columns, Object... values) private static MatrixCursor rows(String[] columns, Object[][] values)
{ {
var rows = new MatrixCursor(columns); var rows = new MatrixCursor(columns);
rows.addRow(values);
for (Object[] rowValues : values)
{
rows.addRow(rowValues);
}
return rows; return rows;
} }
private static MatrixCursor row(String column, String value) private static MatrixCursor row(String column, String value)
{ {
return rows(new String[] { column }, value); return rows(new String[] { column }, new Object[][] { new Object[] { value } });
}
private static MatrixCursor success()
{
return success(true);
}
private static MatrixCursor success(boolean success)
{
return row("success", Boolean.toString(success));
} }
private static MatrixCursor error(String message) private static MatrixCursor error(String message)
@@ -305,6 +522,11 @@ public class LpaBridgeProvider extends ContentProvider
return row("error", message); return row("error", message);
} }
private static MatrixCursor missingArgError(String argName)
{
return error("missing_arg_" + argName);
}
private static MatrixCursor empty() private static MatrixCursor empty()
{ {
return new MatrixCursor(new String[0]); return new MatrixCursor(new String[0]);