API da ponte JavaScript
Informações do dispositivo e do app
Que telefone é este, qual compilação, um identificador estável por instalação e as APIs padrão de localização e câmera.
getUserMedia padrão web#
navigator.mediaDevices.getUserMedia — camera and microphone
Exemplo
A API web padrão de câmera e microfone funciona dentro do app. O app mostra o pedido de permissão do próprio Android e entrega o stream à página.
Retorna: uma Promise de um MediaStream, ou uma rejeição (NotAllowedError quando o usuário recusou ou a chave estava desativada na compilação, NotFoundError quando não há esse dispositivo). Requer: Câmera e/ou Microfone na Etapa 4 (Acesso). Quando você compila a partir de um ZIP ou de uma página HTML e seu código chama getUserMedia({ video: … }) ou { audio: … } (ou usa <input capture>, BarcodeDetector, SpeechRecognition), a compilação encontra a chamada e adiciona a permissão correspondente para você; { video: false } não adiciona Câmera. Em uma compilação de site (URL), marque você mesmo.
Mostre a câmera traseira em um 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>Grave uma nota de voz só com o microfone.
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);
}Tire uma foto a partir do stream da câmera e libere a câmera.
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);
}Observações: Quando você pede câmera e microfone juntos, o app pede os dois ao usuário de uma vez. Se a chave estava desativada quando você compilou, o pedido é recusado sem nenhum aviso — compile de novo com ela ativada. Sempre pare as tracks quando terminar, ou a luz da câmera/microfone continua acesa. Vídeo com DRM (Widevine) é permitido sem nenhuma chave. Câmera e microfone são sensíveis: declare-os no seu formulário de Segurança dos dados do Play.
IdleDetector padrão web#
new IdleDetector() — Idle Detection
Exemplo
A API padrão Idle Detection: saiba quando o usuário para de usar o telefone (sem entrada por um tempo) ou bloqueia a tela, e quando ele volta — para um status "ausente", pausar uma sessão ao vivo ou bloquear um cofre.
Retorna: IdleDetector.requestPermission() → 'granted' ou 'denied' (precisa de um toque); detector.start({threshold, signal}) → Promise (rejeita NotAllowedError sem a permissão, TypeError abaixo de 60000 ms); depois userState ('active'/'idle'), screenState ('locked'/'unlocked') e um evento change. Requer: nada para ativar e nenhuma permissão do 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();
});Retome a observação na próxima abertura sem um novo toque, depois que o usuário já disse sim:
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();Deixe o usuário alterar o tempo do bloqueio automático: pare o detector em execução, inicie um novo e informe os erros reais:
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;
}
}Observações: no app, "entrada" é a entrada do usuário NESTE app enquanto ele está em primeiro plano; enquanto outro app está em primeiro plano com a tela ligada, o usuário conta como ativo (um app não vê a entrada de outros apps — o Chrome no Android responde do mesmo jeito); com a tela desligada ou bloqueada, o tempo ocioso é contado a partir do último toque. A permissão é lembrada, e navigator.permissions.query({name:'idle-detection'}) a informa. Transportes internos: __idleState, __idlePermission; mudanças de bloqueio/desbloqueio chegam como appmint:idle. Apps criados com IA usam watchIdle().
TextDetector padrão web#
new TextDetector().detect(image) — text recognition (OCR)
Exemplo
Lê o texto de uma imagem no próprio telefone — o total de um recibo, um número de série, um cartão de visita — com o reconhecedor ML Kit do Google. O formato é o TextDetector da Shape Detection API, o que o Chrome oferece atrás de uma flag.
Retorna: Promise de um array, uma entrada por linha: { rawValue, boundingBox, cornerPoints } nos pixels da imagem. Rejeita NotSupportedError sem os serviços do Google Play e OperationError quando o reconhecimento falha. Requer: nada para ativar e nenhuma permissão (para ler da câmera, tire a foto com <input type="file" accept="image/*" capture> ou um stream da câmera).
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 + ')';
}
});Leia um número de série a partir da prévia da câmera ao vivo (um <video> em reprodução é passado como está e seu quadro atual é lido):
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.');
});Contorne cada linha reconhecida sobre a foto, usando boundingBox (pixels da imagem):
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;
}Oculte o botão de escanear onde não há reconhecimento de texto (sem serviços do Google Play) e extraia dados de contato de um cartão 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],
});
});Observações: Apenas escritas latinas (inglês, espanhol, francês, alemão, indonésio, vietnamita, turco e outros); chinês, devanágari, japonês, coreano e árabe não são reconhecidos. O modelo vem dos serviços do Google Play: a compilação o solicita na instalação quando seu código o usa; caso contrário, a primeira chamada em um telefone novo espera alguns segundos enquanto ele é baixado. Imagens de outro site precisam permitir CORS (um canvas contaminado rejeita SecurityError). Transportes internos: __ocrAvailable, __ocrRecognize (respondido como appmint:ocr). Apps criados com IA usam recognizeText().
__idlePermission ponte#
window.WebToApk.__idlePermission(request: Boolean): String
IdleDetector.requestPermission() ([request] true, chamado sob um gesto do usuário) registra a concessão; false apenas a lê. "granted" | "prompt".
Exemplo
Transporte interno por trás de IdleDetector.requestPermission() e do estado de permissão 'idle-detection'. As páginas usam a API padrão; nunca chame isto diretamente — os métodos __ podem mudar sem aviso.
Retorna: 'granted' ou 'prompt'. Requer: um gesto do usuário para o pedido.
Use a API pública:
button.onclick = async () => {
const state = await IdleDetector.requestPermission();
console.log(state); // 'granted'
};Detecte o recurso e trate uma recusa como "não permitido":
async function askIdlePermission() {
if (!('IdleDetector' in window)) return false;
try {
return (await IdleDetector.requestPermission()) === 'granted';
} catch (err) {
return false;
}
}Peça a partir de um toque e depois comece 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 });
});Observações: Implementado no shell do app (IdleAndRelatedApps.kt).
__idleState ponte#
window.WebToApk.__idleState(): String
IdleDetector: `{idleMs, locked}` a partir da entrada do próprio app e do keyguard.
Exemplo
Transporte interno por trás de IdleDetector: há quanto tempo o usuário deu a última entrada e se a tela está bloqueada. As páginas usam a API padrão; nunca chame isto diretamente — os métodos __ podem mudar sem aviso.
Retorna: JSON {idleMs, locked}. Mudanças de tela e de app são enviadas como o evento appmint:idle com o mesmo formato. Requer: nada.
Use a API pública:
const idle = new IdleDetector();
idle.onchange = () => console.log(idle.userState, idle.screenState);
await idle.start({ threshold: 60000 });Pare de acompanhar com um AbortController quando a tela fechar:
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();Bloqueie o app quando a tela bloquear:
if ('IdleDetector' in window && (await IdleDetector.requestPermission()) === 'granted') {
const d = new IdleDetector();
d.onchange = () => { if (d.screenState === 'locked') showPinScreen(); };
await d.start({ threshold: 60000 });
}Observações: Implementado no shell do app (IdleAndRelatedApps.kt).
__permissionState ponte#
window.WebToApk.__permissionState(name: String): String
Estado, na API de Permissões, de um nome de permissão do W3C, a partir das concessões reais do Android: "granted" | "denied" | "prompt", ou "" quando o shell não responde esse nome (a consulta da página então vai para a implementação própria da WebView).
Exemplo
Transporte interno por trás de navigator.permissions.query({ name }). As páginas usam a API padrão; nunca chame isso diretamente - os métodos __ podem mudar sem aviso.
Retorna: 'granted', 'denied', 'prompt', ou '' para um nome que o app não responde (a consulta então vai para a própria WebView). Requer: nada para ativar.
Use a API pública:
const status = await navigator.permissions.query({ name: 'camera' });
showCameraHint(status.state); // 'granted' | 'denied' | 'prompt'
status.addEventListener('change', () => showCameraHint(status.state));Verifique várias permissões de uma 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]));Só peça quando o estado não for denied e diga o que fazer quando for:
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));
}Observações: Responde camera, microphone, geolocation, notifications, screen-wake-lock, clipboard-read, clipboard-write. Um recurso que a compilação deixou desativado (ou cuja permissão não está no manifest) é denied. Os estados ao vivo são relidos em appmint:permissions-changed (disparado após cada diálogo de permissão) e quando a página volta a ficar visível.
getAppName ponte#
window.WebToApk.getAppName(): String
O nome do app que o criador escolheu no assistente — o que o launcher mostra.
Exemplo
Fornece o nome do app que você escolheu no assistente de compilação — o nome sob o ícone do launcher.
Retorna: uma string imediatamente. Requer: nada.
Use o nome real do app em um título e em um texto de compartilhamento.
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 + '!' });
});Coloque o nome do app no título da página e em um cabeçalho:
if (window.WebToApk && WebToApk.getAppName) {
const name = WebToApk.getAppName();
document.title = name;
document.querySelector('h1').textContent = name;
}Monte um e-mail de suporte que cita o app e sua versão:
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();Observações: Este é o nome do assistente, não o <title> da página. Não muda durante a execução. Para a versão, use getAppVersion().
getAppVersion ponte#
window.WebToApk.getAppVersion(): String
O versionName do pacote instalado, ex.: "1.4.2" — a string que o criador define ao compilar, não o versionCode. Volta para "1.0" se o pacote não puder ser lido, para que a página possa imprimi-lo sem checar null.
Exemplo
Fornece o nome da versão do app instalado, por exemplo "1.4.2".
Retorna: uma string imediatamente. Requer: nada.
Mostre a versão em uma tela Sobre.
var version = (window.WebToApk && window.WebToApk.getAppVersion)
? WebToApk.getAppVersion()
: 'web';
document.getElementById('about').textContent = 'Version ' + version;Peça uma atualização quando a versão instalada for antiga:
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;
}Adicione a versão e o código da versão de getDeviceInfo() a um relatório de erro:
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();Observações: Este é o NOME da versão que você definiu ao compilar, não o código da versão. Se o pacote não puder ser lido, retorna "1.0", então nunca é vazio. O código da versão e as datas de instalação estão em getDeviceInfo() em app.
getDeviceInfo ponte#
window.WebToApk.getDeviceInfo(): String
WebToApk.getDeviceInfo() -> string JSON, NENHUMA permissão necessária WebToApk.getPhoneIdentity() -> operadora/SIM, precisa da permissão Telefone WebToApk.getInstallId() -> UUID estável por instalação
Descrito pelo seu grupo, Informações do dispositivo / app, e não separadamente.
Exemplo
Fornece dados sobre o telefone, o Android, a tela, seu app e o estado atual (bateria, memória, rede) em uma única chamada.
Retorna: uma string JSON imediatamente — use JSON.parse. Chaves de nível superior: android, hardware, screen, app, runtime, installId. Requer: nada — nenhuma permissão.
Mostre as informações do telefone.
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 as chaves:
{
"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-…"
}Avise em um telefone com pouco armazenamento ou pouca memória antes de um download 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;
}Observações: socManufacturer / socModel existem apenas no Android 12+. networkType é wifi, cellular, ethernet, other, none ou unknown. installer é com.android.vending para instalações do Play e vazio para instalações manuais. Os valores são lidos no momento da chamada — chame de novo para atualizar bateria ou rede. Não há IMEI nem número de série: o Android os bloqueia para todos os apps; use getInstallId().
getInstallId ponte#
window.WebToApk.getInstallId(): String
WebToApk.getDeviceInfo() -> string JSON, NENHUMA permissão necessária WebToApk.getPhoneIdentity() -> operadora/SIM, precisa da permissão Telefone WebToApk.getInstallId() -> UUID estável por instalação
Descrito pelo seu grupo, Informações do dispositivo / app, e não separadamente.
Exemplo
Fornece um ID aleatório que permanece o mesmo para esta instalação do seu app.
Retorna: uma string (um UUID como "3f2b…-…") imediatamente. Requer: nada.
Use-o como chave do dispositivo para seu servidor ou para uma verificação de "um teste grátis 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() })
});Envie o id ao seu servidor em um cabeçalho e mantenha-o fora da 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();
}Anexe-o a relatórios de falha para que os problemas de uma instalação possam ser encontrados.
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');
}Observações: O ID é criado no primeiro uso e guardado no armazenamento do app. Ele permanece o mesmo após atualizações do app. Muda quando o usuário desinstala o app ou limpa seus dados. Não é um ID de hardware — o Android não fornece IMEI nem número de série aos apps. O mesmo valor também está em getDeviceInfo() como installId.
getPhoneIdentity ponte#
window.WebToApk.getPhoneIdentity(): String
WebToApk.getDeviceInfo() -> string JSON, NENHUMA permissão necessária WebToApk.getPhoneIdentity() -> operadora/SIM, precisa da permissão Telefone WebToApk.getInstallId() -> UUID estável por instalação
Descrito pelo seu grupo, Informações do dispositivo / app, e não separadamente.
Exemplo
Fornece os dados da operadora móvel e do SIM: nome da operadora, país do SIM, código da operadora, tipo de telefone e estado do SIM.
Retorna: uma string JSON imediatamente — use JSON.parse: { carrierName, simCountryIso, networkCountryIso, simOperator, phoneType, simState, hardwareIdAvailable: false }, ou { error }. Requer: ative Telefone na Etapa 4 (Acesso) ao compilar; caso contrário retorna { "error": "not_enabled" }.
Mostre a operadora e escolha o código do 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"
}Escolha um código de discagem a partir do país do SIM para um formulário de cadastro.
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();Distinga um tablet só com Wi-Fi de um telefone com 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
}
}Observações: phoneType é gsm, cdma, sip ou none (tablets só com Wi-Fi). simOperator é o código MCC+MNC (ex.: "40445"). Não há IMEI, MEID nem número de série — hardwareIdAvailable é sempre false, porque o Android os oculta de todos os apps desde o Android 10; use getInstallId() para um id estável. A chave Telefone adiciona uma permissão sensível à sua página no Play; declare para que você a usa no formulário de Segurança dos dados.
isTv ponte#
window.WebToApk.isTv(): Boolean
Verdadeiro quando o app roda em uma televisão (Android TV, Google TV, Fire TV).
Exemplo
Informa à página se o app está rodando em uma televisão (Android TV, Google TV, Fire TV), para que ela possa mostrar um layout adequado ao controle remoto.
Retorna: true ou false (de forma síncrona). Requer: nada. Com Suporte a Android TV ativado no assistente, o app também recebe um banner de TV no launcher da TV e, em uma TV, a página ganha um anel de foco nítido, e links, botões, campos e elementos ligados com onclick ou com papel de botão ficam acessíveis com as setas do controle; OK clica no elemento em foco e Voltar funciona como no telefone.
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); }Escolha uma tela inicial diferente para TV e mantenha o layout de telefone como padrão.
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; });Observações: Teste com o emulador de Android TV e seu D-pad. Publicar para TVs no Google Play é uma etapa separada: no Play Console, inclua o app no formato Android TV (Versão → Configurações avançadas → Formatos) e adicione uma captura de tela de TV.