API del puente de JavaScript
Información del dispositivo y de la app
Qué teléfono es, qué compilación, un identificador estable por instalación y las API estándar de ubicación y cámara.
getUserMedia estándar web#
navigator.mediaDevices.getUserMedia — camera and microphone
Ejemplo
La API web estándar de cámara y micrófono funciona dentro de la app. La app muestra la solicitud de permiso propia de Android y entrega el flujo a la página.
Devuelve: una Promise de un MediaStream, o un rechazo (NotAllowedError cuando el usuario lo rechazó o el interruptor estaba desactivado al compilar, NotFoundError cuando no existe ese dispositivo). Requiere: Cámara y/o Micrófono en el Paso 4 (Acceso). Cuando compilas desde un ZIP o una página HTML y tu código llama a getUserMedia({ video: … }) o { audio: … } (o usa <input capture>, BarcodeDetector, SpeechRecognition), la compilación encuentra la llamada y añade el permiso correspondiente por ti; { video: false } no añade Cámara. En una compilación de sitio web (URL), márcalos tú mismo.
Muestra la cámara trasera en un elemento de vídeo.
<video id="preview" autoplay playsinline muted></video>
<button id="startCam">Start camera</button>
<script>
document.getElementById('startCam').addEventListener('click', async function () {
if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
showMessage('Camera is not available here.');
return;
}
try {
var stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' }, // 'user' = front camera
audio: false
});
document.getElementById('preview').srcObject = stream;
} catch (err) {
if (err.name === 'NotAllowedError') showMessage('Camera permission was refused.');
else showMessage('Camera error: ' + err.name);
}
});
</script>Graba una nota de voz solo con el micrófono.
async function recordVoice(seconds) {
var stream;
try {
stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
} catch (err) {
showMessage('Microphone permission was refused.');
return;
}
var chunks = [];
var rec = new MediaRecorder(stream);
rec.ondataavailable = function (e) { chunks.push(e.data); };
rec.onstop = function () {
stream.getTracks().forEach(function (t) { t.stop(); }); // turn the mic off
var blob = new Blob(chunks, { type: rec.mimeType });
document.getElementById('player').src = URL.createObjectURL(blob);
};
rec.start();
setTimeout(function () { rec.stop(); }, seconds * 1000);
}Toma una foto del flujo de la cámara y libera la cámara.
async function snapshot() {
if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) return null;
let stream;
try {
stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' } });
} catch (err) {
showMessage(err.name === 'NotFoundError' ? 'No camera on this device.' : 'Camera permission was refused.');
return null;
}
const video = document.createElement('video');
video.srcObject = stream;
await video.play();
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth; canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0);
stream.getTracks().forEach(function (t) { t.stop(); });
return canvas.toDataURL('image/jpeg', 0.9);
}Notas: Cuando pides cámara y micrófono juntos, la app pide ambos permisos al usuario a la vez. Si el interruptor estaba desactivado al compilar, la solicitud se rechaza sin ninguna petición: vuelve a compilar con él activado. Detén siempre las pistas cuando termines, o la luz de la cámara/micrófono seguirá encendida. El vídeo con DRM (Widevine) se permite sin ningún interruptor. La cámara y el micrófono son sensibles: decláralos en tu formulario de seguridad de los datos de Play.
IdleDetector estándar web#
new IdleDetector() — Idle Detection
Ejemplo
La API estándar Idle Detection: sabe cuándo el usuario deja de usar el teléfono (sin entrada durante un rato) o bloquea la pantalla, y cuándo vuelve, para un estado de "ausente", pausar una sesión en vivo o bloquear una caja fuerte.
Devuelve: IdleDetector.requestPermission() → 'granted' o 'denied' (necesita un toque); detector.start({threshold, signal}) → Promise (rechaza con NotAllowedError sin el permiso, TypeError por debajo de 60000 ms); luego userState ('active'/'idle'), screenState ('locked'/'unlocked') y un evento change. Requiere: nada que activar y ningún permiso de Android.
document.getElementById('away').addEventListener('click', async () => {
if (!('IdleDetector' in window)) return;
if ((await IdleDetector.requestPermission()) !== 'granted') return;
const ac = new AbortController();
const idle = new IdleDetector();
idle.addEventListener('change', () => {
setStatus(idle.userState === 'idle' || idle.screenState === 'locked' ? 'Away' : 'Online');
});
await idle.start({ threshold: 5 * 60000, signal: ac.signal });
stopButton.onclick = () => ac.abort();
});Reanuda la observación en el siguiente inicio sin un nuevo toque, una vez que el usuario ya ha dicho que sí:
async function watchAwayIfAllowed() {
if (!('IdleDetector' in window)) return;
const perm = await navigator.permissions.query({ name: 'idle-detection' });
if (perm.state !== 'granted') return; // 'prompt' until requestPermission() ran from a tap
const idle = new IdleDetector();
idle.onchange = () => {
if (idle.screenState === 'locked') pauseLiveSession();
else if (idle.userState === 'active') resumeLiveSession();
};
await idle.start({ threshold: 60000 }); // 1 minute is the smallest threshold
}
watchAwayIfAllowed();Deja que el usuario cambie el retardo del bloqueo automático: detén el detector en marcha, inicia uno nuevo e informa de los errores reales:
let vaultWatch = null;
async function setAutoLock(minutes) {
if (vaultWatch) vaultWatch.abort(); // aborting stops the old detector
vaultWatch = new AbortController();
const idle = new IdleDetector();
idle.addEventListener('change', () => {
if (idle.userState === 'idle' || idle.screenState === 'locked') lockVault();
});
try {
await idle.start({ threshold: minutes * 60000, signal: vaultWatch.signal });
} catch (err) {
if (err.name === 'NotAllowedError') showMessage('Tap "Enable auto-lock" first.');
else if (err instanceof TypeError) showMessage('Pick at least 1 minute.');
else throw err;
}
}Notas: en la app, "input" es la entrada del usuario en ESTA app mientras está al frente; mientras otra app está al frente con la pantalla encendida, el usuario cuenta como activo (una app no puede ver la entrada de otras apps; Chrome en Android responde igual); cuando la pantalla está apagada o bloqueada, el tiempo de inactividad se cuenta desde el último toque. El permiso se recuerda y navigator.permissions.query({name:'idle-detection'}) lo responde. Transportes internos: __idleState, __idlePermission; los cambios de bloqueo/desbloqueo llegan como appmint:idle. Las apps creadas con IA usan watchIdle().
TextDetector estándar web#
new TextDetector().detect(image) — text recognition (OCR)
Ejemplo
Lee el texto de una imagen en el propio teléfono (el total de un recibo, un número de serie, una tarjeta de visita) con el reconocedor ML Kit de Google. La forma es el TextDetector de la Shape Detection API, el que Chrome incluye detrás de una bandera.
Devuelve: una Promise con un array, una entrada por línea: { rawValue, boundingBox, cornerPoints } en píxeles de la imagen. Rechaza con NotSupportedError sin los servicios de Google Play y con OperationError cuando falla el reconocimiento. Requiere: nada que activar y ningún permiso (para leer desde la cámara, toma la foto con <input type="file" accept="image/*" capture> o un flujo de cámara).
document.getElementById('photo').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file || !('TextDetector' in window)) return;
status.textContent = 'Reading…';
try {
const lines = await new TextDetector().detect(file); // a Blob, <img>, <canvas>, <video> or ImageBitmap
const text = lines.map((l) => l.rawValue).join('\n');
const total = /total\s*[:$€£]?\s*([\d.,]+)/i.exec(text);
status.textContent = total ? 'Total: ' + total[1] : text;
} catch (err) {
status.textContent = 'Could not read the text (' + err.name + ')';
}
});Lee un número de serie desde la vista previa en vivo de la cámara (un <video> en reproducción se pasa tal cual y se lee su fotograma actual):
const video = document.querySelector('video');
async function startPreview() {
video.srcObject = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
await video.play();
}
document.getElementById('read-serial').addEventListener('click', async () => {
if (!('TextDetector' in window)) return;
const lines = await new TextDetector().detect(video);
const hit = lines.map((l) => l.rawValue).find((t) => /S\/?N[:\s]*[A-Z0-9-]{6,}/i.test(t));
serialField.value = hit ? hit.replace(/^.*S\/?N[:\s]*/i, '') : '';
if (!hit) showMessage('No serial number in view — move closer and try again.');
});Dibuja el contorno de cada línea reconocida sobre la foto, usando boundingBox (píxeles de la imagen):
async function outlineText(img, canvas) {
await img.decode(); // detect() rejects InvalidStateError before it has loaded
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const g = canvas.getContext('2d');
g.drawImage(img, 0, 0);
const lines = await new TextDetector().detect(img);
g.strokeStyle = '#ff3b30';
g.lineWidth = 3;
for (const { boundingBox: b, rawValue } of lines) {
g.strokeRect(b.x, b.y, b.width, b.height);
console.log(rawValue);
}
return lines.length;
}Oculta el botón de escaneo donde no hay reconocimiento de texto (sin servicios de Google Play) y extrae los datos de contacto de una tarjeta de visita:
const scanBtn = document.getElementById('scan-card');
scanBtn.hidden = !('TextDetector' in window);
document.getElementById('card-photo').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
const text = (await new TextDetector().detect(file)).map((l) => l.rawValue).join('\n');
fillContact({
email: (text.match(/[\w.+-]+@[\w-]+\.[\w.]+/) || [''])[0],
phone: (text.match(/\+?\d[\d\s().-]{7,}\d/) || [''])[0],
});
});Notas: Solo escrituras latinas (inglés, español, francés, alemán, indonesio, vietnamita, turco y otras); no se reconocen chino, devanagari, japonés, coreano ni árabe. El modelo viene de los servicios de Google Play: la compilación lo pide en la instalación cuando tu código lo usa; si no, la primera llamada en un teléfono nuevo espera unos segundos mientras se descarga. Las imágenes de otro sitio deben permitir CORS (un canvas contaminado rechaza SecurityError). Transportes internos: __ocrAvailable, __ocrRecognize (respondido como appmint:ocr). Las apps creadas con IA usan recognizeText().
__idlePermission puente#
window.WebToApk.__idlePermission(request: Boolean): String
IdleDetector.requestPermission() ([request] true, llamado con un gesto del usuario) registra la concesión; false solo la lee. "granted" | "prompt".
Ejemplo
Transporte interno detrás de IdleDetector.requestPermission() y el estado del permiso 'idle-detection'. Las páginas usan la API estándar; nunca llames a esto directamente: los métodos __ pueden cambiar sin aviso.
Devuelve: 'granted' o 'prompt'. Requiere: un gesto del usuario para la solicitud.
Usa la API pública:
button.onclick = async () => {
const state = await IdleDetector.requestPermission();
console.log(state); // 'granted'
};Detecta si la función existe y trata un rechazo como "no permitido":
async function askIdlePermission() {
if (!('IdleDetector' in window)) return false;
try {
return (await IdleDetector.requestPermission()) === 'granted';
} catch (err) {
return false;
}
}Pídelo desde un toque y luego empieza a detectar:
startBtn.addEventListener('click', async () => {
if (!(await askIdlePermission())) { showMessage('Idle detection was not allowed.'); return; }
const idle = new IdleDetector();
idle.addEventListener('change', () => console.log(idle.userState));
await idle.start({ threshold: 60000 });
});Notas: Implementado en el shell de la app (IdleAndRelatedApps.kt).
__idleState puente#
window.WebToApk.__idleState(): String
IdleDetector: `{idleMs, locked}` a partir de la propia entrada de la app y del bloqueo de pantalla.
Ejemplo
Transporte interno detrás de IdleDetector: cuánto tiempo ha pasado desde la última entrada del usuario y si la pantalla está bloqueada. Las páginas usan la API estándar; nunca llames a esto directamente: los métodos __ pueden cambiar sin aviso.
Devuelve: JSON {idleMs, locked}. Los cambios de pantalla y de app se envían como el evento appmint:idle con la misma forma. Requiere: nada.
Usa la API pública:
const idle = new IdleDetector();
idle.onchange = () => console.log(idle.userState, idle.screenState);
await idle.start({ threshold: 60000 });Deja de observar con un AbortController cuando se cierre la pantalla:
const ctl = new AbortController();
const idle = new IdleDetector();
idle.addEventListener('change', () => {
if (idle.userState === 'idle') showAwayBanner();
});
await idle.start({ threshold: 60000, signal: ctl.signal });
// later: ctl.abort();Bloquea la app cuando se bloquee la pantalla:
if ('IdleDetector' in window && (await IdleDetector.requestPermission()) === 'granted') {
const d = new IdleDetector();
d.onchange = () => { if (d.screenState === 'locked') showPinScreen(); };
await d.start({ threshold: 60000 });
}Notas: Implementado en el shell de la app (IdleAndRelatedApps.kt).
__permissionState puente#
window.WebToApk.__permissionState(name: String): String
Estado de la API Permissions de un nombre de permiso W3C, a partir de las concesiones reales de Android: "granted" | "denied" | "prompt", o "" cuando el shell no responde a ese nombre (la consulta de la página pasa entonces a la implementación propia del WebView).
Ejemplo
Transporte interno detrás de navigator.permissions.query({ name }). Las páginas usan la API estándar; nunca llames a esto directamente: los métodos __ pueden cambiar sin previo aviso.
Devuelve: 'granted', 'denied', 'prompt', o '' para un nombre que la app no responde (la consulta pasa entonces al propio WebView). Requiere: nada que activar.
Usa la API pública:
const status = await navigator.permissions.query({ name: 'camera' });
showCameraHint(status.state); // 'granted' | 'denied' | 'prompt'
status.addEventListener('change', () => showCameraHint(status.state));Comprueba varios permisos a la vez:
const names = ['camera', 'microphone', 'geolocation', 'notifications'];
const states = await Promise.all(names.map((name) =>
navigator.permissions.query({ name }).then((s) => s.state, () => 'unknown')));
names.forEach((name, i) => console.log(name, states[i]));Pide permiso solo cuando el estado no sea denied, e indica qué hacer cuando lo es:
async function ensureLocation() {
const s = await navigator.permissions.query({ name: 'geolocation' });
if (s.state === 'denied') { showMessage('Turn location on in the phone settings.'); return null; }
return new Promise((ok, fail) => navigator.geolocation.getCurrentPosition(ok, fail));
}Notas: Responde camera, microphone, geolocation, notifications, screen-wake-lock, clipboard-read, clipboard-write. Una capacidad que la compilación dejó desactivada (o cuyo permiso no está en el manifiesto) es denied. Los estados en vivo se vuelven a leer con appmint:permissions-changed (disparado tras cada diálogo de permiso) y cuando la página vuelve a ser visible.
getAppName puente#
window.WebToApk.getAppName(): String
El nombre de la app que el creador eligió en el asistente: lo que muestra el launcher.
Ejemplo
Da el nombre de app que elegiste en el asistente de compilación: el nombre bajo el icono del launcher.
Devuelve: una cadena al instante. Requiere: nada.
Usa el nombre real de la app en un título y en un texto para compartir.
var appName = (window.WebToApk && window.WebToApk.getAppName)
? WebToApk.getAppName()
: document.title; // in a browser: the page title
document.getElementById('welcome').textContent = 'Welcome to ' + appName;
document.getElementById('shareBtn').addEventListener('click', function () {
if (navigator.share) navigator.share({ text: 'Try ' + appName + '!' });
});Pon el nombre de la app en el título de la página y en un encabezado:
if (window.WebToApk && WebToApk.getAppName) {
const name = WebToApk.getAppName();
document.title = name;
document.querySelector('h1').textContent = name;
}Crea un correo de soporte que nombre la app y su versión:
function supportMailto() {
const b = window.WebToApk;
const name = b && b.getAppName ? b.getAppName() : 'our app';
const version = b && b.getAppVersion ? b.getAppVersion() : 'web';
const subject = encodeURIComponent('Help with ' + name + ' ' + version);
return 'mailto:[email protected]?subject=' + subject;
}
document.getElementById('contact').href = supportMailto();Notas: Este es el nombre del asistente, no el <title> de la página. No cambia en tiempo de ejecución. Para la versión, usa getAppVersion().
getAppVersion puente#
window.WebToApk.getAppVersion(): String
El versionName del paquete instalado, p. ej. "1.4.2": la cadena que el creador define al compilar, no el versionCode. Si no se puede leer el paquete, vuelve a "1.0", así que una página puede imprimirlo sin comprobar si es nulo.
Ejemplo
Da el nombre de versión de la app instalada, por ejemplo "1.4.2".
Devuelve: una cadena al instante. Requiere: nada.
Muestra la versión en una pantalla Acerca de.
var version = (window.WebToApk && window.WebToApk.getAppVersion)
? WebToApk.getAppVersion()
: 'web';
document.getElementById('about').textContent = 'Version ' + version;Pide una actualización cuando la versión instalada sea antigua:
function isOlder(a, b) { // "1.4.2" < "1.10.0"
const x = a.split('.').map(Number), y = b.split('.').map(Number);
for (let i = 0; i < Math.max(x.length, y.length); i++) {
if ((x[i] || 0) !== (y[i] || 0)) return (x[i] || 0) < (y[i] || 0);
}
return false;
}
if (window.WebToApk && WebToApk.getAppVersion && isOlder(WebToApk.getAppVersion(), '1.5.0')) {
document.getElementById('update-banner').hidden = false;
}Añade la versión y el código de versión de getDeviceInfo() a un informe de error:
function reportInfo() {
const b = window.WebToApk;
if (!(b && b.getAppVersion)) return 'web';
let text = 'v' + b.getAppVersion();
if (b.getDeviceInfo) {
const info = JSON.parse(b.getDeviceInfo());
text += ' (build ' + info.app.versionCode + ')';
}
return text;
}
document.getElementById('report').value = reportInfo();Notas: Esta es la versión NOMBRE que defines al compilar, no el código de versión. Si no se puede leer el paquete, devuelve "1.0", así que nunca está vacío. El código de versión y las fechas de instalación están en getDeviceInfo() bajo app.
getDeviceInfo puente#
window.WebToApk.getDeviceInfo(): String
WebToApk.getDeviceInfo() -> cadena JSON, NO requiere permiso WebToApk.getPhoneIdentity() -> operador/SIM, necesita el permiso de Teléfono WebToApk.getInstallId() -> UUID estable por instalación
Se describe en su grupo, Información del dispositivo / de la app, y no por separado.
Ejemplo
Da datos del teléfono, de Android, de la pantalla, de tu app y del estado actual (batería, memoria, red) en una sola llamada.
Devuelve: una cadena JSON al instante; usa JSON.parse. Claves de nivel superior: android, hardware, screen, app, runtime, installId. Requiere: nada, ningún permiso.
Muestra la información del teléfono.
function showPhoneInfo() {
if (!(window.WebToApk && window.WebToApk.getDeviceInfo)) {
document.getElementById('out').textContent = navigator.userAgent; // browser: only the user agent
return;
}
var info = JSON.parse(WebToApk.getDeviceInfo());
document.getElementById('out').innerHTML =
'Android: ' + info.android.release + ' (SDK ' + info.android.sdkInt + ')<br>' +
'Version ID: ' + info.android.buildId + '<br>' +
'Build number: ' + info.android.incremental + '<br>' +
'Phone: ' + info.hardware.manufacturer + ' ' + info.hardware.model + '<br>' +
'Screen: ' + info.screen.widthPx + 'x' + info.screen.heightPx + ' @' + info.screen.refreshRate + 'Hz<br>' +
'Battery: ' + info.runtime.batteryLevel + '%' + (info.runtime.charging ? ' (charging)' : '') + '<br>' +
'Network: ' + info.runtime.networkType + '<br>' +
'App: ' + info.app.versionName + ' (' + info.app.versionCode + ')<br>' +
'WebView: ' + info.app.webViewPackage;
}Todas las claves:
{
"android": { "release": "14", "sdkInt": 34, "codename": "REL", "buildId": "UP1A.231005.007", "incremental": "R.1a2b3c", "display": "…", "fingerprint": "…", "securityPatch": "2026-08-05" },
"hardware": { "manufacturer": "OPPO", "brand": "OPPO", "model": "CPH2269", "device": "…", "product": "…", "board": "…", "hardware": "…", "abis": ["arm64-v8a"], "socManufacturer": "…", "socModel": "…", "isEmulator": false },
"screen": { "widthPx": 720, "heightPx": 1600, "density": 2, "densityDpi": 320, "refreshRate": 90, "nightMode": true },
"app": { "packageName": "com.example.app", "versionName": "1.0", "versionCode": 1, "targetSdk": 35, "firstInstallTime": 1737000000000, "lastUpdateTime": 1737000000000, "installer": "com.android.vending", "webViewPackage": "com.google.android.webview 131.0" },
"runtime": { "totalMemoryBytes": 4000000000, "availableMemoryBytes": 1500000000, "lowMemory": false, "storageFreeBytes": 20000000000, "storageTotalBytes": 64000000000, "batteryLevel": 80, "charging": false, "locale": "en-IN", "timezone": "Asia/Kolkata", "networkType": "wifi" },
"installId": "3f2b8c1e-…"
}Avisa en un teléfono con poco almacenamiento o poca memoria antes de una descarga grande.
function canDownload(bytesNeeded) {
if (!(window.WebToApk && WebToApk.getDeviceInfo)) return true; // browser: let the download try
const r = JSON.parse(WebToApk.getDeviceInfo()).runtime;
if (r.storageFreeBytes < bytesNeeded * 2) { showMessage('Not enough free storage.'); return false; }
if (r.lowMemory) showMessage('The phone is low on memory; this may be slow.');
return true;
}Notas: socManufacturer / socModel existen solo en Android 12+. networkType es wifi, cellular, ethernet, other, none o unknown. installer es com.android.vending para instalaciones de Play y está vacío para instalaciones manuales. Los valores se leen en el momento de la llamada: llama de nuevo para actualizar la batería o la red. No hay IMEI ni número de serie: Android los bloquea para todas las apps; usa getInstallId().
getInstallId puente#
window.WebToApk.getInstallId(): String
WebToApk.getDeviceInfo() -> cadena JSON, NO requiere permiso WebToApk.getPhoneIdentity() -> operador/SIM, necesita el permiso de Teléfono WebToApk.getInstallId() -> UUID estable por instalación
Se describe en su grupo, Información del dispositivo / de la app, y no por separado.
Ejemplo
Da un ID aleatorio que se mantiene igual para esta instalación de tu app.
Devuelve: una cadena (un UUID como "3f2b…-…") al instante. Requiere: nada.
Úsalo como clave de dispositivo para tu servidor o para una comprobación de "una prueba gratuita por dispositivo".
function deviceKey() {
if (window.WebToApk && window.WebToApk.getInstallId) {
return WebToApk.getInstallId();
}
// In a browser: keep your own random ID in localStorage (per browser, cleared with site data).
var k = localStorage.getItem('installId');
if (!k) {
k = 'w-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10);
localStorage.setItem('installId', k);
}
return k;
}
fetch('https://api.example.com/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ device: deviceKey() })
});Envía el id a tu servidor en una cabecera y mantenlo fuera de la URL.
async function apiGet(path) {
const id = (window.WebToApk && WebToApk.getInstallId) ? WebToApk.getInstallId() : null;
const res = await fetch('https://api.example.com' + path, {
headers: id ? { 'X-Install-Id': id } : {}
});
return res.json();
}Adjúntalo a los informes de fallos para poder encontrar los problemas de una instalación concreta.
if (window.WebToApk && WebToApk.setCrashUserId && WebToApk.getInstallId) {
// returns false when Crash reports are off or the id has other characters
const ok = WebToApk.setCrashUserId(WebToApk.getInstallId());
if (!ok) console.log('crash user id not set');
}Notas: El ID se crea en el primer uso y se guarda en el almacenamiento de la app. Sigue igual tras las actualizaciones de la app. Cambia cuando el usuario desinstala la app o borra sus datos. No es un ID de hardware: Android no da IMEI ni número de serie a las apps. El mismo valor también está en getDeviceInfo() como installId.
getPhoneIdentity puente#
window.WebToApk.getPhoneIdentity(): String
WebToApk.getDeviceInfo() -> cadena JSON, NO requiere permiso WebToApk.getPhoneIdentity() -> operador/SIM, necesita el permiso de Teléfono WebToApk.getInstallId() -> UUID estable por instalación
Se describe en su grupo, Información del dispositivo / de la app, y no por separado.
Ejemplo
Da los datos del operador móvil y de la SIM: nombre del operador, país de la SIM, código de operador, tipo de teléfono y estado de la SIM.
Devuelve: una cadena JSON al instante; usa JSON.parse: { carrierName, simCountryIso, networkCountryIso, simOperator, phoneType, simState, hardwareIdAvailable: false }, o { error }. Requiere: activar Teléfono en el Paso 4 (Acceso) al compilar; si no, devuelve { "error": "not_enabled" }.
Muestra el operador y elige el prefijo del país.
function showCarrier() {
if (!(window.WebToApk && window.WebToApk.getPhoneIdentity)) return;
var p = JSON.parse(WebToApk.getPhoneIdentity());
if (p.error === 'not_enabled') { showMessage('Turn on Phone when you build the app'); return; }
if (p.error) { showMessage('Could not read SIM info: ' + p.error); return; }
if (p.simState !== 'ready') {
showMessage('No SIM ready (' + p.simState + ')'); // absent | pin_required | network_locked | unknown
return;
}
document.getElementById('carrier').textContent = p.carrierName; // e.g. "Jio 4G"
document.getElementById('country').value = p.simCountryIso.toUpperCase(); // e.g. "IN"
}Elige un prefijo telefónico según el país de la SIM para un formulario de registro.
const DIAL = { IN: '+91', US: '+1', GB: '+44', DE: '+49' };
function defaultDialCode() {
if (!(window.WebToApk && WebToApk.getPhoneIdentity)) return '+1';
const p = JSON.parse(WebToApk.getPhoneIdentity());
if (p.error || p.simState !== 'ready') return '+1';
return DIAL[p.simCountryIso.toUpperCase()] || '+1';
}
document.getElementById('dial').value = defaultDialCode();Distingue una tableta solo con Wi-Fi de un teléfono con phoneType.
if (window.WebToApk && WebToApk.getPhoneIdentity) {
const p = JSON.parse(WebToApk.getPhoneIdentity());
if (!p.error && p.phoneType === 'none') {
document.getElementById('sms-login').hidden = true; // no cellular radio: hide SMS sign-in
}
}Notas: phoneType es gsm, cdma, sip o none (tabletas solo con Wi-Fi). simOperator es el código MCC+MNC (p. ej. "40445"). No hay IMEI, MEID ni número de serie: hardwareIdAvailable siempre es false, porque Android los oculta a todas las apps desde Android 10; usa getInstallId() para un id estable. El interruptor de Teléfono añade un permiso sensible a tu ficha de Play; declara para qué lo usas en el formulario de seguridad de los datos.
isTv puente#
window.WebToApk.isTv(): Boolean
Verdadero cuando la app se ejecuta en una televisión (Android TV, Google TV, Fire TV).
Ejemplo
Indica a la página si la app se ejecuta en una televisión (Android TV, Google TV, Fire TV), para que pueda mostrar un diseño adecuado para el mando a distancia.
Devuelve: true o false (de forma síncrona). Requiere: nada. Con Compatibilidad con Android TV activada en el asistente, la app también obtiene un banner de TV en el launcher de TV, y en una TV la página obtiene un anillo de foco claro, y los enlaces, botones, campos y elementos conectados con onclick o un rol de botón se pueden alcanzar con las flechas del mando; OK hace clic en el elemento enfocado y Atrás funciona como en un teléfono.
var onTv = !!(window.WebToApk && typeof window.WebToApk.isTv === 'function' && window.WebToApk.isTv());
if (onTv) {
document.documentElement.classList.add('tv'); /* bigger text, no hover-only menus */
document.querySelector('#play').focus(); /* start the remote on the main action */
}html.tv body { font-size: 22px; }
html.tv :focus-visible { transform: scale(1.05); }Elige una pantalla de inicio distinta para TV y mantén el diseño del teléfono como predeterminado.
function isTv() {
return !!(window.WebToApk && typeof WebToApk.isTv === 'function' && WebToApk.isTv());
}
history.replaceState({}, '', isTv() ? '/tv-home' : '/home');
if (isTv()) document.querySelectorAll('.touch-only').forEach(function (el) { el.hidden = true; });Notas: Prueba con el emulador de Android TV y su D-pad. Publicar en TV en Google Play es un paso aparte: en Play Console, activa en la app el factor de forma de Android TV (Lanzamiento → Configuración avanzada → Factores de forma) y añade una captura de pantalla de TV.