字段获取代码(Android) · Android Input Examples

注册目录 · 中文 · English · 字段索引 · Field Lookup · Telegram: @ttjjda

中文

三组 Java 示例展示系统输入、应用状态和通用编码,使用 Android 公开 API 和 Java 标准库。编译使用 API 33 或更高版本,运行基线为 API 28。

以下权限声明放在 AndroidManifest.xml 的 <manifest> 下、<application> 外。电话权限还需按功能在运行时申请;网络状态读取需要 ACCESS_NETWORK_STATE。仅声明权限不等于用户已经授予。代码不会自动申请权限。权限前置失败、特定读取段捕获的异常与其它异常各按下文分支处理,不把所有错误统一改成正常状态。

xml 复制代码
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />

字段索引

按字段定位取值方式。context 是调用应用的 Context;state 是下方 RegistrationState 的实例。所有示例辅助类的完整代码均在本页后半部分。

语言与地区

lg / lc:先由应用语言管理器解析 Locale。其 Locale 缓存可直接复用;语言文本还有独立的条件缓存,非null命中时不重读资源或校验格式,地区没有这份文本缓存。下面的 appLocale() 演示资源读取和空值fallback,language() / localeCountry() 演示缓存未命中后的格式转换;它们不自行恢复强制语言、缓存开关或失效回调。

java 复制代码
java.util.Locale locale = AndroidFields.appLocale(context);
String language = locale.getLanguage();
String country = locale.getCountry();
String lg = language.matches("[a-z]{2,3}") ? language : "zz";
String lc = country.matches("[A-Z]{2}|[0-9]{3}") ? country : "ZZ";
持久标识

fdid:light 中的 phoneid_id 与 phoneid_timestamp 成对读取;ID 为 null 或时间为 -1 才进入生成/保存分支,已有空串也不自动按缺失处理。下方 RegistrationState 是自定义示例存储,readString("fdid") 不是此字段的真实 getter。标识与缓存

性能设备标识

expid:实际读取 light 的 perf_device_id;任何非 null 文本先复用,包括空串或非法 UUID,后续编码解析仍可失败。缺失才延迟读取 Android ID:正常非空白值按完整普通 UTF-8 生成 nameUUID,正常 null/Unicode 全空白才随机生成;读取异常不等于正常 null。state.getOrCreateExamplePerfUuid() 只演示自定义存储的初始化,不绑定实际 resolver 活槽或偏好后端。UUID 字节编码

访问会话

access_session_id:先读会话管理器的内存 wrapper;已有 wrapper 即复用,包括其中 UUID 为 null 的状态。缺 wrapper 才读取 reg_prefs.access_session_id,按空白/UUID 解析分支决定生成;生成后先持久化再发布缓存。普通读取不触发原生同步,显式 reset 才另按 bootstrap 与 record 状态处理;自定义 state 偏好读取不能替代这段生命周期。会话说明

Android ID 读取

AndroidFields.androidId(context) 通过 Android 的 Settings.Secure.ANDROID_ID 读取调用应用可见的标识。

设备代号

device_name:android.os.Build.DEVICE。对应 AndroidFields.deviceName(),不是 Build.MODEL。

设备总内存

device_ram:getMemoryInfo() 填充 totalMem 后除以 1024³,用实际格式化 Locale 的 DecimalFormat("#.##") 输出。服务缺失或该读取的 NullPointerException 分支先取 -1 字节,再执行相同除法/格式化,不直接发送 "-1";其它异常不统一吞掉。totalRamGiB() 演示一次读取/变换,实际字段所属实例可缓存非 null 文本。

进程编号

pid:android.os.Process.myPid();AndroidFields.pid() 返回十进制文本。

ADB 开关

db:Settings.Global.getInt(context.getContentResolver(), Settings.Global.ADB_ENABLED, 0);对应 AndroidFields.adbEnabled(context)。

电话权限

read_phone_permission_granted:用 Context.checkSelfPermission() 检查所需电话权限;AndroidFields.phonePermissionsGranted(context) 返回 boolean,写入字段时用 granted ? "1" : "0";它不会申请权限。

本机号码

simnum:权限前置不满足时,号码读取返回 null,长度分类为0;允许时遍历活动订阅的 SubscriptionInfo.getNumber(),保留首个非 null 值(包括空串),之后才进入默认电话服务的 getLine1Number() 兜底。不能因系统升级而改用 getPhoneNumber() 并声称是同一选择规则。订阅枚举异常传播;只有电话服务兜底段的 Exception 返回 null。simnum(number) 只判断已捕获字符串的 Java length≥6,不重读系统。

SIM 状态

sim_state:AndroidFields.simState(context) 读取默认SIM状态,服务不存在时为-1。指定卡槽的 simState(context, slotIndex) 是额外原始API示例;slotIndex是逻辑卡槽编号,不能直接传订阅ID,也不替代请求的默认卡读取。两者均不是 sim_type 分类。

运营商名称

sim_operator_name / network_operator_name:本字段路径使用默认电话服务的 getSimOperatorName() / getNetworkOperatorName();服务或正常返回值为 null 时归为空串,异常仍传播。simOperator() / networkOperator() 不自行创建默认数据订阅范围的服务来替换该来源。

网络分类

network_radio_type:AndroidFields.networkRadioType(snapshot) 只映射已选定的 mobile/wifi/subtype 输入。旧快照路径由 legacyNetworkSnapshot(context) 读取,只有 type0 是 mobile、type1 是 Wi-Fi,其它类型不是以太网自动归1。SDK≥31且网络管理器已初始化的新路径,transport1/3 归Wi-Fi类1;其它分类再按 capability18 区分2/3,其中3没有 mobile 位。新路径的 callback、权限、主线程 subtype 缓存须由同一网络管理器提供,不能用一次同步 activeNetwork 读取替代。null 或既非 mobile 又非 wifi 为 -1;未知 mobile subtype 为0。500ms只合并通知,不是字段有效期。

飞行模式

airplane_mode_type:Settings.Global.AIRPLANE_MODE_ON;对应 AndroidFields.airplaneMode(context),返回归一化的 0/1。

蜂窝信号等级

cellular_strength:同组开关允许时,SDK≥28、电话服务存在且第一次 getSignalStrength() 非 null,才在第二次调用结果上取 getLevel()。缺支持/服务/首次对象时为5;第二次返回 null、权限或设备能力异常不统一转5。signalLevel() 保留两次读取;两次读取不构成原子快照,不能用保存第一次对象来替换。

漫游状态

roaming_type 的原始输入:AndroidFields.roaming(context) 调用 isNetworkRoaming()。有网络快照时,false/true 分别编码为 "0"/"1",电话服务对象不存在返回 null 时编码为 "2";无快照则省略字段,读取异常另行处理。

恢复文件存在性

hasinrc:new java.io.File(context.getFilesDir(), "rc2").exists();对应 state.hasRecoveryRecord()。这里只检查存在性,不读取或解密 id。

关联应用信息

is_foa_fdid_app_installed 表示可见应用中是否存在符合关联条件的应用。

UUID 字节编码

expid / access_session_id 的表示转换:FieldEncoding.uuidBytes(uuid) 把已有 UUID 转成大端 16 字节并做 Base64url。它不获取或创建 UUID。

Signal 编号编码

e_regid 编码已有编号的4字节大端表示;e_skey_id 编码同一选定预密钥 protobuf id 的低24位、3字节大端表示,不额外拒绝高位非零或负的 Java int。SQL行号只参与记录选择,不替代该protobuf id。三个 e_skey_* 从一次记录快照读取,签名长度按记录保留。

Android 标识字段

aid 是 Android 设备标识的派生表示。

广告标识

advertising_id:Google Play services 的 AdvertisingIdClient.getAdvertisingIdInfo(context) 提供ID与追踪限制,需在工作线程调用。字段所选的新provider分支在LAT=true时省略;旧直接读取分支不读取LAT,不能给它强加同一省略条件。国家/流程开关另行决定是否获取,没有失败后自动切换另一分支或此调用链的广告ID持久缓存。

推送令牌

push_token:FCM 的 FirebaseMessaging.getInstance().getToken() 与 onNewToken() 回调;需先完成应用的 Firebase 配置。

AndroidFields

AndroidFields 获取调用应用可访问的系统输入。字段所用的默认电话服务与显式创建订阅范围的服务不同;SIM卡槽示例也是额外原始API入口,不替换请求的默认卡读取。号码提示保留活动订阅顺序和首个非null值。服务对象可由实际owner缓存,但不因此缓存它返回的号码、运营商或信号。

networkRadioType() 只负责协议映射;legacyNetworkSnapshot() 仅用于已选定的旧快照分支,新分支须读取实际callback状态。totalRamGiB() 是单次计算,不建立字段owner的文本缓存。

java 复制代码
// File: AndroidFields.java
package wiki.examples;

import android.Manifest;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.os.Build;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Locale;

public final class AndroidFields {
    private AndroidFields() {}

    public static Locale appLocale(Context context) {
        android.os.LocaleList locales =
                context.getResources().getConfiguration().getLocales();
        Locale value = locales.isEmpty() ? Locale.getDefault() : locales.get(0);
        if (value != null) return value;
        Locale fallback = Locale.getDefault();
        return fallback != null ? fallback : Locale.US;
    }

    public static String language(Context context) {
        String value = appLocale(context).getLanguage();
        return value.matches("[a-z]{2,3}") ? value : "zz";
    }

    public static String localeCountry(Context context) {
        String value = appLocale(context).getCountry();
        return value.matches("[A-Z]{2}|[0-9]{3}") ? value : "ZZ";
    }

    public static String deviceName() { return Build.DEVICE; }
    public static String pid() {
        return Integer.toString(android.os.Process.myPid());
    }

    public static String totalRamGiB(Context context) {
        ActivityManager service = context.getSystemService(ActivityManager.class);
        long total = -1L;
        if (service != null) {
            ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
            try {
                service.getMemoryInfo(info);
                total = info.totalMem;
            } catch (NullPointerException unavailable) {
                total = -1L;
            }
        }
        return new DecimalFormat("#.##").format(total / 1073741824.0);
    }

    public static String androidId(Context context) {
        return Settings.Secure.getString(context.getContentResolver(),
                Settings.Secure.ANDROID_ID);
    }

    public static int adbEnabled(Context context) {
        return Settings.Global.getInt(context.getContentResolver(),
                Settings.Global.ADB_ENABLED, 0);
    }

    public static int airplaneMode(Context context) {
        int value = Settings.Global.getInt(context.getContentResolver(),
                Settings.Global.AIRPLANE_MODE_ON, 0);
        return value == 0 ? 0 : 1;
    }

    public static boolean phonePermissionsGranted(Context context) {
        if (context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE)
                != PackageManager.PERMISSION_GRANTED) return false;
        return Build.VERSION.SDK_INT < 30
                || context.checkSelfPermission(Manifest.permission.READ_PHONE_NUMBERS)
                == PackageManager.PERMISSION_GRANTED;
    }

    public static TelephonyManager defaultTelephony(Context context) {
        return context.getSystemService(TelephonyManager.class);
    }

    @SuppressWarnings("deprecation")
    public static String firstDeviceNumber(Context context) {
        if (!phonePermissionsGranted(context)) return null;
        SubscriptionManager subscriptions =
                context.getSystemService(SubscriptionManager.class);
        if (subscriptions == null) throw new NullPointerException("SubscriptionManager");
        List<SubscriptionInfo> active = subscriptions.getActiveSubscriptionInfoList();
        if (active != null) {
            for (SubscriptionInfo item : active) {
                String value = item.getNumber();
                if (value != null) return value;
            }
        }
        try {
            TelephonyManager service = defaultTelephony(context);
            return service == null ? null : service.getLine1Number();
        } catch (Exception unavailableFallback) {
            return null;
        }
    }

    public static String simnum(String number) {
        return number != null && number.length() >= 6 ? "1" : "0";
    }

    public static int simState(Context context) {
        TelephonyManager service = defaultTelephony(context);
        return service == null ? -1 : service.getSimState();
    }

    public static Integer simState(Context context, int slotIndex) {
        if (slotIndex < 0) {
            throw new IllegalArgumentException("Expected a logical SIM slot index");
        }
        TelephonyManager service = context.getSystemService(TelephonyManager.class);
        return service == null ? null : service.getSimState(slotIndex);
    }

    public static String simOperator(Context context) {
        TelephonyManager service = defaultTelephony(context);
        String value = service == null ? null : service.getSimOperatorName();
        return value == null ? "" : value;
    }

    public static String networkOperator(Context context) {
        TelephonyManager service = defaultTelephony(context);
        String value = service == null ? null : service.getNetworkOperatorName();
        return value == null ? "" : value;
    }

    public static int signalLevel(Context context) {
        TelephonyManager service = defaultTelephony(context);
        if (Build.VERSION.SDK_INT < 28 || service == null
                || service.getSignalStrength() == null) return 5;
        // Preserve the second read: it can change or fail independently.
        return service.getSignalStrength().getLevel();
    }

    public static Boolean roaming(Context context) {
        TelephonyManager service = defaultTelephony(context);
        return service == null ? null : service.isNetworkRoaming();
    }

    public static final class NetworkSnapshot {
        public final boolean mobile;
        public final boolean wifi;
        public final int subtype;
        public NetworkSnapshot(boolean mobile, boolean wifi, int subtype) {
            this.mobile = mobile;
            this.wifi = wifi;
            this.subtype = subtype;
        }
    }

    @SuppressWarnings("deprecation")
    public static NetworkSnapshot legacyNetworkSnapshot(Context context) {
        ConnectivityManager service = context.getSystemService(ConnectivityManager.class);
        android.net.NetworkInfo info = service == null ? null : service.getActiveNetworkInfo();
        return info == null ? null : new NetworkSnapshot(
                info.getType() == 0, info.getType() == 1, info.getSubtype());
    }

    public static int networkRadioType(NetworkSnapshot snapshot) {
        if (snapshot == null) return -1;
        if (!snapshot.mobile) return snapshot.wifi ? 1 : -1;
        return radioCode(snapshot.subtype);
    }

    public static int radioCode(int androidType) {
        switch (androidType) {
            case TelephonyManager.NETWORK_TYPE_GPRS: return 104;
            case TelephonyManager.NETWORK_TYPE_EDGE: return 100;
            case TelephonyManager.NETWORK_TYPE_UMTS: return 102;
            case TelephonyManager.NETWORK_TYPE_CDMA: return 108;
            case TelephonyManager.NETWORK_TYPE_EVDO_0:
            case TelephonyManager.NETWORK_TYPE_EVDO_A:
            case TelephonyManager.NETWORK_TYPE_EVDO_B: return 103;
            case TelephonyManager.NETWORK_TYPE_1xRTT: return 109;
            case TelephonyManager.NETWORK_TYPE_HSDPA: return 105;
            case TelephonyManager.NETWORK_TYPE_HSUPA: return 106;
            case TelephonyManager.NETWORK_TYPE_HSPA: return 107;
            case TelephonyManager.NETWORK_TYPE_IDEN: return 101;
            case TelephonyManager.NETWORK_TYPE_LTE: return 111;
            case TelephonyManager.NETWORK_TYPE_EHRPD: return 110;
            case TelephonyManager.NETWORK_TYPE_HSPAP: return 112;
            case TelephonyManager.NETWORK_TYPE_TD_SCDMA: return 116;
            case TelephonyManager.NETWORK_TYPE_IWLAN: return 115;
            case TelephonyManager.NETWORK_TYPE_NR: return 113;
            default: return 0;
        }
    }
}

Android ID 与应用签名、用户及设备有关,不能把另一应用读取的值当成相同值。ANDROID_ID

本机号码接口不保证提供可用号码;信号与电话网络接口各有权限和设备能力条件,不支持的设备可抛出 UnsupportedOperationException,服务对象非 null 不代表所有电话能力均可用。SubscriptionManager · TelephonyManager · NetworkCapabilities · SIM 默认卡与卡槽

RegistrationState

这个示例展示如何在自己的应用中读写状态。wiki_example_registration_state 是示例自定义的偏好文件名,键名由调用方定义,不是 WhatsApp 的存储文件名或通用字段读取接口。读取 fdid 等字段的前提是应用已按相应规则保存了真实值。

持久标识在缺失时按应用的初始化流程生成。访问会话重置与安装标识初始化分别处理。示例使用系统 SharedPreferences,并发写入由调用方协调。

java 复制代码
// File: RegistrationState.java
package wiki.examples;

import android.content.Context;
import android.content.SharedPreferences;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

public final class RegistrationState {
    private final Context context;
    private final SharedPreferences preferences;

    public RegistrationState(Context context) {
        this.context = context.getApplicationContext();
        preferences = this.context.getSharedPreferences(
                "wiki_example_registration_state", Context.MODE_PRIVATE);
    }

    public String readString(String key) {
        return preferences.getString(key, null);
    }

    public void saveString(String key, String value) {
        preferences.edit().putString(key, value).apply();
    }

    public String getOrCreateExamplePerfUuid() {
        String existing = readString("example_perf_uuid");
        if (existing != null) return existing;
        String androidId = AndroidFields.androidId(context);
        boolean blank = androidId == null || androidId.isEmpty();
        if (!blank) {
            blank = true;
            for (int i = 0; i < androidId.length(); i++) {
                char value = androidId.charAt(i);
                if (!Character.isWhitespace(value) && !Character.isSpaceChar(value)) {
                    blank = false;
                    break;
                }
            }
        }
        UUID created = blank ? UUID.randomUUID()
                : UUID.nameUUIDFromBytes(androidId.getBytes(StandardCharsets.UTF_8));
        if (readString("example_perf_uuid") == null) {
            saveString("example_perf_uuid", created.toString());
        }
        return readString("example_perf_uuid");
    }

    public int readCount(String key) {
        return preferences.getInt(key, 0);
    }

    public long readDurationMillis(String key) {
        return preferences.getLong(key, 0L);
    }

    public boolean hasRecoveryRecord() {
        return new File(context.getFilesDir(), "rc2").exists();
    }
}

hasRecoveryRecord() 只检查调用应用自己的文件。存在性不是解密或有效性检查。应用私有存储

FieldEncoding

本类展示 UUID、公开编号和原始字节的通用编码。UUID 文本和字节表示有所区别,e_regid 与 e_skey_id 的字节长度也不同。表单编码另见请求封装。

java 复制代码
// File: FieldEncoding.java
package wiki.examples;

import android.util.Base64;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

public final class FieldEncoding {
    private FieldEncoding() {}

    public static String base64url(byte[] value) {
        return Base64.encodeToString(value,
                Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
    }

    public static String uuidBytes(UUID value) {
        return base64url(ByteBuffer.allocate(16).order(ByteOrder.BIG_ENDIAN)
                .putLong(value.getMostSignificantBits())
                .putLong(value.getLeastSignificantBits()).array());
    }

    public static String registrationId(int value) {
        return base64url(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN)
                .putInt(value).array());
    }

    public static String signedPrekeyId(int value) {
        byte[] bytes = {(byte) (value >>> 16),
                (byte) (value >>> 8), (byte) value};
        return base64url(bytes);
    }

    public static String percentEncodeBytes(byte[] value) {
        char[] hex = "0123456789ABCDEF".toCharArray();
        StringBuilder out = new StringBuilder();
        for (byte item : value) {
            int b = item & 0xFF;
            boolean unreserved = (b >= 'A' && b <= 'Z')
                    || (b >= 'a' && b <= 'z') || (b >= '0' && b <= '9')
                    || b == '-' || b == '.' || b == '_' || b == '~';
            if (unreserved) out.append((char) b);
            else out.append('%').append(hex[b >>> 4]).append(hex[b & 15]);
        }
        return out.toString();
    }
}

基础接口:Base64 · ByteBuffer

服务接入

字段 接入入口 接入要求
advertising_id AdvertisingIdClient.getAdvertisingIdInfo(context),读取返回对象的 getId() 和 isLimitAdTrackingEnabled() 添加 Google Play services 广告标识依赖,按所选 SDK 声明相应权限;在工作线程调用,处理服务不可用与追踪限制。
push_token FCM 的 FirebaseMessaging.getInstance().getToken(),以及 onNewToken() 回调 先完成 Firebase 应用配置;异步接收并保存真实 token,随刷新更新。
recaptcha reCAPTCHA 服务 提供适用验证路线的校验结果。
authkey / e_ident / e_regid / e_skey_* / e_pq_last_resort_* 应用自己的连接认证与 Signal 会话/密钥模块 Android KeyStore 不是这些协议记录的通用查询器;公开材料必须与已有身份及记录配对。
id / backup_token / foa_backup_token / cred_token 各自的恢复、备份或凭据模块 id读取rc2;exist按号码选择source1并在发送前保存,code直接解码reg_prefs.token_used_during_reg、非空复用/零长才生成;foa与cred各按自己的条件来源处理。
client_metrics / offline_ab / feo2_query_status / language_selector_* / mistyped 应用的业务事件与持久状态 由实际事件累计、写入或消费;不在序列化时编造交互记录。

Google/Firebase 返回值属于调用应用、服务项目和对应操作,示例应用取到的 token 不能直接当成另一个客户端的凭据。

AdvertisingIdClient · Firebase Cloud Messaging

字段组装与专题边界

原始API值、派生字段、缓存槽与最终请求不是同一对象。RegistrationState 不接管应用的真实生命周期;电话环境四字段还受同组开关控制,db 有独立开关。空串、null、缺键、0与false分别处理,不为缺失的服务结果填造字段。

专题 与本页代码的衔接
请求加密 普通文本和预编码字节分别进入表单序列化。
发送流程 最终表单、HTTP 请求及业务响应的处理。

exist 与 code 的备份读取、尝试计数和纠错字段按各自文章处理:code只读已保存的mistyped文本,reason在异步准备前捕获且空串保留;普通页面hasav为Retriever→2、否则RECEIVE_SMS获准→1、其它正常结果→0,特定fallback也可直接传0。FEO2诊断读取不主动query或清能力缓存,不能因为读取统计/诊断而重新执行其producer。

编码后的值由所选通道完成表单序列化。

exist 参数 · code 参数

English

Three Java examples cover system inputs, application state, and general encoding using public Android APIs and Java standard libraries. Compile against API 33 or newer. The runtime baseline is API 28.

Place these declarations directly under <manifest>, outside <application>, in AndroidManifest.xml. Request the phone permissions required by your feature at runtime; ACCESS_NETWORK_STATE is needed for network reads. The code does not request permissions. An unmet permission gate, an exception caught by a specific read segment, and other failures follow the separate branches below rather than one universal normal fallback.

xml 复制代码
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />

Field Input Lookup

Locate the acquisition step by field. context belongs to the calling app; state is an instance of the RegistrationState class provided below. Full helper implementations follow the lookup sections.

Language and Region

lg / lc: use the application language manager's resolved Locale. Its Locale cache can be reused; a separate gated language-text cache skips resources and validation on a non-null hit, while country has no such text cache. appLocale() below illustrates resource acquisition/null fallback, and language() / localeCountry() illustrate formatting on a cache miss. They do not restore forced-language state, cache gates, or invalidation callbacks.

java 复制代码
java.util.Locale locale = AndroidFields.appLocale(context);
String language = locale.getLanguage();
String country = locale.getCountry();
String lg = language.matches("[a-z]{2,3}") ? language : "zz";
String lc = country.matches("[A-Z]{2}|[0-9]{3}") ? country : "ZZ";
Application Persistent Identifiers

fdid: read phoneid_id and phoneid_timestamp together from light. A null ID or timestamp -1 enters generation/persistence; an existing empty string is not automatically missing. RegistrationState below owns an example store; readString("fdid") is not this field's actual getter. See identifiers and caches.

Performance Device Identifier

expid: read light perf_device_id; reuse any non-null text first, including empty/invalid UUID text that can fail during later encoding. Only a miss lazily reads Android ID: use its complete ordinary UTF-8 bytes for a nameUUID if nonblank, or generate randomly for a normal null/Unicode-blank result. A read exception is not a normal null. state.getOrCreateExamplePerfUuid() illustrates initialization in a custom store, without binding the actual resolver slot/backend. See UUID encoding.

Current Access Session

access_session_id: read the session manager's memory wrapper first, reusing an existing wrapper even when its UUID is null. Only a missing wrapper reads reg_prefs.access_session_id and applies blank/UUID parsing before generation; generation persists before publishing the cache. Ordinary reads do not synchronize native state. Explicit reset separately follows bootstrap/record state. Reading the custom state store does not replace this lifecycle. See session behavior.

Android ID Input

AndroidFields.androidId(context) reads the identifier visible to the calling app through Android's Settings.Secure.ANDROID_ID.

Device Codename

device_name: AndroidFields.deviceName() reads android.os.Build.DEVICE, the build codename rather than Build.MODEL.

Total Device Memory

device_ram: divide getMemoryInfo().totalMem by 1024³ and use DecimalFormat("#.##") with the actual formatting Locale. A missing service or the handled NullPointerException supplies -1 bytes before the same division/formatting, rather than directly sending "-1"; other errors are not broadly swallowed. totalRamGiB() demonstrates one read/transform, while the field owner can cache non-null text.

Application Process ID

pid: AndroidFields.pid() returns android.os.Process.myPid() as decimal text.

ADB Setting Input

db: AndroidFields.adbEnabled(context) uses Settings.Global.getInt(context.getContentResolver(), Settings.Global.ADB_ENABLED, 0).

Phone Permission Checks

read_phone_permission_granted: AndroidFields.phonePermissionsGranted(context) checks the required permissions using Context.checkSelfPermission(). Serialize its boolean result as granted ? "1" : "0". It does not request permissions.

Device Number Input

simnum: an unmet permission gate returns null from the number reader and thus classification 0. Otherwise iterate active subscriptions using SubscriptionInfo.getNumber(), retaining the first non-null value, including empty text, before falling back to default telephony getLine1Number(). Switching to getPhoneNumber() on newer systems is a different selection rule. Subscription-enumeration exceptions propagate; only the telephony fallback catches Exception and yields null. simnum(number) checks Java length≥6 on the captured string without rereading the system.

SIM State Input

sim_state: AndroidFields.simState(context) reads default-SIM state and gives -1 for an absent service. The slot-specific overload is an additional raw API: use a logical slot index rather than a subscription ID, without replacing default-SIM request acquisition. Neither result is sim_type classification.

Operator Name Inputs

sim_operator_name / network_operator_name: this field path calls default telephony getSimOperatorName() / getNetworkOperatorName(). A null service/normal API result becomes empty text; exceptions propagate. simOperator() / networkOperator() do not substitute a service scoped to the default data subscription.

Network Classification

network_radio_type: AndroidFields.networkRadioType(snapshot) maps selected mobile/wifi/subtype inputs. legacyNetworkSnapshot(context) reads the legacy path: only type 0 is mobile and type 1 Wi-Fi; other types do not automatically map Ethernet to 1. With SDK≥31 and an initialized network manager, the newer path groups transport 1/3 as Wi-Fi class 1; other cases use capability 18 for classes 2/3, and class 3 has no mobile bit. That path requires the same manager's callback, permissions, and main-thread subtype cache, rather than a replacement synchronous activeNetwork read. Null or neither mobile nor wifi maps to -1; unknown mobile subtype maps to 0. The 500 ms timer coalesces notifications rather than defining field validity.

Airplane Mode Input

airplane_mode_type: AndroidFields.airplaneMode(context) reads Settings.Global.AIRPLANE_MODE_ON and normalizes it to 0/1.

Cellular Signal Input

cellular_strength: when the group gate allows collection, SDK≥28, a telephony service, and a non-null first getSignalStrength() are required before taking getLevel() from a second call. Missing support/service/first object gives 5. A null second result or permission/device-capability exception is not uniformly converted to 5. signalLevel() retains two non-atomic reads rather than saving and reusing the first object.

Roaming State Input

roaming_type: AndroidFields.roaming(context) calls isNetworkRoaming(). With a network snapshot, encode false/true as "0"/"1", or "2" when the absent telephony service yields null. Omit the field without a snapshot; handle read exceptions separately.

Recovery Record Check

hasinrc: state.hasRecoveryRecord() checks new java.io.File(context.getFilesDir(), "rc2").exists(). It checks existence without reading or decrypting id.

is_foa_fdid_app_installed indicates whether a visible app meets the related-app criteria.

UUID Byte Representation

expid / access_session_id: FieldEncoding.uuidBytes(uuid) encodes an existing UUID as 16 big-endian bytes using unpadded Base64url. It does not acquire or create the UUID.

Signal Record ID Representation

e_regid encodes the existing ID as 4 big-endian bytes. e_skey_id encodes the selected prekey's protobuf ID low 24 bits as 3 big-endian bytes, without extra rejection of high bits or negative Java int values. The SQL row ID selects a record rather than replacing its protobuf ID. Read all three e_skey_* from one snapshot, preserving the stored.

Android Identifier Field

aid is a derived Android device identifier.

Advertising Identifier Input

advertising_id: Google Play services AdvertisingIdClient.getAdvertisingIdInfo(context) supplies ID/tracking information off the main thread. The selected newer provider branch omits the field for LAT=true; the older direct-read branch does not read LAT and must not inherit that condition. Country/flow gates separately control acquisition. This call chain has neither automatic failure-to-other-branch fallback nor persistent advertising-ID caching.

Push Provider Token

push_token: configure Firebase, obtain the token with FirebaseMessaging.getInstance().getToken(), and handle refreshes through onNewToken().

AndroidFields

AndroidFields reads inputs visible to the calling app. The default telephony service used by these fields differs from an explicitly subscription-scoped service. The slot-specific example is an additional raw API, not a replacement for default-SIM request acquisition. Number hints preserve subscription order and the first non-null value. An owner can cache a service object without caching its returned number, operator, or signal.

networkRadioType() only maps the protocol enum; use legacyNetworkSnapshot() only for the selected legacy path and the actual callback state for the newer path. totalRamGiB() performs one calculation without establishing the field owner's text cache.

java 复制代码
// File: AndroidFields.java
package wiki.examples;

import android.Manifest;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.os.Build;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Locale;

public final class AndroidFields {
    private AndroidFields() {}

    public static Locale appLocale(Context context) {
        android.os.LocaleList locales =
                context.getResources().getConfiguration().getLocales();
        Locale value = locales.isEmpty() ? Locale.getDefault() : locales.get(0);
        if (value != null) return value;
        Locale fallback = Locale.getDefault();
        return fallback != null ? fallback : Locale.US;
    }

    public static String language(Context context) {
        String value = appLocale(context).getLanguage();
        return value.matches("[a-z]{2,3}") ? value : "zz";
    }

    public static String localeCountry(Context context) {
        String value = appLocale(context).getCountry();
        return value.matches("[A-Z]{2}|[0-9]{3}") ? value : "ZZ";
    }

    public static String deviceName() { return Build.DEVICE; }
    public static String pid() {
        return Integer.toString(android.os.Process.myPid());
    }

    public static String totalRamGiB(Context context) {
        ActivityManager service = context.getSystemService(ActivityManager.class);
        long total = -1L;
        if (service != null) {
            ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
            try {
                service.getMemoryInfo(info);
                total = info.totalMem;
            } catch (NullPointerException unavailable) {
                total = -1L;
            }
        }
        return new DecimalFormat("#.##").format(total / 1073741824.0);
    }

    public static String androidId(Context context) {
        return Settings.Secure.getString(context.getContentResolver(),
                Settings.Secure.ANDROID_ID);
    }

    public static int adbEnabled(Context context) {
        return Settings.Global.getInt(context.getContentResolver(),
                Settings.Global.ADB_ENABLED, 0);
    }

    public static int airplaneMode(Context context) {
        int value = Settings.Global.getInt(context.getContentResolver(),
                Settings.Global.AIRPLANE_MODE_ON, 0);
        return value == 0 ? 0 : 1;
    }

    public static boolean phonePermissionsGranted(Context context) {
        if (context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE)
                != PackageManager.PERMISSION_GRANTED) return false;
        return Build.VERSION.SDK_INT < 30
                || context.checkSelfPermission(Manifest.permission.READ_PHONE_NUMBERS)
                == PackageManager.PERMISSION_GRANTED;
    }

    public static TelephonyManager defaultTelephony(Context context) {
        return context.getSystemService(TelephonyManager.class);
    }

    @SuppressWarnings("deprecation")
    public static String firstDeviceNumber(Context context) {
        if (!phonePermissionsGranted(context)) return null;
        SubscriptionManager subscriptions =
                context.getSystemService(SubscriptionManager.class);
        if (subscriptions == null) throw new NullPointerException("SubscriptionManager");
        List<SubscriptionInfo> active = subscriptions.getActiveSubscriptionInfoList();
        if (active != null) {
            for (SubscriptionInfo item : active) {
                String value = item.getNumber();
                if (value != null) return value;
            }
        }
        try {
            TelephonyManager service = defaultTelephony(context);
            return service == null ? null : service.getLine1Number();
        } catch (Exception unavailableFallback) {
            return null;
        }
    }

    public static String simnum(String number) {
        return number != null && number.length() >= 6 ? "1" : "0";
    }

    public static int simState(Context context) {
        TelephonyManager service = defaultTelephony(context);
        return service == null ? -1 : service.getSimState();
    }

    public static Integer simState(Context context, int slotIndex) {
        if (slotIndex < 0) {
            throw new IllegalArgumentException("Expected a logical SIM slot index");
        }
        TelephonyManager service = context.getSystemService(TelephonyManager.class);
        return service == null ? null : service.getSimState(slotIndex);
    }

    public static String simOperator(Context context) {
        TelephonyManager service = defaultTelephony(context);
        String value = service == null ? null : service.getSimOperatorName();
        return value == null ? "" : value;
    }

    public static String networkOperator(Context context) {
        TelephonyManager service = defaultTelephony(context);
        String value = service == null ? null : service.getNetworkOperatorName();
        return value == null ? "" : value;
    }

    public static int signalLevel(Context context) {
        TelephonyManager service = defaultTelephony(context);
        if (Build.VERSION.SDK_INT < 28 || service == null
                || service.getSignalStrength() == null) return 5;
        // Preserve the second read: it can change or fail independently.
        return service.getSignalStrength().getLevel();
    }

    public static Boolean roaming(Context context) {
        TelephonyManager service = defaultTelephony(context);
        return service == null ? null : service.isNetworkRoaming();
    }

    public static final class NetworkSnapshot {
        public final boolean mobile;
        public final boolean wifi;
        public final int subtype;
        public NetworkSnapshot(boolean mobile, boolean wifi, int subtype) {
            this.mobile = mobile;
            this.wifi = wifi;
            this.subtype = subtype;
        }
    }

    @SuppressWarnings("deprecation")
    public static NetworkSnapshot legacyNetworkSnapshot(Context context) {
        ConnectivityManager service = context.getSystemService(ConnectivityManager.class);
        android.net.NetworkInfo info = service == null ? null : service.getActiveNetworkInfo();
        return info == null ? null : new NetworkSnapshot(
                info.getType() == 0, info.getType() == 1, info.getSubtype());
    }

    public static int networkRadioType(NetworkSnapshot snapshot) {
        if (snapshot == null) return -1;
        if (!snapshot.mobile) return snapshot.wifi ? 1 : -1;
        return radioCode(snapshot.subtype);
    }

    public static int radioCode(int androidType) {
        switch (androidType) {
            case TelephonyManager.NETWORK_TYPE_GPRS: return 104;
            case TelephonyManager.NETWORK_TYPE_EDGE: return 100;
            case TelephonyManager.NETWORK_TYPE_UMTS: return 102;
            case TelephonyManager.NETWORK_TYPE_CDMA: return 108;
            case TelephonyManager.NETWORK_TYPE_EVDO_0:
            case TelephonyManager.NETWORK_TYPE_EVDO_A:
            case TelephonyManager.NETWORK_TYPE_EVDO_B: return 103;
            case TelephonyManager.NETWORK_TYPE_1xRTT: return 109;
            case TelephonyManager.NETWORK_TYPE_HSDPA: return 105;
            case TelephonyManager.NETWORK_TYPE_HSUPA: return 106;
            case TelephonyManager.NETWORK_TYPE_HSPA: return 107;
            case TelephonyManager.NETWORK_TYPE_IDEN: return 101;
            case TelephonyManager.NETWORK_TYPE_LTE: return 111;
            case TelephonyManager.NETWORK_TYPE_EHRPD: return 110;
            case TelephonyManager.NETWORK_TYPE_HSPAP: return 112;
            case TelephonyManager.NETWORK_TYPE_TD_SCDMA: return 116;
            case TelephonyManager.NETWORK_TYPE_IWLAN: return 115;
            case TelephonyManager.NETWORK_TYPE_NR: return 113;
            default: return 0;
        }
    }
}

Android ID is scoped to signing key, user, and device. ANDROID_ID

Phone-number availability is not guaranteed. Telephony/signal calls require specific permissions and device features; unsupported devices can throw UnsupportedOperationException even when the service object exists. SubscriptionManager · TelephonyManager

RegistrationState

This demonstrates state storage in your own app. The preference filename is defined by the example and keys are caller-defined. It is not WhatsApp storage or a universal field getter. Reading a stored field presupposes that its actual producer saved the correct value.

Persistent identifiers are initialized when absent. Access-session resets and installation identifier initialization are separate operations. The example uses Android's SharedPreferences, with callers responsible for coordinating concurrent writes.

java 复制代码
// File: RegistrationState.java
package wiki.examples;

import android.content.Context;
import android.content.SharedPreferences;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

public final class RegistrationState {
    private final Context context;
    private final SharedPreferences preferences;

    public RegistrationState(Context context) {
        this.context = context.getApplicationContext();
        preferences = this.context.getSharedPreferences(
                "wiki_example_registration_state", Context.MODE_PRIVATE);
    }

    public String readString(String key) {
        return preferences.getString(key, null);
    }

    public void saveString(String key, String value) {
        preferences.edit().putString(key, value).apply();
    }

    public String getOrCreateExamplePerfUuid() {
        String existing = readString("example_perf_uuid");
        if (existing != null) return existing;
        String androidId = AndroidFields.androidId(context);
        boolean blank = androidId == null || androidId.isEmpty();
        if (!blank) {
            blank = true;
            for (int i = 0; i < androidId.length(); i++) {
                char value = androidId.charAt(i);
                if (!Character.isWhitespace(value) && !Character.isSpaceChar(value)) {
                    blank = false;
                    break;
                }
            }
        }
        UUID created = blank ? UUID.randomUUID()
                : UUID.nameUUIDFromBytes(androidId.getBytes(StandardCharsets.UTF_8));
        if (readString("example_perf_uuid") == null) {
            saveString("example_perf_uuid", created.toString());
        }
        return readString("example_perf_uuid");
    }

    public int readCount(String key) {
        return preferences.getInt(key, 0);
    }

    public long readDurationMillis(String key) {
        return preferences.getLong(key, 0L);
    }

    public boolean hasRecoveryRecord() {
        return new File(context.getFilesDir(), "rc2").exists();
    }
}

hasRecoveryRecord() only checks the calling app's own file. Existence is not decryption or validity verification. App-specific storage

FieldEncoding

These helpers demonstrate general encoding for UUIDs, public identifiers, and raw bytes. UUID text and byte representations differ, as do the byte lengths of e_regid and e_skey_id. See request packaging for form encoding.

java 复制代码
// File: FieldEncoding.java
package wiki.examples;

import android.util.Base64;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

public final class FieldEncoding {
    private FieldEncoding() {}

    public static String base64url(byte[] value) {
        return Base64.encodeToString(value,
                Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
    }

    public static String uuidBytes(UUID value) {
        return base64url(ByteBuffer.allocate(16).order(ByteOrder.BIG_ENDIAN)
                .putLong(value.getMostSignificantBits())
                .putLong(value.getLeastSignificantBits()).array());
    }

    public static String registrationId(int value) {
        return base64url(ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN)
                .putInt(value).array());
    }

    public static String signedPrekeyId(int value) {
        byte[] bytes = {(byte) (value >>> 16),
                (byte) (value >>> 8), (byte) value};
        return base64url(bytes);
    }

    public static String percentEncodeBytes(byte[] value) {
        char[] hex = "0123456789ABCDEF".toCharArray();
        StringBuilder out = new StringBuilder();
        for (byte item : value) {
            int b = item & 0xFF;
            boolean unreserved = (b >= 'A' && b <= 'Z')
                    || (b >= 'a' && b <= 'z') || (b >= '0' && b <= '9')
                    || b == '-' || b == '.' || b == '_' || b == '~';
            if (unreserved) out.append((char) b);
            else out.append('%').append(hex[b >>> 4]).append(hex[b & 15]);
        }
        return out.toString();
    }
}

API reference: Base64 · ByteBuffer

External Service Integration

Field Integration Entry Requirement
advertising_id Call AdvertisingIdClient.getAdvertisingIdInfo(context), then read getId() and isLimitAdTrackingEnabled(). Add the advertising-ID dependency and required permissions; call off the main thread and handle service availability/tracking preference.
push_token Use FirebaseMessaging.getInstance().getToken() and the onNewToken() callback. Configure Firebase, then asynchronously store/update the actual token.
recaptcha reCAPTCHA service Supplies the verification outcome for applicable routes.
authkey / e_ident / e_regid / e_skey_* / e_pq_last_resort_* Use the application's connection-authentication and Signal session/key modules. Android KeyStore is not a universal getter for these protocol records; public material must match the existing identity/record.
id / backup_token / foa_backup_token / cred_token Respective recovery, backup, or credential modules id reads rc2. exist selects number-bound source1 and saves before sending; code directly decodes reg_prefs.token_used_during_reg, reusing nonempty bytes and generating only for zero length. foa and cred retain their own conditional sources.
client_metrics / offline_ab / feo2_query_status / language_selector_* / mistyped Use application business events and persisted state. Accumulate, persist, or consume at actual events rather than inventing state during serialization.

Google/Firebase results belong to the calling application, service project, and action; an example application's token is not another client's credential.

AdvertisingIdClient · Firebase Cloud Messaging

Field Assembly and Topic Boundaries

Raw API values, derived fields, cache slots, and final requests are different objects. RegistrationState does not take over the application's actual lifecycle. Four telephony-environment fields share a collection gate, while db has its own. Handle empty text, null, omission, 0, and false separately rather than inventing missing service outcomes.

Topic Connection to These Helpers
Request encryption Ordinary text and preencoded bytes take separate paths into form serialization.
Sending Final form handling, HTTP requests, and business responses.

Keep each article's request-specific backup reads, attempt counters, and correction rules. code reads saved mistyped text; reason is captured before asynchronous preparation, preserving empty text. Standard-page hasav is Retriever→2, otherwise granted RECEIVE_SMS→1, other normal results→0; a specific fallback can also pass 0 directly. Reading FEO2 diagnostics does not query or clear capabilities, and reading metrics/diagnostics does not rerun their producers.

The selected transport serializes the encoded values into the final form.

exist parameters · code parameters

相关推荐
booboosui22 分钟前
停一个活只有杀进程:界面上少了「说点要求」
android
传奇开心果编程3 小时前
【Flutter踩坑排雷】第28课:平台目录缺失与修复坑|`flutter create` 的“重建”与“覆盖”边界
android·flutter·ios·移动开发·跨平台·dart·声明式ui
longxibo12 小时前
【AIBIZ低代码平台:能长出业务系统的平台】
android·低代码·rxjava
AI_Gump20 小时前
Harness篇|DeepSeek Harness 上手机:五条安卓移植路线与 App 内 Linux 拆解
android·linux·人工智能·智能手机·deepseek
疯华整猫1 天前
否决 Flutter 提案那天,我发现自己也说不清 Framework 管什么
android
传奇开心果编程2 天前
【Flutter踩坑排雷】第25课:ScrollController 误用坑|跳转失效、监听泄漏与“控制器未附加”
android·flutter·ios·移动开发·跨平台·dart·声明式ui
慕容雪_2 天前
Capacitor8-Android打包完整手册
android·打包·capacitor
阿杰m2 天前
Android源码第十三章:Bluetooth——沿一次发现走到控制器
android
booboosui2 天前
派活给另一个 AI:前后两次文件夹快照,就是交付清单
android