Implemented calendar integration
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="de.sebse.fuplanner">
|
||||
|
||||
<uses-permission
|
||||
@@ -21,6 +22,10 @@
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||
|
||||
<!-- Calendar integration -->
|
||||
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
||||
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup_descriptor"
|
||||
@@ -58,7 +63,6 @@
|
||||
android:syncable="true">
|
||||
|
||||
</provider>
|
||||
|
||||
<activity
|
||||
android:name=".services.fulogin.FUAuthenticatorActivity"
|
||||
android:label="@string/title_activity_fuauthenticator" />
|
||||
|
||||
@@ -1,24 +1,38 @@
|
||||
package de.sebse.fuplanner.fragments;
|
||||
|
||||
import android.Manifest;
|
||||
import android.accounts.Account;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import de.sebse.fuplanner.MainActivity;
|
||||
import de.sebse.fuplanner.R;
|
||||
import de.sebse.fuplanner.services.fulogin.AccountGeneral;
|
||||
import de.sebse.fuplanner.services.kvv.sync.KVVContentProvider;
|
||||
import de.sebse.fuplanner.services.kvv.ui.Download;
|
||||
import de.sebse.fuplanner.tools.CustomAccountManager;
|
||||
import de.sebse.fuplanner.tools.MainActivityListener;
|
||||
import de.sebse.fuplanner.tools.Preferences;
|
||||
import de.sebse.fuplanner.tools.RequestPermissionsResultListener;
|
||||
import de.sebse.fuplanner.tools.logging.Logger;
|
||||
|
||||
public class PrefsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private MainActivityListener mMainActivityListener;
|
||||
|
||||
public static PrefsFragment newInstance() {
|
||||
PrefsFragment fragment = new PrefsFragment();
|
||||
Bundle args = new Bundle();
|
||||
@@ -84,5 +98,44 @@ public class PrefsFragment extends PreferenceFragmentCompat implements SharedPre
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.equals(requireContext().getString(R.string.pref_add_calendar))
|
||||
&& getActivity() != null
|
||||
&& Preferences.getBoolean(getActivity(), R.string.pref_add_calendar)) {
|
||||
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED
|
||||
|| ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.WRITE_CALENDAR, Manifest.permission.READ_CALENDAR}, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
if (context instanceof MainActivityListener) {
|
||||
mMainActivityListener = (MainActivityListener) context;
|
||||
mMainActivityListener.onTitleTextChange(R.string.settings);
|
||||
mMainActivityListener.addRequestPermissionsResultListener(getRequestPermissionsResultListener(), "PrefFragment");
|
||||
} else
|
||||
throw new RuntimeException(context.toString() + " must implement MainActivityListener");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mMainActivityListener.removeRequestPermissionsResultListener("PrefFragment");
|
||||
mMainActivityListener = null;
|
||||
}
|
||||
|
||||
private RequestPermissionsResultListener getRequestPermissionsResultListener() {
|
||||
return (requestCode, permissions, grantResults) -> {
|
||||
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED
|
||||
|| ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
|
||||
Preferences.setBoolean(requireContext(), R.string.pref_add_calendar, false);
|
||||
setPreferenceScreen(null);
|
||||
setPreferencesFromResource(R.xml.preferences, null);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -104,9 +104,10 @@ public class ModulesList extends HTTPService {
|
||||
} catch (FileNotFoundException ignored) {
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
delete();
|
||||
}
|
||||
if (this.mModules == null) {
|
||||
recv(success -> {}, log::e);
|
||||
recv(success -> {}, log::e, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package de.sebse.fuplanner.services.kvv;
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@@ -369,6 +371,8 @@ public class ModulesResources extends PartModules<ArrayList<Resource>> {
|
||||
// Saves file in folder: DOWNLOADS/moduleName
|
||||
File folder = new File(Environment.getExternalStoragePublicDirectory(
|
||||
Environment.DIRECTORY_DOWNLOADS), moduleName);
|
||||
//ContextCompat.checkSelfPermission(getContext(), )
|
||||
log.d("FILE", folder.toString());
|
||||
if (!folder.mkdirs()) {
|
||||
log.w( "Directory not created");
|
||||
}
|
||||
|
||||
@@ -1,26 +1,40 @@
|
||||
package de.sebse.fuplanner.services.kvv.sync;
|
||||
|
||||
import android.Manifest;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SyncResult;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.provider.CalendarContract.Calendars;
|
||||
import android.provider.CalendarContract.Events;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Iterator;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import de.sebse.fuplanner.R;
|
||||
import de.sebse.fuplanner.fragments.moddetails.ModulePart;
|
||||
import de.sebse.fuplanner.services.kvv.KVV;
|
||||
import de.sebse.fuplanner.services.kvv.types.Announcement;
|
||||
import de.sebse.fuplanner.services.kvv.types.Assignment;
|
||||
import de.sebse.fuplanner.services.kvv.types.AssignmentList;
|
||||
import de.sebse.fuplanner.services.kvv.types.Event;
|
||||
import de.sebse.fuplanner.services.kvv.types.EventList;
|
||||
import de.sebse.fuplanner.services.kvv.types.Grade;
|
||||
import de.sebse.fuplanner.services.kvv.types.Gradebook;
|
||||
@@ -28,9 +42,11 @@ import de.sebse.fuplanner.services.kvv.types.Modules;
|
||||
import de.sebse.fuplanner.services.kvv.types.Resource;
|
||||
import de.sebse.fuplanner.tools.CustomNotificationManager;
|
||||
import de.sebse.fuplanner.tools.NewAsyncQueue;
|
||||
import de.sebse.fuplanner.tools.Preferences;
|
||||
import de.sebse.fuplanner.tools.UtilsDate;
|
||||
import de.sebse.fuplanner.tools.logging.Logger;
|
||||
|
||||
import static android.provider.CalendarContract.CALLER_IS_SYNCADAPTER;
|
||||
import static de.sebse.fuplanner.MainActivity.FRAGMENT_MODULES_DETAILS;
|
||||
|
||||
public class KVVSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
@@ -98,7 +114,7 @@ public class KVVSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
String authority,
|
||||
ContentProviderClient provider,
|
||||
SyncResult syncResult) {
|
||||
if (!mBound) {
|
||||
if (!mBound && !mWaitForBound) {
|
||||
Intent intent = new Intent(getContext(), KVV.class);
|
||||
getContext().bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
|
||||
mWaitForBound = true;
|
||||
@@ -138,9 +154,13 @@ public class KVVSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
sendNotifications(assignments, module.assignments, module.title, Assignment::getTitle, Assignment::getId,
|
||||
module.getID(), ModulePart.ASSIGNMENT,
|
||||
R.string.assignment_updated, R.string.assignment_added, R.string.assignment_removed);
|
||||
//ArrayList<Event> differencesAdd = new ArrayList<>();
|
||||
//ArrayList<Pair<Event, Event>> differencesUpd = new ArrayList<>();
|
||||
//ArrayList<Event> differencesDel = new ArrayList<>();
|
||||
sendNotifications(events, module.events, module.title, evt -> evt.getTitle()+" - "+UtilsDate.getModifiedDate(evt.getStartDate()), event -> event.getStartDate() +event.getType()+event.getTitle(),
|
||||
module.getID(), ModulePart.EVENT,
|
||||
R.string.event_updated, R.string.event_added, R.string.event_removed);
|
||||
R.string.event_updated, R.string.event_added, R.string.event_removed/*,
|
||||
differencesAdd, differencesUpd, differencesDel*/);
|
||||
sendNotifications(gradebook, module.gradebook, module.title, Grade::getItemName, Grade::getItemName,
|
||||
module.getID(), ModulePart.GRADEBOOK,
|
||||
R.string.gradebook_updated, R.string.gradebook_added, R.string.gradebook_removed);
|
||||
@@ -154,20 +174,159 @@ public class KVVSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
if (--latch[0] == 0) mQueue.next();
|
||||
}, true);
|
||||
}
|
||||
|
||||
// Add events to calendar
|
||||
if (createCalendar(account)) {
|
||||
iterator = success.latestSemesterIterator();
|
||||
while (iterator.hasNext()) {
|
||||
Modules.Module module = iterator.next();
|
||||
addToCalendar(module.events, account);
|
||||
}
|
||||
forceSync();
|
||||
}
|
||||
}, msg -> {
|
||||
log.e(msg);
|
||||
mQueue.next();
|
||||
}, true);
|
||||
});
|
||||
mQueue.add(() -> {
|
||||
if (mBound) {
|
||||
getContext().unbindService(mConnection);
|
||||
}
|
||||
mBound = false;
|
||||
mKVV = null;
|
||||
getContext().unbindService(mConnection);
|
||||
mQueue.next();
|
||||
});
|
||||
}
|
||||
|
||||
static Uri asSyncAdapter(Uri uri, String account, String accountType) {
|
||||
return uri.buildUpon()
|
||||
.appendQueryParameter(CALLER_IS_SYNCADAPTER, "true")
|
||||
.appendQueryParameter(Calendars.ACCOUNT_NAME, account)
|
||||
.appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
|
||||
}
|
||||
|
||||
private static Uri createCalendarWithName(Context ctx, Account account, String calendarName) {
|
||||
String accountName = account.name;
|
||||
String accountType = account.type;
|
||||
Uri target = asSyncAdapter(Calendars.CONTENT_URI, accountName, accountType);
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Calendars._ID, calendarName.hashCode());
|
||||
values.put(Calendars.ACCOUNT_NAME, accountName);
|
||||
values.put(Calendars.ACCOUNT_TYPE, accountType);
|
||||
values.put(Calendars.NAME, calendarName);
|
||||
values.put(Calendars.CALENDAR_DISPLAY_NAME, calendarName);
|
||||
values.put(Calendars.CALENDAR_COLOR, 0x00FF00);
|
||||
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ);
|
||||
values.put(Calendars.OWNER_ACCOUNT, accountName);
|
||||
values.put(Calendars.VISIBLE, 1);
|
||||
values.put(Calendars.SYNC_EVENTS, 1);
|
||||
values.put(Calendars.CALENDAR_TIME_ZONE, "Europe/Berlin");
|
||||
values.put(Calendars.CAN_PARTIALLY_UPDATE, 1);
|
||||
values.put(Calendars.CAL_SYNC1, System.currentTimeMillis());
|
||||
|
||||
return ctx.getContentResolver().insert(target, values);
|
||||
}
|
||||
|
||||
private int createEvents(Context ctx, Account account, String calendarName, EventList events) {
|
||||
String accountName = account.name;
|
||||
String accountType = account.type;
|
||||
Uri target = asSyncAdapter(Events.CONTENT_URI, accountName, accountType);
|
||||
|
||||
ContentValues[] contentValues = new ContentValues[events.size()];
|
||||
for (int i = 0; i < contentValues.length; i++) {
|
||||
Event event = events.get(i);
|
||||
ContentValues values = new ContentValues();
|
||||
//values.put(Events._ID, event.hashCode());
|
||||
values.put(Events.TITLE, event.getTitle());
|
||||
values.put(Events.DTSTART, event.getStartDate());
|
||||
values.put(Events.DTEND, event.getEndDate());
|
||||
values.put(Events.CALENDAR_ID, calendarName.hashCode());
|
||||
contentValues[i] = values;
|
||||
//ctx.getContentResolver().insert(target, values);
|
||||
}
|
||||
log.d(contentValues.length);
|
||||
return ctx.getContentResolver().bulkInsert(target, contentValues);
|
||||
}
|
||||
|
||||
private static Cursor getCalendars(Context ctx, Account account) {
|
||||
Uri target = asSyncAdapter(Calendars.CONTENT_URI, account.name, account.type);
|
||||
|
||||
return ctx.getContentResolver().query(target, new String[]{Calendars.CALENDAR_DISPLAY_NAME, Calendars.ACCOUNT_NAME}, null, null, null);
|
||||
}
|
||||
|
||||
private static Cursor getEvents(Context ctx, Account account) {
|
||||
Uri target = asSyncAdapter(Events.CONTENT_URI, account.name, account.type);
|
||||
|
||||
return ctx.getContentResolver().query(target, new String[]{Events.TITLE, Events.DTSTART}, null, null, null);
|
||||
}
|
||||
|
||||
private static int deleteCalendars(Context ctx, Account account) {
|
||||
String accountName = account.name;
|
||||
String accountType = account.type;
|
||||
Uri target = asSyncAdapter(Calendars.CONTENT_URI, account.name, account.type);
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(Calendars.ACCOUNT_NAME, accountName);
|
||||
values.put(Calendars.ACCOUNT_TYPE, accountType);
|
||||
|
||||
return ctx.getContentResolver().delete(target, null, null);
|
||||
}
|
||||
|
||||
private void addToCalendar(EventList events, Account account) {
|
||||
if (events == null) {
|
||||
return;
|
||||
}
|
||||
if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_CALENDAR) == PackageManager.PERMISSION_GRANTED
|
||||
&& ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) {
|
||||
boolean integrationEnabled = Preferences.getBoolean(getContext(), R.string.pref_add_calendar);
|
||||
if (integrationEnabled) {
|
||||
createEvents(getContext(), account, getContext().getString(R.string.app_name), events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean createCalendar(Account account) {
|
||||
if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_CALENDAR) == PackageManager.PERMISSION_GRANTED
|
||||
&& ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) {
|
||||
boolean integrationEnabled = Preferences.getBoolean(getContext(), R.string.pref_add_calendar);
|
||||
if (integrationEnabled) {
|
||||
//log.w("No calendar found! Add calendar...");
|
||||
deleteCalendars(getContext(), account);
|
||||
createCalendarWithName(getContext(), account, getContext().getString(R.string.app_name));
|
||||
return true;
|
||||
} else {
|
||||
log.w("Calendar found and integration disabled! Delete calendar...");
|
||||
deleteCalendars(getContext(), account);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
log.w("Permission calendar not granted!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void forceSync() {
|
||||
// Force a sync
|
||||
Bundle extras = new Bundle();
|
||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
|
||||
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
|
||||
AccountManager am = AccountManager.get(getContext());
|
||||
Account[] acc = am.getAccountsByType("com.google");
|
||||
Account account = null;
|
||||
if (acc.length>0) {
|
||||
account = acc[0];
|
||||
ContentResolver.requestSync(account, "com.android.calendar", extras);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void sendNotifications(Iterable<T> oldList, Iterable<T> newList, String title, StringInterface<T> titleInterface, StringInterface<T> idInterface, String moduleId, int modulePart, @StringRes int updateRes, @StringRes int addRes, @StringRes int removeRes) {
|
||||
if (oldList == null || newList == null) {
|
||||
sendNotifications(oldList, newList, title, titleInterface, idInterface, moduleId, modulePart, updateRes, addRes, removeRes, null, null, null);
|
||||
}
|
||||
|
||||
private <T> void sendNotifications(Iterable<T> oldList, Iterable<T> newList, String title, StringInterface<T> titleInterface, StringInterface<T> idInterface, String moduleId, int modulePart, @StringRes int updateRes, @StringRes int addRes, @StringRes int removeRes, ArrayList<T> changesAdd, ArrayList<Pair<T, T>> changesUpd, ArrayList<T> changesDel) {
|
||||
if (oldList == null || newList == null) {
|
||||
return;
|
||||
}
|
||||
ArrayList<T> obsoletes = new ArrayList<>();
|
||||
@@ -182,6 +341,9 @@ public class KVVSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
found = true;
|
||||
if (newEntry.hashCode() != oldEntry.hashCode()) {
|
||||
CustomNotificationManager.sendNotification(getContext(), getContext().getString(updateRes, title), titleInterface.get(newEntry), FRAGMENT_MODULES_DETAILS, targetData);
|
||||
if (changesUpd != null) {
|
||||
changesUpd.add(new Pair<>(oldEntry, newEntry));
|
||||
}
|
||||
}
|
||||
obsoletes.remove(oldEntry);
|
||||
break;
|
||||
@@ -189,10 +351,16 @@ public class KVVSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
}
|
||||
if (!found) {
|
||||
CustomNotificationManager.sendNotification(getContext(), getContext().getString(addRes, title), titleInterface.get(newEntry), FRAGMENT_MODULES_DETAILS, targetData);
|
||||
if (changesAdd != null) {
|
||||
changesAdd.add(newEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (T oldEntry: obsoletes) {
|
||||
CustomNotificationManager.sendNotification(getContext(), getContext().getString(removeRes, title), titleInterface.get(oldEntry), FRAGMENT_MODULES_DETAILS, targetData);
|
||||
if (changesDel != null) {
|
||||
changesDel.add(oldEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
<string name="pref_sync_frequency_title">Synchronisationshäufigkeit</string>
|
||||
<string name="pref_sync_frequency_summary">Stellt Häufigkeit der automatischen Synchronisation ein</string>
|
||||
<string name="pref_sync_frequency_dialog">Sync-Frequenz</string>
|
||||
<string name="pref_add_calendar_title">Zum Kalender hinzufügen</string>
|
||||
<string name="pref_add_calendar_summary">Wenn ausgewählt, wird </string>
|
||||
<string name="meals">Hauptgerichte</string>
|
||||
<string name="special_meals">Spezial Gerichte</string>
|
||||
<string name="side_dishes">Beilagen</string>
|
||||
|
||||
@@ -77,6 +77,9 @@
|
||||
<string name="pref_night_mode" translatable="false">pref_night_mode</string>
|
||||
<string name="pref_night_mode_default" translatable="false">auto</string>
|
||||
|
||||
<string name="pref_add_calendar" translatable="false">pref_add_calendar</string>
|
||||
<string name="pref_add_calendar_default" translatable="false">false</string>
|
||||
|
||||
<!-- Other preferences -->
|
||||
|
||||
<string name="pref_last_visited_news" translatable="false">pref_last_visited_news</string>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<string name="schedule">Schedule</string>
|
||||
<string name="courses">Courses</string>
|
||||
<string name="canteen_plan">Canteen Plan</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="settings">Preferences</string>
|
||||
<string name="options">Options</string>
|
||||
<string name="log_out">Log out</string>
|
||||
<string name="share">Share</string>
|
||||
@@ -59,6 +59,8 @@
|
||||
<string name="pref_sync_frequency_title">Sync frequency</string>
|
||||
<string name="pref_sync_frequency_summary">Set automatic background sync frequency</string>
|
||||
<string name="pref_sync_frequency_dialog">Frequency Selection</string>
|
||||
<string name="pref_add_calendar_title">Add to Calendar</string>
|
||||
<string name="pref_add_calendar_summary">If checked schedule will be added to your calendar app</string>
|
||||
<string name="meals">Meals</string>
|
||||
<string name="special_meals">Special meals</string>
|
||||
<string name="side_dishes">Side Dishes</string>
|
||||
|
||||
@@ -33,6 +33,11 @@
|
||||
android:entries="@array/pref_sync_frequency_entries"
|
||||
android:entryValues="@array/pref_sync_frequency_values"
|
||||
android:dialogTitle="@string/pref_sync_frequency_dialog" />
|
||||
<CheckBoxPreference
|
||||
android:key="@string/pref_add_calendar"
|
||||
android:defaultValue="@string/pref_add_calendar_default"
|
||||
android:title="@string/pref_add_calendar_title"
|
||||
android:summary="@string/pref_add_calendar_summary" />
|
||||
<PreferenceScreen
|
||||
android:title="@string/open_data_policy"
|
||||
android:summary="@string/open_data_policy_summary">
|
||||
|
||||
Reference in New Issue
Block a user