mirror of
https://github.com/etesync/android
synced 2024-11-22 16:08:13 +00:00
Version 0.4.4
* allow to enter account name at the end of account setup (fixes #13) * advise user to enter email address as account name (closes #24) * support for iCal TRANSP (availability free/busy field) * create better UIDs using iCal4j UidGenerator and Settings.Secure.ANDROID_ID * better ORGANIZER/ATTENDEE support, but there are big Android issues yet * various improvements and bug fixes
This commit is contained in:
parent
44cf628dd6
commit
1986837be8
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="at.bitfire.davdroid"
|
||||
android:versionCode="16"
|
||||
android:versionName="0.4.3-alpha" >
|
||||
android:versionCode="17"
|
||||
android:versionName="0.4.4-alpha" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="14"
|
||||
|
BIN
res/drawable-hdpi/extra_actions_about.png
Normal file
BIN
res/drawable-hdpi/extra_actions_about.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 683 B |
BIN
res/drawable-mdpi/extra_actions_about.png
Normal file
BIN
res/drawable-mdpi/extra_actions_about.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 465 B |
BIN
res/drawable-xhdpi/extra_actions_about.png
Normal file
BIN
res/drawable-xhdpi/extra_actions_about.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 860 B |
BIN
res/drawable-xxhdpi/extra_actions_about.png
Normal file
BIN
res/drawable-xxhdpi/extra_actions_about.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
50
res/layout/account_details.xml
Normal file
50
res/layout/account_details.xml
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:scrollbars="vertical" >
|
||||
|
||||
<GridLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:columnCount="2"
|
||||
android:padding="10dp"
|
||||
android:useDefaultMargins="true" >
|
||||
|
||||
<TextView
|
||||
android:layout_columnSpan="2"
|
||||
android:layout_gravity="left"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:text="@string/account_details"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
|
||||
<TextView
|
||||
android:layout_gravity="left"
|
||||
android:text="@string/account_name"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/account_name"
|
||||
android:layout_gravity="fill_horizontal"
|
||||
android:hint="@string/account_name_hint"
|
||||
android:inputType="textEmailAddress" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/account_name_info"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_columnSpan="2"
|
||||
android:layout_gravity="left"
|
||||
android:drawableLeft="@drawable/extra_actions_about"
|
||||
android:drawablePadding="10dp"
|
||||
android:padding="10dp"
|
||||
android:text="@string/account_name_info"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<Space
|
||||
android:layout_gravity="left|top"
|
||||
android:layout_row="3" />
|
||||
|
||||
</GridLayout>
|
||||
|
||||
</ScrollView>
|
11
res/menu/account_details.xml
Normal file
11
res/menu/account_details.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item
|
||||
android:id="@+id/add_account"
|
||||
android:icon="@drawable/navigation_accept"
|
||||
android:showAsAction="always|withText"
|
||||
android:title="@string/add_account">
|
||||
</item>
|
||||
|
||||
</menu>
|
@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" ><item
|
||||
android:id="@+id/add_account"
|
||||
android:icon="@drawable/navigation_accept"
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item
|
||||
android:id="@+id/next"
|
||||
android:icon="@drawable/navigation_forward"
|
||||
android:showAsAction="always|withText"
|
||||
android:title="@string/add_account" android:orderInCategory="2">
|
||||
android:title="@string/next">
|
||||
</item>
|
||||
|
||||
|
||||
|
||||
</menu>
|
@ -75,5 +75,11 @@
|
||||
* <a href="http://simple.sourceforge.net/">Simple XML Serialization</a> (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License, Version 2.0</a>)<br/>
|
||||
* <a href="http://projectlombok.org/">Project Lombok</a> (<a href="http://opensource.org/licenses/mit-license.php">MIT License</a>)</p>
|
||||
]]></string>
|
||||
<string name="account_details">Konto-Details</string>
|
||||
<string name="account_name">Kontoname:</string>
|
||||
<string name="account_name_hint">Mein CalDAV/CardDAV-Konto</string>
|
||||
<string name="email_address">Email-Adresse:</string>
|
||||
<string name="organizer_hint">"ORGANIZER der von Ihnen angelegten Termine; notwendig für Teilnehmer-Info"</string>
|
||||
<string name="account_name_info">"Verwenden Sie Ihre Email-Addresse als Kontoname, da Android den Kontonamen als ORGANIZER-Feld in Terminen benutzt. Sie können keine zwei Konten mit dem gleichen Namen anlegen.</string>
|
||||
|
||||
</resources>
|
@ -83,5 +83,11 @@
|
||||
* <a href="http://simple.sourceforge.net/">Simple XML Serialization</a> (<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License, Version 2.0</a>)<br/>
|
||||
* <a href="http://projectlombok.org/">Project Lombok</a> (<a href="http://opensource.org/licenses/mit-license.php">MIT License</a>)</p>
|
||||
]]></string>
|
||||
<string name="account_details">Account details</string>
|
||||
<string name="account_name">Account name:</string>
|
||||
<string name="account_name_hint">My CalDAV/CardDAV Account</string>
|
||||
<string name="email_address">Email address:</string>
|
||||
<string name="organizer_hint">"ORGANIZER of your events; required if you use attendee info"</string>
|
||||
<string name="account_name_info">"Use your email address as account name because Android will use the account name as ORGANIZER field for events you create. You can't have two accounts with the same name.</string>
|
||||
|
||||
</resources>
|
||||
|
@ -9,7 +9,7 @@ package at.bitfire.davdroid;
|
||||
|
||||
public class Constants {
|
||||
public static final String
|
||||
APP_VERSION = "0.4.3-alpha",
|
||||
APP_VERSION = "0.4.4-alpha",
|
||||
|
||||
ACCOUNT_TYPE = "bitfire.at.davdroid",
|
||||
|
||||
|
@ -50,12 +50,13 @@ import net.fortuna.ical4j.model.property.RDate;
|
||||
import net.fortuna.ical4j.model.property.RRule;
|
||||
import net.fortuna.ical4j.model.property.Status;
|
||||
import net.fortuna.ical4j.model.property.Summary;
|
||||
import net.fortuna.ical4j.model.property.Transp;
|
||||
import net.fortuna.ical4j.model.property.Uid;
|
||||
import net.fortuna.ical4j.model.property.Version;
|
||||
import net.fortuna.ical4j.util.UidGenerator;
|
||||
import android.text.format.Time;
|
||||
import android.util.Log;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.syncadapter.DavSyncAdapter;
|
||||
|
||||
|
||||
public class Event extends Resource {
|
||||
@ -76,6 +77,7 @@ public class Event extends Resource {
|
||||
@Getter @Setter private Boolean forPublic;
|
||||
@Getter @Setter private Status status;
|
||||
|
||||
@Getter @Setter private boolean opaque;
|
||||
|
||||
@Getter @Setter private Organizer organizer;
|
||||
@Getter private List<Attendee> attendees = new LinkedList<Attendee>();
|
||||
@ -122,8 +124,7 @@ public class Event extends Resource {
|
||||
uid = event.getUid().getValue();
|
||||
else {
|
||||
Log.w(TAG, "Received VEVENT without UID, generating new one");
|
||||
UidGenerator uidGenerator = new UidGenerator(Integer.toString(android.os.Process.myPid()));
|
||||
uid = uidGenerator.generateUid().getValue();
|
||||
uid = DavSyncAdapter.generateUID();
|
||||
}
|
||||
|
||||
dtStart = event.getStartDate(); validateTimeZone(dtStart);
|
||||
@ -144,6 +145,10 @@ public class Event extends Resource {
|
||||
|
||||
status = event.getStatus();
|
||||
|
||||
opaque = true;
|
||||
if (event.getTransparency() == Transp.TRANSPARENT)
|
||||
opaque = false;
|
||||
|
||||
organizer = event.getOrganizer();
|
||||
for (Object o : event.getProperties(Property.ATTENDEE))
|
||||
attendees.add((Attendee)o);
|
||||
@ -196,6 +201,8 @@ public class Event extends Resource {
|
||||
|
||||
if (status != null)
|
||||
props.add(status);
|
||||
if (!opaque)
|
||||
props.add(Transp.TRANSPARENT);
|
||||
|
||||
if (organizer != null)
|
||||
props.add(organizer);
|
||||
|
@ -7,6 +7,7 @@
|
||||
******************************************************************************/
|
||||
package at.bitfire.davdroid.resource;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
@ -120,7 +121,10 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
values.put(Calendars.CALENDAR_COLOR, color);
|
||||
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER);
|
||||
values.put(Calendars.ALLOWED_AVAILABILITY, Events.AVAILABILITY_BUSY + "," + Events.AVAILABILITY_FREE + "," + Events.AVAILABILITY_TENTATIVE);
|
||||
values.put(Calendars.ALLOWED_ATTENDEE_TYPES, Attendees.TYPE_NONE + "," + Attendees.TYPE_REQUIRED + "," + Attendees.TYPE_OPTIONAL + "," + Attendees.TYPE_RESOURCE);
|
||||
values.put(Calendars.ALLOWED_ATTENDEE_TYPES, Attendees.TYPE_NONE + "," + Attendees.TYPE_OPTIONAL + "," + Attendees.TYPE_REQUIRED + "," + Attendees.TYPE_RESOURCE);
|
||||
values.put(Calendars.ALLOWED_REMINDERS, Reminders.METHOD_ALERT);
|
||||
values.put(Calendars.CAN_ORGANIZER_RESPOND, 1);
|
||||
values.put(Calendars.CAN_MODIFY_TIME_ZONE, 1);
|
||||
values.put(Calendars.OWNER_ACCOUNT, account.name);
|
||||
values.put(Calendars.SYNC_EVENTS, 1);
|
||||
values.put(Calendars.VISIBLE, 1);
|
||||
@ -196,7 +200,7 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
/* 8 */ Events.STATUS, Events.ACCESS_LEVEL,
|
||||
/* 10 */ Events.RRULE, Events.RDATE, Events.EXRULE, Events.EXDATE,
|
||||
/* 14 */ Events.HAS_ATTENDEE_DATA, Events.ORGANIZER, Events.SELF_ATTENDEE_STATUS,
|
||||
/* 17 */ entryColumnUID(), Events.DURATION
|
||||
/* 17 */ entryColumnUID(), Events.DURATION, Events.AVAILABILITY
|
||||
}, null, null, null);
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
e.setUid(cursor.getString(17));
|
||||
@ -270,12 +274,15 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
e.setStatus(Status.VEVENT_CANCELLED);
|
||||
}
|
||||
|
||||
// availability
|
||||
e.setOpaque(cursor.getInt(19) != Events.AVAILABILITY_FREE);
|
||||
|
||||
// attendees
|
||||
if (cursor.getInt(14) != 0) { // has attendees
|
||||
try {
|
||||
e.setOrganizer(new Organizer("mailto:" + cursor.getString(15)));
|
||||
e.setOrganizer(new Organizer(new URI("mailto", cursor.getString(15), null)));
|
||||
} catch (URISyntaxException ex) {
|
||||
Log.e(TAG, "Error parsing organizer URI, ignoring");
|
||||
Log.e(TAG, "Error when creating ORGANIZER URI, ignoring", ex);
|
||||
}
|
||||
|
||||
Uri attendeesUri = Attendees.CONTENT_URI.buildUpon()
|
||||
@ -287,7 +294,7 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
}, Attendees.EVENT_ID + "=?", new String[] { String.valueOf(e.getLocalID()) }, null);
|
||||
while (c != null && c.moveToNext()) {
|
||||
try {
|
||||
Attendee attendee = new Attendee("mailto:" + c.getString(0));
|
||||
Attendee attendee = new Attendee(new URI("mailto", c.getString(0), null));
|
||||
ParameterList params = attendee.getParameters();
|
||||
|
||||
String cn = c.getString(1);
|
||||
@ -314,13 +321,7 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
}
|
||||
|
||||
// status
|
||||
int status = Attendees.ATTENDEE_STATUS_NONE;
|
||||
if (relationship == Attendees.RELATIONSHIP_ORGANIZER) // we are organizer
|
||||
status = cursor.getInt(16);
|
||||
else
|
||||
status = c.getInt(4);
|
||||
|
||||
switch (status) {
|
||||
switch (c.getInt(4)) {
|
||||
case Attendees.ATTENDEE_STATUS_INVITED:
|
||||
params.add(PartStat.NEEDS_ACTION);
|
||||
break;
|
||||
@ -337,7 +338,7 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
|
||||
e.addAttendee(attendee);
|
||||
} catch (URISyntaxException ex) {
|
||||
Log.e(TAG, "Couldn't parse attendee member URI, ignoring member");
|
||||
Log.e(TAG, "Couldn't parse attendee information, ignoring", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -429,7 +430,10 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
.withValue(Events.ALL_DAY, event.isAllDay() ? 1 : 0)
|
||||
.withValue(Events.DTSTART, event.getDtStartInMillis())
|
||||
.withValue(Events.EVENT_TIMEZONE, event.getDtStartTzID())
|
||||
.withValue(Events.HAS_ATTENDEE_DATA, event.getAttendees().isEmpty() ? 0 : 1);
|
||||
.withValue(Events.HAS_ATTENDEE_DATA, event.getAttendees().isEmpty() ? 0 : 1)
|
||||
.withValue(Events.GUESTS_CAN_INVITE_OTHERS, 1)
|
||||
.withValue(Events.GUESTS_CAN_MODIFY, 1)
|
||||
.withValue(Events.GUESTS_CAN_SEE_GUESTS, 1);
|
||||
|
||||
boolean recurring = false;
|
||||
if (event.getRrule() != null) {
|
||||
@ -478,6 +482,12 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
if (event.getDescription() != null)
|
||||
builder = builder.withValue(Events.DESCRIPTION, event.getDescription());
|
||||
|
||||
if (event.getOrganizer() != null && event.getOrganizer().getCalAddress() != null) {
|
||||
URI organizer = event.getOrganizer().getCalAddress();
|
||||
if (organizer.getScheme().equalsIgnoreCase("mailto"))
|
||||
builder = builder.withValue(Events.ORGANIZER, organizer.getSchemeSpecificPart());
|
||||
}
|
||||
|
||||
Status status = event.getStatus();
|
||||
if (status != null) {
|
||||
int statusCode = Events.STATUS_TENTATIVE;
|
||||
@ -488,6 +498,8 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
builder = builder.withValue(Events.STATUS, statusCode);
|
||||
}
|
||||
|
||||
builder = builder.withValue(Events.AVAILABILITY, event.isOpaque() ? Events.AVAILABILITY_BUSY : Events.AVAILABILITY_FREE);
|
||||
|
||||
if (event.getForPublic() != null)
|
||||
builder = builder.withValue(Events.ACCESS_LEVEL, event.getForPublic() ? Events.ACCESS_PUBLIC : Events.ACCESS_PRIVATE);
|
||||
|
||||
@ -547,7 +559,7 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
|
||||
int status = Attendees.ATTENDEE_STATUS_NONE;
|
||||
PartStat partStat = (PartStat)attendee.getParameter(Parameter.PARTSTAT);
|
||||
if (partStat == PartStat.NEEDS_ACTION)
|
||||
if (partStat == null || partStat == PartStat.NEEDS_ACTION)
|
||||
status = Attendees.ATTENDEE_STATUS_INVITED;
|
||||
else if (partStat == PartStat.ACCEPTED)
|
||||
status = Attendees.ATTENDEE_STATUS_ACCEPTED;
|
||||
|
@ -9,7 +9,6 @@ package at.bitfire.davdroid.resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.UUID;
|
||||
|
||||
import net.fortuna.ical4j.model.ValidationException;
|
||||
import android.accounts.Account;
|
||||
@ -23,6 +22,7 @@ import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.CalendarContract;
|
||||
import android.util.Log;
|
||||
import at.bitfire.davdroid.syncadapter.DavSyncAdapter;
|
||||
|
||||
public abstract class LocalCollection<T extends Resource> {
|
||||
private static final String TAG = "davdroid.LocalCollection";
|
||||
@ -100,9 +100,9 @@ public abstract class LocalCollection<T extends Resource> {
|
||||
where, null, null);
|
||||
LinkedList<T> fresh = new LinkedList<T>();
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String uid = UUID.randomUUID().toString(),
|
||||
resourceName = uid + fileExtension();
|
||||
T resource = findById(cursor.getLong(0), resourceName, null, true); //new Event(cursor.getLong(0), resourceName, null);
|
||||
String uid = DavSyncAdapter.generateUID(),
|
||||
resourceName = uid.replace("@", "_") + fileExtension();
|
||||
T resource = findById(cursor.getLong(0), resourceName, null, true);
|
||||
resource.setUid(uid);
|
||||
|
||||
// new record: set generated resource name in database
|
||||
|
@ -10,11 +10,11 @@ package at.bitfire.davdroid.resource;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.fortuna.ical4j.data.ParserException;
|
||||
import net.fortuna.ical4j.model.ValidationException;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import net.fortuna.ical4j.data.ParserException;
|
||||
import net.fortuna.ical4j.model.ValidationException;
|
||||
|
||||
@ToString
|
||||
public abstract class Resource {
|
||||
|
143
src/at/bitfire/davdroid/syncadapter/AccountDetailsFragment.java
Normal file
143
src/at/bitfire/davdroid/syncadapter/AccountDetailsFragment.java
Normal file
@ -0,0 +1,143 @@
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.Fragment;
|
||||
import android.content.ContentResolver;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.LocalCalendar;
|
||||
|
||||
public class AccountDetailsFragment extends Fragment implements TextWatcher {
|
||||
public static final String KEY_SERVER_INFO = "server_info";
|
||||
|
||||
ServerInfo serverInfo;
|
||||
|
||||
EditText editAccountName;
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.account_details, container, false);
|
||||
|
||||
serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO);
|
||||
|
||||
editAccountName = (EditText)v.findViewById(R.id.account_name);
|
||||
editAccountName.addTextChangedListener(this);
|
||||
|
||||
TextView textAccountNameInfo = (TextView)v.findViewById(R.id.account_name_info);
|
||||
if (!serverInfo.hasEnabledCalendars())
|
||||
textAccountNameInfo.setVisibility(View.GONE);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.account_details, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.add_account:
|
||||
addAccount();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// actions
|
||||
|
||||
void addAccount() {
|
||||
ServerInfo serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO);
|
||||
try {
|
||||
String accountName = editAccountName.getText().toString();
|
||||
|
||||
AccountManager accountManager = AccountManager.get(getActivity());
|
||||
Account account = new Account(accountName, Constants.ACCOUNT_TYPE);
|
||||
Bundle userData = new Bundle();
|
||||
userData.putString(Constants.ACCOUNT_KEY_BASE_URL, serverInfo.getBaseURL());
|
||||
userData.putString(Constants.ACCOUNT_KEY_USERNAME, serverInfo.getUserName());
|
||||
userData.putString(Constants.ACCOUNT_KEY_AUTH_PREEMPTIVE, Boolean.toString(serverInfo.isAuthPreemptive()));
|
||||
|
||||
boolean syncContacts = false;
|
||||
for (ServerInfo.ResourceInfo addressBook : serverInfo.getAddressBooks())
|
||||
if (addressBook.isEnabled()) {
|
||||
userData.putString(Constants.ACCOUNT_KEY_ADDRESSBOOK_PATH, addressBook.getPath());
|
||||
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
|
||||
syncContacts = true;
|
||||
continue;
|
||||
}
|
||||
if (syncContacts) {
|
||||
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
|
||||
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
|
||||
} else
|
||||
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0);
|
||||
|
||||
if (accountManager.addAccountExplicitly(account, serverInfo.getPassword(), userData)) {
|
||||
// account created, now create calendars
|
||||
boolean syncCalendars = false;
|
||||
for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars())
|
||||
if (calendar.isEnabled()) {
|
||||
LocalCalendar.create(account, getActivity().getContentResolver(), calendar);
|
||||
syncCalendars = true;
|
||||
}
|
||||
if (syncCalendars) {
|
||||
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 1);
|
||||
ContentResolver.setSyncAutomatically(account, CalendarContract.AUTHORITY, true);
|
||||
} else
|
||||
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 0);
|
||||
|
||||
getActivity().finish();
|
||||
} else
|
||||
Toast.makeText(getActivity(), "Couldn't create account (account with this name already existing?)", Toast.LENGTH_LONG).show();
|
||||
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// input validation
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
boolean ok = false;
|
||||
ok = editAccountName.getText().length() > 0;
|
||||
MenuItem item = menu.findItem(R.id.add_account);
|
||||
item.setEnabled(ok);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
getActivity().invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
}
|
@ -3,6 +3,9 @@ package at.bitfire.davdroid.syncadapter;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import net.fortuna.ical4j.util.SimpleHostInfo;
|
||||
import net.fortuna.ical4j.util.UidGenerator;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.auth.AuthenticationException;
|
||||
|
||||
@ -16,6 +19,7 @@ import android.content.OperationApplicationException;
|
||||
import android.content.SyncResult;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import at.bitfire.davdroid.resource.LocalCollection;
|
||||
import at.bitfire.davdroid.resource.RemoteCollection;
|
||||
@ -26,13 +30,25 @@ public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
|
||||
protected AccountManager accountManager;
|
||||
|
||||
private static String androidID;
|
||||
|
||||
|
||||
|
||||
public DavSyncAdapter(Context context) {
|
||||
super(context, true);
|
||||
|
||||
androidID = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
|
||||
accountManager = AccountManager.get(context);
|
||||
}
|
||||
|
||||
|
||||
public static String generateUID() {
|
||||
UidGenerator generator = new UidGenerator(new SimpleHostInfo(androidID), String.valueOf(android.os.Process.myPid()));
|
||||
return generator.generateUid().getValue();
|
||||
}
|
||||
|
||||
|
||||
protected abstract Map<LocalCollection<?>, RemoteCollection<?>> getSyncPairs(Account account, ContentProviderClient provider);
|
||||
|
||||
|
||||
|
@ -7,6 +7,9 @@
|
||||
******************************************************************************/
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import android.app.DialogFragment;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentTransaction;
|
||||
@ -38,11 +41,6 @@ public class EnterCredentialsFragment extends Fragment implements TextWatcher {
|
||||
Button btnNext;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.enter_credentials, container, false);
|
||||
@ -103,10 +101,9 @@ public class EnterCredentialsFragment extends Fragment implements TextWatcher {
|
||||
void queryServer() {
|
||||
FragmentTransaction ft = getFragmentManager().beginTransaction();
|
||||
|
||||
String host_path = editBaseURL.getText().toString();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
|
||||
String host_path = editBaseURL.getText().toString();
|
||||
args.putString(QueryServerDialogFragment.EXTRA_BASE_URL, URIUtils.sanitize(protocol + host_path));
|
||||
args.putString(QueryServerDialogFragment.EXTRA_USER_NAME, editUserName.getText().toString());
|
||||
args.putString(QueryServerDialogFragment.EXTRA_PASSWORD, editPassword.getText().toString());
|
||||
@ -118,16 +115,23 @@ public class EnterCredentialsFragment extends Fragment implements TextWatcher {
|
||||
}
|
||||
|
||||
|
||||
// form validation
|
||||
// input validation
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
boolean ok =
|
||||
editBaseURL.getText().length() > 0 &&
|
||||
!editBaseURL.getText().toString().startsWith("/") && // host name required
|
||||
editUserName.getText().length() > 0 &&
|
||||
editPassword.getText().length() > 0;
|
||||
|
||||
// check host name
|
||||
try {
|
||||
URL url = new URL(protocol + editBaseURL.getText().toString());
|
||||
if (url.getHost() == null || url.getHost().isEmpty())
|
||||
ok = false;
|
||||
} catch (MalformedURLException e) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
MenuItem item = menu.findItem(R.id.next);
|
||||
item.setEnabled(ok);
|
||||
}
|
||||
|
@ -7,19 +7,8 @@
|
||||
******************************************************************************/
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.ListFragment;
|
||||
import android.content.ContentResolver;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.ContactsContract;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@ -30,10 +19,7 @@ import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.LocalCalendar;
|
||||
|
||||
public class SelectCollectionsFragment extends ListFragment {
|
||||
public static final String KEY_SERVER_INFO = "server_info";
|
||||
@ -45,6 +31,12 @@ public class SelectCollectionsFragment extends ListFragment {
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
setListAdapter(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
@ -84,8 +76,32 @@ public class SelectCollectionsFragment extends ListFragment {
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.add_account:
|
||||
addAccount();
|
||||
case R.id.next:
|
||||
ServerInfo serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO);
|
||||
|
||||
// synchronize only selected collections
|
||||
for (ServerInfo.ResourceInfo addressBook : serverInfo.getAddressBooks())
|
||||
addressBook.setEnabled(false);
|
||||
for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars())
|
||||
calendar.setEnabled(false);
|
||||
|
||||
ListAdapter adapter = getListView().getAdapter();
|
||||
for (long id : getListView().getCheckedItemIds()) {
|
||||
int position = (int)id + 1; // +1 because header view is inserted at pos. 0
|
||||
ServerInfo.ResourceInfo info = (ServerInfo.ResourceInfo)adapter.getItem(position);
|
||||
info.setEnabled(true);
|
||||
}
|
||||
|
||||
// pass to "account details" fragment
|
||||
AccountDetailsFragment accountDetails = new AccountDetailsFragment();
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putSerializable(SelectCollectionsFragment.KEY_SERVER_INFO, serverInfo);
|
||||
accountDetails.setArguments(arguments);
|
||||
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(R.id.fragment_container, accountDetails)
|
||||
.addToBackStack(null)
|
||||
.commitAllowingStateLoss();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
@ -94,7 +110,7 @@ public class SelectCollectionsFragment extends ListFragment {
|
||||
}
|
||||
|
||||
|
||||
// form validation
|
||||
// input validation
|
||||
|
||||
@Override
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
@ -103,65 +119,7 @@ public class SelectCollectionsFragment extends ListFragment {
|
||||
ok = getListView().getCheckedItemCount() > 0;
|
||||
} catch(IllegalStateException e) {
|
||||
}
|
||||
MenuItem item = menu.findItem(R.id.add_account);
|
||||
MenuItem item = menu.findItem(R.id.next);
|
||||
item.setEnabled(ok);
|
||||
}
|
||||
|
||||
|
||||
// actions
|
||||
|
||||
void addAccount() {
|
||||
List<ServerInfo.ResourceInfo> addressBooks = new LinkedList<ServerInfo.ResourceInfo>(),
|
||||
calendars = new LinkedList<ServerInfo.ResourceInfo>();
|
||||
|
||||
ListAdapter adapter = getListView().getAdapter();
|
||||
for (long id : getListView().getCheckedItemIds()) {
|
||||
int position = (int)id + 1; // +1 because header view is inserted at pos. 0
|
||||
ServerInfo.ResourceInfo info = (ServerInfo.ResourceInfo)adapter.getItem(position);
|
||||
switch (info.getType()) {
|
||||
case ADDRESS_BOOK:
|
||||
addressBooks.add(info);
|
||||
break;
|
||||
case CALENDAR:
|
||||
calendars.add(info);
|
||||
}
|
||||
}
|
||||
|
||||
ServerInfo serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO);
|
||||
try {
|
||||
URI baseURI = new URI(serverInfo.getBaseURL());
|
||||
String accountName = serverInfo.getUserName() + "@" + baseURI.getHost() + baseURI.getPath();
|
||||
|
||||
AccountManager accountManager = AccountManager.get(getActivity());
|
||||
Account account = new Account(accountName, Constants.ACCOUNT_TYPE);
|
||||
Bundle userData = new Bundle();
|
||||
userData.putString(Constants.ACCOUNT_KEY_BASE_URL, serverInfo.getBaseURL());
|
||||
userData.putString(Constants.ACCOUNT_KEY_USERNAME, serverInfo.getUserName());
|
||||
userData.putString(Constants.ACCOUNT_KEY_AUTH_PREEMPTIVE, Boolean.toString(serverInfo.isAuthPreemptive()));
|
||||
|
||||
if (!addressBooks.isEmpty()) {
|
||||
userData.putString(Constants.ACCOUNT_KEY_ADDRESSBOOK_PATH, addressBooks.get(0).getPath());
|
||||
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
|
||||
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
|
||||
} else
|
||||
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0);
|
||||
|
||||
if (accountManager.addAccountExplicitly(account, serverInfo.getPassword(), userData)) {
|
||||
// account created, now create calendars
|
||||
if (!calendars.isEmpty()) {
|
||||
for (ServerInfo.ResourceInfo calendar : calendars)
|
||||
LocalCalendar.create(account, getActivity().getContentResolver(), calendar);
|
||||
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 1);
|
||||
ContentResolver.setSyncAutomatically(account, CalendarContract.AUTHORITY, true);
|
||||
} else
|
||||
ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 0);
|
||||
|
||||
getActivity().finish();
|
||||
} else
|
||||
Toast.makeText(getActivity(), "Couldn't create account (already existing?)", Toast.LENGTH_LONG).show();
|
||||
|
||||
} catch (URISyntaxException e) {
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
@ -25,7 +26,17 @@ public class ServerInfo implements Serializable {
|
||||
private String errorMessage;
|
||||
|
||||
private boolean calDAV, cardDAV;
|
||||
private List<ResourceInfo> addressBooks, calendars;
|
||||
private List<ResourceInfo>
|
||||
addressBooks = new LinkedList<ResourceInfo>(),
|
||||
calendars = new LinkedList<ResourceInfo>();
|
||||
|
||||
public boolean hasEnabledCalendars() {
|
||||
for (ResourceInfo calendar : calendars)
|
||||
if (calendar.enabled)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@RequiredArgsConstructor(suppressConstructorProperties=true)
|
||||
@Data
|
||||
@ -37,6 +48,8 @@ public class ServerInfo implements Serializable {
|
||||
CALENDAR
|
||||
}
|
||||
|
||||
boolean enabled = false;
|
||||
|
||||
final Type type;
|
||||
final String path, title, description, color;
|
||||
|
||||
|
@ -10,7 +10,6 @@ package at.bitfire.davdroid.test;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import net.fortuna.ical4j.data.ParserException;
|
||||
import android.content.res.AssetManager;
|
||||
import android.test.InstrumentationTestCase;
|
||||
@ -26,10 +25,11 @@ public class CalendarTest extends InstrumentationTestCase {
|
||||
|
||||
public void testTimeZonesByEvolution() throws IOException, ParserException {
|
||||
Event e = parseCalendar("vienna-evolution.ics");
|
||||
Assert.assertEquals("Test-Ereignis im schönen Wien", e.getSummary());
|
||||
assertEquals("Test-Ereignis im schönen Wien", e.getSummary());
|
||||
|
||||
//DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Europe/Vienna:20131009T170000
|
||||
Assert.assertEquals(1381327200, e.getDtStartInMillis());
|
||||
/*assertEquals(1381330800000L, e.getDtStartInMillis());
|
||||
assertEquals(1381334400000L, (long)e.getDtEndInMillis());*/
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user