mirror of
https://github.com/etesync/android
synced 2024-11-26 01:48:34 +00:00
New DebugInfoActivity
* DebugInfoActivity shows and allows to share sync exceptions * log sync phase
This commit is contained in:
parent
808958a69b
commit
c2e9b27831
@ -18,8 +18,8 @@ android {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 23
|
||||
|
||||
versionCode 73
|
||||
versionName "0.9-alpha1"
|
||||
versionCode 74
|
||||
versionName "0.9-alpha2"
|
||||
|
||||
buildConfigField "java.util.Date", "buildTime", "new java.util.Date()"
|
||||
}
|
||||
|
@ -86,6 +86,10 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.DebugInfoActivity"
|
||||
android:label="@string/debug_info_title">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.setup.AddAccountActivity"
|
||||
android:excludeFromRecents="true" >
|
||||
|
@ -8,6 +8,9 @@
|
||||
package at.bitfire.davdroid.syncadapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
import android.content.ContentProviderClient;
|
||||
@ -15,14 +18,13 @@ import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SyncResult;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.squareup.okhttp.HttpUrl;
|
||||
import com.squareup.okhttp.MediaType;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import com.squareup.okhttp.Request;
|
||||
import com.squareup.okhttp.RequestBody;
|
||||
import com.squareup.okhttp.Response;
|
||||
@ -33,7 +35,6 @@ import org.apache.commons.io.Charsets;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@ -56,12 +57,13 @@ import at.bitfire.dav4android.property.SupportedAddressData;
|
||||
import at.bitfire.davdroid.ArrayUtils;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.HttpClient;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.LocalAddressBook;
|
||||
import at.bitfire.davdroid.resource.LocalContact;
|
||||
import at.bitfire.davdroid.ui.DebugInfoActivity;
|
||||
import at.bitfire.vcard4android.Contact;
|
||||
import at.bitfire.vcard4android.ContactsStorageException;
|
||||
import ezvcard.VCardVersion;
|
||||
import ezvcard.property.Uid;
|
||||
import ezvcard.util.IOUtils;
|
||||
import lombok.Cleanup;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -90,6 +92,20 @@ public class ContactsSyncAdapterService extends Service {
|
||||
|
||||
|
||||
private static class ContactsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
private static final int
|
||||
NOTIFICATION_ERROR = 1,
|
||||
|
||||
SYNC_PHASE_QUERY_CAPABILITIES = 0,
|
||||
SYNC_PHASE_PROCESS_LOCALLY_DELETED = 1,
|
||||
SYNC_PHASE_PREPARE_LOCALLY_CREATED = 2,
|
||||
SYNC_PHASE_UPLOAD_DIRTY = 3,
|
||||
SYNC_PHASE_CHECK_STATE = 4,
|
||||
SYNC_PHASE_LIST_LOCAL = 5,
|
||||
SYNC_PHASE_LIST_REMOTE = 6,
|
||||
SYNC_PHASE_COMPARE_ENTRIES = 7,
|
||||
SYNC_PHASE_DOWNLOAD_REMOTE = 8,
|
||||
SYNC_PHASE_SAVE_STATE = 9;
|
||||
|
||||
public ContactsSyncAdapter(Context context) {
|
||||
super(context, false);
|
||||
}
|
||||
@ -103,10 +119,16 @@ public class ContactsSyncAdapterService extends Service {
|
||||
|
||||
HttpUrl addressBookURL = HttpUrl.parse(settings.getAddressBookURL());
|
||||
DavAddressBook dav = new DavAddressBook(httpClient, addressBookURL);
|
||||
try {
|
||||
|
||||
// dismiss previous error notifications
|
||||
NotificationManager notificationManager = (NotificationManager)getContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(account.name, NOTIFICATION_ERROR);
|
||||
|
||||
// prepare local address book
|
||||
LocalAddressBook addressBook = new LocalAddressBook(account, provider);
|
||||
|
||||
int syncPhase = SYNC_PHASE_QUERY_CAPABILITIES;
|
||||
try {
|
||||
// prepare remote address book
|
||||
boolean hasVCard4 = false;
|
||||
dav.propfind(0, SupportedAddressData.NAME, GetCTag.NAME);
|
||||
@ -117,6 +139,7 @@ public class ContactsSyncAdapterService extends Service {
|
||||
hasVCard4 = true;
|
||||
Constants.log.info("Server advertises VCard/4 support: " + hasVCard4);
|
||||
|
||||
syncPhase = SYNC_PHASE_PROCESS_LOCALLY_DELETED;
|
||||
// Remove locally deleted contacts from server (if they have a name, i.e. if they were uploaded before),
|
||||
// but only if they don't have changed on the server. Then finally remove them from the local address book.
|
||||
LocalContact[] localList = addressBook.getDeleted();
|
||||
@ -133,8 +156,10 @@ public class ContactsSyncAdapterService extends Service {
|
||||
} else
|
||||
Constants.log.info("Removing local contact #" + local.getId() + " which has been deleted locally and was never uploaded");
|
||||
local.delete();
|
||||
syncResult.stats.numDeletes++;
|
||||
}
|
||||
|
||||
syncPhase = SYNC_PHASE_PREPARE_LOCALLY_CREATED;
|
||||
// assign file names and UIDs to new contacts so that we can use the file name as an index
|
||||
localList = addressBook.getWithoutFileName();
|
||||
for (LocalContact local : localList) {
|
||||
@ -143,6 +168,7 @@ public class ContactsSyncAdapterService extends Service {
|
||||
local.updateFileNameAndUID(uuid);
|
||||
}
|
||||
|
||||
syncPhase = SYNC_PHASE_UPLOAD_DIRTY;
|
||||
// upload dirty contacts
|
||||
localList = addressBook.getDirty();
|
||||
for (LocalContact local : localList) {
|
||||
@ -181,6 +207,7 @@ public class ContactsSyncAdapterService extends Service {
|
||||
local.clearDirty(eTag);
|
||||
}
|
||||
|
||||
syncPhase = SYNC_PHASE_CHECK_STATE;
|
||||
// check CTag (ignore on manual sync)
|
||||
String currentCTag = null;
|
||||
GetCTag getCTag = (GetCTag) dav.properties.get(GetCTag.NAME);
|
||||
@ -197,6 +224,7 @@ public class ContactsSyncAdapterService extends Service {
|
||||
Constants.log.info("Remote address book didn't change (CTag=" + currentCTag + "), no need to list VCards");
|
||||
|
||||
} else /* remote CTag has changed */ {
|
||||
syncPhase = SYNC_PHASE_LIST_LOCAL;
|
||||
// fetch list of local contacts and build hash table to index file name
|
||||
localList = addressBook.getAll();
|
||||
Map<String, LocalContact> localContacts = new HashMap<>(localList.length);
|
||||
@ -205,6 +233,7 @@ public class ContactsSyncAdapterService extends Service {
|
||||
localContacts.put(contact.getFileName(), contact);
|
||||
}
|
||||
|
||||
syncPhase = SYNC_PHASE_LIST_REMOTE;
|
||||
// fetch list of remote VCards and build hash table to index file name
|
||||
Constants.log.info("Listing remote VCards");
|
||||
dav.queryMemberETags();
|
||||
@ -215,6 +244,7 @@ public class ContactsSyncAdapterService extends Service {
|
||||
remoteContacts.put(fileName, vCard);
|
||||
}
|
||||
|
||||
syncPhase = SYNC_PHASE_COMPARE_ENTRIES;
|
||||
/* check which contacts
|
||||
1. are not present anymore remotely -> delete immediately on local side
|
||||
2. updated remotely -> add to downloadNames
|
||||
@ -226,6 +256,7 @@ public class ContactsSyncAdapterService extends Service {
|
||||
if (remote == null) {
|
||||
Constants.log.info(localName + " is not on server anymore, deleting");
|
||||
localContacts.get(localName).delete();
|
||||
syncResult.stats.numDeletes++;
|
||||
} else {
|
||||
// contact is still on server, check whether it has been updated remotely
|
||||
GetETag getETag = (GetETag)remote.properties.get(GetETag.NAME);
|
||||
@ -233,7 +264,9 @@ public class ContactsSyncAdapterService extends Service {
|
||||
throw new DavException("Server didn't provide ETag");
|
||||
String localETag = localContacts.get(localName).eTag,
|
||||
remoteETag = getETag.eTag;
|
||||
if (!remoteETag.equals(localETag)) {
|
||||
if (remoteETag.equals(localETag))
|
||||
syncResult.stats.numSkippedEntries++;
|
||||
else {
|
||||
Constants.log.info(localName + " has been changed on server (current ETag=" + remoteETag + ", last known ETag=" + localETag + ")");
|
||||
toDownload.add(remote);
|
||||
}
|
||||
@ -249,6 +282,7 @@ public class ContactsSyncAdapterService extends Service {
|
||||
toDownload.addAll(remoteContacts.values());
|
||||
}
|
||||
|
||||
syncPhase = SYNC_PHASE_DOWNLOAD_REMOTE;
|
||||
Constants.log.info("Downloading " + toDownload.size() + " contacts (" + MAX_MULTIGET + " at once)");
|
||||
|
||||
// prepare downloader which may be used to download external resource like contact photos
|
||||
@ -267,7 +301,7 @@ public class ContactsSyncAdapterService extends Service {
|
||||
String eTag = ((GetETag)remote.properties.get(GetETag.NAME)).eTag;
|
||||
|
||||
@Cleanup InputStream stream = body.byteStream();
|
||||
processVCard(addressBook, localContacts, remote.fileName(), eTag, stream, body.contentType().charset(Charsets.UTF_8), downloader);
|
||||
processVCard(syncResult, addressBook, localContacts, remote.fileName(), eTag, stream, body.contentType().charset(Charsets.UTF_8), downloader);
|
||||
|
||||
} else {
|
||||
// multiple contacts, use multi-get
|
||||
@ -298,11 +332,12 @@ public class ContactsSyncAdapterService extends Service {
|
||||
throw new DavException("Received multi-get response without address data");
|
||||
|
||||
@Cleanup InputStream stream = new ByteArrayInputStream(addressData.vCard.getBytes());
|
||||
processVCard(addressBook, localContacts, remote.fileName(), eTag, stream, charset, downloader);
|
||||
processVCard(syncResult, addressBook, localContacts, remote.fileName(), eTag, stream, charset, downloader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
syncPhase = SYNC_PHASE_SAVE_STATE;
|
||||
/* Save sync state (CTag). It doesn't matter if it has changed during the sync process
|
||||
(for instance, because another client has uploaded changes), because this will simply
|
||||
cause all remote entries to be listed at the next sync. */
|
||||
@ -310,15 +345,49 @@ public class ContactsSyncAdapterService extends Service {
|
||||
addressBook.setCTag(currentCTag);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("davdroid", "XXX", e);
|
||||
} catch (IOException e) {
|
||||
Constants.log.error("I/O exception during sync, trying again later", e);
|
||||
syncResult.stats.numIoExceptions++;
|
||||
|
||||
} catch(HttpException e) {
|
||||
Constants.log.error("HTTP Exception during sync", e);
|
||||
syncResult.stats.numParseExceptions++;
|
||||
|
||||
Intent detailsIntent = new Intent(getContext(), DebugInfoActivity.class);
|
||||
detailsIntent.putExtra(DebugInfoActivity.KEY_EXCEPTION, e);
|
||||
detailsIntent.putExtra(DebugInfoActivity.KEY_ACCOUNT, account);
|
||||
detailsIntent.putExtra(DebugInfoActivity.KEY_PHASE, syncPhase);
|
||||
|
||||
Notification.Builder builder = new Notification.Builder(getContext());
|
||||
Notification notification = null;
|
||||
builder .setSmallIcon(R.drawable.ic_launcher)
|
||||
.setContentTitle(getContext().getString(R.string.sync_error_title, account.name))
|
||||
.setContentIntent(PendingIntent.getActivity(getContext(), 0, detailsIntent, PendingIntent.FLAG_UPDATE_CURRENT));
|
||||
|
||||
String[] phases = getContext().getResources().getStringArray(R.array.sync_error_phases);
|
||||
if (phases.length > syncPhase)
|
||||
builder.setContentText(getContext().getString(R.string.sync_error_http, phases[syncPhase]));
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
if (Build.VERSION.SDK_INT >= 21)
|
||||
builder.setCategory(Notification.CATEGORY_ERROR);
|
||||
notification = builder.build();
|
||||
} else {
|
||||
notification = builder.getNotification();
|
||||
}
|
||||
notificationManager.notify(account.name, NOTIFICATION_ERROR, notification);
|
||||
|
||||
} catch(DavException e) {
|
||||
;
|
||||
} catch(ContactsStorageException e) {
|
||||
syncResult.databaseError = true;
|
||||
}
|
||||
|
||||
Constants.log.info("Sync complete for authority " + authority);
|
||||
}
|
||||
|
||||
|
||||
private void processVCard(LocalAddressBook addressBook, Map<String, LocalContact>localContacts, String fileName, String eTag, InputStream stream, Charset charset, Contact.Downloader downloader) throws IOException, ContactsStorageException {
|
||||
private void processVCard(SyncResult syncResult, LocalAddressBook addressBook, Map<String, LocalContact>localContacts, String fileName, String eTag, InputStream stream, Charset charset, Contact.Downloader downloader) throws IOException, ContactsStorageException {
|
||||
Contact contacts[] = Contact.fromStream(stream, charset, downloader);
|
||||
if (contacts.length == 1) {
|
||||
Contact newData = contacts[0];
|
||||
@ -329,16 +398,17 @@ public class ContactsSyncAdapterService extends Service {
|
||||
Constants.log.info("Updating " + fileName + " in local address book");
|
||||
localContact.eTag = eTag;
|
||||
localContact.update(newData);
|
||||
syncResult.stats.numUpdates++;
|
||||
} else {
|
||||
Constants.log.info("Adding " + fileName + " to local address book");
|
||||
localContact = new LocalContact(addressBook, newData, fileName, eTag);
|
||||
localContact.add();
|
||||
syncResult.stats.numInserts++;
|
||||
}
|
||||
} else
|
||||
Constants.log.error("Received VCard with not exactly one VCARD, ignoring " + fileName);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
157
app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.java
Normal file
157
app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.java
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import at.bitfire.dav4android.exception.HttpException;
|
||||
import at.bitfire.davdroid.BuildConfig;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import lombok.Cleanup;
|
||||
|
||||
public class DebugInfoActivity extends Activity {
|
||||
public static final String
|
||||
KEY_EXCEPTION = "exception",
|
||||
KEY_ACCOUNT = "account",
|
||||
KEY_PHASE = "phase";
|
||||
|
||||
private static final String APP_ID = "at.bitfire.davdroid";
|
||||
|
||||
String report;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.debug_info_activity);
|
||||
|
||||
TextView tvReport = (TextView)findViewById(R.id.text_report);
|
||||
tvReport.setText(generateReport(getIntent().getExtras()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.exception_details_activity, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void onShare(MenuItem item) {
|
||||
if (!TextUtils.isEmpty(report)) {
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_SUBJECT, "DAVdroid Exception Details");
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, report);
|
||||
sendIntent.setType("text/plain");
|
||||
startActivity(sendIntent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected String generateReport(Bundle extras) {
|
||||
Exception exception = null;
|
||||
Account account = null;
|
||||
Integer phase = null;
|
||||
|
||||
if (extras != null) {
|
||||
exception = (Exception) extras.getSerializable(KEY_EXCEPTION);
|
||||
account = (Account) extras.getParcelable(KEY_ACCOUNT);
|
||||
phase = extras.getInt(KEY_PHASE);
|
||||
}
|
||||
|
||||
StringBuilder report = new StringBuilder();
|
||||
|
||||
try {
|
||||
report.append(
|
||||
"SYSTEM INFORMATION\n" +
|
||||
"Android version: " + Build.VERSION.RELEASE + " (" + Build.DISPLAY + ")\n" +
|
||||
"Device: " + Build.MANUFACTURER + " / " + Build.MODEL + " (" + Build.DEVICE + ")\n\n"
|
||||
);
|
||||
} catch (Exception ex) {
|
||||
Constants.log.error("Couldn't get system details", ex);
|
||||
}
|
||||
|
||||
try {
|
||||
PackageManager pm = getPackageManager();
|
||||
String installedFrom = pm.getInstallerPackageName("at.bitfire.davdroid");
|
||||
if (TextUtils.isEmpty(installedFrom))
|
||||
installedFrom = "APK (directly)";
|
||||
else {
|
||||
PackageInfo installer = pm.getPackageInfo(installedFrom, PackageManager.GET_META_DATA);
|
||||
if (installer != null)
|
||||
installedFrom = pm.getApplicationLabel(installer.applicationInfo).toString();
|
||||
}
|
||||
report.append(
|
||||
"SOFTWARE INFORMATION\n" +
|
||||
"DAVdroid version: " + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ") " + BuildConfig.buildTime.toString() + "\n" +
|
||||
"Installed from: " + installedFrom + "\n\n"
|
||||
);
|
||||
} catch(Exception ex) {
|
||||
Constants.log.error("Couldn't get software information", ex);
|
||||
}
|
||||
|
||||
report.append(
|
||||
"CONFIGURATION\n" +
|
||||
"System-wide synchronization: " + (ContentResolver.getMasterSyncAutomatically() ? "automatically" : "manually") + " (overrides account settings)\n"
|
||||
);
|
||||
if (account != null)
|
||||
report.append(
|
||||
"Account name: " + account.name + "\n" +
|
||||
"Address book synchronization: " + syncStatus(account, ContactsContract.AUTHORITY) + "\n" +
|
||||
"Calendar synchronization: " + syncStatus(account, CalendarContract.AUTHORITY) + "\n" +
|
||||
"OpenTasks synchronization: " + syncStatus(account, "org.dmfs.tasks") + "\n\n"
|
||||
);
|
||||
|
||||
if (phase != null) {
|
||||
report.append("SYNCHRONIZATION INFO\nSychronization phase: " + phase + "\n\n");
|
||||
}
|
||||
|
||||
if (exception instanceof HttpException) {
|
||||
HttpException http = (HttpException)exception;
|
||||
if (http.request != null)
|
||||
report.append("HTTP REQUEST:\n" + http.request + "\n\n");
|
||||
if (http.response != null)
|
||||
report.append("HTTP RESPONSE:\n" + http.response + "\n\n");
|
||||
}
|
||||
|
||||
if (exception != null) {
|
||||
report.append("STACK TRACE\n");
|
||||
StringWriter writer = new StringWriter();
|
||||
@Cleanup PrintWriter printWriter = new PrintWriter(writer);
|
||||
exception.printStackTrace(printWriter);
|
||||
report.append(writer.toString());
|
||||
}
|
||||
|
||||
return report.toString();
|
||||
}
|
||||
|
||||
protected static String syncStatus(Account account, String authority) {
|
||||
return ContentResolver.getIsSyncable(account, authority) > 0 ?
|
||||
(ContentResolver.getSyncAutomatically(account, ContactsContract.AUTHORITY) ? "automatically" : "manually") :
|
||||
"—";
|
||||
}
|
||||
|
||||
}
|
@ -11,6 +11,7 @@ import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Debug;
|
||||
import android.provider.Settings;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
@ -58,6 +59,10 @@ public class MainActivity extends Activity {
|
||||
startActivity(new Intent(this, AddAccountActivity.class));
|
||||
}
|
||||
|
||||
public void showDebugInfo(MenuItem item) {
|
||||
startActivity(new Intent(this, DebugInfoActivity.class));
|
||||
}
|
||||
|
||||
public void showSettings(MenuItem item) {
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
}
|
||||
|
28
app/src/main/res/layout/debug_info_activity.xml
Normal file
28
app/src/main/res/layout/debug_info_activity.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="10dp">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:typeface="monospace"
|
||||
android:id="@+id/text_report"/>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
18
app/src/main/res/menu/exception_details_activity.xml
Normal file
18
app/src/main/res/menu/exception_details_activity.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:icon="@android:drawable/ic_menu_share"
|
||||
android:title="@string/share"
|
||||
android:showAsAction="always"
|
||||
android:onClick="onShare" />
|
||||
|
||||
</menu>
|
@ -11,5 +11,6 @@
|
||||
<item android:onClick="addAccount" android:title="@string/setup_add_account" android:showAsAction="always" android:icon="@drawable/ic_action_new_account"/>
|
||||
<item android:onClick="showSyncSettings" android:title="@string/main_manage_accounts" android:showAsAction="always" android:icon="@drawable/show_sync_settings"/>
|
||||
<item android:onClick="showSettings" android:title="@string/settings_title" android:showAsAction="never" android:icon="@drawable/ic_action_settings"/>
|
||||
<item android:onClick="showDebugInfo" android:title="@string/main_show_debug_info" android:showAsAction="never" />
|
||||
<item android:onClick="showWebsite" android:title="@string/help" android:showAsAction="ifRoom" android:icon="@drawable/view_website"/>
|
||||
</menu>
|
||||
|
@ -177,6 +177,18 @@
|
||||
<string name="setup_account_name_info">"Verwenden Sie Ihre Email-Adresse als Kontoname, da Android den Kontonamen als ORGANIZER-Feld in Terminen benutzt. Sie können keine zwei Konten mit dem gleichen Namen anlegen.</string>
|
||||
<string name="setup_read_only">schreibgeschützt</string>
|
||||
|
||||
<string name="sync_error_title">Synchronisierung fehlgeschlagen</string>
|
||||
<string name="sync_error_title">Synchronisierung von %s fehlgeschlagen</string>
|
||||
<string name="sync_error_http">HTTP-Fehler beim %1$s</string>
|
||||
<string-array name="sync_error_phases">
|
||||
<item>Abfragen der Server-Fähigkeiten</item>
|
||||
<item>Verarbeiten lokal gelöschter Einträge</item>
|
||||
<item>Vorbereiten neuer lokaler Einträge</item>
|
||||
<item>Hochladen neuer/geänderter lokaler Einträge</item>
|
||||
<item>Abfragen des Sync.-Zustands</item>
|
||||
<item>Auflisten lokaler Einträge</item>
|
||||
<item>Auflisten der Server-Einträge</item>
|
||||
<item>Herunterladen von Server-Einträgen</item>
|
||||
<item>Speichern des Sync.-Zustands</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
@ -11,9 +11,10 @@
|
||||
|
||||
<!-- common strings -->
|
||||
<string name="app_name">DAVdroid</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="skip">Skip</string>
|
||||
<string name="help">Help</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="skip">Skip</string>
|
||||
|
||||
<string name="exception_cert_path_validation">Untrusted certificate in certificate path. See FAQ for more info.</string>
|
||||
<string name="exception_http">HTTP error: %s</string>
|
||||
@ -23,6 +24,7 @@
|
||||
|
||||
<!-- MainActivity -->
|
||||
<string name="main_manage_accounts">Manage sync accounts</string>
|
||||
<string name="main_show_debug_info">Show debug info</string>
|
||||
|
||||
<string name="html_main_workaround"><![CDATA[
|
||||
<p>Thank you for buying DAVdroid via Google Play and thus supporting the project. Unfortunately, there are two issues with Google Play:</p>
|
||||
@ -188,6 +190,20 @@
|
||||
<string name="setup_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>
|
||||
<string name="setup_read_only">read-only</string>
|
||||
|
||||
<string name="sync_error_title">Synchronization failed</string>
|
||||
<!-- sync errors and DebugInfoActivity -->
|
||||
<string name="debug_info_title">Debug info</string>
|
||||
<string name="sync_error_title">Synchronization of %s failed</string>
|
||||
<string name="sync_error_http">HTTP error while %1$s</string>
|
||||
<string-array name="sync_error_phases">
|
||||
<item>querying capabilities</item>
|
||||
<item>processing locally deleted entries</item>
|
||||
<item>preparing locally created entries</item>
|
||||
<item>uploading created/modified entries</item>
|
||||
<item>checking sync state</item>
|
||||
<item>listing local entries</item>
|
||||
<item>listing remote entries</item>
|
||||
<item>downloading remote entries</item>
|
||||
<item>saving sync state</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 8258787df4c29697e76c683d1b9e4caea42205ec
|
||||
Subproject commit e6c3ee6da90a94d3c77675b8fdd9be7e2d5f83e3
|
Loading…
Reference in New Issue
Block a user