Sem servidor o app não finge mais que está tudo normal: - o app guarda a última aparência e os ajustes vindos da placa (ponte CarinhaApp); a cópia do APK abre com eles; - a carinha dorme e não acorda com nada; no canto direito aparece a placa ✕ celular animada com "sem conexão com a placa"; - o app tenta a placa a cada 10 s e volta sozinho; se a placa cai com a página aberta, em 15 s ela dorme e mostra o aviso; - endereço padrão passa a ser a Orange Pi (192.168.1.35:8100). Também no app (0.9.0): - modo quiosque como dono do aparelho (Quiosque/Admin, menu "Modo quiosque"); a Samsung recusa com bootloader desbloqueado, vale no LineageOS; - carinha como tela inicial (HOME); - android/enxugar-s10.ps1 desativa 57 apps sem uso no S10 (-Reativar desfaz). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
119 lines
4.9 KiB
Java
119 lines
4.9 KiB
Java
package br.carinha.rosto;
|
||
|
||
import android.Manifest;
|
||
import android.app.Activity;
|
||
import android.app.ActivityManager;
|
||
import android.app.admin.DevicePolicyManager;
|
||
import android.app.admin.SystemUpdatePolicy;
|
||
import android.content.ComponentName;
|
||
import android.content.Context;
|
||
import android.content.Intent;
|
||
import android.content.IntentFilter;
|
||
import android.os.BatteryManager;
|
||
import android.os.Build;
|
||
import android.provider.Settings;
|
||
import android.util.Log;
|
||
|
||
/**
|
||
* Modo quiosque: com o app como dono do aparelho, a carinha é a tela inicial,
|
||
* abre sozinha ao ligar e ninguém sai dela (sem barra de status, sem tela de bloqueio).
|
||
* Sem ser dono, tudo aqui é ignorado e o app funciona como antes.
|
||
*/
|
||
public final class Quiosque {
|
||
private static final String TAG = "CarinhaQuiosque";
|
||
/** Pausado pelo menu para manutenção; volta sozinho quando o celular reinicia. */
|
||
private static boolean pausado;
|
||
|
||
private Quiosque() { }
|
||
|
||
private static DevicePolicyManager dpm(Context c) {
|
||
return (DevicePolicyManager) c.getSystemService(Context.DEVICE_POLICY_SERVICE);
|
||
}
|
||
|
||
private static ComponentName admin(Context c) { return new ComponentName(c, Admin.class); }
|
||
|
||
public static boolean dono(Context c) {
|
||
DevicePolicyManager d = dpm(c);
|
||
return d != null && d.isDeviceOwnerApp(c.getPackageName());
|
||
}
|
||
|
||
public static boolean pausado() { return pausado; }
|
||
|
||
/** Chamado no onResume: reaplica as travas (idempotente). */
|
||
public static void aplicar(Activity a) {
|
||
if (pausado || !dono(a)) return;
|
||
DevicePolicyManager d = dpm(a);
|
||
ComponentName adm = admin(a);
|
||
String pacote = a.getPackageName();
|
||
try {
|
||
d.setLockTaskPackages(adm, new String[]{pacote});
|
||
// a carinha é a tela inicial: botão Home e o fim do boot caem nela
|
||
IntentFilter inicio = new IntentFilter(Intent.ACTION_MAIN);
|
||
inicio.addCategory(Intent.CATEGORY_HOME);
|
||
inicio.addCategory(Intent.CATEGORY_DEFAULT);
|
||
d.addPersistentPreferredActivity(adm, inicio, new ComponentName(a, MainActivity.class));
|
||
// tela ligada enquanto estiver na tomada, USB ou carregador sem fio
|
||
d.setGlobalSetting(adm, Settings.Global.STAY_ON_WHILE_PLUGGED_IN, String.valueOf(
|
||
BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS));
|
||
if (Build.VERSION.SDK_INT >= 23) {
|
||
d.setKeyguardDisabled(adm, true);
|
||
d.setStatusBarDisabled(adm, true);
|
||
// atualização do sistema só de madrugada (3h–5h), nunca no meio do dia
|
||
d.setSystemUpdatePolicy(adm, SystemUpdatePolicy.createWindowedInstallPolicy(180, 300));
|
||
// câmera e microfone sem perguntar, inclusive depois de reinstalar o app
|
||
for (String p : new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO})
|
||
d.setPermissionGrantState(adm, pacote, p, DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
|
||
}
|
||
// o botão Ligar continua abrindo o menu de desligar/reiniciar
|
||
if (Build.VERSION.SDK_INT >= 28)
|
||
d.setLockTaskFeatures(adm, DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS);
|
||
if (!travado(a)) a.startLockTask();
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "aplicar: " + e);
|
||
}
|
||
}
|
||
|
||
/** Solta a tela até o próximo reinício, para mexer nas configurações do Android. */
|
||
public static void pausar(Activity a) {
|
||
if (!dono(a)) return;
|
||
pausado = true;
|
||
DevicePolicyManager d = dpm(a);
|
||
ComponentName adm = admin(a);
|
||
try {
|
||
if (travado(a)) a.stopLockTask();
|
||
d.clearPackagePersistentPreferredActivities(adm, a.getPackageName());
|
||
if (Build.VERSION.SDK_INT >= 23) {
|
||
d.setKeyguardDisabled(adm, false);
|
||
d.setStatusBarDisabled(adm, false);
|
||
}
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "pausar: " + e);
|
||
}
|
||
}
|
||
|
||
/** Volta a travar sem precisar reiniciar. */
|
||
public static void retomar(Activity a) {
|
||
pausado = false;
|
||
aplicar(a);
|
||
}
|
||
|
||
/** Deixa de ser dono do aparelho de vez (para desinstalar ou trocar de app). */
|
||
@SuppressWarnings("deprecation")
|
||
public static void remover(Activity a) {
|
||
if (!dono(a)) return;
|
||
pausar(a);
|
||
try {
|
||
dpm(a).clearDeviceOwnerApp(a.getPackageName());
|
||
} catch (Exception e) {
|
||
Log.e(TAG, "remover: " + e);
|
||
}
|
||
}
|
||
|
||
private static boolean travado(Activity a) {
|
||
ActivityManager am = (ActivityManager) a.getSystemService(Context.ACTIVITY_SERVICE);
|
||
if (am == null) return false;
|
||
if (Build.VERSION.SDK_INT >= 23) return am.getLockTaskModeState() != ActivityManager.LOCK_TASK_MODE_NONE;
|
||
return am.isInLockTaskMode();
|
||
}
|
||
}
|