package com.miniabs; import java.util.UUID; import org.json.JSONObject; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.util.Base64; /** Settings and sign-in state. */ final class Prefs { static final String MODE_JWT = "jwt"; // ABS 2.26+: short access token + refresh token static final String MODE_LEGACY = "legacy"; // older servers: one long-lived token static final String MODE_KEY = "key"; // API key pasted by the user private static Prefs instance; final SharedPreferences sp; private Prefs(Context c) { sp = PreferenceManager.getDefaultSharedPreferences(c); } static synchronized Prefs get(Context c) { if (instance == null) instance = new Prefs(c.getApplicationContext()); return instance; } private String s(String k) { return sp.getString(k, ""); } String server() { return s("server"); } String username() { return s("username"); } String token() { return s("access"); } String refreshToken() { return s("refresh"); } String mode() { return s("mode"); } boolean wifiOnly() { return sp.getBoolean("wifi_only", true); } boolean covers() { return sp.getBoolean("covers", true); } String libraryId() { return s("library_id"); } String libraryName() { return s("library_name"); } int skipBack() { return num("skip_back", 15); } int skipFwd() { return num("skip_fwd", 30); } private int num(String k, int def) { try { return Integer.parseInt(sp.getString(k, String.valueOf(def))); } catch (Exception e) { return def; } } boolean loggedIn() { return server().length() > 0 && token().length() > 0; } boolean canRefresh() { return MODE_JWT.equals(mode()) && refreshToken().length() > 0; } void setServer(String server) { sp.edit().putString("server", server).remove("trust_all").commit(); } synchronized void setAuth(String user, String mode, String access, String refresh) { SharedPreferences.Editor e = sp.edit() .putString("username", user) .putString("mode", mode) .putString("refresh", refresh == null ? "" : refresh); putToken(e, access); e.commit(); } synchronized void setTokens(String access, String refresh) { SharedPreferences.Editor e = sp.edit(); if (refresh != null && refresh.length() > 0) e.putString("refresh", refresh); putToken(e, access); e.commit(); } /** Stores the token with its lifetime measured on this phone's clock (old phones' clocks drift). */ private void putToken(SharedPreferences.Editor e, String access) { long lifetime = 0; try { String[] parts = access.split("\\."); if (parts.length == 3) { JSONObject p = new JSONObject(new String(Base64.decode(parts[1], Base64.URL_SAFE), "UTF-8")); long exp = p.optLong("exp", 0), iat = p.optLong("iat", 0); if (exp > 0 && iat > 0 && exp > iat) lifetime = exp - iat; } } catch (Exception ignored) { // not a JWT, or unreadable: treat as non-expiring } e.putString("access", access) .putLong("token_lifetime", lifetime) .putLong("token_obtained", System.currentTimeMillis()); } /** Seconds left on the access token; Long.MAX_VALUE when it doesn't expire. */ long tokenSecondsLeft() { long life = sp.getLong("token_lifetime", 0); if (life <= 0) return Long.MAX_VALUE; long age = (System.currentTimeMillis() - sp.getLong("token_obtained", 0)) / 1000; if (age < 0) return 0; // clock went backwards; be safe and refresh return life - age; } long tokenObtainedAt() { return sp.getLong("token_obtained", 0); } void logout() { sp.edit().remove("access").remove("refresh").remove("mode") .remove("library_id").remove("library_name").remove("last_episode").commit(); } void setLibrary(String id, String name) { sp.edit().putString("library_id", id).putString("library_name", name).commit(); } String deviceId() { String id = s("device_id"); if (id.length() == 0) { id = UUID.randomUUID().toString(); sp.edit().putString("device_id", id).commit(); } return id; } void setLastEpisode(Episode e) { sp.edit().putString("last_episode", e == null ? "" : e.toJson().toString()).commit(); } Episode lastEpisode() { String j = s("last_episode"); if (j.length() == 0) return null; try { return Episode.fromStored(new JSONObject(j)); } catch (Exception e) { return null; } } }