Add /preferences + /setPreference

This commit is contained in:
Laiteux
2025-11-03 15:11:40 +04:00
parent c20c4a5395
commit 6b56a946ee

View File

@@ -26,6 +26,7 @@ import kotlin.jvm.functions.Function2;
import kotlin.coroutines.Continuation; import kotlin.coroutines.Continuation;
import kotlin.coroutines.EmptyCoroutineContext; import kotlin.coroutines.EmptyCoroutineContext;
import kotlinx.coroutines.BuildersKt; import kotlinx.coroutines.BuildersKt;
import kotlinx.coroutines.flow.FlowKt;
import kotlinx.coroutines.sync.Mutex; import kotlinx.coroutines.sync.Mutex;
import kotlinx.coroutines.sync.MutexKt; import kotlinx.coroutines.sync.MutexKt;
@@ -39,12 +40,16 @@ import com.google.gson.GsonBuilder;
import im.angry.openeuicc.OpenEuiccApplication; import im.angry.openeuicc.OpenEuiccApplication;
import im.angry.openeuicc.di.AppContainer; import im.angry.openeuicc.di.AppContainer;
import im.angry.openeuicc.di.UnprivilegedAppContainer;
import im.angry.openeuicc.core.EuiccChannel; import im.angry.openeuicc.core.EuiccChannel;
import im.angry.openeuicc.core.EuiccChannelManager;
import im.angry.openeuicc.core.DefaultEuiccChannelManager; 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.LPAUtilsKt; import im.angry.openeuicc.util.LPAUtilsKt;
import im.angry.openeuicc.util.ActivationCode; import im.angry.openeuicc.util.ActivationCode;
import im.angry.openeuicc.util.PreferenceUtilsKt;
import im.angry.openeuicc.util.PreferenceFlowWrapper;
import net.typeblog.lpac_jni.LocalProfileInfo; import net.typeblog.lpac_jni.LocalProfileInfo;
import net.typeblog.lpac_jni.ProfileDownloadCallback; import net.typeblog.lpac_jni.ProfileDownloadCallback;
@@ -61,7 +66,6 @@ public class LpaBridgeProvider extends ContentProvider
} }
@Override @Override
@SuppressWarnings("unchecked")
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
{ {
MatrixCursor rows; MatrixCursor rows;
@@ -93,65 +97,46 @@ public class LpaBridgeProvider extends ContentProvider
try try
{ {
switch (path) rows = switch (path)
{ {
case "ping": // out: string ping=pong
// out: string ping=pong case "ping" -> handlePing(args);
rows = handlePing(args); // out (many): string name, bool enabled
break; case "preferences" -> handleGetPreferences(args);
case "cards": // in: string name, bool enabled
// out (many, can be empty): int slotId, int portId // out: bool success
rows = handleGetCards(args); case "setPreference" -> handleSetPreference(args);
break; // out (many, can be empty): int slotId, int portId
case "profiles": case "cards" -> handleGetCards(args);
// in: int slotId, int portId // in: int slotId, int portId
// out (many, can be empty): string iccid, bool isEnabled, string name, string nickname // out (many, can be empty): string iccid, bool isEnabled, string name, string nickname
rows = handleGetProfiles(args); case "profiles" -> handleGetProfiles(args);
break; // in: int slotId, int portId
case "activeProfile": // out (single, can be empty): string iccid, bool isEnabled, string name, string nickname
// in: int slotId, int portId case "activeProfile" -> handleGetActiveProfile(args);
// out (single, can be empty): string iccid, bool isEnabled, string name, string nickname // in: int slotId, int portId, (either {string activationCode} or {string address, string? matchingId}), string? confirmationCode, string? imei
rows = handleGetActiveProfile(args); // out (single, can be empty): string iccid, bool isEnabled, string name, string nickname
break; case "downloadProfile" -> handleDownloadProfile(args);
case "downloadProfile": // in: int slotId, int portId, string iccid
// in: int slotId, int portId, (either {string activationCode} or {string address, string? matchingId}), string? confirmationCode, string? imei // out: bool success
// out (single, can be empty): string iccid, bool isEnabled, string name, string nickname case "deleteProfile" -> handleDeleteProfile(args);
rows = handleDownloadProfile(args); // in: int slotId, int portId, string iccid, bool refresh=true
break; // out: bool success
case "deleteProfile": case "enableProfile" -> handleEnableProfile(args);
// in: int slotId, int portId, string iccid // in: int slotId, int portId, string iccid, bool refresh=true
// out: bool success // out: bool success
rows = handleDeleteProfile(args); case "disableProfile" -> handleDisableProfile(args);
break; // in: int slotId, int portId, bool refresh=true
case "enableProfile": // out: bool success
// in: int slotId, int portId, string iccid, bool refresh=true case "disableActiveProfile" -> handleDisableActiveProfile(args);
// out: bool success // in: int slotId, int portId, string iccid, bool enable=true, bool refresh=true
rows = handleEnableProfile(args); // out: bool success
break; case "switchProfile" -> handleSwitchProfile(args);
case "disableProfile": // in: int slotId, int portId, string iccid, string nickname
// in: int slotId, int portId, string iccid, bool refresh=true // out: bool success
// out: bool success case "setNickname" -> handleSetNickname(args);
rows = handleDisableProfile(args); default -> error("unknown_path");
break; };
case "disableActiveProfile":
// in: int slotId, int portId, bool refresh=true
// out: bool success
rows = handleDisableActiveProfile(args);
break;
case "switchProfile":
// in: int slotId, int portId, string iccid, bool enable=true, bool refresh=true
// out: bool success
rows = handleSwitchProfile(args);
break;
case "setNickname":
// in: int slotId, int portId, string iccid, string nickname
// out: bool success
rows = handleSetNickname(args);
break;
default:
rows = error("unknown_path");
break;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -172,9 +157,9 @@ public class LpaBridgeProvider extends ContentProvider
} }
rows = projectColumns(rows, projection, new String[] { "error" }); rows = projectColumns(rows, projection, new String[] { "error" });
// return rows;
String rowsJson = rowsToJson(rows); String rowsJson = rowsToJson(rows);
return row("rows", rowsJson); return row("rows", rowsJson);
} }
@@ -201,6 +186,52 @@ public class LpaBridgeProvider extends ContentProvider
return row("ping", "pong"); return row("ping", "pong");
} }
private MatrixCursor handleGetPreferences(Map<String, String> args) throws Exception
{
var preferences = List.of
(
"verboseLogging",
"safeguardActiveProfile",
"filterProfileList",
"ignoreTlsCertificate",
"notificationsDownload",
"notificationsDelete",
"notificationsSwitch"
);
if (!(appContainer instanceof UnprivilegedAppContainer))
preferences.add(1, "forceUseTelephonyManager");
var columns = new String[] { "name", "enabled" };
var values = new Object[preferences.size()][2];
for (int i = 0; i < preferences.size(); i++)
{
String name = preferences.get(i);
values[i][0] = name;
values[i][1] = getPreference(name);
}
return rows(columns, values);
}
private MatrixCursor handleSetPreference(Map<String, String> args) throws Exception
{
String[] name = new String[1];
boolean[] enabled = new boolean[1];
if (!tryGetArgAsString(args, "name", name))
return missingArgError("name");
if (!tryGetArgAsBoolean(args, "enabled", enabled))
return missingArgError("enabled");
setPreference(name[0], enabled[0]);
return success();
}
private MatrixCursor handleGetCards(Map<String, String> args) throws Exception private MatrixCursor handleGetCards(Map<String, String> args) throws Exception
{ {
var euiccChannelManager = (DefaultEuiccChannelManager) appContainer.getEuiccChannelManager(); var euiccChannelManager = (DefaultEuiccChannelManager) appContainer.getEuiccChannelManager();
@@ -392,6 +423,8 @@ public class LpaBridgeProvider extends ContentProvider
if (!tryGetArgAsString(args, "iccid", iccid)) if (!tryGetArgAsString(args, "iccid", iccid))
return missingArgError("iccid"); return missingArgError("iccid");
safeguardActiveProfile(args, iccid[0]);
boolean success = withEuiccChannel boolean success = withEuiccChannel
( (
args, args,
@@ -432,6 +465,8 @@ public class LpaBridgeProvider extends ContentProvider
if (!tryGetArgAsBoolean(args, "refresh", refresh)) if (!tryGetArgAsBoolean(args, "refresh", refresh))
refresh[0] = true; refresh[0] = true;
safeguardActiveProfile(args, iccid[0]);
boolean success = withEuiccChannel boolean success = withEuiccChannel
( (
args, args,
@@ -448,6 +483,8 @@ public class LpaBridgeProvider extends ContentProvider
if (!tryGetArgAsBoolean(args, "refresh", refresh)) if (!tryGetArgAsBoolean(args, "refresh", refresh))
refresh[0] = true; refresh[0] = true;
safeguardActiveProfile(args, null);
String iccid = withEuiccChannel String iccid = withEuiccChannel
( (
args, args,
@@ -524,7 +561,6 @@ public class LpaBridgeProvider extends ContentProvider
// region LPA Helpers // region LPA Helpers
@SuppressWarnings("unchecked")
private EuiccChannel findEuiccChannel(DefaultEuiccChannelManager euiccChannelManager, int slotId, int portId) throws Exception private EuiccChannel findEuiccChannel(DefaultEuiccChannelManager euiccChannelManager, int slotId, int portId) throws Exception
{ {
var findEuiccChannelByPortMethod = DefaultEuiccChannelManager.class.getDeclaredMethod("findEuiccChannelByPort", int.class, int.class, Continuation.class); var findEuiccChannelByPortMethod = DefaultEuiccChannelManager.class.getDeclaredMethod("findEuiccChannelByPort", int.class, int.class, Continuation.class);
@@ -565,11 +601,110 @@ public class LpaBridgeProvider extends ContentProvider
private List<LocalProfileInfo> getProfiles(Map<String, String> args) throws Exception private List<LocalProfileInfo> getProfiles(Map<String, String> args) throws Exception
{ {
return withEuiccChannel @SuppressWarnings("unchecked")
var profiles = (List<LocalProfileInfo>) withEuiccChannel
( (
args, args,
(channel, _) -> channel.getLpa().getProfiles() (channel, _) -> channel.getLpa().getProfiles()
); );
boolean filterProfileList = getPreference("filterProfileList");
if (filterProfileList)
return LPAUtilsKt.getOperational(profiles);
return profiles;
}
// endregion
// region Preference Helpers
private List<String> invertedPreferences = List.of
(
"safeguardActiveProfile",
"filterProfileList"
);
private PreferenceFlowWrapper<Boolean> getPreferenceFlow(String name) throws Exception
{
var preferenceRepository = PreferenceUtilsKt.getPreferenceRepository(getContext());
return switch (name)
{
case "verboseLogging" -> preferenceRepository.getVerboseLoggingFlow();
case "forceUseTelephonyManager" -> preferenceRepository.getForceUseTMAPIFlow();
case "safeguardActiveProfile" -> preferenceRepository.getDisableSafeguardFlow();
case "filterProfileList" -> preferenceRepository.getUnfilteredProfileListFlow();
case "ignoreTlsCertificate" -> preferenceRepository.getIgnoreTLSCertificateFlow();
case "notificationsDownload" -> preferenceRepository.getNotificationDownloadFlow();
case "notificationsDelete" -> preferenceRepository.getNotificationDeleteFlow();
case "notificationsSwitch" -> preferenceRepository.getNotificationSwitchFlow();
default -> throw new Exception("unknown_name");
};
}
private boolean getPreference(String name) throws Exception
{
var preferenceFlow = getPreferenceFlow(name);
boolean enabled = BuildersKt.runBlocking
(
EmptyCoroutineContext.INSTANCE,
(_, continuation) -> FlowKt.first(preferenceFlow, continuation)
);
if (invertedPreferences.contains(name))
enabled = !enabled;
return enabled;
}
private void setPreference(String name, boolean enabled) throws Exception
{
var preferenceFlow = getPreferenceFlow(name);
if (invertedPreferences.contains(name))
enabled = !enabled;
final boolean enabledFinal = enabled;
BuildersKt.runBlocking
(
EmptyCoroutineContext.INSTANCE,
(_, continuation) -> preferenceFlow.updatePreference(enabledFinal, continuation)
);
}
private void safeguardActiveProfile(Map<String, String> args, String iccid) throws Exception
{
int[] slotId = new int[1];
int[] portId = new int[1];
requireSlotAndPort(args, slotId, portId);
if (slotId[0] == EuiccChannelManager.USB_CHANNEL_ID)
return;
boolean safeguardEnabled = getPreference("safeguardActiveProfile");
if (!safeguardEnabled)
return;
boolean isTargetActive = iccid == null;
if (!isTargetActive)
{
var profiles = getProfiles(args);
var activeProfile = LPAUtilsKt.getEnabled(profiles);
if (activeProfile == null)
return;
isTargetActive = iccid.equals(activeProfile.getIccid());
}
if (isTargetActive)
throw new Exception("safeguard_active_profile");
} }
// endregion // endregion