package com.plusonelabs.calendar.prefs; import android.content.Context; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import android.view.ContextThemeWrapper; import com.plusonelabs.calendar.DateUtil; import com.plusonelabs.calendar.EndedSomeTimeAgo; import com.plusonelabs.calendar.R; import com.plusonelabs.calendar.widget.EventEntryLayout; import org.joda.time.DateTimeZone; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; import static com.plusonelabs.calendar.EventAppWidgetProvider.getWidgetIds; import static com.plusonelabs.calendar.Theme.themeNameToResId; import static com.plusonelabs.calendar.prefs.ApplicationPreferences.*; import static com.plusonelabs.calendar.prefs.SettingsStorage.*; /** * @author yvolk@yurivolkov.com */ public class InstanceSettings { private static volatile boolean instancesLoaded = false; private static final Map<Integer, InstanceSettings> instances = new ConcurrentHashMap<>(); private final Context context; private volatile ContextThemeWrapper entryThemeContext = null; private volatile ContextThemeWrapper headerThemeContext = null; private final int widgetId; private final String widgetInstanceName; private boolean justCreated = true; private Set<String> activeCalendars = new HashSet<>(); private int eventRange = Integer.valueOf(PREF_EVENT_RANGE_DEFAULT); private EndedSomeTimeAgo eventsEnded = EndedSomeTimeAgo.NONE; private boolean fillAllDayEvents = PREF_FILL_ALL_DAY_DEFAULT; private String hideBasedOnKeywords = ""; private int pastEventsBackgroundColor = PREF_PAST_EVENTS_BACKGROUND_COLOR_DEFAULT; private boolean showDaysWithoutEvents = false; private boolean showDayHeaders = true; private boolean showPastEventsWithDefaultColor = false; private boolean showEndTime = PREF_SHOW_END_TIME_DEFAULT; private boolean showLocation = PREF_SHOW_LOCATION_DEFAULT; private String dateFormat = PREF_DATE_FORMAT_DEFAULT; private boolean abbreviateDates = PREF_ABBREVIATE_DATES_DEFAULT; private String lockedTimeZoneId = ""; private EventEntryLayout eventEntryLayout = EventEntryLayout.DEFAULT; private boolean titleMultiline = PREF_MULTILINE_TITLE_DEFAULT; private boolean showOnlyClosestInstanceOfRecurringEvent = false; private boolean indicateAlerts = true; private boolean indicateRecurring = false; private String entryTheme = PREF_ENTRY_THEME_DEFAULT; private String headerTheme = PREF_HEADER_THEME_DEFAULT; private boolean showWidgetHeader = true; private int backgroundColor = PREF_BACKGROUND_COLOR_DEFAULT; private String textSizeScale = PREF_TEXT_SIZE_SCALE_DEFAULT; private String dayHeaderAlignment = PREF_DAY_HEADER_ALIGNMENT_DEFAULT; @NonNull public static InstanceSettings fromId(Context context, Integer widgetId) { ensureInstancesAreLoaded(context); InstanceSettings settings = instances.get(widgetId); return settings != null ? settings : newInstance(context, widgetId); } @NonNull private static InstanceSettings newInstance(Context context, Integer widgetId) { synchronized (instances) { InstanceSettings settings = instances.get(widgetId); if (settings == null) { if (widgetId != 0 && (ApplicationPreferences.getWidgetId(context) == widgetId || instances.isEmpty())) { settings = fromApplicationPreferences(context, widgetId); } else { settings = new InstanceSettings(context, widgetId, ""); } instances.put(widgetId, settings); } return settings; } } private static void ensureInstancesAreLoaded(Context context) { if (instancesLoaded) { return; } synchronized (instances) { if (!instancesLoaded) { for (int widgetId : getWidgetIds(context)) { InstanceSettings settings; try { settings = fromJson(context, loadJson(context, getStorageKey(widgetId))); instances.put(widgetId, settings); } catch (Exception e) { // Starting from API21 android.system.ErrnoException may be thrown Log.e("loadInstances", "widgetId:" + widgetId, e); newInstance(context, widgetId); } } instancesLoaded = true; } } } public static void fromJson(Context context, JSONArray jsonArray) throws JSONException { synchronized (instances) { instances.clear(); for (int index = 0; index < jsonArray.length(); index++) { JSONObject json = jsonArray.optJSONObject(index); if (json != null) { InstanceSettings settings = fromJson(context, json); if (settings.getWidgetId() != 0) { instances.put(settings.widgetId, settings); } } } instancesLoaded = true; } } public static InstanceSettings fromJson(Context context, JSONObject json) throws JSONException { InstanceSettings settings = new InstanceSettings(context, json.optInt(PREF_WIDGET_ID), json.optString(PREF_WIDGET_INSTANCE_NAME)); if (settings.widgetId == 0) { return settings; } settings.justCreated = false; if (json.has(PREF_ACTIVE_CALENDARS)) { settings.activeCalendars = jsonArray2StringSet(json.getJSONArray(PREF_ACTIVE_CALENDARS)); } if (json.has(PREF_EVENT_RANGE)) { settings.eventRange = json.getInt(PREF_EVENT_RANGE); } if (json.has(PREF_EVENTS_ENDED)) { settings.eventsEnded = EndedSomeTimeAgo.fromValue(json.getString(PREF_EVENTS_ENDED)); } if (json.has(PREF_FILL_ALL_DAY)) { settings.fillAllDayEvents = json.getBoolean(PREF_FILL_ALL_DAY); } if (json.has(PREF_HIDE_BASED_ON_KEYWORDS)) { settings.hideBasedOnKeywords = json.getString(PREF_HIDE_BASED_ON_KEYWORDS); } if (json.has(PREF_PAST_EVENTS_BACKGROUND_COLOR)) { settings.pastEventsBackgroundColor = json.getInt(PREF_PAST_EVENTS_BACKGROUND_COLOR); } if (json.has(PREF_SHOW_DAYS_WITHOUT_EVENTS)) { settings.showDaysWithoutEvents = json.getBoolean(PREF_SHOW_DAYS_WITHOUT_EVENTS); } if (json.has(PREF_SHOW_DAY_HEADERS)) { settings.showDayHeaders = json.getBoolean(PREF_SHOW_DAY_HEADERS); } if (json.has(PREF_SHOW_PAST_EVENTS_WITH_DEFAULT_COLOR)) { settings.showPastEventsWithDefaultColor = json.getBoolean(PREF_SHOW_PAST_EVENTS_WITH_DEFAULT_COLOR); } if (json.has(PREF_SHOW_END_TIME)) { settings.showEndTime = json.getBoolean(PREF_SHOW_END_TIME); } if (json.has(PREF_SHOW_LOCATION)) { settings.showLocation = json.getBoolean(PREF_SHOW_LOCATION); } if (json.has(PREF_DATE_FORMAT)) { settings.dateFormat = json.getString(PREF_DATE_FORMAT); } if (json.has(PREF_ABBREVIATE_DATES)) { settings.abbreviateDates = json.getBoolean(PREF_ABBREVIATE_DATES); } if (json.has(PREF_LOCKED_TIME_ZONE_ID)) { settings.setLockedTimeZoneId(json.getString(PREF_LOCKED_TIME_ZONE_ID)); } if (json.has(PREF_EVENT_ENTRY_LAYOUT)) { settings.eventEntryLayout = EventEntryLayout.fromValue(json.getString(PREF_EVENT_ENTRY_LAYOUT)); } if (json.has(PREF_MULTILINE_TITLE)) { settings.titleMultiline = json.getBoolean(PREF_MULTILINE_TITLE); } if (json.has(PREF_SHOW_ONLY_CLOSEST_INSTANCE_OF_RECURRING_EVENT)) { settings.showOnlyClosestInstanceOfRecurringEvent = json.getBoolean( PREF_SHOW_ONLY_CLOSEST_INSTANCE_OF_RECURRING_EVENT); } if (json.has(PREF_INDICATE_ALERTS)) { settings.indicateAlerts = json.getBoolean(PREF_INDICATE_ALERTS); } if (json.has(PREF_INDICATE_RECURRING)) { settings.indicateRecurring = json.getBoolean(PREF_INDICATE_RECURRING); } if (json.has(PREF_ENTRY_THEME)) { settings.entryTheme = json.getString(PREF_ENTRY_THEME); } if (json.has(PREF_HEADER_THEME)) { settings.headerTheme = json.getString(PREF_HEADER_THEME); } if (json.has(PREF_SHOW_WIDGET_HEADER)) { settings.showWidgetHeader = json.getBoolean(PREF_SHOW_WIDGET_HEADER); } if (json.has(PREF_BACKGROUND_COLOR)) { settings.backgroundColor = json.getInt(PREF_BACKGROUND_COLOR); } if (json.has(PREF_TEXT_SIZE_SCALE)) { settings.textSizeScale = json.getString(PREF_TEXT_SIZE_SCALE); } if (json.has(PREF_DAY_HEADER_ALIGNMENT)) { settings.dayHeaderAlignment = json.getString(PREF_DAY_HEADER_ALIGNMENT); } return settings; } private static Set<String> jsonArray2StringSet(JSONArray jsonArray) { Set<String> set = new HashSet<>(); for (int index = 0; index < jsonArray.length(); index++) { String value = jsonArray.optString(index); if (value != null) { set.add(value); } } return set; } public static void save(Context context, Integer widgetId) { if (widgetId == 0) { return; } InstanceSettings settings = fromApplicationPreferences(context, widgetId); InstanceSettings settingStored = fromId(context, widgetId); if (!settings.equals(settingStored)) { settings.save(); instances.put(widgetId, settings); } } public static InstanceSettings fromApplicationPreferences(Context context, int widgetId) { InstanceSettings settings = new InstanceSettings(context, widgetId, ApplicationPreferences.getString(context, PREF_WIDGET_INSTANCE_NAME, ApplicationPreferences.getString(context, PREF_WIDGET_INSTANCE_NAME, ""))); settings.justCreated = false; settings.activeCalendars = ApplicationPreferences.getActiveCalendars(context); settings.eventRange = ApplicationPreferences.getEventRange(context); settings.eventsEnded = ApplicationPreferences.getEventsEnded(context); settings.fillAllDayEvents = ApplicationPreferences.getFillAllDayEvents(context); settings.hideBasedOnKeywords = ApplicationPreferences.getHideBasedOnKeywords(context); settings.pastEventsBackgroundColor = ApplicationPreferences.getPastEventsBackgroundColor(context); settings.showDaysWithoutEvents = ApplicationPreferences.getShowDaysWithoutEvents(context); settings.showDayHeaders = ApplicationPreferences.getShowDayHeaders(context); settings.showPastEventsWithDefaultColor = ApplicationPreferences.getShowPastEventsWithDefaultColor(context); settings.showEndTime = ApplicationPreferences.getShowEndTime(context); settings.showLocation = ApplicationPreferences.getShowLocation(context); settings.dateFormat = ApplicationPreferences.getDateFormat(context); settings.abbreviateDates = ApplicationPreferences.getAbbreviateDates(context); settings.setLockedTimeZoneId(ApplicationPreferences.getLockedTimeZoneId(context)); settings.eventEntryLayout = ApplicationPreferences.getEventEntryLayout(context); settings.titleMultiline = ApplicationPreferences.isTitleMultiline(context); settings.showOnlyClosestInstanceOfRecurringEvent = ApplicationPreferences .getShowOnlyClosestInstanceOfRecurringEvent(context); settings.indicateAlerts = ApplicationPreferences.getBoolean(context, PREF_INDICATE_ALERTS, true); settings.indicateRecurring = ApplicationPreferences.getBoolean(context, PREF_INDICATE_RECURRING, false); settings.entryTheme = ApplicationPreferences.getString(context, PREF_ENTRY_THEME, PREF_ENTRY_THEME_DEFAULT); settings.headerTheme = ApplicationPreferences.getString(context, PREF_HEADER_THEME, PREF_HEADER_THEME_DEFAULT); settings.showWidgetHeader = ApplicationPreferences.getBoolean(context, PREF_SHOW_WIDGET_HEADER, true); settings.backgroundColor = ApplicationPreferences.getInt(context, PREF_BACKGROUND_COLOR, PREF_BACKGROUND_COLOR_DEFAULT); settings.textSizeScale = ApplicationPreferences.getString(context, PREF_TEXT_SIZE_SCALE, PREF_TEXT_SIZE_SCALE_DEFAULT); settings.dayHeaderAlignment = ApplicationPreferences.getString(context, PREF_DAY_HEADER_ALIGNMENT, PREF_DAY_HEADER_ALIGNMENT_DEFAULT); return settings; } public static JSONArray toJson(Context context) { ensureInstancesAreLoaded(context); return new JSONArray(instances.values()); } @NonNull private static String getStorageKey(int widgetId) { return "instanceSettings" + widgetId; } public static void delete(Context context, int widgetId) { ensureInstancesAreLoaded(context); synchronized (instances) { if (instances.containsKey(widgetId)) { instances.remove(widgetId); } SettingsStorage.delete(context, getStorageKey(widgetId)); if (ApplicationPreferences.getWidgetId(context) == widgetId) { ApplicationPreferences.setWidgetId(context, 0); } } } private InstanceSettings(Context context, int widgetId, String widgetInstanceName) { this.context = context; this.widgetId = widgetId; this.widgetInstanceName = uniqueInstanceName(widgetInstanceName); } private String uniqueInstanceName(String widgetInstanceName) { int index = instances.size(); String name = TextUtils.isEmpty(widgetInstanceName) ? defaultInstanceName(++index) : widgetInstanceName; while (existsInstanceName(name)) { name = defaultInstanceName(++index); } return name; } private String defaultInstanceName(int index) { return context.getText(R.string.app_name) + " " + index; } private boolean existsInstanceName(String name) { for (InstanceSettings settings : instances.values()) { if (settings.getWidgetId() != widgetId && settings.getWidgetInstanceName().equals(name)) { return true; } } return false; } private void save() { try { saveJson(context, getStorageKey(widgetId), toJson()); } catch (IOException e) { Log.e("save", toString(), e); } } public JSONObject toJson() { JSONObject json = new JSONObject(); try { json.put(PREF_WIDGET_ID, widgetId); json.put(PREF_WIDGET_INSTANCE_NAME, widgetInstanceName); json.put(PREF_ACTIVE_CALENDARS, new JSONArray(activeCalendars)); json.put(PREF_EVENT_RANGE, eventRange); json.put(PREF_EVENTS_ENDED, eventsEnded.save()); json.put(PREF_FILL_ALL_DAY, fillAllDayEvents); json.put(PREF_HIDE_BASED_ON_KEYWORDS, hideBasedOnKeywords); json.put(PREF_PAST_EVENTS_BACKGROUND_COLOR, pastEventsBackgroundColor); json.put(PREF_SHOW_DAYS_WITHOUT_EVENTS, showDaysWithoutEvents); json.put(PREF_SHOW_DAY_HEADERS, showDayHeaders); json.put(PREF_SHOW_PAST_EVENTS_WITH_DEFAULT_COLOR, showPastEventsWithDefaultColor); json.put(PREF_SHOW_END_TIME, showEndTime); json.put(PREF_SHOW_LOCATION, showLocation); json.put(PREF_DATE_FORMAT, dateFormat); json.put(PREF_ABBREVIATE_DATES, abbreviateDates); json.put(PREF_LOCKED_TIME_ZONE_ID, lockedTimeZoneId); json.put(PREF_EVENT_ENTRY_LAYOUT, eventEntryLayout.value); json.put(PREF_MULTILINE_TITLE, titleMultiline); json.put(PREF_SHOW_ONLY_CLOSEST_INSTANCE_OF_RECURRING_EVENT, showOnlyClosestInstanceOfRecurringEvent); json.put(PREF_INDICATE_ALERTS, indicateAlerts); json.put(PREF_INDICATE_RECURRING, indicateRecurring); json.put(PREF_ENTRY_THEME, entryTheme); json.put(PREF_HEADER_THEME, headerTheme); json.put(PREF_SHOW_WIDGET_HEADER, showWidgetHeader); json.put(PREF_BACKGROUND_COLOR, backgroundColor); json.put(PREF_TEXT_SIZE_SCALE, textSizeScale); json.put(PREF_DAY_HEADER_ALIGNMENT, dayHeaderAlignment); } catch (JSONException e) { throw new RuntimeException("Saving settings to JSON", e); } return json; } public Context getContext() { return context; } public int getWidgetId() { return widgetId; } public String getWidgetInstanceName() { return widgetInstanceName; } public boolean isJustCreated() { return justCreated; } public Set<String> getActiveCalendars() { return activeCalendars; } public int getEventRange() { return eventRange; } public EndedSomeTimeAgo getEventsEnded() { return eventsEnded; } public boolean getFillAllDayEvents() { return fillAllDayEvents; } public String getHideBasedOnKeywords() { return hideBasedOnKeywords; } public int getPastEventsBackgroundColor() { return pastEventsBackgroundColor; } public boolean getShowDaysWithoutEvents() { return showDaysWithoutEvents; } public boolean getShowDayHeaders() { return showDayHeaders; } public boolean getShowPastEventsWithDefaultColor() { return showPastEventsWithDefaultColor; } public boolean getShowEndTime() { return showEndTime; } public boolean getShowLocation() { return showLocation; } public String getDateFormat() { return dateFormat; } public boolean getAbbreviateDates() { return abbreviateDates; } private void setLockedTimeZoneId(String lockedTimeZoneId) { this.lockedTimeZoneId = DateUtil.validatedTimeZoneId(lockedTimeZoneId); } public String getLockedTimeZoneId() { return lockedTimeZoneId; } public boolean isTimeZoneLocked() { return !TextUtils.isEmpty(lockedTimeZoneId); } public DateTimeZone getTimeZone() { return DateTimeZone.forID(DateUtil.validatedTimeZoneId( isTimeZoneLocked() ? lockedTimeZoneId : TimeZone.getDefault().getID())); } public EventEntryLayout getEventEntryLayout() { return eventEntryLayout; } public boolean isTitleMultiline() { return titleMultiline; } public boolean getShowOnlyClosestInstanceOfRecurringEvent() { return showOnlyClosestInstanceOfRecurringEvent; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InstanceSettings settings = (InstanceSettings) o; return toJson().toString().equals(settings.toJson().toString()); } @Override public int hashCode() { return toJson().toString().hashCode(); } public boolean getIndicateAlerts() { return indicateAlerts; } public boolean getIndicateRecurring() { return indicateRecurring; } public String getHeaderTheme() { return headerTheme; } public ContextThemeWrapper getHeaderThemeContext() { if (headerThemeContext == null) { headerThemeContext = new ContextThemeWrapper(context, themeNameToResId(headerTheme)); } return headerThemeContext; } public String getEntryTheme() { return entryTheme; } public ContextThemeWrapper getEntryThemeContext() { if (entryThemeContext == null) { entryThemeContext = new ContextThemeWrapper(context, themeNameToResId(entryTheme)); } return entryThemeContext; } public boolean getShowWidgetHeader() { return showWidgetHeader; } public int getBackgroundColor() { return backgroundColor; } public String getTextSizeScale() { return textSizeScale; } public String getDayHeaderAlignment() { return dayHeaderAlignment; } public static Map<Integer, InstanceSettings> getInstances(Context context) { ensureInstancesAreLoaded(context); return instances; } }