/* * mygram-droid: Android TV client for Foldergram. * Copyright (C) 2026 Danilo M. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ package mygram.droid; import android.content.Context; import android.content.SharedPreferences; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; /** Settings stored on the device. */ final class Prefs { private static SharedPreferences prefs; private Prefs() { } static void init(Context c) { prefs = c.getSharedPreferences("mygram", Context.MODE_PRIVATE); } /** Base URL like "http://192.0.2.10:4141", or null before setup. */ static String server() { return prefs.getString("server", null); } /** Viewer password; empty for a public instance. */ static String password() { return prefs.getString("password", ""); } static int photoSeconds() { return prefs.getInt("photo", 8); } static int backgroundSeconds() { return prefs.getInt("background", 15); } static void setServer(String base, String password) { prefs.edit().putString("server", base).putString("password", password).apply(); } static void setPassword(String password) { prefs.edit().putString("password", password).apply(); } static void setTimes(int photo, int background) { prefs.edit().putInt("photo", photo).putInt("background", background).apply(); } static boolean validPin(String pin) { return pin.matches("[0-9]{4,8}"); } static boolean hasPin() { return !prefs.getString("pin", "").isEmpty(); } /** Stores "salt:sha256(salt + pin)", never the PIN; null removes it. */ static void setPin(String pin) { if (pin == null) { prefs.edit().remove("pin").apply(); return; } byte[] b = new byte[16]; new SecureRandom().nextBytes(b); String salt = hex(b); prefs.edit().putString("pin", salt + ":" + hash(salt, pin)).apply(); } static boolean checkPin(String pin) { String[] stored = prefs.getString("pin", "").split(":"); return stored.length == 2 && MessageDigest.isEqual(stored[1].getBytes(StandardCharsets.UTF_8), hash(stored[0], pin).getBytes(StandardCharsets.UTF_8)); } // ponytail: one SHA-256 round; a 4-8 digit PIN is a privacy screen, not a secret against someone with the device storage. static String hash(String salt, String pin) { try { return hex(MessageDigest.getInstance("SHA-256").digest((salt + pin).getBytes(StandardCharsets.UTF_8))); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(e); // every Java platform ships SHA-256 } } private static String hex(byte[] b) { StringBuilder s = new StringBuilder(); for (byte x : b) s.append(String.format("%02x", x)); return s.toString(); } }