html2apk 0.11.0 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -528,6 +528,9 @@ Exemplos de aliases:
528
528
  | `acompanharLocalizacao()` | `watchLocation()` |
529
529
  | `pararLocalizacao()` | `stopLocationWatch()` |
530
530
  | `autenticarBiometria()` | `authenticateBiometric()` |
531
+ | `solicitarBloqueio()` | `requestDeviceLock()` |
532
+ | `solicitarSegundoPlano()` | `requestBackgroundExecution()` |
533
+ | `configurarInicioAutomatico()` | `setAutoStartOnBoot()` |
531
534
  | `salvarSeguro()` | `saveSecure()` |
532
535
  | `lerSeguro()` | `readSecure()` |
533
536
  | `removerSeguro()` | `deleteSecure()` |
@@ -741,11 +744,11 @@ Use OneSignal para pushes enviados remotamente pelo painel/API do OneSignal. Use
741
744
  Arquivos, galeria e compartilhamento:
742
745
 
743
746
  ```js
744
- const imagem = await escolherImagem();
745
- const imagens = await escolherImagens({ multiplas: true });
747
+ const imagem = await escolherImagem(); // Retorna: { uri, nome, tamanho, mimeType }
748
+ const imagens = await escolherImagens({ multiplas: true }); // Array de objetos
746
749
  const pdf = await escolherArquivo({ tipos: ["application/pdf"] });
747
750
  const arquivos = await escolherArquivos({ multiplo: true });
748
- const pasta = await escolherPasta();
751
+ const pasta = await escolherPasta(); // Retorna: { uri, nome, treeUri }
749
752
 
750
753
  await salvarArquivo({
751
754
  nome: "relatorio.txt",
@@ -765,11 +768,11 @@ aoReceberCompartilhamento((dados) => {
765
768
  });
766
769
 
767
770
  const texto = await ocr(imagem);
768
- console.log(texto.texto);
771
+ console.log(texto.texto); // Retorna: { texto, blocos: [...] }
769
772
 
770
773
  await falar("Ola mundo", { idioma: "pt-BR", velocidade: 1 });
771
774
  const voz = await ouvir({ idioma: "pt-BR" });
772
- console.log(voz.texto);
775
+ console.log(voz.texto); // Retorna: { texto, error }
773
776
 
774
777
  aoConectarBT((dispositivo) => {
775
778
  console.log("Bluetooth conectado", dispositivo.nome);
@@ -783,7 +786,7 @@ aoDarErroBT((erro) => {
783
786
  console.log("Erro Bluetooth", erro.mensagem || erro.message);
784
787
  });
785
788
 
786
- const dispositivos = await procurarBT();
789
+ const dispositivos = await procurarBT(); // Retorna: [{ id, nome, host }, ...]
787
790
  if (dispositivos[0]) {
788
791
  await conectarBT(dispositivos[0].id);
789
792
  await enviarBT({ mensagem: "Ola por Bluetooth" });
@@ -801,7 +804,7 @@ aoDarErroWiFi((erro) => {
801
804
  console.log("Erro Wi-Fi", erro.mensagem || erro.message);
802
805
  });
803
806
 
804
- const dispositivosWifi = await procurarWiFi();
807
+ const dispositivosWifi = await procurarWiFi(); // Retorna: [{ id, nome, host, porta }, ...]
805
808
  if (dispositivosWifi[0]) {
806
809
  await conectarWiFi(dispositivosWifi[0].id);
807
810
  await enviarWiFi({ mensagem: "Ola por Wi-Fi" });
@@ -882,7 +885,7 @@ await salvarArquivo("wallpaper.jpg", foto.base64, {
882
885
 
883
886
  const resultado = await definirPapelParede("wallpaper.jpg", {
884
887
  alvo: "inicio" // "inicio", "bloqueio" ou "ambos"
885
- });
888
+ }); // Retorna: { applied, systemApplied, lockApplied, error }
886
889
 
887
890
  console.log(resultado.applied, resultado.systemApplied, resultado.lockApplied);
888
891
  ```
@@ -892,17 +895,19 @@ console.log(resultado.applied, resultado.systemApplied, resultado.lockApplied);
892
895
  Camera, QR Code, localizacao, biometria e storage seguro:
893
896
 
894
897
  ```js
895
- const foto = await tirarFoto({ base64: true });
898
+ const foto = await tirarFoto({ base64: true }); // Retorna: { base64, mimeType, uri }
896
899
 
897
- const qr = await escanearQRCode();
900
+ const qr = await escanearQRCode(); // Retorna: { text, format, cancelled }
898
901
  if (qr) {
899
902
  console.log(qr.text);
900
903
  }
901
904
 
902
905
  const local = await obterLocalizacao({ altaPrecisao: true, timeoutMs: 10000 });
906
+ // Retorna: { latitude, longitude, precisao, altitude, error }
903
907
  console.log(local.latitude, local.longitude);
904
908
 
905
- const watch = await acompanharLocalizacao({ intervaloMs: 5000 });
909
+ const watch = await acompanharLocalizacao({ intervaloMs: 5000 });
910
+ // Retorna: { watchId, error }
906
911
  const pararEvento = aoMudarLocalizacao((evento) => {
907
912
  console.log(evento.latitude, evento.longitude);
908
913
  });
@@ -913,13 +918,36 @@ pararEvento();
913
918
  const bio = await autenticarBiometria({
914
919
  titulo: "Confirmar acesso",
915
920
  descricao: "Use a biometria do aparelho"
916
- });
921
+ }); // Retorna: { authenticated, supported, canceled, message }
917
922
 
918
923
  if (bio.authenticated) {
919
924
  await salvarSeguro("token", "abc123");
920
925
  const token = await lerSeguro("token");
921
926
  await removerSeguro("token");
922
927
  }
928
+
929
+ const auth = await solicitarBloqueio({
930
+ titulo: "Acesso Restrito",
931
+ descricao: "Confirme sua senha de tela"
932
+ }); // Retorna: { autenticado, suportado, cancelado, mensagem }
933
+
934
+ if (auth.autenticado) {
935
+ // Acesso permitido
936
+ }
937
+
938
+ const bg = await solicitarSegundoPlano();
939
+ // Retorna: { ok, abriuInicioAutomatico, abriuOtimizacaoBateria }
940
+ if (bg.ok) {
941
+ toast("Obrigado por permitir rodar em segundo plano!");
942
+ }
943
+
944
+ // Para abrir o aplicativo sozinho quando o aparelho for ligado:
945
+ await configurarInicioAutomatico(true); // Retorna: { ok, enabled }
946
+
947
+ const inicio = await obterLinkInicial(); // Retorna string: "html2apk://boot" ou "https://..."
948
+ if (inicio === "html2apk://boot") {
949
+ console.log("App abriu sozinho pelo boot do aparelho");
950
+ }
923
951
  ```
924
952
 
925
953
  O retorno de arquivos tem este formato:
@@ -938,7 +966,8 @@ O retorno de arquivos tem este formato:
938
966
  Microfone:
939
967
 
940
968
  ```js
941
- const inicio = await ouvirMic();
969
+ const inicio = await ouvirMic();
970
+ // Retorna: { recording: true, settingsOpened: boolean, error: string }
942
971
  if (inicio.settingsOpened) {
943
972
  console.log("Libere Microfone nas configuracoes e tente novamente");
944
973
  } else {
package/package.json CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "name": "html2apk",
3
- "version": "0.11.0",
4
- "author": { "name": "Dev Caio Multiversando", "email": "dev@caio.local" },
5
-
3
+ "version": "12.0.0",
4
+ "author": {
5
+ "name": "Dev Caio Multiversando",
6
+ "email": "dev@caio.local"
7
+ },
6
8
  "description": "Node CLI and library to turn an HTML/CSS/JS folder into an Android APK through Cordova.",
7
9
  "main": "index.js",
8
10
  "bin": {
@@ -25,7 +27,6 @@
25
27
  "desktop": "node bin/html2apk-desktop.js",
26
28
  "build-desktop-win": "node scripts/build-desktop-portable.js",
27
29
  "build-desktop-deb": "npx electron-builder --linux deb",
28
-
29
30
  "test": "node --test test/config.test.js",
30
31
  "build-win": "pkg . --targets node20-win-x64 --output dist/html2apk.exe",
31
32
  "build-linux": "pkg . --targets node20-linux-x64 --output dist/html2apk-linux",
@@ -995,7 +995,12 @@ function nativeFunctionLabHtml() {
995
995
  acompanharLocalizacao: { title: "acompanharLocalizacao()", run: async function () { var result = await fn("acompanharLocalizacao")({ intervaloMs: 5000 }); state.watchId = result && result.watchId; return result; } },
996
996
  pararLocalizacao: { title: "pararLocalizacao()", run: function () { return fn("pararLocalizacao")(state.watchId || ""); } },
997
997
  aoMudarLocalizacao: { title: "aoMudarLocalizacao()", run: function () { if (state.stopLocationEvent) { state.stopLocationEvent(); } state.stopLocationEvent = fn("aoMudarLocalizacao")(function (event) { log("localizacao:mudou", event, "ok"); }); return { listening: true }; } },
998
+ medirVelocidade: { title: "medirVelocidade()", run: async function () { if (state.pararMedicao) { await state.pararMedicao(); } state.pararMedicao = await fn("medirVelocidade")(function(kmh, local) { log("medirVelocidade", { kmh: kmh, original: local }, "ok"); }); return { measuring: true }; } },
999
+ pararVelocidade: { title: "parar medidor de velocidade", run: async function () { if (state.pararMedicao) { await state.pararMedicao(); state.pararMedicao = null; return { stopped: true }; } return { stopped: false }; } },
998
1000
  autenticarBiometria: { title: "autenticarBiometria()", run: function () { return fn("autenticarBiometria")({ titulo: "Teste html2apk", descricao: "Confirme para testar a bridge" }); } },
1001
+ solicitarBloqueio: { title: "solicitarBloqueio()", run: function () { return fn("solicitarBloqueio")({ titulo: "Acesso Restrito", descricao: "Confirme a senha de tela" }); } },
1002
+ solicitarSegundoPlano: { title: "solicitarSegundoPlano()", run: function () { return fn("solicitarSegundoPlano")(); } },
1003
+ configurarInicioAutomatico: { title: "configurarInicioAutomatico()", run: function () { state.autoStart = !state.autoStart; return fn("configurarInicioAutomatico")(state.autoStart); } },
999
1004
  salvarSeguro: { title: "salvarSeguro()", run: function () { return fn("salvarSeguro")("tokenTeste", { token: "abc123", criadoEm: Date.now() }); } },
1000
1005
  lerSeguro: { title: "lerSeguro()", run: function () { return fn("lerSeguro")("tokenTeste"); } },
1001
1006
  lerSeguroCompleto: { title: "lerSeguroCompleto()", run: function () { return fn("lerSeguroCompleto")("tokenTeste"); } },
@@ -1008,9 +1013,10 @@ function nativeFunctionLabHtml() {
1008
1013
  abrirConfigPapel: { title: "abrirConfiguracaoPapelParede()", run: function () { return fn("abrirConfiguracaoPapelParede")(); } },
1009
1014
  definirImagemEscolhida: { title: "imagem escolhida -> papel de parede", run: async function () { if (!state.lastImage) { state.lastImage = await fn("escolherImagem")(); } if (!state.lastImage || !state.lastImage.uri) { return { canceled: true }; } return fn("definirPapelParede")({ uri: state.lastImage.uri, alvo: "inicio", mimeType: state.lastImage.mimeType || "image/*" }); } },
1010
1015
 
1011
- registrarEventos: { title: "registrar eventos", run: function () { return registerEvents(); } },
1016
+ registrarEventos: { title: "registrarEventos()", run: function () { return fn("registrarEventos")({ aoClicarNotificacao: function (e) { alert("Clicou na notificacao: " + JSON.stringify(e)); }, aoMudarEstadoApp: function (e) { console.log("Estado mudou:", e); } }); } },
1012
1017
  obterNotificacaoInicial: { title: "obterNotificacaoInicial()", run: function () { return fn("obterNotificacaoInicial")(); } },
1013
- obterLinkInicial: { title: "obterLinkInicial()", run: function () { return fn("obterLinkInicial")(); } }
1018
+ obterLinkInicial: { title: "obterLinkInicial()", run: function () { return fn("obterLinkInicial")(); } },
1019
+ aoLigarDispositivo: { title: "aoLigarDispositivo()", run: function () { return fn("aoLigarDispositivo")(() => alert("App iniciou via boot!")); } }
1014
1020
  };
1015
1021
 
1016
1022
  var groups = [
@@ -1025,9 +1031,9 @@ function nativeFunctionLabHtml() {
1025
1031
  { title: "Arquivos e midia", ids: ["escolherImagem", "escolherImagens", "escolherArquivo", "escolherArquivos", "escolherVideo", "escolherPasta", "salvarArquivoPicker", "salvarArquivoCrud", "lerArquivo", "lerArquivoCompleto", "listarArquivos", "infoArquivo", "arquivoExiste", "abrirArquivo", "compartilharArquivo", "baixarArquivo", "baixarBase64", "baixarArquivoLocal", "excluirArquivo"] },
1026
1032
  { title: "Abrir apps externos", ids: ["abrirNoApp", "abrirForaDoApp", "abrirUrl", "abrirUrlExterno", "discar", "abrirMapa", "abrirWhatsapp"] },
1027
1033
  { title: "Diagnostico", ids: ["infoDispositivo", "infoRede", "infoBateria", "infoMemoria", "infoArmazenamento", "infoDesempenho", "appsAbertos", "infoAppsAbertos"] },
1028
- { title: "Localizacao e seguranca", ids: ["obterLocalizacao", "acompanharLocalizacao", "pararLocalizacao", "aoMudarLocalizacao", "autenticarBiometria", "salvarSeguro", "lerSeguro", "lerSeguroCompleto", "listarSeguro", "removerSeguro", "limparSeguro"] },
1034
+ { title: "Localizacao e seguranca", ids: ["obterLocalizacao", "acompanharLocalizacao", "pararLocalizacao", "aoMudarLocalizacao", "medirVelocidade", "pararVelocidade", "autenticarBiometria", "solicitarBloqueio", "solicitarSegundoPlano", "configurarInicioAutomatico", "salvarSeguro", "lerSeguro", "lerSeguroCompleto", "listarSeguro", "removerSeguro", "limparSeguro"] },
1029
1035
  { title: "Papel de parede", ids: ["infoPapelParede", "definirPapelParede", "abrirConfigPapel", "definirImagemEscolhida"] },
1030
- { title: "Eventos", ids: ["registrarEventos", "obterNotificacaoInicial", "obterLinkInicial"] }
1036
+ { title: "Eventos", ids: ["registrarEventos", "obterNotificacaoInicial", "obterLinkInicial", "aoLigarDispositivo"] }
1031
1037
  ];
1032
1038
 
1033
1039
  var safeActionIds = [
@@ -1051,7 +1057,7 @@ function nativeFunctionLabHtml() {
1051
1057
  "abrirSobreposicao", "abrirConfigPapel", "escolherImagem", "escolherImagens",
1052
1058
  "escolherArquivo", "escolherArquivos", "escolherVideo", "escolherPasta",
1053
1059
  "salvarArquivoPicker", "baixarArquivoLocal", "tirarFoto", "capturarVideo",
1054
- "escanearQRCode", "ouvir", "autenticarBiometria"
1060
+ "escanearQRCode", "ouvir", "autenticarBiometria", "solicitarBloqueio"
1055
1061
  ];
1056
1062
  var dangerActionIds = ["fecharApp", "minimizarApp", "limparSeguro", "excluirArquivo", "pararIconeFlutuante"];
1057
1063
  var initialSmokeIds = ["registrarEventos", "statusPermissoes", "infoDispositivo", "infoRede", "infoBateria", "volumeAtual"];
@@ -49,7 +49,7 @@
49
49
  <img src="../../../html2apk.png" alt="" class="brand-icon">
50
50
  <div>
51
51
  <strong>html<span>2apk</span></strong>
52
- <small id="appVersion">v0.11.0</small>
52
+ <small id="appVersion">v12.0.0</small>
53
53
  </div>
54
54
  </div>
55
55
 
@@ -673,6 +673,20 @@ const nativeCodeEntries = [
673
673
  returns: { pt: "Callback recebe os dados da notificação. Retorna função para cancelar.", en: "Callback receives the notification data. Returns an unsubscribe function." },
674
674
  handling: { pt: "Use para atualizar tela/estado quando `notificar()` ou uma notificacao agendada passar pela bridge. Para clique, use `aoClicarNotificacao()`.", en: "Use it to update UI/state when `notify()` or a scheduled notification goes through the bridge. For clicks, use `onNotificationClick()`." }
675
675
  },
676
+ {
677
+ syntax: { pt: "obterLinkInicial()", en: "getInitialLink()" },
678
+ java: "Intent / Activity",
679
+ description: { pt: "Retorna a URL que abriu o aplicativo (Deep Link), se houver.", en: "Returns the URL that opened the application (Deep Link), if any." },
680
+ returns: { pt: "String (ex: 'meuapp://conteudo/123').", en: "String (e.g. 'myapp://content/123')." },
681
+ handling: { pt: "Util para repassar campanhas de marketing no inicio do app.", en: "Useful for passing marketing campaigns at app startup." }
682
+ },
683
+ {
684
+ syntax: { pt: "aoLigarDispositivo(callback)", en: "onDeviceBoot(callback)" },
685
+ java: "Intent / BootReceiver",
686
+ description: { pt: "Executa uma funcao caso o aplicativo tenha sido aberto silenciosamente pelo boot do celular.", en: "Executes a function if the application was silently opened by the device boot." },
687
+ returns: { pt: "Nenhum.", en: "None." },
688
+ handling: { pt: "Depende de `configurarInicioAutomatico(true)` e permissao de sobreposicao no Android 10+.", en: "Depends on `setAutoStartOnBoot(true)` and overlay permission on Android 10+." }
689
+ },
676
690
  {
677
691
  syntax: { pt: "obterLinkInicial() / aoAbrirLink(fn)", en: "getInitialLink() / onOpenLink(fn)" },
678
692
  java: "getInitialLink",
@@ -974,6 +988,27 @@ const nativeCodeEntries = [
974
988
  returns: { pt: "{ supported, authenticated, canceled, message }.", en: "{ supported, authenticated, canceled, message }." },
975
989
  handling: { pt: "Funciona em Android 9+. Se `supported` vier falso, use PIN/senha do proprio app como fallback.", en: "Works on Android 9+. If `supported` is false, use your app's own PIN/password fallback." }
976
990
  },
991
+ {
992
+ syntax: { pt: "solicitarBloqueio({ titulo })", en: "requestDeviceLock({ title })" },
993
+ java: "KeyguardManager",
994
+ description: { pt: "Abre a tela de bloqueio do Android (PIN, padrao, senha).", en: "Opens Android device lock screen (PIN, pattern, password)." },
995
+ returns: { pt: "{ supported, authenticated, canceled, message }.", en: "{ supported, authenticated, canceled, message }." },
996
+ handling: { pt: "Funciona se o usuario tiver senha cadastrada.", en: "Works if the user has a secure lock screen configured." }
997
+ },
998
+ {
999
+ syntax: { pt: "solicitarSegundoPlano()", en: "requestBackgroundExecution()" },
1000
+ java: "Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS",
1001
+ description: { pt: "Solicita ao sistema que o app funcione ativamente no background e inicie junto com o aparelho.", en: "Requests the system to run the app actively in the background and autostart on boot." },
1002
+ returns: { pt: "{ ok, openedAutoStart, openedBatteryOptimization }.", en: "{ ok, openedAutoStart, openedBatteryOptimization }." },
1003
+ handling: { pt: "Usa permissoes restritas. Play Store pode rejeitar apps sem justificativa valida para manter CPU ligada.", en: "Uses restricted permissions. Play Store might reject apps without a valid reason to keep CPU awake." }
1004
+ },
1005
+ {
1006
+ syntax: { pt: "configurarInicioAutomatico(true/false)", en: "setAutoStartOnBoot(true/false)" },
1007
+ java: "BootReceiver -> Intent",
1008
+ description: { pt: "Define se o aplicativo deve ser aberto automaticamente quando o celular for ligado.", en: "Sets whether the app should automatically open when the device boots." },
1009
+ returns: { pt: "{ ok, enabled }.", en: "{ ok, enabled }." },
1010
+ handling: { pt: "Exige permissao de sobrepor a outros apps no Android 10+.", en: "Requires draw over other apps permission on Android 10+." }
1011
+ },
977
1012
  {
978
1013
  syntax: { pt: "salvarSeguro('token', valor) / lerSeguro('token')", en: "saveSecure('token', value) / readSecure('token')" },
979
1014
  java: "Android Keystore",
@@ -2226,6 +2261,83 @@ if (bio.authenticated) {
2226
2261
  }`
2227
2262
  }
2228
2263
  },
2264
+ {
2265
+ when: { pt: "Para exigir a senha/PIN/padrao da tela de bloqueio do aparelho.", en: "To require the device's lock screen PIN/pattern/password." },
2266
+ example: {
2267
+ pt: `const auth = await solicitarBloqueio({
2268
+ titulo: "Acesso Restrito",
2269
+ descricao: "Confirme a senha de tela"
2270
+ });
2271
+
2272
+ if (auth.autenticado) {
2273
+ abrirNoApp("#/area-secreta");
2274
+ } else if (!auth.suportado) {
2275
+ toast("Aparelho sem senha configurada");
2276
+ }`,
2277
+ en: `const auth = await requestDeviceLock({
2278
+ title: "Restricted Access",
2279
+ description: "Confirm device password"
2280
+ });
2281
+
2282
+ if (auth.authenticated) {
2283
+ openInApp("#/secret-area");
2284
+ } else if (!auth.supported) {
2285
+ toast("Device has no secure lock screen");
2286
+ }`
2287
+ }
2288
+ },
2289
+ {
2290
+ when: { pt: "Para manter o app ativo em segundo plano ou inicia-lo ao ligar (alarmes, rastreadores).", en: "To keep the app active in background or start on boot (alarms, trackers)." },
2291
+ example: {
2292
+ pt: `const resultado = await solicitarSegundoPlano();
2293
+
2294
+ if (resultado.abriuInicioAutomatico) {
2295
+ toast("Ligue a chave do nosso aplicativo");
2296
+ } else if (resultado.abriuOtimizacaoBateria) {
2297
+ toast("Selecione 'Nao Otimizar' ou 'Sem Restricoes'");
2298
+ } else {
2299
+ toast("Tudo pronto, permissao ja ativa!");
2300
+ }`,
2301
+ en: `const result = await requestBackgroundExecution();
2302
+
2303
+ if (result.openedAutoStart) {
2304
+ toast("Turn on the switch for our application");
2305
+ } else if (result.openedBatteryOptimization) {
2306
+ toast("Select 'No Restrictions' or 'Don't Optimize'");
2307
+ } else {
2308
+ toast("All set, permission already granted!");
2309
+ }`
2310
+ }
2311
+ },
2312
+ {
2313
+ when: { pt: "Para fazer o app abrir sozinho na tela do usuario assim que o celular for ligado.", en: "To make the app automatically open on the user's screen as soon as the device boots." },
2314
+ example: {
2315
+ pt: `// 1. Opcional mas recomendado: pedir permissao de sobreposicao
2316
+ // para nao ser bloqueado no Android 10+
2317
+ await abrirSobreposicao();
2318
+
2319
+ // 2. Ligar a chave
2320
+ await configurarInicioAutomatico(true);
2321
+
2322
+ // 3. Ao iniciar seu app, use o ouvinte para se esconder:
2323
+ aoLigarDispositivo(async () => {
2324
+ console.log("App abriu sozinho pelo boot!");
2325
+ await minimizarApp(); // Esconde a tela na mesma hora
2326
+ });`,
2327
+ en: `// 1. Optional but recommended: request overlay permission
2328
+ // to avoid being blocked on Android 10+
2329
+ await openOverlay();
2330
+
2331
+ // 2. Turn on the switch
2332
+ await setAutoStartOnBoot(true);
2333
+
2334
+ // 3. When starting your app, use the listener:
2335
+ onDeviceBoot(async () => {
2336
+ console.log("App opened automatically by boot!");
2337
+ await minimizeApp(); // Hides the app silently
2338
+ });`
2339
+ }
2340
+ },
2229
2341
  {
2230
2342
  when: { pt: "Para guardar tokens ou preferencias sensiveis cifradas pelo Android Keystore.", en: "To store tokens or sensitive preferences encrypted by Android Keystore." },
2231
2343
  example: {
@@ -4085,7 +4197,7 @@ async function init() {
4085
4197
  elements.iconPreview.src = iconPreviewPath(state.defaultIconPath);
4086
4198
  }
4087
4199
  } catch {
4088
- elements.appVersion.textContent = "v0.11.0";
4200
+ elements.appVersion.textContent = "v12.0.0";
4089
4201
  }
4090
4202
 
4091
4203
  setTimeout(finishBoot, 1800);
@@ -40,6 +40,7 @@
40
40
  <uses-feature android:name="android.hardware.nfc" android:required="false" />
41
41
  <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
42
42
  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
43
+ <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
43
44
  </config-file>
44
45
 
45
46
  <config-file target="AndroidManifest.xml" parent="/manifest/application">
@@ -16,5 +16,18 @@ public class BootReceiver extends BroadcastReceiver {
16
16
  }
17
17
 
18
18
  NotificationStore.rescheduleAll(context, exactAllowed);
19
+
20
+ android.content.SharedPreferences prefs = context.getSharedPreferences(Html2ApkBridge.PREFS_NAME, Context.MODE_PRIVATE);
21
+ if (prefs.getBoolean("html2apk_boot_start", false)) {
22
+ try {
23
+ Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
24
+ if (launchIntent != null) {
25
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
26
+ launchIntent.putExtra(Html2ApkBridge.EXTRA_INITIAL_LINK, "html2apk://boot");
27
+ context.startActivity(launchIntent);
28
+ }
29
+ } catch (Exception ignored) {
30
+ }
31
+ }
19
32
  }
20
33
  }
@@ -68,16 +68,10 @@ public class FloatingIconService extends Service {
68
68
 
69
69
  ImageView icon = new ImageView(this);
70
70
  int size = dp(58);
71
- int padding = dp(8);
72
71
  icon.setImageDrawable(getApplicationInfo().loadIcon(getPackageManager()));
73
- icon.setPadding(padding, padding, padding, padding);
72
+ icon.setScaleType(ImageView.ScaleType.FIT_CENTER);
74
73
  icon.setAlpha(opacity);
75
74
 
76
- GradientDrawable background = new GradientDrawable();
77
- background.setShape(GradientDrawable.OVAL);
78
- background.setColor(0xff126fff);
79
- icon.setBackground(background);
80
-
81
75
  int windowType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
82
76
  ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
83
77
  : WindowManager.LayoutParams.TYPE_PHONE;
@@ -150,6 +150,7 @@ public class Html2ApkBridge extends CordovaPlugin {
150
150
  private static final int REQUEST_CAPTURE_PHOTO = 7414;
151
151
  private static final int REQUEST_CAPTURE_VIDEO = 7415;
152
152
  private static final int REQUEST_SPEECH_RECOGNITION = 7416;
153
+ private static final int REQUEST_DEVICE_CREDENTIAL = 7417;
153
154
  private static final String PREFS_NAME = "html2apk_bridge";
154
155
  private static final String PREF_PERMISSION_PREFIX = "permission_requested_";
155
156
  private static final String STORED_FILES_DIR = "html2apk-files";
@@ -176,6 +177,7 @@ public class Html2ApkBridge extends CordovaPlugin {
176
177
  private CallbackContext mediaCaptureCallback;
177
178
  private CallbackContext pendingLocationCallback;
178
179
  private CallbackContext biometricCallback;
180
+ private CallbackContext deviceCredentialCallback;
179
181
  private CallbackContext pendingDownloadCallback;
180
182
  private CallbackContext speechRecognitionCallback;
181
183
  private CallbackContext pendingSpeakCallback;
@@ -804,6 +806,21 @@ public class Html2ApkBridge extends CordovaPlugin {
804
806
  return true;
805
807
  }
806
808
 
809
+ if ("requestDeviceLock".equals(action)) {
810
+ requestDeviceLock(args.optJSONObject(0), callbackContext);
811
+ return true;
812
+ }
813
+
814
+ if ("requestBackgroundExecution".equals(action)) {
815
+ requestBackgroundExecution(args.optJSONObject(0), callbackContext);
816
+ return true;
817
+ }
818
+
819
+ if ("setAutoStartOnBoot".equals(action)) {
820
+ setAutoStartOnBoot(args.optJSONObject(0), callbackContext);
821
+ return true;
822
+ }
823
+
807
824
  if ("saveSecureItem".equals(action)) {
808
825
  callbackContext.success(saveSecureItem(args.optJSONObject(0)));
809
826
  return true;
@@ -1109,6 +1126,11 @@ public class Html2ApkBridge extends CordovaPlugin {
1109
1126
 
1110
1127
  if (requestCode == REQUEST_CAPTURE_PHOTO || requestCode == REQUEST_CAPTURE_VIDEO) {
1111
1128
  handleMediaCaptureResult(resultCode, intent);
1129
+ return;
1130
+ }
1131
+
1132
+ if (requestCode == REQUEST_DEVICE_CREDENTIAL) {
1133
+ handleDeviceCredentialResult(resultCode);
1112
1134
  }
1113
1135
  }
1114
1136
 
@@ -2169,12 +2191,20 @@ public class Html2ApkBridge extends CordovaPlugin {
2169
2191
  intent.putExtra(Intent.EXTRA_TITLE, title);
2170
2192
  if (streams.size() == 1) {
2171
2193
  intent.putExtra(Intent.EXTRA_STREAM, streams.get(0));
2194
+ intent.setClipData(ClipData.newRawUri("", streams.get(0)));
2172
2195
  intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2173
2196
  } else if (streams.size() > 1) {
2174
2197
  intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, streams);
2198
+ ClipData clipData = ClipData.newRawUri("", streams.get(0));
2199
+ for (int i = 1; i < streams.size(); i++) {
2200
+ clipData.addItem(new ClipData.Item(streams.get(i)));
2201
+ }
2202
+ intent.setClipData(clipData);
2175
2203
  intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2176
2204
  }
2177
- cordova.getActivity().startActivity(Intent.createChooser(intent, title));
2205
+ Intent chooser = Intent.createChooser(intent, title);
2206
+ chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
2207
+ cordova.getActivity().startActivity(chooser);
2178
2208
 
2179
2209
  JSONObject result = new JSONObject();
2180
2210
  result.put("ok", true);
@@ -3709,12 +3739,16 @@ public class Html2ApkBridge extends CordovaPlugin {
3709
3739
  Intent intent = new Intent(Intent.ACTION_SEND);
3710
3740
  intent.setType("application/vnd.android.package-archive");
3711
3741
  intent.putExtra(Intent.EXTRA_STREAM, uri);
3742
+ intent.setClipData(ClipData.newRawUri("", uri));
3712
3743
  intent.putExtra(Intent.EXTRA_TITLE, title);
3713
3744
  if (text.length() > 0) {
3714
3745
  intent.putExtra(Intent.EXTRA_TEXT, text);
3715
3746
  }
3716
3747
  intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
3717
- cordova.getActivity().startActivity(Intent.createChooser(intent, title));
3748
+
3749
+ Intent chooser = Intent.createChooser(intent, title);
3750
+ chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
3751
+ cordova.getActivity().startActivity(chooser);
3718
3752
 
3719
3753
  JSONObject result = storedFileResult(outputFile, "application/vnd.android.package-archive", "apk");
3720
3754
  String[] splitSourceDirs = appInfo.splitSourceDirs;
@@ -6180,6 +6214,143 @@ public class Html2ApkBridge extends CordovaPlugin {
6180
6214
  biometricCallback = null;
6181
6215
  }
6182
6216
 
6217
+ private void requestDeviceLock(final JSONObject options, final CallbackContext callbackContext) throws Exception {
6218
+ if (deviceCredentialCallback != null) {
6219
+ rejectBusyCallback(callbackContext, "Device lock authentication");
6220
+ return;
6221
+ }
6222
+
6223
+ KeyguardManager keyguardManager = (KeyguardManager) context().getSystemService(Context.KEYGUARD_SERVICE);
6224
+ if (keyguardManager == null || !keyguardManager.isDeviceSecure()) {
6225
+ JSONObject result = new JSONObject();
6226
+ result.put("supported", false);
6227
+ result.put("suportado", false);
6228
+ result.put("authenticated", false);
6229
+ result.put("autenticado", false);
6230
+ result.put("canceled", false);
6231
+ result.put("cancelado", false);
6232
+ result.put("message", "Device secure lock screen is not configured.");
6233
+ result.put("mensagem", "A tela de bloqueio do dispositivo nao esta configurada.");
6234
+ callbackContext.success(result);
6235
+ return;
6236
+ }
6237
+
6238
+ final JSONObject safeOptions = options == null ? new JSONObject() : options;
6239
+ String title = safeOptions.optString("titulo", safeOptions.optString("title", "Autenticacao"));
6240
+ String description = safeOptions.optString("descricao", safeOptions.optString("description", ""));
6241
+
6242
+ Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(title, description);
6243
+ if (intent == null) {
6244
+ JSONObject result = new JSONObject();
6245
+ result.put("supported", false);
6246
+ result.put("suportado", false);
6247
+ result.put("authenticated", false);
6248
+ result.put("autenticado", false);
6249
+ result.put("canceled", false);
6250
+ result.put("cancelado", false);
6251
+ result.put("message", "Could not create device credential intent.");
6252
+ result.put("mensagem", "Nao foi possivel criar a tela de autenticacao.");
6253
+ callbackContext.success(result);
6254
+ return;
6255
+ }
6256
+
6257
+ deviceCredentialCallback = callbackContext;
6258
+ cordova.setActivityResultCallback(this);
6259
+ cordova.getActivity().startActivityForResult(intent, REQUEST_DEVICE_CREDENTIAL);
6260
+ }
6261
+
6262
+ private void handleDeviceCredentialResult(int resultCode) {
6263
+ CallbackContext callback = deviceCredentialCallback;
6264
+ deviceCredentialCallback = null;
6265
+ if (callback == null) {
6266
+ return;
6267
+ }
6268
+
6269
+ try {
6270
+ boolean authenticated = resultCode == Activity.RESULT_OK;
6271
+ boolean canceled = resultCode == Activity.RESULT_CANCELED;
6272
+ JSONObject result = new JSONObject();
6273
+ result.put("supported", true);
6274
+ result.put("suportado", true);
6275
+ result.put("authenticated", authenticated);
6276
+ result.put("autenticado", authenticated);
6277
+ result.put("canceled", canceled);
6278
+ result.put("cancelado", canceled);
6279
+ result.put("message", authenticated ? "" : "Authentication failed or canceled.");
6280
+ result.put("mensagem", authenticated ? "" : "Autenticacao falhou ou cancelada.");
6281
+ callback.success(result);
6282
+ } catch (Exception error) {
6283
+ callback.error(error.getMessage());
6284
+ }
6285
+ }
6286
+
6287
+ private void requestBackgroundExecution(final JSONObject options, final CallbackContext callbackContext) throws Exception {
6288
+ boolean openedAutoStart = false;
6289
+ boolean openedBatteryOpt = false;
6290
+
6291
+ try {
6292
+ Intent intent = new Intent();
6293
+ String manufacturer = Build.MANUFACTURER;
6294
+ if ("xiaomi".equalsIgnoreCase(manufacturer)) {
6295
+ intent.setComponent(new android.content.ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity"));
6296
+ } else if ("oppo".equalsIgnoreCase(manufacturer)) {
6297
+ intent.setComponent(new android.content.ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity"));
6298
+ } else if ("vivo".equalsIgnoreCase(manufacturer)) {
6299
+ intent.setComponent(new android.content.ComponentName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity"));
6300
+ } else if ("Letv".equalsIgnoreCase(manufacturer)) {
6301
+ intent.setComponent(new android.content.ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity"));
6302
+ } else if ("Honor".equalsIgnoreCase(manufacturer)) {
6303
+ intent.setComponent(new android.content.ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity"));
6304
+ } else if ("huawei".equalsIgnoreCase(manufacturer)) {
6305
+ intent.setComponent(new android.content.ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity"));
6306
+ }
6307
+
6308
+ java.util.List<android.content.pm.ResolveInfo> list = context().getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
6309
+ if (list.size() > 0) {
6310
+ cordova.getActivity().startActivity(intent);
6311
+ openedAutoStart = true;
6312
+ }
6313
+ } catch (Exception e) {
6314
+ // Ignorar falhas do AutoStart
6315
+ }
6316
+
6317
+ try {
6318
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
6319
+ android.os.PowerManager pm = (android.os.PowerManager) context().getSystemService(Context.POWER_SERVICE);
6320
+ String packageName = context().getPackageName();
6321
+ if (pm != null && !pm.isIgnoringBatteryOptimizations(packageName)) {
6322
+ Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
6323
+ intent.setData(Uri.parse("package:" + packageName));
6324
+ cordova.getActivity().startActivity(intent);
6325
+ openedBatteryOpt = true;
6326
+ }
6327
+ }
6328
+ } catch (Exception e) {
6329
+ // Ignorar falhas da bateria
6330
+ }
6331
+
6332
+ JSONObject result = new JSONObject();
6333
+ result.put("openedAutoStart", openedAutoStart);
6334
+ result.put("abriuInicioAutomatico", openedAutoStart);
6335
+ result.put("openedBatteryOptimization", openedBatteryOpt);
6336
+ result.put("abriuOtimizacaoBateria", openedBatteryOpt);
6337
+ result.put("ok", openedAutoStart || openedBatteryOpt);
6338
+
6339
+ callbackContext.success(result);
6340
+ }
6341
+
6342
+ private void setAutoStartOnBoot(final JSONObject options, final CallbackContext callbackContext) {
6343
+ boolean enable = options != null && options.optBoolean("ativar", options.optBoolean("enable", false));
6344
+ SharedPreferences prefs = context().getSharedPreferences(Html2ApkBridge.PREFS_NAME, Context.MODE_PRIVATE);
6345
+ prefs.edit().putBoolean("html2apk_boot_start", enable).apply();
6346
+
6347
+ JSONObject result = new JSONObject();
6348
+ result.put("ok", true);
6349
+ result.put("enabled", enable);
6350
+ result.put("ativado", enable);
6351
+ callbackContext.success(result);
6352
+ }
6353
+
6183
6354
  private JSONObject permissionStatus(JSONArray requested) throws Exception {
6184
6355
  JSONArray permissions = requested == null ? new JSONArray() : requested;
6185
6356
  JSONObject result = new JSONObject();
@@ -1327,9 +1327,47 @@ var api = {
1327
1327
  aoMudarLocalizacao: function (listener) {
1328
1328
  return onEvent("localizacao:mudou", listener);
1329
1329
  },
1330
+ medirVelocidade: function (callback, options) {
1331
+ var listener = function (local) {
1332
+ if (typeof callback !== "function") return;
1333
+ var ms = typeof local.velocidade === "number" ? local.velocidade : 0;
1334
+ var kmh = ms * 3.6;
1335
+ callback(kmh, local);
1336
+ };
1337
+ var stopEvent = api.aoMudarLocalizacao(listener);
1338
+ return api.acompanharLocalizacao(Object.assign({ altaPrecisao: true, intervaloMs: 2000 }, options || {}))
1339
+ .then(function (result) {
1340
+ var watchId = result && result.watchId;
1341
+ return function pararMedicao() {
1342
+ stopEvent();
1343
+ if (watchId) {
1344
+ return api.pararLocalizacao(watchId);
1345
+ }
1346
+ return Promise.resolve();
1347
+ };
1348
+ });
1349
+ },
1330
1350
  autenticarBiometria: function (options) {
1331
1351
  return call("authenticateBiometric", [options || {}]);
1332
1352
  },
1353
+ solicitarBloqueio: function (options) {
1354
+ return call("requestDeviceLock", [options || {}]);
1355
+ },
1356
+ solicitarSegundoPlano: function (options) {
1357
+ return call("requestBackgroundExecution", [options || {}]);
1358
+ },
1359
+ configurarInicioAutomatico: function (enable) {
1360
+ return call("setAutoStartOnBoot", [{ ativar: !!enable }]);
1361
+ },
1362
+ aoLigarDispositivo: function (callback) {
1363
+ if (typeof callback === "function") {
1364
+ call("getInitialLink").then(function(link) {
1365
+ if (link === "html2apk://boot") {
1366
+ callback();
1367
+ }
1368
+ });
1369
+ }
1370
+ },
1333
1371
  salvarSeguro: function (keyOrOptions, value, options) {
1334
1372
  return call("saveSecureItem", [secureItemOptions(keyOrOptions, value, options)]);
1335
1373
  },
@@ -1649,7 +1687,12 @@ Object.assign(api, {
1649
1687
  watchLocation: api.acompanharLocalizacao,
1650
1688
  stopLocationWatch: api.pararLocalizacao,
1651
1689
  onLocationChange: api.aoMudarLocalizacao,
1690
+ measureSpeed: api.medirVelocidade,
1652
1691
  authenticateBiometric: api.autenticarBiometria,
1692
+ requestDeviceLock: api.solicitarBloqueio,
1693
+ requestBackgroundExecution: api.solicitarSegundoPlano,
1694
+ setAutoStartOnBoot: api.configurarInicioAutomatico,
1695
+ onDeviceBoot: api.aoLigarDispositivo,
1653
1696
  saveSecure: api.salvarSeguro,
1654
1697
  secureSet: api.salvarSeguro,
1655
1698
  readSecure: api.lerSeguro,
@@ -1308,9 +1308,47 @@
1308
1308
  aoMudarLocalizacao: function (listener) {
1309
1309
  return onEvent("localizacao:mudou", listener);
1310
1310
  },
1311
+ medirVelocidade: function (callback, options) {
1312
+ var listener = function (local) {
1313
+ if (typeof callback !== "function") return;
1314
+ var ms = typeof local.velocidade === "number" ? local.velocidade : 0;
1315
+ var kmh = ms * 3.6;
1316
+ callback(kmh, local);
1317
+ };
1318
+ var stopEvent = api.aoMudarLocalizacao(listener);
1319
+ return api.acompanharLocalizacao(Object.assign({ altaPrecisao: true, intervaloMs: 2000 }, options || {}))
1320
+ .then(function (result) {
1321
+ var watchId = result && result.watchId;
1322
+ return function pararMedicao() {
1323
+ stopEvent();
1324
+ if (watchId) {
1325
+ return api.pararLocalizacao(watchId);
1326
+ }
1327
+ return Promise.resolve();
1328
+ };
1329
+ });
1330
+ },
1311
1331
  autenticarBiometria: function (options) {
1312
1332
  return call("authenticateBiometric", [options || {}]);
1313
1333
  },
1334
+ solicitarBloqueio: function (options) {
1335
+ return call("requestDeviceLock", [options || {}]);
1336
+ },
1337
+ solicitarSegundoPlano: function (options) {
1338
+ return call("requestBackgroundExecution", [options || {}]);
1339
+ },
1340
+ configurarInicioAutomatico: function (enable) {
1341
+ return call("setAutoStartOnBoot", [{ ativar: !!enable }]);
1342
+ },
1343
+ aoLigarDispositivo: function (callback) {
1344
+ if (typeof callback === "function") {
1345
+ call("getInitialLink").then(function(link) {
1346
+ if (link === "html2apk://boot") {
1347
+ callback();
1348
+ }
1349
+ });
1350
+ }
1351
+ },
1314
1352
  salvarSeguro: function (keyOrOptions, value, options) {
1315
1353
  return call("saveSecureItem", [secureItemOptions(keyOrOptions, value, options)]);
1316
1354
  },
@@ -1621,7 +1659,12 @@
1621
1659
  watchLocation: api.acompanharLocalizacao,
1622
1660
  stopLocationWatch: api.pararLocalizacao,
1623
1661
  onLocationChange: api.aoMudarLocalizacao,
1662
+ measureSpeed: api.medirVelocidade,
1624
1663
  authenticateBiometric: api.autenticarBiometria,
1664
+ requestDeviceLock: api.solicitarBloqueio,
1665
+ requestBackgroundExecution: api.solicitarSegundoPlano,
1666
+ setAutoStartOnBoot: api.configurarInicioAutomatico,
1667
+ onDeviceBoot: api.aoLigarDispositivo,
1625
1668
  saveSecure: api.salvarSeguro,
1626
1669
  secureSet: api.salvarSeguro,
1627
1670
  readSecure: api.lerSeguro,