diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml
new file mode 100644
index 00000000..ddb5d55c
--- /dev/null
+++ b/.idea/assetWizardSettings.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 00000000..7ac24c77
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..37a75096
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..e2f077d6
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e5771f98..3cc0b3b9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
### ~
+* adds individual "enabled" prefs for each calendar (Solstices/Equinoxes, Moon Phases) (#3).
+* fixes bug "missing calendars/events when closing app while task is still running" (#4); moves calendar notifications into a foreground service.
+* changes the "Calendar Integration" pref to match calendar state (vs desired state); existing calendars (and prefs) should be preserved when updating (or removing / re-adding) the app.
+* changes the strategy used when initializing calendars; existing calendars are no longer cleared prior to (re)adding; subsequent calls to "add" instead "update" a calendar (insert, replace).
+* changes when permissions are requested; adds a request on first launch (or if data is cleared) before allowing access to remaining UI; permissions are used to recover calendars from previous installations.
+* misc improvements to permissions handling; more robust; support for actions on individual calendars.
+
### v0.1.1 (2018-12-12)
* fixes broken build (jcenter fails to resolve deps).
* updates Android gradle plugin to `com.android.tools.build:gradle:3.0.0` (#2).
diff --git a/SuntimesCalendars.iml b/SuntimesCalendars.iml
new file mode 100644
index 00000000..ec68be22
--- /dev/null
+++ b/SuntimesCalendars.iml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/app.iml b/app/app.iml
new file mode 100644
index 00000000..33c7c22f
--- /dev/null
+++ b/app/app.iml
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a8bd8026..419a6965 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -27,6 +27,13 @@
+
+
+
+
+
+
items = loadItems(this.getIntent(), true);
+ savePendingItems(this, taskIntent, items);
+ calendarTaskService.runCalendarTask(context, taskIntent, false, false,null);
+
+ if (mainFragment != null)
{
SharedPreferences.Editor pref = PreferenceManager.getDefaultSharedPreferences(context).edit();
- pref.putBoolean(SuntimesCalendarSettings.PREF_KEY_CALENDARS_ENABLED, enabled);
- pref.apply();
-
- if (tmp_calendarPref != null)
+ for (SuntimesCalendarTask.SuntimesCalendarTaskItem item : items)
{
- tmp_calendarPref.setChecked(enabled);
- tmp_calendarPref = null;
+ boolean enabled = (item.getAction() == SuntimesCalendarTask.SuntimesCalendarTaskItem.ACTION_UPDATE);
+ pref.putBoolean(SuntimesCalendarSettings.PREF_KEY_CALENDARS_CALENDAR + item.getCalendar(), enabled);
+ pref.apply();
+
+ CheckBoxPreference calendarPref = mainFragment.getCalendarPref(item.getCalendar());
+ if (calendarPref != null) {
+ calendarPref.setChecked(enabled);
+ }
+ }
+ }
+ }
+ break;
+
+ case REQUEST_CALENDARS_ENABLED:
+ case REQUEST_CALENDARS_DISABLED:
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
+ {
+ boolean enabled = requestCode == (REQUEST_CALENDARS_ENABLED);
+ Intent taskIntent = new Intent(this, SuntimesCalendarSyncService.class);
+ taskIntent.setAction( !enabled ? SuntimesCalendarTaskService.ACTION_CLEAR_CALENDARS : SuntimesCalendarTaskService.ACTION_UPDATE_CALENDARS );
+
+ ArrayList items = new ArrayList<>();
+ if (enabled) {
+ items = loadItems(this.getIntent(), true);
+ }
+
+ savePendingItems(this, taskIntent, items);
+ calendarTaskService.runCalendarTask(context, taskIntent, !enabled, true,null);
+
+ SharedPreferences.Editor pref = PreferenceManager.getDefaultSharedPreferences(context).edit();
+ pref.putBoolean(SuntimesCalendarSettings.PREF_KEY_CALENDARS_ENABLED, enabled);
+ pref.apply();
+
+ if (mainFragment != null)
+ {
+ CheckBoxPreference calendarsPref = mainFragment.getCalendarsEnabledPref();
+ if (calendarsPref != null) {
+ calendarsPref.setChecked(enabled);
}
}
}
@@ -263,75 +412,235 @@ protected void onRestoreInstanceState( Bundle bundle )
needsSuntimesPermissions = bundle.getBoolean("needsSuntimesPermissions");
}
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
/**
- * CalendarPrefsFragment
+ * CalendarPrefsFragmentBase
*/
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- public static class CalendarPrefsFragment extends PreferenceFragment
+ public static class CalendarPrefsFragmentBase extends PreferenceFragment
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
- Log.i(TAG, "CalendarPrefsFragment: Arguments: " + getArguments());
- PreferenceManager.setDefaultValues(getActivity(), R.xml.preference_calendars, false);
- addPreferencesFromResource(R.xml.preference_calendars);
-
if (savedInstanceState != null && savedInstanceState.containsKey("providerVersion")) {
providerVersion = savedInstanceState.getInt("providerVersion");
}
+ }
+ @Override
+ public void onSaveInstanceState(Bundle outState)
+ {
+ super.onSaveInstanceState(outState);
+ if (providerVersion != null) {
+ outState.putInt("providerVersion", providerVersion);
+ }
+ }
+
+ protected Integer providerVersion = null;
+ public void setProviderVersion( Integer version )
+ {
+ providerVersion = version;
+ }
+
+ protected Preference.OnPreferenceClickListener onAboutClick = null;
+ public void setAboutClickListener( Preference.OnPreferenceClickListener onClick )
+ {
+ onAboutClick = onClick;
+ }
+
+ protected boolean checkDependencies()
+ {
+ return (providerVersion != null && providerVersion >= MIN_PROVIDER_VERSION);
+ }
+
+ protected void showPermissionRational(final Activity activity, final int requestCode)
+ {
+ String permissionMessage = activity.getString(R.string.privacy_permission_calendar);
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ builder.setTitle(activity.getString(R.string.privacy_permissiondialog_title))
+ .setMessage(fromHtml(permissionMessage))
+ .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener()
+ {
+ public void onClick(DialogInterface dialog, int which)
+ {
+ ActivityCompat.requestPermissions(activity, new String[] { Manifest.permission.WRITE_CALENDAR }, requestCode);
+ }
+ });
+ builder.show();
+ }
+
+ protected void initAboutDialog()
+ {
Preference aboutPref = findPreference("app_about");
if (aboutPref != null && onAboutClick != null) {
aboutPref.setOnPreferenceClickListener(onAboutClick);
}
+ }
- final Activity activity = getActivity();
- final CheckBoxPreference calendarsEnabledPref = (CheckBoxPreference) findPreference(SuntimesCalendarSettings.PREF_KEY_CALENDARS_ENABLED);
- final Preference.OnPreferenceChangeListener onPreferenceChanged0 = new Preference.OnPreferenceChangeListener()
+ protected ProgressDialog progressDialog;
+ protected String progressMessage;
+ public void updateProgressDialog(String message)
+ {
+ if (progressDialog != null && progressDialog.isShowing()) {
+ progressDialog.setMessage(message);
+ progressMessage = message;
+ }
+ }
+
+ protected void initProgressDialog()
+ {
+ progressDialog = new ProgressDialog(getActivity());
+ progressDialog.setCanceledOnTouchOutside(false);
+ progressDialog.setIndeterminate(true);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * FirstLaunchFragment
+ */
+ public static class FirstLaunchFragment extends CalendarPrefsFragmentBase
+ {
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ Log.i(TAG, "FirstLaunchFragment: Arguments: " + getArguments());
+ PreferenceManager.setDefaultValues(getActivity(), R.xml.preference_firstlaunch, false);
+ addPreferencesFromResource(R.xml.preference_firstlaunch);
+
+ Preference aboutPref = findPreference("app_about");
+ if (aboutPref != null && onAboutClick != null) {
+ aboutPref.setOnPreferenceClickListener(onAboutClick);
+ }
+
+ CheckBoxPreference permissionsPref = (CheckBoxPreference) findPreference(SuntimesCalendarSettings.PREF_KEY_CALENDARS_PERMISSIONS);
+ permissionsPref.setChecked(false);
+ permissionsPref.setOnPreferenceChangeListener(onPermissionsPrefChanged);
+
+ if (needsSuntimesPermissions || !checkDependencies())
{
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue)
+ if (needsSuntimesPermissions)
+ showPermissionDeniedMessage(getActivity(), getActivity().getWindow().getDecorView().findViewById(android.R.id.content));
+ else showMissingDepsMessage(getActivity(), getActivity().getWindow().getDecorView().findViewById(android.R.id.content));
+ }
+ }
+
+ Preference.OnPreferenceChangeListener onPermissionsPrefChanged = new Preference.OnPreferenceChangeListener()
+ {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue)
+ {
+ Activity activity = getActivity();
+ boolean checkPrefs = (Boolean)newValue;
+ if (checkPrefs && activity != null)
{
- boolean enabled = (Boolean)newValue;
- int calendarPermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_CALENDAR);
- if (calendarPermission != PackageManager.PERMISSION_GRANTED)
+ if (!hasCalendarPermissions(activity))
{
- final int requestCode = (enabled ? REQUEST_CALENDARPREFSFRAGMENT_ENABLED : REQUEST_CALENDARPREFSFRAGMENT_DISABLED);
- if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_CALENDAR))
- {
- String permissionMessage = activity.getString(R.string.privacy_permission_calendar);
- AlertDialog.Builder builder = new AlertDialog.Builder(activity);
- builder.setTitle(activity.getString(R.string.privacy_permissiondialog_title))
- .setMessage(fromHtml(permissionMessage))
- .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener()
- {
- public void onClick(DialogInterface dialog, int which)
- {
- ActivityCompat.requestPermissions(activity, new String[] { Manifest.permission.WRITE_CALENDAR }, requestCode);
- tmp_calendarPref = calendarsEnabledPref;
- }
- });
-
- //if (Build.VERSION.SDK_INT >= 11)
- //builder.setIconAttribute(R.attr.icActionWarning);
- //else builder.setIcon(R.drawable.ic_action_warning);
-
- builder.show();
- return false;
-
+ if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_CALENDAR)) {
+ showPermissionRational(activity, REQUEST_CALENDAR_FIRSTLAUNCH);
} else {
- ActivityCompat.requestPermissions(activity, new String[] { Manifest.permission.WRITE_CALENDAR }, requestCode);
- tmp_calendarPref = calendarsEnabledPref;
- return false;
+ ActivityCompat.requestPermissions(activity, new String[] { Manifest.permission.WRITE_CALENDAR }, REQUEST_CALENDAR_FIRSTLAUNCH);
}
} else {
- return runCalendarTask(activity, enabled);
+ SuntimesCalendarSettings.saveFirstLaunch(activity);
+ activity.recreate();
}
}
- };
- calendarsEnabledPref.setOnPreferenceChangeListener(onPreferenceChanged0);
+ return false;
+ }
+ };
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * CalendarPrefsFragment
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public static class CalendarPrefsFragment extends CalendarPrefsFragmentBase
+ {
+ private CheckBoxPreference calendarsEnabledPref = null;
+ public CheckBoxPreference getCalendarsEnabledPref()
+ {
+ return calendarsEnabledPref;
+ }
+
+ private HashMap calendarPrefs = new HashMap<>();
+ public CheckBoxPreference getCalendarPref(String calendar)
+ {
+ return calendarPrefs.get(calendar);
+ }
+
+ private boolean isBusy = false;
+ public void setIsBusy(boolean isBusy)
+ {
+ this.isBusy = isBusy;
+ if (progressDialog != null)
+ {
+ if (isBusy)
+ {
+ if (!progressDialog.isShowing()) {
+ progressDialog.show();
+ }
+
+ } else {
+ if (progressDialog.isShowing()) {
+ progressDialog.dismiss();
+ }
+ clearPrefListeners();
+ updatePrefs(getActivity());
+ initPrefListeners(getActivity());
+ }
+ }
+ }
+
+ @Override
+ public void onStart()
+ {
+ super.onStart();
+ if (progressDialog != null && isBusy) {
+ progressDialog.show();
+ updateProgressDialog(progressMessage);
+ }
+ }
+
+ @Override
+ public void onStop()
+ {
+ super.onStop();
+ if (progressDialog != null && progressDialog.isShowing()) {
+ progressDialog.dismiss();
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ Log.i(TAG, "CalendarPrefsFragment: Arguments: " + getArguments());
+ PreferenceManager.setDefaultValues(getActivity(), R.xml.preference_calendars, false);
+ addPreferencesFromResource(R.xml.preference_calendars);
+
+ final Activity activity = getActivity();
+ initAboutDialog();
+ initProgressDialog();
+
+ calendarsEnabledPref = (CheckBoxPreference) findPreference(SuntimesCalendarSettings.PREF_KEY_CALENDARS_ENABLED);
+ for (String calendar : SuntimesCalendarAdapter.ALL_CALENDARS)
+ {
+ CheckBoxPreference calendarPref = (CheckBoxPreference)findPreference(SuntimesCalendarSettings.PREF_KEY_CALENDARS_CALENDAR + calendar);
+ calendarPrefs.put(calendar, calendarPref);
+ }
+
+ updatePrefs(activity);
+ initPrefListeners(activity);
if (needsSuntimesPermissions || !checkDependencies())
{
@@ -348,35 +657,177 @@ public void onClick(DialogInterface dialog, int which)
showPermissionDeniedMessage(getActivity(), getActivity().getWindow().getDecorView().findViewById(android.R.id.content));
else showMissingDepsMessage(getActivity(), getActivity().getWindow().getDecorView().findViewById(android.R.id.content));
}
+ setIsBusy(isBusy);
}
- @Override
- public void onSaveInstanceState(Bundle outState)
+ private void initPrefListeners(Activity activity)
{
- super.onSaveInstanceState(outState);
- if (providerVersion != null) {
- outState.putInt("providerVersion", providerVersion);
+ if (activity == null)
+ return;
+
+ calendarsEnabledPref.setOnPreferenceChangeListener(onPreferenceChanged0(activity));
+ for (String calendar : calendarPrefs.keySet())
+ {
+ CheckBoxPreference calendarPref = calendarPrefs.get(calendar);
+ calendarPref.setOnPreferenceChangeListener(onPreferenceChanged1(activity, calendar));
}
}
- private Integer providerVersion = null;
- public void setProviderVersion( Integer version )
+ private void clearPrefListeners()
{
- providerVersion = version;
+ calendarsEnabledPref.setOnPreferenceChangeListener(null);
+ for (String calendar : calendarPrefs.keySet())
+ {
+ CheckBoxPreference calendarPref = calendarPrefs.get(calendar);
+ calendarPref.setOnPreferenceChangeListener(null);
+ }
}
- private Preference.OnPreferenceClickListener onAboutClick = null;
- public void setAboutClickListener( Preference.OnPreferenceClickListener onClick )
+ private void updatePrefs(Activity activity)
{
- onAboutClick = onClick;
+ if (activity == null)
+ return;
+
+ SuntimesCalendarAdapter adapter = new SuntimesCalendarAdapter(activity.getContentResolver());
+ SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(activity).edit();
+
+ if (hasCalendarPermissions(activity))
+ {
+ boolean calendarsEnabled0 = adapter.hasCalendars();
+ boolean calendarsEnabled1 = calendarsEnabledPref.isChecked();
+ if (calendarsEnabled0 != calendarsEnabled1)
+ {
+ Log.w(TAG, "onCreate: out of sync! setting pref to " + (calendarsEnabled0 ? "enabled" : "disabled"));
+ prefs.putBoolean(SuntimesCalendarSettings.PREF_KEY_CALENDARS_ENABLED, calendarsEnabled0);
+ prefs.apply();
+ calendarsEnabledPref.setChecked(calendarsEnabled0);
+ }
+
+ for (String calendar : calendarPrefs.keySet())
+ {
+ CheckBoxPreference calendarPref = calendarPrefs.get(calendar);
+ if (calendarsEnabledPref.isChecked())
+ {
+ boolean enabled0 = adapter.hasCalendar(calendar);
+ boolean enabled1 = SuntimesCalendarSettings.loadPrefCalendarEnabled(activity, calendar);
+ if (enabled0 != enabled1)
+ {
+ Log.w(TAG, "onCreate: out of sync! setting " + calendar + " to " + (enabled0 ? "enabled" : "disabled"));
+ prefs.putBoolean(SuntimesCalendarSettings.PREF_KEY_CALENDARS_CALENDAR + calendar, enabled0);
+ prefs.apply();
+ calendarPref.setChecked(enabled0);
+ }
+ }
+ }
+ }
}
- protected boolean checkDependencies()
+ private Preference.OnPreferenceChangeListener onPreferenceChanged0(final Activity activity)
{
- return (providerVersion != null && providerVersion >= MIN_PROVIDER_VERSION);
+ return new Preference.OnPreferenceChangeListener()
+ {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue)
+ {
+ boolean enabled = (Boolean)newValue;
+ if (!hasCalendarPermissions(activity))
+ {
+ final int requestCode = (enabled ? REQUEST_CALENDARS_ENABLED : REQUEST_CALENDARS_DISABLED);
+ if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_CALENDAR))
+ {
+ if (enabled) {
+ savePendingItems(activity, activity.getIntent());
+ }
+ showPermissionRational(activity, requestCode);
+ return false;
+
+ } else {
+ if (enabled) {
+ savePendingItems(activity, activity.getIntent());
+ }
+ ActivityCompat.requestPermissions(activity, new String[] { Manifest.permission.WRITE_CALENDAR }, requestCode);
+ return false;
+ }
+
+ } else {
+ Intent taskIntent = new Intent(getActivity(), SuntimesCalendarSyncService.class);
+ taskIntent.setAction( !enabled ? SuntimesCalendarTaskService.ACTION_CLEAR_CALENDARS : SuntimesCalendarTaskService.ACTION_UPDATE_CALENDARS );
+ savePendingItems(activity, taskIntent);
+ return calendarTaskService.runCalendarTask(activity, taskIntent, !enabled, true,null);
+ }
+ }
+ };
+ }
+
+ private Preference.OnPreferenceChangeListener onPreferenceChanged1(final Activity activity, final String calendar)
+ {
+ return new Preference.OnPreferenceChangeListener()
+ {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue)
+ {
+ boolean calendarsEnabled = SuntimesCalendarSettings.loadCalendarsEnabledPref(activity);
+ if (calendarsEnabled)
+ {
+ boolean enabled = (Boolean)newValue;
+ if (!hasCalendarPermissions(activity))
+ {
+ final int requestCode = (enabled ? REQUEST_CALENDAR_ENABLED : REQUEST_CALENDAR_DISABLED);
+ if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_CALENDAR))
+ {
+ savePendingItem(activity, activity.getIntent(), calendar, enabled);
+ showPermissionRational(activity, requestCode);
+ return false;
+
+ } else {
+ savePendingItem(activity, activity.getIntent(), calendar, enabled);
+ ActivityCompat.requestPermissions(activity, new String[] { Manifest.permission.WRITE_CALENDAR }, requestCode);
+ return false;
+ }
+
+ } else {
+ Intent taskIntent = new Intent(getActivity(), SuntimesCalendarSyncService.class);
+ taskIntent.setAction( SuntimesCalendarTaskService.ACTION_UPDATE_CALENDARS );
+ savePendingItem(activity, taskIntent, calendar, enabled);
+ return calendarTaskService.runCalendarTask(activity, taskIntent, false, true,null);
+ }
+
+ } else {
+ return true;
+ }
+ }
+ };
}
}
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**private static class OnServiceResponse extends SuntimesCalendarSyncService.SuntimesCalendarServiceListener
+ {
+ @Override
+ public void onStartCommand(boolean result)
+ {
+ super.onStartCommand(result);
+ }
+
+ public OnServiceResponse() {
+ super();
+ }
+ public OnServiceResponse(Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public OnServiceResponse createFromParcel(Parcel in) {
+ return new OnServiceResponse(in);
+ }
+ public OnServiceResponse[] newArray(int size) {
+ return new OnServiceResponse[size];
+ }
+ };
+ }*/
+
protected static void showMissingDepsMessage(final Activity context, View view)
{
if (view != null)
@@ -439,98 +890,48 @@ public void onClick(View v)
}
}
-
- private static CheckBoxPreference tmp_calendarPref = null;
- private static SuntimesCalendarTask calendarTask = null;
- private static boolean runCalendarTask(final Activity activity, boolean enabled)
+ /**
+ * loadItems
+ */
+ public static ArrayList loadItems(Intent intent, boolean clearPending)
{
- if (calendarTask != null)
- {
- switch (calendarTask.getStatus())
- {
- case PENDING:
- Log.w(TAG, "runCalendarTask: A task is already pending! ignoring...");
- return false;
-
- case RUNNING:
- Log.w(TAG, "runCalendarTask: A task is already running! ignoring...");
- return false;
- }
+ SuntimesCalendarTask.SuntimesCalendarTaskItem[] items;
+ Parcelable[] parcelableArray = intent.getParcelableArrayExtra(SuntimesCalendarTaskService.EXTRA_CALENDAR_ITEMS);
+ if (parcelableArray != null) {
+ items = Arrays.copyOf(parcelableArray, parcelableArray.length, SuntimesCalendarTask.SuntimesCalendarTaskItem[].class);
+ } else items = new SuntimesCalendarTask.SuntimesCalendarTaskItem[0];
+
+ if (clearPending) {
+ intent.removeExtra(SuntimesCalendarTaskService.EXTRA_CALENDAR_ITEMS);
}
+ return new ArrayList<>(Arrays.asList(items));
+ }
- calendarTask = new SuntimesCalendarTask(activity);
- if (!enabled) {
- calendarTask.setFlagClearCalendars(true);
- }
- calendarTask.setTaskListener(new SuntimesCalendarTask.SuntimesCalendarTaskListener()
- {
- private ProgressDialog progress;
-
- @Override
- public void onStarted(boolean flag_clear)
- {
- if (!flag_clear)
- {
- //Toast.makeText(activity, activity.getString(R.string.calendars_notification_adding), Toast.LENGTH_SHORT).show();
-
- progress = new ProgressDialog(activity);
- progress.setIndeterminate(true);
- progress.setMessage(activity.getString(R.string.calendars_notification_adding));
- progress.setCanceledOnTouchOutside(false);
- progress.show();
- }
- }
-
- @Override
- public void onSuccess(boolean flag_clear)
- {
- if (progress != null) {
- progress.dismiss();
- }
-
- if (!flag_clear)
- Toast.makeText(activity, activity.getString(R.string.calendars_notification_added), Toast.LENGTH_SHORT).show();
- else Toast.makeText(activity, activity.getString(R.string.calendars_notification_cleared), Toast.LENGTH_SHORT).show();
- }
-
- @Override
- public void onFailed(final String errorMsg)
- {
- if (progress != null) {
- progress.dismiss();
- }
-
- super.onFailed(errorMsg);
- AlertDialog.Builder errorDialog = new AlertDialog.Builder(activity);
- errorDialog.setTitle(activity.getString(R.string.calendars_notification_adding_failed))
- .setMessage(errorMsg)
- .setIcon(R.drawable.ic_action_about)
- .setNeutralButton(activity.getString(R.string.actionCopyError), new DialogInterface.OnClickListener()
- {
- public void onClick(DialogInterface dialog, int which)
- {
- ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
- if (clipboard != null) {
- ClipData clip = ClipData.newPlainText("SuntimesCalendarErrorMsg", errorMsg);
- clipboard.setPrimaryClip(clip);
- Toast.makeText(activity, activity.getString(R.string.actionCopyError_toast), Toast.LENGTH_SHORT).show();
- }
- }
- })
- .setPositiveButton(android.R.string.ok, null);
- errorDialog.show();
+ /**
+ * saveItems
+ */
+ public static void savePendingItems(Activity activity, Intent intent)
+ {
+ ArrayList items = new ArrayList<>();
+ for (String calendar : SuntimesCalendarAdapter.ALL_CALENDARS) {
+ if (SuntimesCalendarSettings.loadPrefCalendarEnabled(activity, calendar)) {
+ items.add(new SuntimesCalendarTask.SuntimesCalendarTaskItem(calendar, SuntimesCalendarTask.SuntimesCalendarTaskItem.ACTION_UPDATE));
}
- });
+ }
+ savePendingItems(activity, intent, items);
+ }
- calendarTask.execute();
- return true;
+ public static void savePendingItems(Activity activity, Intent intent, ArrayList items)
+ {
+ intent.putExtra(SuntimesCalendarTaskService.EXTRA_CALENDAR_ITEMS, items.toArray(new SuntimesCalendarTask.SuntimesCalendarTaskItem[0]));
}
- public static Spanned fromHtml(String htmlString )
+ public static void savePendingItem(Activity activity, Intent intent, String calendar, boolean enabled)
{
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
- return Html.fromHtml(htmlString, Html.FROM_HTML_MODE_LEGACY);
- else return Html.fromHtml(htmlString);
+ ArrayList items = new ArrayList<>();
+ int action = (enabled ? SuntimesCalendarTask.SuntimesCalendarTaskItem.ACTION_UPDATE : SuntimesCalendarTask.SuntimesCalendarTaskItem.ACTION_DELETE);
+ items.add(new SuntimesCalendarTask.SuntimesCalendarTaskItem(calendar, action));
+ savePendingItems(activity, intent, items);
}
/**
@@ -552,4 +953,11 @@ public boolean onPreferenceClick(Preference preference)
return false;
}
};
+
+ public static Spanned fromHtml(String htmlString )
+ {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
+ return Html.fromHtml(htmlString, Html.FROM_HTML_MODE_LEGACY);
+ else return Html.fromHtml(htmlString);
+ }
}
diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarAdapter.java b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarAdapter.java
index aeec119a..46aa9b65 100644
--- a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarAdapter.java
+++ b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarAdapter.java
@@ -37,6 +37,7 @@ public class SuntimesCalendarAdapter
public static final String CALENDAR_SOLSTICE = "solsticeCalendar";
public static final String CALENDAR_MOONPHASE = "moonPhaseCalendar";
+ public static final String[] ALL_CALENDARS = new String[] {CALENDAR_SOLSTICE, CALENDAR_MOONPHASE};
private ContentResolver contentResolver;
@@ -77,6 +78,23 @@ public boolean removeCalendars()
} else return false;
}
+ /**
+ * Removes individual calendars by name.
+ * @param calendar calendar name
+ * @return true calendar was removed, false otherwise
+ */
+ public boolean removeCalendar(String calendar)
+ {
+ long calendarID = queryCalendarID(calendar);
+ if (calendarID != -1)
+ {
+ Uri deleteUri = ContentUris.withAppendedId(CalendarContract.Calendars.CONTENT_URI, calendarID);
+ contentResolver.delete(deleteUri, null, null);
+ Log.d(TAG, "removeCalendar: removed calendar " + calendarID);
+ return true;
+ } else return false;
+ }
+
/**
* @param calendarID the calendar's ID
* @param title the event title
@@ -146,6 +164,30 @@ public boolean hasCalendar(String calendarName)
return (cursor != null && cursor.getCount() > 0);
}
+ /**
+ * @return true if any calendars are being managed by the "Suntimes" local account, false no calendars exist.
+ */
+ public boolean hasCalendars()
+ {
+ try {
+ for (String calendar : ALL_CALENDARS)
+ {
+ Cursor cursor = queryCalendar(calendar);
+ if (cursor != null)
+ {
+ boolean hasCalendar = (cursor.getCount() > 0);
+ cursor.close();
+ if (hasCalendar) {
+ return true;
+ }
+ }
+ }
+ } catch (SecurityException e) {
+ return false;
+ }
+ return false;
+ }
+
/**
* @param calendarName
* @param displayName
diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarErrorActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarErrorActivity.java
new file mode 100644
index 00000000..e37d6e4b
--- /dev/null
+++ b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarErrorActivity.java
@@ -0,0 +1,105 @@
+/*
+ Copyright (C) 2018 Forrest Guice
+ This file is part of SuntimesCalendars.
+
+ SuntimesCalendars is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SuntimesCalendars 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.
+
+ You should have received a copy of the GNU General Public License
+ along with SuntimesCalendars. If not, see .
+*/
+
+package com.forrestguice.suntimeswidget.calendar;
+
+import android.app.Dialog;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Toast;
+
+import com.forrestguice.suntimescalendars.R;
+
+public class SuntimesCalendarErrorActivity extends AppCompatActivity
+{
+ public static final String EXTRA_ERROR_MESSAGE = "calendar_error";
+
+ public SuntimesCalendarErrorActivity()
+ {
+ super();
+ }
+
+ @Override
+ public void onCreate(Bundle icicle)
+ {
+ setResult(RESULT_OK);
+ super.onCreate(icicle);
+
+ final Context context = this;
+ final String errorMsg = getIntent().getStringExtra(EXTRA_ERROR_MESSAGE);
+
+ AlertDialog.Builder errorDialog = new AlertDialog.Builder(context);
+ errorDialog.setTitle(context.getString(R.string.calendars_notification_adding_failed))
+ .setMessage(errorMsg)
+ .setIcon(R.drawable.ic_action_about)
+ .setNeutralButton(context.getString(R.string.actionCopyError), null)
+ .setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog)
+ {
+ SuntimesCalendarErrorActivity.this.finish();
+ overridePendingTransition(0, 0);
+ }
+ })
+ .setPositiveButton(android.R.string.ok, null);
+
+ Dialog dialog = errorDialog.create();
+ dialog.setCanceledOnTouchOutside(false);
+ dialog.setOnShowListener(new DialogInterface.OnShowListener()
+ {
+ @Override
+ public void onShow(DialogInterface dialog)
+ {
+ Button neutralButton = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEUTRAL);
+ neutralButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v)
+ {
+ ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+ if (clipboard != null) {
+ ClipData clip = ClipData.newPlainText("SuntimesCalendarErrorMsg", errorMsg);
+ clipboard.setPrimaryClip(clip);
+ Toast.makeText(context, context.getString(R.string.actionCopyError_toast), Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ }
+ });
+ dialog.show();
+ }
+
+ @Override
+ protected void onSaveInstanceState( Bundle bundle )
+ {
+ super.onSaveInstanceState(bundle);
+ }
+
+ @Override
+ protected void onRestoreInstanceState( Bundle bundle )
+ {
+ super.onRestoreInstanceState(bundle);
+ }
+
+}
diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarSettings.java
index 7f9beefd..dc87ffba 100644
--- a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarSettings.java
+++ b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarSettings.java
@@ -33,6 +33,31 @@ public class SuntimesCalendarSettings
public static final String PREF_KEY_CALENDAR_WINDOW1 = "app_calendars_window1";
public static final String PREF_DEF_CALENDAR_WINDOW1 = "63072000000"; // 2 years
+ public static final String PREF_KEY_CALENDARS_CALENDAR = "app_calendars_calendar_";
+
+ public static final String PREF_KEY_CALENDARS_FIRSTLAUNCH = "app_calendars_firstlaunch";
+ public static final String PREF_KEY_CALENDARS_PERMISSIONS = "app_calendars_permissions";
+
+ /**
+ * @param context
+ * @return
+ */
+ public static boolean isFirstLaunch(Context context)
+ {
+ SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
+ return pref.getBoolean(PREF_KEY_CALENDARS_FIRSTLAUNCH, true);
+ }
+
+ /**
+ * @param context
+ */
+ public static void saveFirstLaunch(Context context)
+ {
+ SharedPreferences.Editor pref = PreferenceManager.getDefaultSharedPreferences(context).edit();
+ pref.putBoolean(PREF_KEY_CALENDARS_FIRSTLAUNCH, false);
+ pref.apply();
+ }
+
/**
* @param context
* @param enabled
@@ -73,4 +98,14 @@ public static long loadPrefCalendarWindow1(Context context)
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return Long.parseLong(prefs.getString(PREF_KEY_CALENDAR_WINDOW1, PREF_DEF_CALENDAR_WINDOW1));
}
+
+ /**
+ * @param context context used to access preferences
+ * @return true calendar is enabled, false otherwise
+ */
+ public static boolean loadPrefCalendarEnabled(Context context, String calendar)
+ {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ return prefs.getBoolean(PREF_KEY_CALENDARS_CALENDAR + calendar, false);
+ }
}
diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarSyncService.java b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarSyncService.java
index 438f6411..baf427ce 100644
--- a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarSyncService.java
+++ b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarSyncService.java
@@ -21,6 +21,7 @@
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
+
import android.support.annotation.Nullable;
public class SuntimesCalendarSyncService extends Service
@@ -28,6 +29,13 @@ public class SuntimesCalendarSyncService extends Service
private static SuntimesCalendarSyncAdapter syncAdapter = null;
private static final Object syncAdapterLock = new Object();
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent)
+ {
+ return syncAdapter.getSyncAdapterBinder();
+ }
+
@Override
public void onCreate()
{
@@ -39,11 +47,4 @@ public void onCreate()
}
}
}
-
- @Nullable
- @Override
- public IBinder onBind(Intent intent)
- {
- return syncAdapter.getSyncAdapterBinder();
- }
}
diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarTask.java
index 0c90f9d6..bfbf7bdd 100644
--- a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarTask.java
+++ b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarTask.java
@@ -18,20 +18,16 @@
package com.forrestguice.suntimeswidget.calendar;
-import android.app.Activity;
-import android.app.PendingIntent;
import android.content.ContentResolver;
-import android.content.ContentUris;
import android.content.Context;
-import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
-import android.provider.CalendarContract;
-import android.support.v4.app.NotificationManagerCompat;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
-import android.support.v7.app.NotificationCompat;
import android.util.Log;
import com.forrestguice.suntimescalendars.R;
@@ -41,13 +37,14 @@
import java.util.Calendar;
import java.util.HashMap;
-public class SuntimesCalendarTask extends AsyncTask
+public class SuntimesCalendarTask extends AsyncTask
{
public static final String TAG = "SuntimesCalendarTask";
private SuntimesCalendarAdapter adapter;
private WeakReference contextRef;
+ private HashMap calendars = new HashMap<>();
private HashMap calendarDisplay = new HashMap<>();
private HashMap calendarColors = new HashMap<>();
@@ -58,20 +55,12 @@ public class SuntimesCalendarTask extends AsyncTask
private long lastSync = -1;
private long calendarWindow0 = -1, calendarWindow1 = -1;
- private NotificationManagerCompat notificationManager;
- private NotificationCompat.Builder notificationBuilder;
-
- public static final int NOTIFICATION_ID = 1000;
- private String notificationTitle;
private String notificationMsgAdding, notificationMsgAdded;
private String notificationMsgClearing, notificationMsgCleared;
private String notificationMsgAddFailed;
- private int notificationIcon = R.drawable.ic_action_calendar;
- private int notificationPriority = NotificationCompat.PRIORITY_LOW;
- private PendingIntent notificationIntent;
private String lastError = null;
- public SuntimesCalendarTask(Activity context)
+ public SuntimesCalendarTask(Context context)
{
contextRef = new WeakReference(context);
adapter = new SuntimesCalendarAdapter(context.getContentResolver());
@@ -101,30 +90,11 @@ public SuntimesCalendarTask(Activity context)
calendarDisplay.put(SuntimesCalendarAdapter.CALENDAR_MOONPHASE, context.getString(R.string.calendar_moonPhase_displayName));
calendarColors.put(SuntimesCalendarAdapter.CALENDAR_MOONPHASE, ContextCompat.getColor(context, R.color.colorMoonCalendar));
- notificationManager = NotificationManagerCompat.from(context);
- notificationBuilder = new NotificationCompat.Builder(context);
- notificationTitle = context.getString(R.string.app_name);
notificationMsgAdding = context.getString(R.string.calendars_notification_adding);
notificationMsgAdded = context.getString(R.string.calendars_notification_added);
notificationMsgClearing = context.getString(R.string.calendars_notification_clearing);
notificationMsgCleared = context.getString(R.string.calendars_notification_cleared);
notificationMsgAddFailed = context.getString(R.string.calendars_notification_adding_failed);
-
- Intent intent = new Intent(Intent.ACTION_VIEW);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
- {
- Uri.Builder uriBuilder = CalendarContract.CONTENT_URI.buildUpon();
- uriBuilder.appendPath("time");
- ContentUris.appendId(uriBuilder, System.currentTimeMillis());
- intent = intent.setData(uriBuilder.build());
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- }
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- notificationIntent = PendingIntent.getActivity(context, 0, intent, 0);
}
private boolean flag_notifications = true;
@@ -133,6 +103,28 @@ public void setFlagClearCalendars( boolean flag )
{
flag_clear = flag;
}
+ public boolean getFlagClearCalendars()
+ {
+ return flag_clear;
+ }
+
+ public void setItems(SuntimesCalendarTaskItem... items)
+ {
+ calendars.clear();
+ for (SuntimesCalendarTaskItem item : items) {
+ calendars.put(item.getCalendar(), item);
+ }
+ }
+ public SuntimesCalendarTaskItem[] getItems() {
+ return calendars.values().toArray(new SuntimesCalendarTaskItem[0]);
+ }
+
+ public void addItems(SuntimesCalendarTaskItem... items)
+ {
+ for (SuntimesCalendarTaskItem item : items) {
+ calendars.put(item.getCalendar(), item); // TODO: preserve existing
+ }
+ }
@Override
protected void onCancelled ()
@@ -150,105 +142,156 @@ protected void onPreExecute()
}
lastError = null;
- if (flag_notifications) {
- notificationBuilder.setContentTitle(notificationTitle)
- .setContentText((flag_clear ? notificationMsgClearing : notificationMsgAdding))
- .setSmallIcon(notificationIcon)
- .setPriority(notificationPriority)
- .setContentIntent(null).setAutoCancel(false)
- .setProgress(0, 0, true);
- notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
- }
+ String message = "";
+ if (flag_clear) {
+ message = notificationMsgClearing;
+ triggerOnStarted(message);
- if (listener != null) {
- listener.onStarted(flag_clear);
+ } else {
+ SuntimesCalendarTaskItem[] items = calendars.values().toArray(new SuntimesCalendarTask.SuntimesCalendarTaskItem[0]);
+ if (items.length > 0) {
+ int action = items[0].getAction();
+ message = (action == SuntimesCalendarTaskItem.ACTION_DELETE) ? notificationMsgClearing : notificationMsgAdding;
+
+ if (action != SuntimesCalendarTaskItem.ACTION_DELETE) {
+ triggerOnStarted(message);
+ } else triggerOnStarted("");
+ } else triggerOnStarted("");
}
}
+ private Calendar[] getWindow()
+ {
+ Calendar startDate = Calendar.getInstance();
+ Calendar endDate = Calendar.getInstance();
+ Calendar now = Calendar.getInstance();
+
+ startDate.setTimeInMillis(now.getTimeInMillis() - calendarWindow0);
+ startDate.set(Calendar.MONTH, 0); // round down to start of year
+ startDate.set(Calendar.DAY_OF_MONTH, 0);
+ startDate.set(Calendar.HOUR_OF_DAY, 0);
+ startDate.set(Calendar.MINUTE, 0);
+ startDate.set(Calendar.SECOND, 0);
+
+ endDate.setTimeInMillis(now.getTimeInMillis() + calendarWindow1);
+ endDate.add(Calendar.YEAR, 1); // round up to end of year
+ endDate.set(Calendar.MONTH, 0);
+ endDate.set(Calendar.DAY_OF_MONTH, 0);
+ endDate.set(Calendar.HOUR_OF_DAY, 0);
+ endDate.set(Calendar.MINUTE, 0);
+ endDate.set(Calendar.SECOND, 0);
+
+ return new Calendar[] { startDate, endDate };
+ }
+
@Override
- protected Boolean doInBackground(Void... params)
+ protected Boolean doInBackground(SuntimesCalendarTaskItem... items)
{
if (Build.VERSION.SDK_INT < 14)
return false;
- boolean retValue = adapter.removeCalendars();
- if (!flag_clear && !isCancelled())
- {
- Calendar startDate = Calendar.getInstance();
- Calendar endDate = Calendar.getInstance();
- Calendar now = Calendar.getInstance();
-
- startDate.setTimeInMillis(now.getTimeInMillis() - calendarWindow0);
- startDate.set(Calendar.MONTH, 0); // round down to start of year
- startDate.set(Calendar.DAY_OF_MONTH, 0);
- startDate.set(Calendar.HOUR_OF_DAY, 0);
- startDate.set(Calendar.MINUTE, 0);
- startDate.set(Calendar.SECOND, 0);
-
- endDate.setTimeInMillis(now.getTimeInMillis() + calendarWindow1);
- endDate.add(Calendar.YEAR, 1); // round up to end of year
- endDate.set(Calendar.MONTH, 0);
- endDate.set(Calendar.DAY_OF_MONTH, 0);
- endDate.set(Calendar.HOUR_OF_DAY, 0);
- endDate.set(Calendar.MINUTE, 0);
- endDate.set(Calendar.SECOND, 0);
-
- Log.d(TAG, "Adding... startWindow: " + calendarWindow0 + " (" + startDate.get(Calendar.YEAR) + "), "
- + "endWindow: " + calendarWindow1 + " (" + endDate.get(Calendar.YEAR) + ")");
-
- try {
- retValue = retValue && initSolsticeCalendar(startDate, endDate);
- retValue = retValue && initMoonPhaseCalendar(startDate, endDate);
-
- } catch (SecurityException e) {
- lastError = "Unable to access provider! " + e;
- Log.e(TAG, lastError);
- return false;
+ if (items.length > 0) {
+ setItems(items);
+ }
+
+ boolean retValue = true;
+
+ if (flag_clear && !isCancelled()) {
+ adapter.removeCalendars();
+ }
+
+ Calendar[] window = getWindow();
+ Log.d(TAG, "Adding... startWindow: " + calendarWindow0 + " (" + window[0].get(Calendar.YEAR) + "), "
+ + "endWindow: " + calendarWindow1 + " (" + window[1].get(Calendar.YEAR) + ")");
+
+ try {
+ for (String calendar : calendars.keySet())
+ {
+ SuntimesCalendarTaskItem item = calendars.get(calendar);
+ switch (item.getAction())
+ {
+ case SuntimesCalendarTaskItem.ACTION_DELETE:
+ onProgressUpdate(notificationMsgClearing);
+ retValue = retValue && adapter.removeCalendar(calendar);
+ break;
+
+ case SuntimesCalendarTaskItem.ACTION_UPDATE:
+ default:
+ onProgressUpdate(notificationMsgAdding);
+ retValue = retValue && initCalendar(calendar, window);
+ break;
+ }
}
+
+ } catch (SecurityException e) {
+ lastError = "Unable to access provider! " + e;
+ Log.e(TAG, lastError);
+ return false;
}
+
return retValue;
}
@Override
protected void onPostExecute(Boolean result)
{
+ Context context = contextRef.get();
if (result)
{
- Context context = contextRef.get();
if (context != null) {
SuntimesCalendarSyncAdapter.writeLastSyncTime(context, Calendar.getInstance());
}
- if (flag_notifications) {
- notificationBuilder.setContentTitle(notificationTitle)
- .setContentText((flag_clear ? notificationMsgCleared : notificationMsgAdded))
- .setSmallIcon(notificationIcon)
- .setPriority(notificationPriority)
- .setContentIntent(notificationIntent).setAutoCancel(true)
- .setProgress(0, 0, false);
- notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
+ String message = (flag_clear ? notificationMsgCleared : notificationMsgAdded);
+ SuntimesCalendarTaskItem[] items = calendars.values().toArray(new SuntimesCalendarTask.SuntimesCalendarTaskItem[0]);
+ if (items.length > 0) {
+ if (items[0].getAction() == SuntimesCalendarTaskItem.ACTION_DELETE) {
+ message = notificationMsgCleared;
+ }
}
- if (flag_clear)
- Log.i(TAG, "Cleared Suntimes Calendars...");
- else Log.i(TAG, "Added Suntimes Calendars...");
-
- if (listener != null) {
- listener.onSuccess(flag_clear);
+ if (listener != null && context != null) {
+ listener.onSuccess(context, this, message);
}
} else {
Log.w(TAG, "Failed to complete task!");
- notificationManager.cancel(NOTIFICATION_ID);
- if (listener != null) {
- listener.onFailed(lastError);
+ if (listener != null && context != null) {
+ listener.onFailed(context, lastError);
}
}
}
- private static final int NOTIFICATION_REQUEST_LASTERROR = 10;
+ /**
+ * initCalendar
+ */
+ private boolean initCalendar(@NonNull String calendar, @NonNull Calendar[] window) throws SecurityException
+ {
+ if (window.length != 2) {
+ Log.e(TAG, "initCalendar: invalid window with length " + window.length);
+ return false;
+
+ } else if (window[0] == null || window[1] == null) {
+ Log.e(TAG, "initCalendar: invalid window; null!");
+ return false;
+ }
+
+ if (calendar.equals(SuntimesCalendarAdapter.CALENDAR_SOLSTICE)) {
+ return initSolsticeCalendar(window[0], window[1]);
+
+ } else if (calendar.equals(SuntimesCalendarAdapter.CALENDAR_MOONPHASE)) {
+ return initMoonPhaseCalendar(window[0], window[1]);
+
+ } else {
+ Log.w(TAG, "initCalendar: unrecognized calendar " + calendar);
+ return false;
+ }
+ }
- private boolean initSolsticeCalendar( Calendar startDate, Calendar endDate ) throws SecurityException
+ /**
+ * initSolsticeCalendar
+ */
+ private boolean initSolsticeCalendar(@NonNull Calendar startDate, @NonNull Calendar endDate ) throws SecurityException
{
if (isCancelled()) {
return false;
@@ -298,7 +341,10 @@ private boolean initSolsticeCalendar( Calendar startDate, Calendar endDate ) thr
} else return false;
}
- private boolean initMoonPhaseCalendar( Calendar startDate, Calendar endDate ) throws SecurityException
+ /**
+ * initMoonPhaseCalendar
+ */
+ private boolean initMoonPhaseCalendar( @NonNull Calendar startDate, @NonNull Calendar endDate ) throws SecurityException
{
if (isCancelled()) {
return false;
@@ -353,17 +399,99 @@ private boolean initMoonPhaseCalendar( Calendar startDate, Calendar endDate ) th
} else return false;
}
+ /**
+ * SuntimesCalendarTaskItem
+ */
+ public static class SuntimesCalendarTaskItem implements Parcelable
+ {
+ public static final int ACTION_UPDATE = 0;
+ public static final int ACTION_DELETE = 2;
+
+ private String calendar;
+ private int action;
+
+ public SuntimesCalendarTaskItem( String calendar, int action )
+ {
+ this.calendar = calendar;
+ this.action = action;
+ }
+
+ private SuntimesCalendarTaskItem(Parcel in)
+ {
+ this.calendar = in.readString();
+ this.action = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags)
+ {
+ dest.writeString(calendar);
+ dest.writeInt(action);
+ }
+
+ @Override
+ public int describeContents()
+ {
+ return 0;
+ }
+
+ public String getCalendar()
+ {
+ return calendar;
+ }
+
+ public int getAction()
+ {
+ return action;
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator()
+ {
+ public SuntimesCalendarTaskItem createFromParcel(Parcel in)
+ {
+ return new SuntimesCalendarTaskItem(in);
+ }
+
+ public SuntimesCalendarTaskItem[] newArray(int size)
+ {
+ return new SuntimesCalendarTaskItem[size];
+ }
+ };
+ }
+
+ /**
+ * SuntimesCalendarTaskListener
+ */
+ public static abstract class SuntimesCalendarTaskListener implements Parcelable
+ {
+ public void onStarted(Context context, SuntimesCalendarTask task, String message) {}
+ public void onSuccess(Context context, SuntimesCalendarTask task, String message) {}
+ public void onFailed(Context context, String errorMsg) {}
+
+ public SuntimesCalendarTaskListener() {}
+
+ protected SuntimesCalendarTaskListener(Parcel in) {}
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+ }
+
private SuntimesCalendarTaskListener listener;
public void setTaskListener( SuntimesCalendarTaskListener listener )
{
this.listener = listener;
- this.listener = listener;
}
- public static abstract class SuntimesCalendarTaskListener
+ protected void triggerOnStarted(String message)
{
- public void onStarted(boolean flag_clear) {}
- public void onSuccess(boolean flag_cleared) {}
- public void onFailed(String errorMsg) {}
+ Context context = contextRef.get();
+ if (listener != null && context != null) {
+ listener.onStarted(context, this, message);
+ }
}
}
diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarTaskService.java b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarTaskService.java
new file mode 100644
index 00000000..36cf96f1
--- /dev/null
+++ b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/SuntimesCalendarTaskService.java
@@ -0,0 +1,315 @@
+/**
+ Copyright (C) 2018 Forrest Guice
+ This file is part of SuntimesCalendars.
+
+ SuntimesCalendars is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SuntimesCalendars 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.
+
+ You should have received a copy of the GNU General Public License
+ along with SuntimesCalendars. If not, see .
+*/
+
+package com.forrestguice.suntimeswidget.calendar;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.CalendarContract;
+import android.support.annotation.Nullable;
+import android.support.v4.app.NotificationManagerCompat;
+import android.support.v7.app.NotificationCompat;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.forrestguice.suntimescalendars.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class SuntimesCalendarTaskService extends Service
+{
+ public static final String TAG = "SuntimesCalendarsTask";
+ public static final String ACTION_UPDATE_CALENDARS = "update_calendars";
+ public static final String ACTION_CLEAR_CALENDARS = "clear_calendars";
+
+ public static final String EXTRA_CALENDAR_ITEMS = "calendar_items";
+ public static final String EXTRA_CALENDAR_LISTENER = "calendar_listener";
+ public static final String EXTRA_SERVICE_LISTENER = "service_listener";
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent)
+ {
+ return taskBinder;
+ }
+
+ private final SuntimesCalendarTaskServiceBinder taskBinder = new SuntimesCalendarTaskServiceBinder();
+ public class SuntimesCalendarTaskServiceBinder extends Binder
+ {
+ SuntimesCalendarTaskService getService() {
+ return SuntimesCalendarTaskService.this;
+ }
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId)
+ {
+ String action = intent.getAction();
+ if (action != null)
+ {
+ SuntimesCalendarServiceListener serviceListener = intent.getParcelableExtra(EXTRA_SERVICE_LISTENER);
+ SuntimesCalendarTask.SuntimesCalendarTaskListener listener = intent.getParcelableExtra(EXTRA_CALENDAR_LISTENER);
+ if (action.equals(ACTION_UPDATE_CALENDARS))
+ {
+ Log.d(TAG, "onStartCommand: " + action);
+ boolean started = runCalendarTask(this, intent, false, false, listener);
+ signalOnStartCommand(started);
+ if (serviceListener != null) {
+ serviceListener.onStartCommand(started);
+ }
+
+ } else if (action.equals(ACTION_CLEAR_CALENDARS)) {
+ Log.d(TAG, "onStartCommand: " + action);
+ boolean started = runCalendarTask(this, intent, true, false, listener);
+ signalOnStartCommand(started);
+ if (serviceListener != null) {
+ serviceListener.onStartCommand(started);
+ }
+
+ } else Log.d(TAG, "onStartCommand: unrecognized action: " + action);
+ } else Log.d(TAG, "onStartCommand: null action");
+ return START_NOT_STICKY;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ public static final int NOTIFICATION_PROGRESS = 10;
+ public static final int NOTIFICATION_COMPLETE = 20;
+
+ private static SuntimesCalendarTask calendarTask = null;
+ private static SuntimesCalendarTask.SuntimesCalendarTaskListener calendarTaskListener;
+ public boolean runCalendarTask(final Context context, Intent intent, boolean clearCalendars, boolean clearPending, @Nullable final SuntimesCalendarTask.SuntimesCalendarTaskListener listener)
+ {
+ ArrayList items = new ArrayList<>();
+ if (!clearCalendars) {
+ items = loadItems(intent, clearPending);
+ }
+
+ if (isBusy()) {
+ Log.w(TAG, "runCalendarTask: A task is already running! ignoring...");
+ return false;
+ }
+
+ calendarTask = new SuntimesCalendarTask(context);
+ calendarTaskListener = (listener != null) ? listener : new SuntimesCalendarTask.SuntimesCalendarTaskListener()
+ {
+ @Override
+ public void onStarted(Context context, SuntimesCalendarTask task, String message)
+ {
+ if (!task.getFlagClearCalendars() && hasUpdateAction(task.getItems()))
+ {
+ signalOnBusyStatusChanged(true);
+ signalOnProgressMessage(getString(R.string.calendars_notification_adding));
+
+ NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context);
+ notificationBuilder.setContentTitle(context.getString(R.string.app_name))
+ .setContentText(message)
+ .setSmallIcon(R.drawable.ic_action_update)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setProgress(0, 0, true);
+ startService(new Intent( context, SuntimesCalendarTaskService.class )); // bind the service to itself (to keep things running if the activity unbinds)
+ startForeground(NOTIFICATION_PROGRESS, notificationBuilder.build());
+
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
+ notificationManager.cancel(NOTIFICATION_COMPLETE);
+ }
+ }
+
+ private boolean hasUpdateAction(SuntimesCalendarTask.SuntimesCalendarTaskItem[] items)
+ {
+ for (SuntimesCalendarTask.SuntimesCalendarTaskItem item : items) {
+ if (item.getAction() == SuntimesCalendarTask.SuntimesCalendarTaskItem.ACTION_UPDATE) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onSuccess(Context context, SuntimesCalendarTask task, String message)
+ {
+ Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
+
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
+ NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context);
+ notificationBuilder.setContentTitle(context.getString(R.string.app_name))
+ .setContentText(message)
+ .setSmallIcon(R.drawable.ic_action_calendar)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setContentIntent(getNotificationIntent()).setAutoCancel(true)
+ .setProgress(0, 0, false);
+
+ notificationManager.notify(NOTIFICATION_COMPLETE, notificationBuilder.build());
+ signalOnBusyStatusChanged(false);
+ stopForeground(true);
+ stopSelf();
+ }
+
+ @Override
+ public void onFailed(final Context context, final String errorMsg)
+ {
+ super.onFailed(context, errorMsg);
+
+ Intent errorIntent = new Intent(context, SuntimesCalendarErrorActivity.class);
+ errorIntent.putExtra(SuntimesCalendarErrorActivity.EXTRA_ERROR_MESSAGE, errorMsg);
+ errorIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ context.startActivity(errorIntent);
+
+ signalOnBusyStatusChanged(false);
+ stopForeground(true);
+ stopSelf();
+ }
+
+ private PendingIntent getNotificationIntent()
+ {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ {
+ Uri.Builder uriBuilder = CalendarContract.CONTENT_URI.buildUpon();
+ uriBuilder.appendPath("time");
+ ContentUris.appendId(uriBuilder, System.currentTimeMillis());
+ intent = intent.setData(uriBuilder.build());
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ }
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return PendingIntent.getActivity(context, 0, intent, 0);
+ }
+ };
+ calendarTask.setTaskListener(calendarTaskListener);
+
+ if (clearCalendars) {
+ calendarTask.setFlagClearCalendars(true);
+ }
+ calendarTask.setItems(items.toArray(new SuntimesCalendarTask.SuntimesCalendarTaskItem[0]));
+ calendarTask.execute();
+ return true;
+ }
+
+ public boolean isBusy()
+ {
+ if (calendarTask != null)
+ {
+ switch (calendarTask.getStatus())
+ {
+ case PENDING:
+ case RUNNING:
+ return true;
+
+ case FINISHED:
+ default:
+ return false;
+ }
+ } else return false;
+ }
+
+ private String lastProgressMessage;
+ public String getLastProgressMessage()
+ {
+ return lastProgressMessage;
+ }
+
+ public static ArrayList loadItems(Intent intent, boolean clearPending)
+ {
+ SuntimesCalendarTask.SuntimesCalendarTaskItem[] items;
+ Parcelable[] parcelableArray = intent.getParcelableArrayExtra(EXTRA_CALENDAR_ITEMS);
+ if (parcelableArray != null) {
+ items = Arrays.copyOf(parcelableArray, parcelableArray.length, SuntimesCalendarTask.SuntimesCalendarTaskItem[].class);
+ } else items = new SuntimesCalendarTask.SuntimesCalendarTaskItem[0];
+
+ if (clearPending) {
+ intent.removeExtra(EXTRA_CALENDAR_ITEMS);
+ }
+ return new ArrayList<>(Arrays.asList(items));
+ }
+
+ /**
+ * SuntimesCalendarServiceListener
+ */
+ public static abstract class SuntimesCalendarServiceListener implements Parcelable
+ {
+ public void onStartCommand(boolean result) {}
+ public void onBusyStatusChanged(boolean isBusy) {}
+ public void onProgressMessage(String message) {}
+
+ public SuntimesCalendarServiceListener() {}
+ protected SuntimesCalendarServiceListener(Parcel in) {}
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+ }
+
+ private ArrayList serviceListeners = new ArrayList<>();
+ public void addCalendarServiceListener(SuntimesCalendarServiceListener listener)
+ {
+ serviceListeners.add(listener);
+ }
+ public void removeCalendarServiceListener(SuntimesCalendarServiceListener listener)
+ {
+ if (serviceListeners.contains(listener)) {
+ serviceListeners.remove(listener);
+ }
+ }
+
+ private void signalOnStartCommand(boolean result)
+ {
+ for (SuntimesCalendarServiceListener listener : serviceListeners) {
+ if (listener != null) {
+ listener.onStartCommand(result);
+ }
+ }
+ }
+
+ private void signalOnBusyStatusChanged(boolean isBusy)
+ {
+ for (SuntimesCalendarServiceListener listener : serviceListeners) {
+ if (listener != null) {
+ listener.onBusyStatusChanged(isBusy);
+ }
+ }
+ }
+
+ private void signalOnProgressMessage(String message)
+ {
+ lastProgressMessage = message;
+ for (SuntimesCalendarServiceListener listener : serviceListeners) {
+ if (listener != null) {
+ listener.onProgressMessage(message);
+ }
+ }
+ }
+
+}
diff --git a/app/src/main/res/drawable-hdpi/ic_action_update.png b/app/src/main/res/drawable-hdpi/ic_action_update.png
new file mode 100644
index 00000000..e2a22fa1
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_update.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_update_light.png b/app/src/main/res/drawable-hdpi/ic_action_update_light.png
new file mode 100644
index 00000000..df87e5ba
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_update_light.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_update.png b/app/src/main/res/drawable-mdpi/ic_action_update.png
new file mode 100644
index 00000000..c8c43e3d
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_update.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_update_light.png b/app/src/main/res/drawable-mdpi/ic_action_update_light.png
new file mode 100644
index 00000000..84b71db9
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_update_light.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_update.png b/app/src/main/res/drawable-xhdpi/ic_action_update.png
new file mode 100644
index 00000000..8ae786b2
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_update.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_update_light.png b/app/src/main/res/drawable-xhdpi/ic_action_update_light.png
new file mode 100644
index 00000000..ca84d91a
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_update_light.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_update.png b/app/src/main/res/drawable-xxhdpi/ic_action_update.png
new file mode 100644
index 00000000..9a8e3795
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_update.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_update_light.png b/app/src/main/res/drawable-xxhdpi/ic_action_update_light.png
new file mode 100644
index 00000000..84987892
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_update_light.png differ
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 53fd8421..a8e40627 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -6,6 +6,7 @@
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4d524895..b3206a67 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -108,4 +108,6 @@
false3153600000063072000000
+ true
+ true
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index b78fc088..974f0783 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -9,6 +9,7 @@
@color/text_accent_light@color/text_disabled_light@drawable/ic_action_calendar_light
+ @drawable/ic_action_update_light@drawable/ic_action_about_light@drawable/ic_action_settings_light@drawable/ic_action_help_light
@@ -21,8 +22,18 @@
@color/text_accent_dark@color/text_disabled_dark@drawable/ic_action_calendar
+ @drawable/ic_action_update@drawable/ic_action_about@drawable/ic_action_settings@drawable/ic_action_help
+
+
+
diff --git a/app/src/main/res/xml/preference_calendars.xml b/app/src/main/res/xml/preference_calendars.xml
index 98c529cf..16194605 100644
--- a/app/src/main/res/xml/preference_calendars.xml
+++ b/app/src/main/res/xml/preference_calendars.xml
@@ -9,6 +9,18 @@
android:title="@string/configLabel_calendars_enabled"
android:summary="@string/configLabel_calendars_enabled_summary" android:defaultValue="@string/def_app_calendars_enabled" />
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file