aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/mygram/droid/Prefs.java
blob: 0ff295dd1ccaabff6a63ab535238ad97c3f52784 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/*
 * mygram-droid: Android TV client for Foldergram.
 * Copyright (C) 2026 Danilo M. <danix@danix.xyz>
 *
 * 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();
	}
}