Crea APK gratis

API del bridge JavaScript

Informazioni su dispositivo e app

Che telefono è, quale build, un identificatore stabile per installazione e le API standard di posizione e fotocamera.

getUserMedia standard web#

navigator.mediaDevices.getUserMedia — camera and microphone

Esempio

La web API standard per fotocamera e microfono funziona dentro l'app. L'app mostra la richiesta di autorizzazione di Android e passa il flusso alla pagina.

Restituisce: una Promise con un MediaStream, oppure un rifiuto (NotAllowedError quando l'utente ha rifiutato o l'opzione era disattivata durante la creazione, NotFoundError quando il dispositivo non esiste). Richiede: Fotocamera e/o Microfono nel Passaggio 4 (Accesso). Quando crei da uno ZIP o da una pagina HTML e il tuo codice chiama getUserMedia({ video: … }) o { audio: … } (oppure usa <input capture>, BarcodeDetector, SpeechRecognition), la build trova la chiamata e aggiunge per te l'autorizzazione corrispondente; { video: false } non aggiunge la fotocamera. Per una build da sito web (URL), attivale tu.

Mostra la fotocamera posteriore in un elemento video.

<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>

Registra una nota vocale solo con il microfono.

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);
}

Scatta una foto dal flusso della fotocamera e rilascia la fotocamera.

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);
}

Note: Quando chiedi fotocamera e microfono insieme, l'app chiede all'utente entrambe le autorizzazioni in una volta. Se l'opzione era disattivata durante la creazione, la richiesta viene rifiutata senza alcuna richiesta all'utente: crea di nuovo l'app con l'opzione attiva. Ferma sempre le tracce quando hai finito, altrimenti la spia della fotocamera/microfono resta accesa. I video DRM (Widevine) sono consentiti senza alcuna opzione. Fotocamera e microfono sono sensibili: dichiarali nel modulo Sicurezza dei dati di Play.

IdleDetector standard web#

new IdleDetector() — Idle Detection

Esempio

La Idle Detection API standard: sai quando l'utente smette di usare il telefono (nessun input per un po') o blocca lo schermo, e quando torna, per uno stato "assente", per mettere in pausa una sessione dal vivo o per bloccare una cassaforte.

Restituisce: IdleDetector.requestPermission() → 'granted' o 'denied' (richiede un tocco); detector.start({threshold, signal}) → Promise (rifiuta con NotAllowedError senza l'autorizzazione, con TypeError sotto 60000 ms); poi userState ('active'/'idle'), screenState ('locked'/'unlocked') e un evento change. Richiede: niente da attivare e nessuna autorizzazione 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();
});

Riprendi il monitoraggio al prossimo avvio senza un nuovo tocco, una volta che l'utente ha già detto 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();

Lascia che l'utente cambi il ritardo del blocco automatico: ferma il rilevatore in esecuzione, avviane uno nuovo e segnala gli errori reali:

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;
  }
}

Note: nell'app, "input" è l'input dell'utente verso QUESTA app mentre è in primo piano; mentre un'altra app è in primo piano con lo schermo acceso, l'utente conta come attivo (un'app non può vedere l'input delle altre app: Chrome su Android risponde allo stesso modo); quando lo schermo è spento o bloccato, il tempo di inattività conta dall'ultimo tocco. L'autorizzazione viene ricordata e navigator.permissions.query({name:'idle-detection'}) la restituisce. Trasporti interni: __idleState, __idlePermission; le variazioni di blocco/sblocco arrivano come appmint:idle. Le app create con l'AI usano watchIdle().

navigator.geolocation standard web#

navigator.geolocation

Esempio

La web API standard della posizione funziona dentro l'app. L'app mostra la richiesta di posizione di Android e passa la risposta alla pagina.

Restituisce: le normali callback W3C: un GeolocationPosition (coords.latitude, coords.longitude, coords.accuracy, …) o un GeolocationPositionError (code 1 = autorizzazione negata, 2 = non disponibile, 3 = timeout). Richiede: GPS (preciso) e/o Posizione (approssimativa) nel Passaggio 4 (Accesso). Quando crei da uno ZIP o da una pagina HTML e il tuo codice chiama navigator.geolocation, getCurrentPosition o watchPosition, la build trova la chiamata e aggiunge la posizione per te. Per una build da sito web (URL), attivala tu.

Ottieni la posizione una sola volta.

function whereAmI() {
  if (!('geolocation' in navigator)) { showMessage('Location is not available here.'); return; }

  navigator.geolocation.getCurrentPosition(
    function (pos) {
      showMessage('You are at ' + pos.coords.latitude.toFixed(5) + ', ' +
                  pos.coords.longitude.toFixed(5) + ' (±' + Math.round(pos.coords.accuracy) + ' m)');
    },
    function (err) {
      if (err.code === 1) showMessage('Location permission was refused.');
      else if (err.code === 3) showMessage('Location timed out. Try again outside.');
      else showMessage('Location is unavailable. Is location turned on in phone settings?');
    },
    { enableHighAccuracy: true, timeout: 15000, maximumAge: 0 }
  );
}

Segui l'utente mentre si muove (un tracker di corsa, una mappa di consegna). Smetti di seguire quando la schermata si chiude.

var watchId = null;

function startTracking() {
  if (!('geolocation' in navigator)) return;
  watchId = navigator.geolocation.watchPosition(
    function (pos) { addPointToMap(pos.coords.latitude, pos.coords.longitude); },
    function (err) { showMessage('Tracking stopped: ' + err.message); },
    { enableHighAccuracy: true }
  );
}

function stopTracking() {
  if (watchId !== null) { navigator.geolocation.clearWatch(watchId); watchId = null; }
}

Controlla prima l'autorizzazione, poi chiedi una posizione solo quando si può ottenere una risposta:

async function locateIfAllowed() {
  if (!('geolocation' in navigator)) return;
  if (navigator.permissions) {
    const s = await navigator.permissions.query({ name: 'geolocation' });
    if (s.state === 'denied') { showMessage('Location is off for this app. Turn it on in Settings.'); return; }
  }
  navigator.geolocation.getCurrentPosition(
    (pos) => centerMap(pos.coords.latitude, pos.coords.longitude),
    (err) => showMessage('Location error ' + err.code + ': ' + err.message),
    { timeout: 10000 }
  );
}

Note: Con GPS attivo, l'app chiede insieme la posizione precisa e quella approssimativa; su Android 12+ l'utente può scegliere "Approssimativa", che funziona comunque (precisione inferiore). Con solo Posizione attivo, viene chiesta solo la posizione approssimativa. Se nessuna delle due è attiva, ogni chiamata fallisce con codice 1 e senza richieste. Dopo che l'utente ha consentito una volta, le chiamate successive ricevono risposta senza richieste. Deve essere attivo anche l'interruttore della posizione del telefono. La posizione è un dato personale: dichiarala nel modulo Sicurezza dei dati di Play; la posizione in background non è fornita.

navigator.getInstalledRelatedApps standard web#

navigator.getInstalledRelatedApps()

Esempio

La related-apps API standard: dice alla pagina quali delle sue app Android (elencate nel web app manifest) sono installate, per nascondere un banner "Installa la nostra altra app" o per aprirla con un collegamento diretto.

Restituisce: una Promise con un array delle voci related_applications del manifest che sono installate: { platform: 'play', id, url?, version? }. Richiede: un <link rel="manifest"> il cui manifest elenchi le app con "platform": "play" e "id": "<package name>", incluso nell'app così che la build possa dichiararle (Android permette a un'app di vedere solo i pacchetti che nomina).

// manifest.webmanifest: { "related_applications": [{ "platform": "play", "id": "com.example.scanner" }] }
const apps = await navigator.getInstalledRelatedApps();
const hasScanner = apps.some((a) => a.id === 'com.example.scanner');
document.getElementById('get-scanner').hidden = hasScanner;

Apri l'app companion quando è installata (l'url della voce proviene dal tuo manifest), altrimenti manda l'utente alla sua pagina sullo store:

// manifest: { "platform": "play", "id": "com.example.scanner", "url": "https://example.com/scan" }
document.getElementById('open-scanner').addEventListener('click', async () => {
  const apps = typeof navigator.getInstalledRelatedApps === 'function'
    ? await navigator.getInstalledRelatedApps() : [];
  const scanner = apps.find((a) => a.id === 'com.example.scanner');
  location.href = scanner && scanner.url
    ? scanner.url
    : 'https://play.google.com/store/apps/details?id=com.example.scanner';
});

Elenca quali tue app sono giĂ sul telefono in una schermata "Altro da noi":

async function renderMoreApps(catalog) {         // [{ id, name }] matching your manifest entries
  if (typeof navigator.getInstalledRelatedApps !== 'function') return; // e.g. inside an iframe
  const installed = new Set((await navigator.getInstalledRelatedApps()).map((a) => a.id));
  document.getElementById('more-apps').innerHTML = catalog.map((app) =>
    `<li>${app.name} — ${installed.has(app.id) ? 'Installed' : '<a href="https://play.google.com/store/apps/details?id=' + app.id + '">Get it</a>'}</li>`
  ).join('');
}

Note: la build legge il manifest nel tuo pacchetto e dichiara ogni pacchetto elencato; un'app non elencata lì non può essere vista e non viene mai segnalata. Le app in modalità sito web tengono il manifest sul sito, quindi non viene dichiarato nulla e l'elenco è vuoto. Trasporto interno: __relatedAppsInstalled.

navigator.permissions standard web#

navigator.permissions.query({ name })

Esempio

La Permissions API standard, con risposte basate sullo stato reale delle autorizzazioni Android del telefono: così una pagina può mostrare "Consenti fotocamera" solo quando si può ancora chiedere, e "Attivala nelle Impostazioni" quando non si può.

Restituisce: una Promise con un PermissionStatus {name, state, onchange}; state è 'granted', 'denied' o 'prompt', e change scatta subito dopo la chiusura di una finestra di autorizzazione o quando l'utente torna dalle Impostazioni. Richiede: niente da attivare.

async function cameraButton() {
  const btn = document.getElementById('scan');
  const status = await navigator.permissions.query({ name: 'camera' });
  function render() {
    if (status.state === 'denied') {
      btn.textContent = 'Camera is off — enable it in Settings';
      btn.disabled = true;
    } else {
      btn.textContent = status.state === 'granted' ? 'Scan' : 'Allow camera and scan';
      btn.disabled = false;
    }
  }
  render();
  status.onchange = render;   // e.g. after the Android dialog, or back from Settings
}
cameraButton();

// The other names the app answers the same way:
for (const name of ['microphone', 'geolocation', 'notifications']) {
  navigator.permissions.query({ name }).then((s) => console.log(name, s.state));
}

Chiedi solo quando la pagina può ancora chiedere, altrimenti manda l'utente alle Impostazioni:

async function ensureMic() {
  if (!(navigator.permissions && navigator.permissions.query)) return true;   // a browser without it
  const s = await navigator.permissions.query({ name: 'microphone' });
  if (s.state === 'denied') {
    showMessage('Microphone is off for this app. Turn it on in Settings.');
    return false;
  }
  return true;   // 'granted', or 'prompt' (Android asks on first use)
}

document.getElementById('record').onclick = async () => {
  if (await ensureMic()) startRecording();
};

Reagisci quando l'utente cambia un'autorizzazione nelle Impostazioni e combinalo con una richiesta reale:

async function watchLocationSwitch() {
  const status = await navigator.permissions.query({ name: 'geolocation' });
  status.addEventListener('change', () => {
    if (status.state === 'granted') {
      navigator.geolocation.getCurrentPosition((p) => showNearby(p.coords.latitude, p.coords.longitude));
    } else {
      hideNearby();
    }
  });
}
watchLocationSwitch();

Note: denied copre anche una funzionalitĂ con cui l'app è stata creata senza (l'opzione era disattivata nella build, quindi Android non può mai concederla) e un rifiuto con "Non chiedere piĂą". screen-wake-lock, clipboard-read e clipboard-write sono 'granted' (l'app li fornisce senza richiesta). Gli altri nomi (midi, accelerometer, …) ricevono risposta dalla WebView come prima. Evento sottostante: appmint:permissions-changed.

TextDetector standard web#

new TextDetector().detect(image) — text recognition (OCR)

Esempio

Legge il testo in un'immagine direttamente sul telefono (il totale di uno scontrino, un numero di serie, un biglietto da visita) con il riconoscitore ML Kit di Google. La forma è il TextDetector della Shape Detection API, quella che Chrome distribuisce dietro un flag.

Restituisce: una Promise con un array, una voce per riga: { rawValue, boundingBox, cornerPoints } nei pixel dell'immagine. Rifiuta con NotSupportedError senza i servizi Google Play e con OperationError quando il riconoscimento fallisce. Richiede: niente da attivare e nessuna autorizzazione (per leggere dalla fotocamera, scatta la foto con <input type="file" accept="image/*" capture> o con un flusso della fotocamera).

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 + ')';
  }
});

Leggi un numero di serie dall'anteprima in diretta della fotocamera (un <video> in riproduzione viene passato così com'è e ne viene letto il fotogramma corrente):

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.');
});

Evidenzia ogni riga riconosciuta sopra la foto, usando boundingBox (pixel dell'immagine):

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;
}

Nascondi il pulsante di scansione dove il riconoscimento del testo non c'è (nessun servizio Google Play) ed estrai i dati di contatto da un biglietto da 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],
  });
});

Note: Solo alfabeti latini (inglese, spagnolo, francese, tedesco, indonesiano, vietnamita, turco e altri); cinese, devanagari, giapponese, coreano e arabo non vengono riconosciuti. Il modello proviene dai servizi Google Play: la build lo richiede all'installazione quando il tuo codice lo usa, altrimenti la prima chiamata su un telefono nuovo attende qualche secondo mentre viene scaricato. Le immagini di un altro sito devono consentire CORS (un canvas contaminato rifiuta con SecurityError). Trasporti interni: __ocrAvailable, __ocrRecognize (risposta come appmint:ocr). Le app create con l'AI usano recognizeText().

__idlePermission bridge#

window.WebToApk.__idlePermission(request: Boolean): String

IdleDetector.requestPermission() ([request] true, chiamata con un gesto dell'utente) registra la concessione; false la legge soltanto. "granted" | "prompt".

Esempio

Trasporto interno dietro IdleDetector.requestPermission() e lo stato di autorizzazione 'idle-detection'. Le pagine usano l'API standard; non chiamarlo mai direttamente: i metodi __ possono cambiare senza preavviso.

Restituisce: 'granted' o 'prompt'. Richiede: un gesto dell'utente per la richiesta.

Usa l'API pubblica:

button.onclick = async () => {
  const state = await IdleDetector.requestPermission();
  console.log(state); // 'granted'
};

Rileva la funzionalitĂ e tratta un rifiuto come "non consentito":

async function askIdlePermission() {
  if (!('IdleDetector' in window)) return false;
  try {
    return (await IdleDetector.requestPermission()) === 'granted';
  } catch (err) {
    return false;
  }
}

Richiedila da un tocco, poi avvia il rilevamento:

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 });
});

Note: Implementato nella shell dell'app (IdleAndRelatedApps.kt).

__idleState bridge#

window.WebToApk.__idleState(): String

IdleDetector: `{idleMs, locked}` dall'input dell'app stessa e dal keyguard.

Esempio

Trasporto interno dietro IdleDetector: da quanto tempo l'utente non dà input e se lo schermo è bloccato. Le pagine usano l'API standard; non chiamarlo mai direttamente: i metodi __ possono cambiare senza preavviso.

Restituisce: JSON {idleMs, locked}. Le variazioni di schermo e app vengono inviate come evento appmint:idle con la stessa forma. Richiede: niente.

Usa l'API pubblica:

const idle = new IdleDetector();
idle.onchange = () => console.log(idle.userState, idle.screenState);
await idle.start({ threshold: 60000 });

Smetti di seguire con un AbortController quando la schermata si chiude:

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();

Blocca l'app quando lo schermo si blocca:

if ('IdleDetector' in window && (await IdleDetector.requestPermission()) === 'granted') {
  const d = new IdleDetector();
  d.onchange = () => { if (d.screenState === 'locked') showPinScreen(); };
  await d.start({ threshold: 60000 });
}

Note: Implementato nella shell dell'app (IdleAndRelatedApps.kt).

__permissionState bridge#

window.WebToApk.__permissionState(name: String): String

Stato della Permissions API per un nome di autorizzazione W3C, dalle concessioni reali di Android: "granted" | "denied" | "prompt", oppure "" quando la shell non risponde a quel nome (la richiesta della pagina passa allora all'implementazione propria della WebView).

Esempio

Trasporto interno dietro navigator.permissions.query({ name }). Le pagine usano l'API standard; non chiamare mai questo direttamente: i metodi __ possono cambiare senza preavviso.

Restituisce: 'granted', 'denied', 'prompt', oppure '' per un nome a cui l'app non risponde (la richiesta passa allora alla WebView). Richiede: niente da attivare.

Usa l'API pubblica:

const status = await navigator.permissions.query({ name: 'camera' });
showCameraHint(status.state);                       // 'granted' | 'denied' | 'prompt'
status.addEventListener('change', () => showCameraHint(status.state));

Controlla piĂą autorizzazioni insieme:

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]));

Chiedi solo quando lo stato non è denied, e di' cosa fare quando lo è:

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));
}

Note: Risponde per camera, microphone, geolocation, notifications, screen-wake-lock, clipboard-read, clipboard-write. Una funzionalità che la build ha lasciato disattivata (o la cui autorizzazione non è nel manifest) è denied. Gli stati in tempo reale vengono riletti su appmint:permissions-changed (emesso dopo ogni finestra di autorizzazione) e quando la pagina torna visibile.

__relatedAppsInstalled bridge#

window.WebToApk.__relatedAppsInstalled(idsJson: String): String

navigator.getInstalledRelatedApps(): quali tra [idsJson] (nomi dei pacchetti dal manifest della pagina) sono installati e visibili a questa app.

Esempio

Trasporto interno dietro navigator.getInstalledRelatedApps(). Le pagine usano l'API standard; non chiamare mai questo direttamente: i metodi __ possono cambiare senza preavviso.

Restituisce: un array JSON dei nomi di pacchetto indicati che sono installati e visibili a questa app. Richiede: i pacchetti dichiarati durante la creazione (dal web app manifest incluso).

Usa l'API pubblica:

const apps = await navigator.getInstalledRelatedApps();
console.log(apps.map((a) => a.id));

Mostra il pulsante "Scarica l'app companion" solo quando non è installata:

async function showCompanionLink() {
  if (!navigator.getInstalledRelatedApps) return;
  const apps = await navigator.getInstalledRelatedApps();
  document.getElementById('get-companion').hidden = apps.length > 0;
}

I pacchetti provengono dal web app manifest collegato dalla pagina:

<link rel="manifest" href="/manifest.webmanifest">
<!-- manifest.webmanifest: "related_applications": [{ "platform": "play", "id": "com.example.companion" }] -->

Note: Implementato nella shell dell'app (IdleAndRelatedApps.kt).

getAppName bridge#

window.WebToApk.getAppName(): String

Il nome dell'app scelto dal creatore nella procedura guidata: quello che mostra il launcher.

Esempio

Fornisce il nome dell'app che hai scelto nella procedura guidata di creazione: il nome sotto l'icona nel launcher.

Restituisce: una stringa subito. Richiede: niente.

Usa il vero nome dell'app in un titolo e in un testo di condivisione.

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 + '!' });
});

Inserisci il nome dell'app nel titolo della pagina e in un'intestazione:

if (window.WebToApk && WebToApk.getAppName) {
  const name = WebToApk.getAppName();
  document.title = name;
  document.querySelector('h1').textContent = name;
}

Crea un'email di assistenza che riporta il nome dell'app e la sua versione:

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();

Note: Questo è il nome inserito nella procedura guidata, non il <title> della pagina. Non cambia durante l'esecuzione. Per la versione usa getAppVersion().

getAppVersion bridge#

window.WebToApk.getAppVersion(): String

Il versionName del pacchetto installato, ad es. "1.4.2": la stringa che il creatore imposta durante la creazione, non il versionCode. Ripiega su "1.0" se il pacchetto non può essere letto, così una pagina può stamparlo senza controllare il valore nullo.

Esempio

Fornisce il nome della versione dell'app installata, ad esempio "1.4.2".

Restituisce: una stringa subito. Richiede: niente.

Mostra la versione in una schermata Informazioni.

var version = (window.WebToApk && window.WebToApk.getAppVersion)
  ? WebToApk.getAppVersion()
  : 'web';

document.getElementById('about').textContent = 'Version ' + version;

Chiedi un aggiornamento quando la versione installata è vecchia:

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;
}

Aggiungi a una segnalazione di bug la versione e il codice versione da getDeviceInfo():

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();

Note: Questo è il NOME della versione che imposti durante la creazione, non il codice versione. Se il pacchetto non può essere letto restituisce "1.0", quindi non è mai vuoto. Il codice versione e le date di installazione sono in getDeviceInfo() sotto app.

getDeviceInfo bridge#

window.WebToApk.getDeviceInfo(): String

WebToApk.getDeviceInfo() -> stringa JSON, NESSUNA autorizzazione richiesta WebToApk.getPhoneIdentity() -> operatore/SIM, richiede l'autorizzazione Telefono WebToApk.getInstallId() -> UUID stabile per installazione

Descritto dal suo gruppo, Informazioni su dispositivo / app, invece che da solo.

Esempio

Fornisce in una sola chiamata informazioni sul telefono, su Android, sullo schermo, sulla tua app e sullo stato attuale (batteria, memoria, rete).

Restituisce: una stringa JSON subito: usa JSON.parse. Chiavi di primo livello: android, hardware, screen, app, runtime, installId. Richiede: niente, nessuna autorizzazione.

Mostra le informazioni sul telefono.

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;
}

Tutte le chiavi:

{
  "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-…"
}

Avvisa su un telefono con poco spazio o poca memoria prima di un 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;
}

Note: socManufacturer / socModel esistono solo su Android 12+. networkType è wifi, cellular, ethernet, other, none o unknown. installer è com.android.vending per le installazioni da Play e vuoto per quelle manuali. I valori vengono letti nel momento in cui chiami: richiama per aggiornare batteria o rete. Non ci sono IMEI o numero di serie: Android li blocca per tutte le app; usa getInstallId().

getInstallId bridge#

window.WebToApk.getInstallId(): String

WebToApk.getDeviceInfo() -> stringa JSON, NESSUNA autorizzazione richiesta WebToApk.getPhoneIdentity() -> operatore/SIM, richiede l'autorizzazione Telefono WebToApk.getInstallId() -> UUID stabile per installazione

Descritto dal suo gruppo, Informazioni su dispositivo / app, invece che da solo.

Esempio

Fornisce un ID casuale che resta lo stesso per questa installazione della tua app.

Restituisce: una stringa (un UUID come "3f2b…-…") subito. Richiede: niente.

Usalo come chiave del dispositivo per il tuo server o per un controllo "una prova gratuita per 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() })
});

Invia l'id al tuo server con un header e tienilo fuori dall'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();
}

Allegalo ai report di arresto anomalo così si possono individuare i problemi di una singola installazione.

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');
}

Note: L'ID viene creato al primo utilizzo e conservato nell'archivio dell'app. Resta lo stesso dopo gli aggiornamenti dell'app. Cambia quando l'utente disinstalla l'app o ne cancella i dati. Non è un ID hardware: Android non fornisce alle app IMEI o numero di serie. Lo stesso valore è presente anche in getDeviceInfo() come installId.

getPhoneIdentity bridge#

window.WebToApk.getPhoneIdentity(): String

WebToApk.getDeviceInfo() -> stringa JSON, NESSUNA autorizzazione richiesta WebToApk.getPhoneIdentity() -> operatore/SIM, richiede l'autorizzazione Telefono WebToApk.getInstallId() -> UUID stabile per installazione

Descritto dal suo gruppo, Informazioni su dispositivo / app, invece che da solo.

Esempio

Fornisce i dati dell'operatore mobile e della SIM: nome dell'operatore, paese della SIM, codice operatore, tipo di telefono e stato della SIM.

Restituisce: una stringa JSON subito: usa JSON.parse: { carrierName, simCountryIso, networkCountryIso, simOperator, phoneType, simState, hardwareIdAvailable: false }, oppure { error }. Richiede: attiva Telefono nel Passaggio 4 (Accesso) quando crei l'app; altrimenti restituisce { "error": "not_enabled" }.

Mostra l'operatore e scegli il prefisso del paese.

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"
}

Scegli un prefisso telefonico dal paese della SIM per un modulo di registrazione.

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();

Distingui un tablet solo Wi-Fi da un telefono 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
  }
}

Note: phoneType è gsm, cdma, sip o none (tablet solo Wi-Fi). simOperator è il codice MCC+MNC (es. "40445"). Non ci sono IMEI, MEID o numero di serie: hardwareIdAvailable è sempre false, perché Android li nasconde a tutte le app dalla versione 10; usa getInstallId() per un id stabile. L'opzione Telefono aggiunge un'autorizzazione sensibile alla tua scheda su Play; dichiara a cosa serve nel modulo Sicurezza dei dati.

isTv bridge#

window.WebToApk.isTv(): Boolean

Vero quando l'app è in esecuzione su una televisione (Android TV, Google TV, Fire TV).

Esempio

Dice alla pagina se l'app è in esecuzione su una televisione (Android TV, Google TV, Fire TV), così da mostrare un layout adatto al telecomando.

Restituisce: true o false (in modo sincrono). Richiede: niente. Con Supporto Android TV attivo nella procedura guidata, l'app ottiene anche un banner TV nel launcher della TV e, su una TV, la pagina riceve un chiaro anello di focus; link, pulsanti, campi ed elementi collegati con onclick o con un ruolo di pulsante diventano raggiungibili con le frecce del telecomando; OK fa clic sull'elemento in focus e Indietro funziona come su un telefono.

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); }

Scegli una schermata iniziale diversa per la TV e tieni il layout per telefono come predefinito.

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; });

Note: Prova con l'emulatore Android TV e il suo D-pad. La pubblicazione per TV su Google Play è un passaggio separato: in Play Console, abilita l'app per il fattore di forma Android TV (Release → Impostazioni avanzate → Fattori di forma) e aggiungi uno screenshot per TV.

Generato dal runtime dell'app e dai suoi file di esempio a ogni build della documentazione. Leggilo in Markdown · Tutte le famiglie.

Verificato rispetto al bridge distribuito il 22/10/2026.