Skip to content

Instantly share code, notes, and snippets.

@DerGoogler
Last active July 12, 2025 16:39
Show Gist options
  • Save DerGoogler/a67a57f4cbd7cc3a8fa2dea99c9d6736 to your computer and use it in GitHub Desktop.
Save DerGoogler/a67a57f4cbd7cc3a8fa2dea99c9d6736 to your computer and use it in GitHub Desktop.

Revisions

  1. DerGoogler revised this gist Jul 12, 2025. No changes.
  2. DerGoogler created this gist Jul 12, 2025.
    267 changes: 267 additions & 0 deletions test.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,267 @@
    class JavaObject {
    static {
    this.reflect = global.require("wx:reflect");
    this.proxyHandlers = new Map();
    this.nextProxyId = 0;
    }

    constructor(className, args = []) {
    if (className) {
    this.classId = JavaObject.getClass(className);
    this.objId = JavaObject.newInstance(this.classId, args);
    }
    }

    call(method, args = []) {
    return JavaObject.callMethod(this.objId, method, args);
    }

    get(field) {
    return JavaObject.getField(this.objId, field);
    }

    set(field, value) {
    JavaObject.setField(this.objId, field, value);
    }

    release() {
    JavaObject.release(this.objId);
    }

    static getClass(name) {
    return this.reflect.getClass(name);
    }

    static newInstance(classId, args) {
    const jsonArgs = (args == null) ? "null" : JSON.stringify(args);
    return this.reflect.newInstance(classId, jsonArgs);
    }

    static callMethod(objId, method, args) {
    const jsonArgs = (args == null) ? "[]" : JSON.stringify(args);
    return this.reflect.callMethod(objId, method, jsonArgs);
    }

    static getField(objId, field) {
    return this.reflect.getField(objId, field);
    }

    static setField(objId, field, value) {
    this.reflect.setField(objId, field, value);
    }

    static release(objId) {
    this.reflect.releaseObject(objId);
    }

    static fromObjId(objId) {
    const obj = new JavaObject(null);
    obj.objId = objId;
    return obj;
    }

    static createProxy(interfaceName, handler) {
    const methodsMap = {};
    const handlerIds = [];

    const allMethods = {
    hashCode: () => 0,
    equals: () => false,
    toString: () => '[JavaScript Proxy]',
    ...handler
    };

    for (const methodName in allMethods) {
    if (typeof allMethods[methodName] === 'function') {
    const methodId = `proxy_handler_${this.nextProxyId++}`;
    methodsMap[methodName] = methodId;
    handlerIds.push(methodId);

    this.proxyHandlers.set(methodId, (...args) => {
    try {
    return allMethods[methodName](...args);
    } catch (e) {
    console.error(`Error in proxy method ${methodName}:`, e);
    if (methodName === 'equals') return false;
    return null;
    }
    });
    }
    }

    const proxyId = this.reflect.createProxy(interfaceName, JSON.stringify(methodsMap));
    const proxy = JavaObject.fromObjId(proxyId);

    proxy.releaseProxy = () => {
    handlerIds.forEach(id => this.proxyHandlers.delete(id));
    proxy.release();
    };

    return proxy;
    }
    }

    class SharedPreferences {
    #prefsId;
    #editorId = null;
    #listeners = new Map();
    #listenerProxy = null;

    constructor(context, name, mode = 0) {
    const contextObj = (context instanceof JavaObject) ? context : JavaObject.fromObjId(context);
    this.#prefsId = contextObj.call("getSharedPreferences", [name, mode]);
    }

    #getEditor() {
    if (!this.#editorId) {
    this.#editorId = JavaObject.callMethod(this.#prefsId, "edit", []);
    }
    return this.#editorId;
    }

    get(key, defaultValue = null) {
    const type = typeof defaultValue;
    switch (type) {
    case 'boolean':
    return JavaObject.callMethod(this.#prefsId, "getBoolean", [key, defaultValue]) == "true";
    case 'number':
    if (Number.isInteger(defaultValue)) {
    const int = JavaObject.callMethod(this.#prefsId, "getInt", [key, defaultValue])
    const parsedInt = Number.parseInt(int)

    if (Number.isNaN(parsedInt)) {
    return defaultValue
    }

    return parsedInt
    }

    const float = JavaObject.callMethod(this.#prefsId, "getFloat", [key, defaultValue])
    const parsedFloat = Number.parseFloat(float)

    if (Number.isNaN(parsedFloat)) {
    return defaultValue
    }

    return parsedFloat
    case 'string':
    return JavaObject.callMethod(this.#prefsId, "getString", [key, defaultValue]);
    default:
    return JavaObject.callMethod(this.#prefsId, "getString", [key, String(defaultValue)]);
    }
    }

    put(key, value) {
    const editor = this.#getEditor();
    const type = typeof value;
    switch (type) {
    case 'boolean':
    JavaObject.callMethod(editor, "putBoolean", [key, value]);
    break;
    case 'number':
    Number.isInteger(value) ?
    JavaObject.callMethod(editor, "putInt", [key, value]) :
    JavaObject.callMethod(editor, "putFloat", [key, value]);
    break;
    case 'string':
    JavaObject.callMethod(editor, "putString", [key, value]);
    break;
    default:
    JavaObject.callMethod(editor, "putString", [key, String(value)]);
    }
    return this;
    }

    remove(key) {
    JavaObject.callMethod(this.#getEditor(), "remove", [key]);
    return this;
    }

    clear() {
    JavaObject.callMethod(this.#getEditor(), "clear", []);
    return this;
    }

    commit() {
    if (!this.#editorId) return false;
    const result = JavaObject.callMethod(this.#editorId, "commit", []);
    this.#editorId = null;
    return result;
    }

    apply() {
    if (!this.#editorId) return;
    JavaObject.callMethod(this.#editorId, "apply", []);
    this.#editorId = null;
    }

    contains(key) {
    return JavaObject.callMethod(this.#prefsId, "contains", [key]);
    }

    getAll() {
    return JavaObject.callMethod(this.#prefsId, "getAll", []);
    }

    registerOnChangeListener(callback) {
    if (typeof callback !== 'function') {
    throw new Error('Callback must be a function');
    }

    const listenerId = `listener_${Math.random().toString(36).substring(2, 9)}`;
    this.#listeners.set(listenerId, callback);

    if (!this.#listenerProxy) {
    const interfaceName = "android.content.SharedPreferences$OnSharedPreferenceChangeListener";
    this.#listenerProxy = JavaObject.createProxy(interfaceName, {
    onSharedPreferenceChanged: (prefsId, key) => {
    this.#listeners.forEach(listener => {
    try {
    listener(key, this);
    } catch (e) {
    console.error('Error in preference change listener:', e);
    }
    });
    },
    });

    JavaObject.callMethod(this.#prefsId, "registerOnSharedPreferenceChangeListener", [this.#listenerProxy.objId]);
    }

    return () => this.unregisterOnChangeListener(listenerId);
    }

    unregisterOnChangeListener(listenerId) {
    if (this.#listeners.delete(listenerId) && this.#listeners.size === 0 && this.#listenerProxy) {
    JavaObject.callMethod(this.#prefsId, "unregisterOnSharedPreferenceChangeListener", [this.#listenerProxy.objId]);
    this.#listenerProxy.releaseProxy();
    this.#listenerProxy = null;
    return true;
    }
    return false;
    }
    }

    window.context = (() => {
    const ActivityThread = new JavaObject("android.app.ActivityThread");
    const thread = ActivityThread.call("currentActivityThread");
    const context = JavaObject.callMethod(thread, "getApplication", []);
    return JavaObject.callMethod(context, "getBaseContext", []);
    })()

    window.prefs = new SharedPreferences(window.context, "my_prefs");

    const unregister = prefs.registerOnChangeListener((key) => {
    console.log(`Preference changed: ${key}`);
    });

    prefs.put("username", "john_doe")
    .put("user_id", 12345)
    .put("premium_user", false)
    .apply();

    const username = prefs.get("username", "");
    const userId = prefs.get("user_id", 0);
    const isPremium = prefs.get("premium_user", false);

    console.log(`User:`, username, `, ID:`, userId, `, Premium:`, isPremium);