Compare commits

...

11 Commits

Author SHA1 Message Date
Rhodey Orbits
fba6dd7915 update readme 2015-10-20 13:42:46 +02:00
Rhodey Orbits
c28a51a266 bumping to 29 / 0.9.7 2015-07-15 12:14:27 -04:00
Rhodey Orbits
751f221fd3 Flock end-of-life
Flock will shutdown permanently on October 1st, 2015. Upon shutdown, all
active subscriptions will be refunded and account information erased. We
apologize for the inconvenience and thank you for supporting the project.
Contacts and calendars can be exported to standard .vcf and .ics files
through the new 'Export data' feature.

1) disable registration
2) disable subscription management
3) remind daily that EOL is approaching
4) allow export of contacts and calendars from EOL message
2015-07-13 12:55:37 -07:00
Rhodey Orbits
33fe753b91 bumping to 28 / 0.9.6 2015-02-27 19:02:24 -08:00
rhodey
aa4d305d2e Merge pull request #89 from haaja/typo_fix
rhodey forgot "choose" vs "chose" again, fixed.
2015-02-27 18:52:25 -08:00
Rhodey Orbits
f2b6e258ac updated german and japanese translations, added finish, russian, and swedish. FREEBIE 2015-02-27 17:21:38 -08:00
Rhodey Orbits
bbff2a5a34 created ContentProviderOperationQueue to help with max size limits when dealing with ContentProviders. Added full set of tests for ContactFactory and fixed a bug in handling of postal address. Added some tests for EventFactory and fixed a bug related to handling of event reminders where the event title and description were both null. Fixed bug in rendering of the intro on tablets. 2015-02-27 16:35:35 -08:00
Janne Haapsaari
ac1dfce553 strings: fix couple of typos 2015-02-25 16:58:17 +01:00
Rhodey Orbits
9c2fa9e35e added download link for the Flock nonplay flavored apk. FREEBIE 2015-02-23 15:24:11 -08:00
Rhodey Orbits
3f00796888 bumping to 27 / 0.9.5, FREEBIE 2015-02-22 21:02:49 -08:00
Rhodey Orbits
10510f8466 added account deletion countdown to subscription expired notification, improved account registration text, and dropped requirement that the user know their current password to change encryption passwords because the device is considered trusted. 2015-02-22 20:42:01 -08:00
45 changed files with 3671 additions and 3035 deletions

View File

@ -1,4 +1,4 @@
# Flock
# Flock (Discontinued)
A secure contact and calendar syncing application for Android.
@ -11,7 +11,9 @@ multiple devices a "sync service" is required, this can be any standards complia
3. RFC 6352 - CardDAV: vCard Extensions to Web Distributed Authoring and Versioning (WebDAV)
4. RFC 4791 - Calendaring Extensions to WebDAV (CalDAV)
Currently available on the Play store.
The Android app can be downloaded through [Google Play](https://play.google.com/store/apps/details?id=org.anhonesteffort.flock) or via our
[alternative distribution channel](https://flock-supplychain.anhonesteffort.org/packages/org.anhonesteffort.flock/current). Note that the
certificate presented by our alternative distribution channel is signed by the same private certificate authority pinned by [Flock's trust store](https://github.com/WhisperSystems/Flock/blob/master/flock/src/main/assets/flock.store).
*[![Play Store Badge](https://developer.android.com/images/brand/en_app_rgb_wo_60.png)](https://play.google.com/store/apps/details?id=org.anhonesteffort.flock)*

20
eol.txt Normal file
View File

@ -0,0 +1,20 @@
// flock end of life
1) client update
** disable new account registration
** disable subscription management
** remind daily that eol is approaching
** allow export of contacts & calendars to external storage
2) publish client update and warn in play store details
3) ** remove statsd code, deploy new servers, terminate statsd
4) update translations while waiting for eol
5) eol
remove apk from play store
terminate all flock amazon resources
refund active subscriptions
cancel active subscriptions
close stripe account
delete flock api project
delete flock payments

View File

@ -8,8 +8,8 @@ android {
defaultConfig {
applicationId "org.anhonesteffort.flock"
versionCode 26
versionName "0.9.4"
versionCode 29
versionName "0.9.7"
minSdkVersion 16
targetSdkVersion 21
}

View File

@ -0,0 +1,147 @@
/*
* Copyright (C) 2015 Open Whisper Systems
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.anhonesteffort.flock.test.sync.calendar;
import android.content.ContentValues;
import android.net.Uri;
import android.provider.CalendarContract;
import android.test.AndroidTestCase;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.Dur;
import net.fortuna.ical4j.model.component.VAlarm;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.parameter.Cn;
import net.fortuna.ical4j.model.parameter.PartStat;
import net.fortuna.ical4j.model.parameter.Role;
import net.fortuna.ical4j.model.property.Attendee;
import org.anhonesteffort.flock.sync.calendar.EventFactory;
import java.net.URI;
import java.util.List;
/**
* rhodey
*/
public class EventFactoryTest extends AndroidTestCase {
public void testGetValuesForAttendees() throws Exception {
final String EMAIL = "doge@such.wow";
final String NAME = "crypto doge";
final Role ROLE = Role.OPT_PARTICIPANT;
final PartStat STATUS = PartStat.DECLINED;
final Calendar inCalendar = new Calendar();
final VEvent inVEvent = new VEvent();
final Attendee inAttendee = new Attendee(new URI("mailto", EMAIL, null));
inAttendee.getParameters().add(new Cn(NAME));
inAttendee.getParameters().add(ROLE);
inAttendee.getParameters().add(STATUS);
inVEvent.getProperties().add(inAttendee);
inCalendar.getComponents().add(inVEvent);
final List<ContentValues> outValuesList = EventFactory.getValuesForAttendees(inCalendar);
assertTrue(outValuesList.size() == 1);
final ContentValues outValues = outValuesList.get(0);
assertTrue(outValues.getAsString(CalendarContract.Attendees.ATTENDEE_EMAIL).equals(EMAIL));
assertTrue(outValues.getAsString(CalendarContract.Attendees.ATTENDEE_NAME).equals(NAME));
assertTrue(outValues.getAsInteger(CalendarContract.Attendees.ATTENDEE_TYPE).equals(CalendarContract.Attendees.TYPE_OPTIONAL));
assertTrue(outValues.getAsInteger(CalendarContract.Attendees.ATTENDEE_RELATIONSHIP).equals(CalendarContract.Attendees.RELATIONSHIP_ATTENDEE));
assertTrue(outValues.getAsInteger(CalendarContract.Attendees.ATTENDEE_STATUS).equals(CalendarContract.Attendees.ATTENDEE_STATUS_DECLINED));
}
public void testAddAttendee() throws Exception {
final String EMAIL = "doge@such.wow";
final String NAME = "crypto doge";
final Integer ROLE = CalendarContract.Attendees.RELATIONSHIP_ORGANIZER;
final Integer TYPE = CalendarContract.Attendees.TYPE_REQUIRED;
final Integer STATUS = CalendarContract.Attendees.STATUS_CONFIRMED;
final ContentValues inValues = new ContentValues();
inValues.put(CalendarContract.Attendees.ATTENDEE_EMAIL, EMAIL);
inValues.put(CalendarContract.Attendees.ATTENDEE_NAME, NAME);
inValues.put(CalendarContract.Attendees.ATTENDEE_RELATIONSHIP, ROLE);
inValues.put(CalendarContract.Attendees.ATTENDEE_TYPE, TYPE);
inValues.put(CalendarContract.Attendees.ATTENDEE_STATUS, STATUS);
final Calendar outCalendar = new Calendar();
outCalendar.getComponents().add(new VEvent());
EventFactory.addAttendee("wow", outCalendar, inValues);
assertTrue(outCalendar.getComponent(VEvent.VEVENT) != null);
final VEvent outVEvent = (VEvent) outCalendar.getComponent(VEvent.VEVENT);
assertTrue(outVEvent.getProperties(Attendee.ATTENDEE).size() == 1);
final Attendee outAttendee = (Attendee) outVEvent.getProperties(Attendee.ATTENDEE).get(0);
final String outEmail = Uri.parse(outAttendee.getValue()).getSchemeSpecificPart();
final String outName = outAttendee.getParameter(Cn.CN).getValue();
final Role outRole = (Role) outAttendee.getParameter(Role.ROLE);
final PartStat outStatus = (PartStat) outAttendee.getParameter(PartStat.PARTSTAT);
assertTrue(outEmail.equals(EMAIL));
assertTrue(outName.equals(NAME));
assertTrue(outRole == Role.CHAIR);
assertTrue(outStatus == PartStat.ACCEPTED);
}
public void testGetValuesForReminders() throws Exception {
final Integer MINUTES_BEFORE_EVENT = 1337;
final Calendar inCalendar = new Calendar();
final VEvent inVEvent = new VEvent();
final VAlarm inAlarm = new VAlarm(new Dur(0, 0, -MINUTES_BEFORE_EVENT, 0));
inVEvent.getAlarms().add(inAlarm);
inCalendar.getComponents().add(inVEvent);
final List<ContentValues> outValuesList = EventFactory.getValuesForReminders(inCalendar);
assertTrue(outValuesList.size() == 1);
final ContentValues outValues = outValuesList.get(0);
assertTrue(outValues.getAsInteger(CalendarContract.Reminders.MINUTES).equals(MINUTES_BEFORE_EVENT));
assertTrue(outValues.getAsInteger(CalendarContract.Reminders.METHOD).equals(CalendarContract.Reminders.METHOD_ALERT));
}
public void testAddReminder() throws Exception {
final Integer MINUTES_BEFORE_EVENT = 1337;
final ContentValues inValues = new ContentValues();
inValues.put(CalendarContract.Reminders.MINUTES, MINUTES_BEFORE_EVENT);
final Calendar outCalendar = new Calendar();
outCalendar.getComponents().add(new VEvent());
EventFactory.addReminder(outCalendar, inValues);
assertTrue(outCalendar.getComponent(VEvent.VEVENT) != null);
final VEvent outVEvent = (VEvent) outCalendar.getComponent(VEvent.VEVENT);
assertTrue(outVEvent.getAlarms().size() == 1);
final VAlarm outAlarm = (VAlarm) outVEvent.getAlarms().get(0);
assertTrue(outAlarm.getTrigger().getDuration().getMinutes() == MINUTES_BEFORE_EVENT);
}
}

View File

@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="com.android.vending.BILLING"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
@ -106,15 +107,6 @@
</intent-filter>
</activity>
<activity android:name="org.anhonesteffort.flock.ManageSubscriptionActivity"
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="org.anhonesteffort.flock.ManageSubscriptionActivity"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".DeleteAllContactsActivity"
android:windowSoftInputMode="adjustPan"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
@ -130,6 +122,13 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateHidden"/>
<activity android:name="org.anhonesteffort.flock.EolActivity" >
<intent-filter>
<action android:name="org.anhonesteffort.flock.EolActivity"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service android:name="org.anhonesteffort.flock.auth.AccountAuthenticatorService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
@ -207,6 +206,9 @@
<service android:name="org.anhonesteffort.flock.ChangeEncryptionPasswordService"
android:exported="false"/>
<service android:name="org.anhonesteffort.flock.ExportService"
android:exported="false"/>
<receiver android:name="org.anhonesteffort.flock.sync.SyncBooter">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
@ -219,6 +221,14 @@
</intent-filter>
</receiver>
<receiver android:name="org.anhonesteffort.flock.EolNotifier">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
<action android:name="org.anhonesteffort.flock.INTENT_ALARM_24_HOURS"/>
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -116,7 +116,6 @@ public class ChangeEncryptionPasswordActivity extends AccountAndKeyRequiredActiv
return;
Bundle result = new Bundle();
String cipherPassphrase = ((TextView)findViewById(R.id.cipher_passphrase)).getText().toString().trim();
String newCipherPassphrase = ((TextView)findViewById(R.id.new_cipher_passphrase)).getText().toString().trim();
String newCipherPassphraseRepeat = ((TextView)findViewById(R.id.new_cipher_passphrase_repeat)).getText().toString().trim();
@ -136,11 +135,10 @@ public class ChangeEncryptionPasswordActivity extends AccountAndKeyRequiredActiv
}
Optional<String> savedPassphrase = KeyStore.getMasterPassphrase(getBaseContext());
if (!savedPassphrase.isPresent() || !savedPassphrase.get().equals(cipherPassphrase)) {
if (!savedPassphrase.isPresent()) {
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_INVALID_CIPHER_PASSPHRASE);
ErrorToaster.handleDisplayToastBundledError(getBaseContext(), result);
((TextView)findViewById(R.id.cipher_passphrase)).setText("");
((TextView)findViewById(R.id.new_cipher_passphrase)).setText("");
((TextView)findViewById(R.id.new_cipher_passphrase_repeat)).setText("");
return;
@ -149,7 +147,7 @@ public class ChangeEncryptionPasswordActivity extends AccountAndKeyRequiredActiv
Intent changeService = new Intent(getBaseContext(), ChangeEncryptionPasswordService.class);
changeService.putExtra(ChangeEncryptionPasswordService.KEY_MESSENGER, new Messenger(new MessageHandler()));
changeService.putExtra(ChangeEncryptionPasswordService.KEY_OLD_MASTER_PASSPHRASE, cipherPassphrase);
changeService.putExtra(ChangeEncryptionPasswordService.KEY_OLD_MASTER_PASSPHRASE, savedPassphrase.get());
changeService.putExtra(ChangeEncryptionPasswordService.KEY_NEW_MASTER_PASSPHRASE, newCipherPassphrase);
changeService.putExtra(ChangeEncryptionPasswordService.KEY_ACCOUNT, account.toBundle());
@ -177,8 +175,7 @@ public class ChangeEncryptionPasswordActivity extends AccountAndKeyRequiredActiv
errorBundler.putInt(ErrorToaster.KEY_STATUS_CODE, message.arg1);
ErrorToaster.handleDisplayToastBundledError(getBaseContext(), errorBundler);
if (findViewById(R.id.cipher_passphrase) != null) {
((TextView)findViewById(R.id.cipher_passphrase)).setText("");
if (findViewById(R.id.new_cipher_passphrase) != null) {
((TextView)findViewById(R.id.new_cipher_passphrase)).setText("");
((TextView)findViewById(R.id.new_cipher_passphrase_repeat)).setText("");
}

View File

@ -0,0 +1,62 @@
/*
* *
* Copyright (C) 2015 Open Whisper Systems
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
* /
*/
package org.anhonesteffort.flock;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
/**
* rhodey
*/
public class EolActivity extends Activity {
public static final String EXTRA_BACK_DISABLED = "EolActivity.EXTRA_BACK_DISABLED";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.eol_activity);
getActionBar().setDisplayHomeAsUpEnabled(false);
getActionBar().setTitle(R.string.shutting_down);
initButtons();
}
private void initButtons() {
findViewById(R.id.button_export).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startService(new Intent(getBaseContext(), ExportService.class));
Toast.makeText(getBaseContext(), R.string.export_started, Toast.LENGTH_SHORT).show();
finish();
}
});
}
@Override
public void onBackPressed() {
if (!getIntent().getBooleanExtra(EXTRA_BACK_DISABLED, false))
super.onBackPressed();
}
}

View File

@ -0,0 +1,101 @@
/*
* *
* Copyright (C) 2015 Open Whisper Systems
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
* /
*/
package org.anhonesteffort.flock;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
/**
* rhodey
*/
public class EolNotifier extends BroadcastReceiver {
public static final String INTENT_ALARM_24_HOURS = "org.anhonesteffort.flock.INTENT_ALARM_24_HOURS";
public static final String KEY_TIME_LAST_ALARM = "KEY_TIME_LAST_ALARM";
private static final String TAG = EolNotifier.class.getSimpleName();
private Long getMsSinceLastAlarm(Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
Long timeLastAlarm = preferences.getLong(KEY_TIME_LAST_ALARM, -1);
if (timeLastAlarm < 0 || timeLastAlarm > System.currentTimeMillis())
return AlarmManager.INTERVAL_DAY;
return System.currentTimeMillis() - timeLastAlarm;
}
private void handleDeviceBooted(Context context) {
Long msSinceLastAlarm = getMsSinceLastAlarm(context);
Long msTillNextAlarm = AlarmManager.INTERVAL_DAY - msSinceLastAlarm;
if (msTillNextAlarm < 0)
msTillNextAlarm = 0L;
Intent alarmIntent = new Intent(INTENT_ALARM_24_HOURS);
PendingIntent pendingAlarm = PendingIntent.getBroadcast(context, 0, alarmIntent, 0);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.setInexactRepeating(
AlarmManager.RTC,
System.currentTimeMillis() + msTillNextAlarm,
AlarmManager.INTERVAL_DAY,
pendingAlarm
);
Log.d(TAG, "scheduled 24 hour alarm to begin firing repeatedly in " + msTillNextAlarm + "ms");
}
private void handleAlarmFired(Context context) {
Log.d(TAG, "EOL alarm fired");
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
preferences.edit().putLong(KEY_TIME_LAST_ALARM, System.currentTimeMillis()).apply();
NotificationDrawer.handleNotifyEol(context);
}
private void scheduleAlarmIfNotExists(Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
if (preferences.getLong(KEY_TIME_LAST_ALARM, -1) == -1L)
handleDeviceBooted(context);
}
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_MY_PACKAGE_REPLACED)) {
Intent nextIntent = new Intent(context, EolActivity.class);
nextIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
nextIntent.putExtra(EolActivity.EXTRA_BACK_DISABLED, true);
context.startActivity(nextIntent);
scheduleAlarmIfNotExists(context);
}
else if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED))
handleDeviceBooted(context);
else if (intent.getAction().equals(INTENT_ALARM_24_HOURS))
handleAlarmFired(context);
else
Log.e(TAG, "received broadcast intent with unknown action " + intent.getAction());
}
}

View File

@ -0,0 +1,462 @@
/*
* *
* Copyright (C) 2015 Open Whisper Systems
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
* /
*/
package org.anhonesteffort.flock;
import android.accounts.Account;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
import net.fortuna.ical4j.data.CalendarOutputter;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.Property;
import net.fortuna.ical4j.model.ValidationException;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.property.Name;
import net.fortuna.ical4j.model.property.Version;
import org.anhonesteffort.flock.auth.DavAccount;
import org.anhonesteffort.flock.sync.AbstractLocalComponentCollection;
import org.anhonesteffort.flock.sync.InvalidLocalComponentException;
import org.anhonesteffort.flock.sync.addressbook.AddressbookSyncScheduler;
import org.anhonesteffort.flock.sync.addressbook.ContactFactory;
import org.anhonesteffort.flock.sync.addressbook.LocalAddressbookStore;
import org.anhonesteffort.flock.sync.addressbook.LocalContactCollection;
import org.anhonesteffort.flock.sync.calendar.CalendarsSyncScheduler;
import org.anhonesteffort.flock.sync.calendar.LocalCalendarStore;
import org.anhonesteffort.flock.sync.calendar.LocalEventCollection;
import org.anhonesteffort.flock.util.guava.Optional;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import ezvcard.VCard;
import ezvcard.VCardVersion;
import ezvcard.io.text.VCardWriter;
import ezvcard.property.Photo;
import ezvcard.property.Uid;
/**
* Programmer: rhodey
*/
public class ExportService extends Service {
private static final String TAG = ExportService.class.getSimpleName();
private static final int NOTIFY_ID = 1025;
private ServiceHandler serviceHandler;
private NotificationManager notifyManager;
private NotificationCompat.Builder notificationBuilder;
private int countFailedContactExports = 0;
private int countFailedEventExports = 0;
private enum EndState {
SUCCESS, PROMPT_LOGIN,
PROMPT_MAKE_SPACE, PROMPT_RESTART
}
private EndState endState = null;
private void handleContactExportFailed() {
countFailedContactExports++;
Log.d(TAG, "contact export failed, counter: " + countFailedContactExports);
}
private void handleEventExportFailed() {
countFailedEventExports++;
Log.d(TAG, "event export failed, counter: " + countFailedEventExports);
}
private void handleInitializeNotification() {
notificationBuilder
.setProgress(0, 0, true)
.setContentTitle(getString(R.string.export))
.setContentText(getString(R.string.exporting_contacts_and_calendars))
.setSmallIcon(R.drawable.flock_actionbar_icon);
startForeground(NOTIFY_ID, notificationBuilder.build());
}
private Optional<LocalContactCollection> getAddressbook(ContentProviderClient client, DavAccount account) {
LocalAddressbookStore addressbookStore = new LocalAddressbookStore(getBaseContext(), client, account);
List<LocalContactCollection> addressbooks = addressbookStore.getCollections();
if (addressbooks.isEmpty()) return Optional.absent();
else return Optional.of(addressbooks.get(0));
}
private List<LocalEventCollection> getCalendars(ContentProviderClient client, Account account)
throws RemoteException
{
LocalCalendarStore calendarStore = new LocalCalendarStore(client, account);
return calendarStore.getCollections();
}
private Optional<File> createExternalFile(String filename) {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
Log.w(TAG, "external media not mounted?");
return Optional.absent();
}
try {
File file = new File(Environment.getExternalStorageDirectory(), filename);
if (file.exists()) return Optional.of(file);
else if (file.createNewFile()) return Optional.of(file);
return Optional.absent();
} catch (IOException e) {
Log.w(TAG, "unable to create file " + filename, e);
return Optional.absent();
}
}
private List<File> createFilesForCollections(List<AbstractLocalComponentCollection<?>> collections) {
List<File> files = new LinkedList<>();
Optional<File> file = null;
int calCount = 1;
for (AbstractLocalComponentCollection collection : collections) {
if (collection instanceof LocalContactCollection)
file = createExternalFile(getString(R.string.flock_contacts_vcf));
else {
file = createExternalFile(getString(R.string.flock_calendar_ical, calCount));
calCount++;
}
if (file.isPresent())
files.add(file.get());
}
return files;
}
private void simulateExport(AbstractLocalComponentCollection<?> collection, File output)
throws IOException, RemoteException
{
FileOutputStream stream = new FileOutputStream(output, false);
try {
for (int i = 0; i < collection.getComponentIds().size(); i++)
stream.write(new byte[512]);
} finally {
stream.close();
}
}
private boolean isStorageSpaceAvailable(List<AbstractLocalComponentCollection<?>> collections, List<File> files)
throws RemoteException
{
if (files.size() != collections.size()) {
Log.w(TAG, "collection count and output file count differ");
return false;
}
try {
for (int i = 0; i < collections.size(); i++)
simulateExport(collections.get(i), files.get(i));
return true;
} catch (IOException e) {
Log.w(TAG, "error during export simulation, not enough space?", e);
return false;
}
}
private void handleExportContacts(LocalContactCollection addressbook, File output)
throws RemoteException, IOException
{
VCardWriter vCardWriter = new VCardWriter(output, false, VCardVersion.V3_0);
try {
for (Long contactId : addressbook.getComponentIds()) {
try {
Optional<VCard> vCard = addressbook.getComponent(contactId);
if (vCard.isPresent()) {
vCard.get().removeProperties(Uid.class);
vCard.get().removeProperties(Photo.class);
vCard.get().removeExtendedProperty(ContactFactory.PROPERTY_STARRED);
vCardWriter.write(vCard.get());
} else {
Log.w(TAG, "couldn't find " + contactId + " in addressbook");
}
} catch (InvalidLocalComponentException e) {
handleContactExportFailed();
}
}
} finally {
vCardWriter.close();
}
}
private void handleExportCalendars(List<LocalEventCollection> eventCollections, List<File> outputs)
throws ValidationException, RemoteException, IOException
{
CalendarOutputter calendarWriter = new CalendarOutputter(false);
for (int i = 0; i < eventCollections.size(); i++) {
LocalEventCollection eventCollection = eventCollections.get(i);
List<Long> eventIds = eventCollection.getComponentIds();
Calendar calendar = new Calendar();
FileOutputStream output = new FileOutputStream(outputs.get(i), false);
Optional<String> displayName = eventCollection.getDisplayName();
if (displayName.isPresent() && !displayName.get().isEmpty())
calendar.getProperties().add(new Name(displayName.get()));
try {
for (Long eventId : eventIds) {
try {
Optional<Calendar> event = eventCollection.getComponent(eventId);
if (event.isPresent()) {
VEvent vEvent = (VEvent) event.get().getComponent(VEvent.VEVENT);
if (vEvent != null) {
if (vEvent.getProperty(Property.ORGANIZER) != null)
vEvent.getProperties().remove(vEvent.getProperty(Property.ORGANIZER));
calendar.getComponents().add(vEvent);
}
else
Log.w(TAG, "couldn't parse VEVENT from local calendar component");
} else {
Log.w(TAG, "couldn't find " + eventId + " in calendar " + eventCollection.getPath());
}
} catch (InvalidLocalComponentException e) {
handleEventExportFailed();
}
}
calendar.getProperties().add(Version.VERSION_2_0);
calendarWriter.output(calendar, output);
} finally {
output.close();
}
}
}
private void handleIndexFilesWithMediaScanner(List<File> files) {
for (File file : files) {
sendBroadcast(new Intent(
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
Uri.fromFile(file)
));
}
}
private void handleExportComplete(EndState endState) {
Log.d(TAG, "HANDLE EXPORT COMPLETE: " + endState);
this.endState = endState;
stopForeground(false);
stopSelf();
}
private void handlePromptLoginAndRetry() {
Log.w(TAG, "HANDLE PROMPT LOGIN AND RETRY");
Intent clickIntent = new Intent(getBaseContext(), CorrectPasswordActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(getBaseContext(), 0, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
notificationBuilder
.setAutoCancel(true)
.setProgress(0, 0, false)
.setContentIntent(pendingIntent)
.setContentTitle(getString(R.string.export_failed))
.setContentText(getString(R.string.tap_to_login_then_retry_export));
notifyManager.notify(NOTIFY_ID, notificationBuilder.build());
}
private void handlePromptClearSpaceAndRetry() {
Log.w(TAG, "HANDLE PROMPT CLEAR SPACE AND RETRY");
notificationBuilder
.setProgress(0, 0, false)
.setContentTitle(getString(R.string.export_failed))
.setContentText(getString(R.string.try_making_more_storage_space_available));
notifyManager.notify(NOTIFY_ID, notificationBuilder.build());
}
private void handleUnrecoverableError() {
Log.w(TAG, "HANDLE UNRECOVERABLE ERROR");
notificationBuilder
.setProgress(0, 0, false)
.setContentTitle(getString(R.string.export_failed))
.setContentText(getString(R.string.try_a_separate_export_app_if_error_continues));
notifyManager.notify(NOTIFY_ID, notificationBuilder.build());
}
private void handleStartExport() {
Log.d(TAG, "HANDLE START EXPORT");
handleInitializeNotification();
try {
Optional<DavAccount> account = DavAccountHelper.getAccount(getBaseContext());
ContentProviderClient contactClient = getBaseContext().getContentResolver()
.acquireContentProviderClient(AddressbookSyncScheduler.CONTENT_AUTHORITY);
ContentProviderClient calendarClient = getBaseContext().getContentResolver()
.acquireContentProviderClient(CalendarsSyncScheduler.CONTENT_AUTHORITY);
if (account.isPresent()) {
try {
Optional<LocalContactCollection> addressbook = getAddressbook(contactClient, account.get());
List<LocalEventCollection> calendars = getCalendars(calendarClient, account.get().getOsAccount());
List<AbstractLocalComponentCollection<?>> collections = new LinkedList<>();
if (!addressbook.isPresent()) {
throw new RemoteException("addressbook missing, what is going on?");
}
collections.add(addressbook.get());
collections.addAll(calendars);
List<File> outputFiles = createFilesForCollections(collections);
if (isStorageSpaceAvailable(collections, outputFiles)) {
File contactsFile = outputFiles.remove(0);
handleExportContacts(addressbook.get(), contactsFile);
handleExportCalendars(calendars, outputFiles);
outputFiles.add(contactsFile);
handleIndexFilesWithMediaScanner(outputFiles);
handleExportComplete(EndState.SUCCESS);
return;
} else {
handleExportComplete(EndState.PROMPT_MAKE_SPACE);
return;
}
} catch (ValidationException e) {
Log.e(TAG, "WTF ical4j", e);
} catch (RemoteException e) {
Log.e(TAG, "why android?", e);
} catch (IOException e) {
Log.e(TAG, "why android?", e);
}
} else {
handleExportComplete(EndState.PROMPT_LOGIN);
return;
}
} catch (Exception e) {
Log.e(TAG, "caught unexpected runtime exception", e);
}
handleExportComplete(EndState.PROMPT_RESTART);
}
@Override
public void onDestroy() {
Log.d(TAG, "ON DESTROY");
switch (endState) {
case PROMPT_LOGIN:
handlePromptLoginAndRetry();
break;
case PROMPT_MAKE_SPACE:
handlePromptClearSpaceAndRetry();
break;
case PROMPT_RESTART:
handleUnrecoverableError();
break;
}
if (endState != EndState.SUCCESS) {
Toast.makeText(getBaseContext(), R.string.export_failed, Toast.LENGTH_SHORT).show();
return;
}
if (countFailedContactExports == 0 && countFailedEventExports == 0) {
notificationBuilder
.setProgress(0, 0, false)
.setContentTitle(getString(R.string.export_complete))
.setContentText(getString(R.string.export_completed_successfully));
} else {
notificationBuilder
.setProgress(0, 0, false)
.setContentTitle(getString(R.string.export_complete))
.setContentText(getString(
R.string.failed_to_copy_contacts_and_events,
countFailedContactExports,
countFailedEventExports
));
}
notifyManager.notify(NOTIFY_ID, notificationBuilder.build());
Toast.makeText(getBaseContext(), R.string.export_complete, Toast.LENGTH_SHORT).show();
}
@Override
public void onCreate() {
HandlerThread thread = new HandlerThread(getClass().getSimpleName(), HandlerThread.NORM_PRIORITY);
thread.start();
serviceHandler = new ServiceHandler(thread.getLooper());
notifyManager = (NotificationManager)getBaseContext().getSystemService(Context.NOTIFICATION_SERVICE);
notificationBuilder = new NotificationCompat.Builder(getBaseContext());
}
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
handleStartExport();
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
serviceHandler.sendMessage(serviceHandler.obtainMessage());
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

View File

@ -134,10 +134,6 @@ public class ImportOwsAccountFragment extends Fragment {
Toast.makeText(getActivity(),
R.string.notification_flock_subscription_expired,
Toast.LENGTH_LONG).show();
Intent nextIntent = new Intent(getActivity(), ManageSubscriptionActivity.class);
nextIntent.putExtra(ManageSubscriptionActivity.KEY_DAV_ACCOUNT_BUNDLE, account.toBundle());
startActivity(nextIntent);
}
private void handleImportAccountAsync() {

View File

@ -1,273 +0,0 @@
/*
* *
* Copyright (C) 2014 Open Whisper Systems
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
* /
*/
package org.anhonesteffort.flock;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.Window;
import com.android.vending.billing.IInAppBillingService;
import org.anhonesteffort.flock.util.guava.Optional;
import org.anhonesteffort.flock.auth.DavAccount;
import org.anhonesteffort.flock.registration.model.SubscriptionPlan;
import org.anhonesteffort.flock.sync.account.AccountStore;
/**
* Programmer: rhodey
*/
public class ManageSubscriptionActivity extends FragmentActivity {
private static final String TAG = "org.anhonesteffort.flock.ManageSubscriptionActivity";
public static final String KEY_DAV_ACCOUNT_BUNDLE = "KEY_DAV_ACCOUNT_BUNDLE";
public static final String KEY_CURRENT_FRAGMENT = "KEY_CURRENT_FRAGMENT";
public static final String KEY_REQUEST_CODE = "KEY_REQUEST_CODE";
public static final String KEY_RESULT_CODE = "KEY_RESULT_CODE";
public static final String KEY_RESULT_DATA = "KEY_RESULT_DATA";
protected IInAppBillingService billingService;
protected DavAccount davAccount;
protected Menu optionsMenu;
private int currentFragment = -1;
protected Optional<Integer> activityRequestCode = Optional.absent();
protected Optional<Integer> activityResultCode = Optional.absent();
protected Optional<Intent> activityResultData = Optional.absent();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
requestWindowFeature(Window.FEATURE_PROGRESS);
setContentView(R.layout.simple_fragment_activity);
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setTitle(R.string.title_manage_subscription);
if (savedInstanceState != null && !savedInstanceState.isEmpty()) {
if (!DavAccount.build(savedInstanceState.getBundle(KEY_DAV_ACCOUNT_BUNDLE)).isPresent()) {
Log.e(TAG, "where did my dav account bundle go?! :(");
finish();
return;
}
davAccount = DavAccount.build(savedInstanceState.getBundle(KEY_DAV_ACCOUNT_BUNDLE)).get();
currentFragment = savedInstanceState.getInt(KEY_CURRENT_FRAGMENT, -1);
activityRequestCode = Optional.fromNullable(savedInstanceState.getInt(KEY_REQUEST_CODE));
activityResultCode = Optional.fromNullable(savedInstanceState.getInt(KEY_RESULT_CODE));
activityResultData = Optional.fromNullable((Intent) savedInstanceState.getParcelable(KEY_RESULT_DATA));
}
else if (getIntent().getExtras() != null) {
if (!DavAccount.build(getIntent().getExtras().getBundle(KEY_DAV_ACCOUNT_BUNDLE)).isPresent()) {
Log.e(TAG, "where did my dav account bundle go?! :(");
finish();
return;
}
davAccount = DavAccount.build(getIntent().getExtras().getBundle(KEY_DAV_ACCOUNT_BUNDLE)).get();
currentFragment = getIntent().getExtras().getInt(KEY_CURRENT_FRAGMENT, -1);
activityRequestCode = Optional.fromNullable(getIntent().getExtras().getInt(KEY_REQUEST_CODE));
activityResultCode = Optional.fromNullable(getIntent().getExtras().getInt(KEY_RESULT_CODE));
activityResultData = Optional.fromNullable((Intent) getIntent().getExtras().getParcelable(KEY_RESULT_DATA));
}
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.manage_subscription, menu);
if (currentFragment == SubscriptionPlan.PLAN_TYPE_NONE)
menu.findItem(R.id.button_send_bitcoin).setVisible(false);
optionsMenu = menu;
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
break;
case R.id.button_send_bitcoin:
Intent nextIntent = new Intent(getBaseContext(), SendBitcoinActivity.class);
nextIntent.putExtra(ManageSubscriptionActivity.KEY_DAV_ACCOUNT_BUNDLE, davAccount.toBundle());
startActivity(nextIntent);
break;
}
return false;
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putBundle(KEY_DAV_ACCOUNT_BUNDLE, davAccount.toBundle());
savedInstanceState.putInt(KEY_CURRENT_FRAGMENT, currentFragment);
if (activityRequestCode.isPresent())
savedInstanceState.putInt(KEY_REQUEST_CODE, activityRequestCode.get());
if (activityResultCode.isPresent())
savedInstanceState.putInt(KEY_RESULT_CODE, activityResultCode.get());
if (activityResultData.isPresent())
savedInstanceState.putParcelable(KEY_RESULT_DATA, activityResultData.get());
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
if (savedInstanceState != null && !savedInstanceState.isEmpty()) {
if (!DavAccount.build(savedInstanceState.getBundle(KEY_DAV_ACCOUNT_BUNDLE)).isPresent()) {
Log.e(TAG, "where did my dav account bundle go?! :(");
finish();
return;
}
davAccount = DavAccount.build(savedInstanceState.getBundle(KEY_DAV_ACCOUNT_BUNDLE)).get();
currentFragment = savedInstanceState.getInt(KEY_CURRENT_FRAGMENT, -1);
activityRequestCode = Optional.fromNullable(savedInstanceState.getInt(KEY_REQUEST_CODE));
activityResultCode = Optional.fromNullable(savedInstanceState.getInt(KEY_RESULT_CODE));
activityResultData = Optional.fromNullable((Intent) savedInstanceState.getParcelable(KEY_RESULT_DATA));
}
super.onRestoreInstanceState(savedInstanceState);
}
protected void updateFragmentWithPlanType(int planType) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
Fragment nextFragment = null;
switch (planType) {
case SubscriptionPlan.PLAN_TYPE_GOOGLE:
nextFragment = new SubscriptionGoogleFragment();
break;
case SubscriptionPlan.PLAN_TYPE_STRIPE:
nextFragment = new SubscriptionStripeFragment();
break;
default:
nextFragment = new UnsubscribedFragment();
if (optionsMenu != null)
optionsMenu.findItem(R.id.button_send_bitcoin).setVisible(false);
break;
}
fragmentTransaction.replace(R.id.fragment_view, nextFragment);
fragmentTransaction.commit();
currentFragment = planType;
}
@Override
public void onResume() {
super.onResume();
if (currentFragment >= 0)
updateFragmentWithPlanType(currentFragment);
else
updateFragmentWithPlanType(AccountStore.getSubscriptionPlanType(getBaseContext()));
}
@Override
public void onBackPressed() {
int activePlanType = AccountStore.getSubscriptionPlanType(getBaseContext());
switch (currentFragment) {
case SubscriptionPlan.PLAN_TYPE_GOOGLE:
if (activePlanType == SubscriptionPlan.PLAN_TYPE_GOOGLE)
super.onBackPressed();
else
updateFragmentWithPlanType(SubscriptionPlan.PLAN_TYPE_NONE);
break;
case SubscriptionPlan.PLAN_TYPE_STRIPE:
if (activePlanType == SubscriptionPlan.PLAN_TYPE_STRIPE)
super.onBackPressed();
else
updateFragmentWithPlanType(SubscriptionPlan.PLAN_TYPE_NONE);
break;
default:
super.onBackPressed();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "ON ACTIVITY RESULT");
activityRequestCode = Optional.of(requestCode);
activityResultCode = Optional.of(resultCode);
activityResultData = Optional.of(data);
}
protected void handleClearActivityResult() {
activityRequestCode = Optional.absent();
activityResultCode = Optional.absent();
activityResultData = Optional.absent();
}
@Override
public void onDestroy() {
super.onDestroy();
if (serviceConnection != null)
unbindService(serviceConnection);
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
billingService = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
billingService = IInAppBillingService.Stub.asInterface(service);
}
};
}

View File

@ -27,6 +27,7 @@ import android.content.SharedPreferences;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import org.anhonesteffort.flock.sync.account.AccountStore;
import org.anhonesteffort.flock.util.guava.Optional;
import org.anhonesteffort.flock.auth.DavAccount;
import org.anhonesteffort.flock.sync.addressbook.AddressbookSyncScheduler;
@ -41,8 +42,8 @@ public class NotificationDrawer extends BroadcastReceiver {
private static final String TAG = "org.anhonesteffort.flock.NotificationDrawer";
private static final int ID_NOTIFICATION_AUTH = 1020;
private static final int ID_NOTIFICATION_SUBSCRIPTION = 1021;
private static final int ID_NOTIFICATION_DEBUG_LOG = 1022;
private static final int ID_NOTIFICATION_EOL = 1023;
private static final String PREFERENCES_NAME = "AbstractDavSyncAdapter.PREFERENCES_NAME";
private static final String KEY_VOID_AUTH_NOTIFICATIONS = "KEY_VOID_AUTH_NOTIFICATIONS";
@ -114,26 +115,21 @@ public class NotificationDrawer extends BroadcastReceiver {
getNotificationManager(context).notify(ID_NOTIFICATION_AUTH, notificationBuilder.build());
}
public static void cancelAuthNotification(Context context) {
getNotificationManager(context).cancel(ID_NOTIFICATION_AUTH);
}
public static void showSubscriptionExpiredNotification(Context context) {
Log.w(TAG, "showSubscriptionExpiredNotification()");
public static void handleNotifyEol(Context context) {
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context);
Intent clickIntent = new Intent(context, ManageSubscriptionActivity.class);
Intent clickIntent = new Intent(context, EolActivity.class);
notificationBuilder.setContentTitle(context.getString(R.string.notification_flock_subscription_expired));
notificationBuilder.setContentText(context.getString(R.string.notification_tap_to_update_subscription));
notificationBuilder.setContentTitle(context.getString(R.string.flock_is_shutting_down));
notificationBuilder.setContentText(context.getString(R.string.tap_for_important_details));
notificationBuilder.setSmallIcon(R.drawable.flock_actionbar_icon);
notificationBuilder.setAutoCancel(true);
Optional<DavAccount> account = DavAccountHelper.getAccount(context);
clickIntent.putExtra(ManageSubscriptionActivity.KEY_DAV_ACCOUNT_BUNDLE, account.get().toBundle());
notificationBuilder.setContentIntent(getPendingActivityIntent(context, clickIntent));
getNotificationManager(context).notify(ID_NOTIFICATION_SUBSCRIPTION, notificationBuilder.build());
getNotificationManager(context).notify(ID_NOTIFICATION_EOL, notificationBuilder.build());
}
public static void cancelAuthNotification(Context context) {
getNotificationManager(context).cancel(ID_NOTIFICATION_AUTH);
}
private static boolean isStopAskingForLogsSet(Context context) {

View File

@ -35,7 +35,6 @@ import android.widget.Toast;
import com.chiralcode.colorpicker.ColorPickerPreference;
import org.anhonesteffort.flock.util.guava.Optional;
import org.anhonesteffort.flock.auth.DavAccount;
import org.anhonesteffort.flock.sync.account.AccountSyncScheduler;
import org.anhonesteffort.flock.sync.addressbook.AddressbookSyncScheduler;
import org.anhonesteffort.flock.sync.calendar.CalendarsSyncScheduler;
@ -58,7 +57,6 @@ public class PreferencesActivity extends PreferenceActivity
public static final String KEY_PREF_ADDRESSBOOKS = "pref_addressbooks";
public static final String KEY_PREF_CATEGORY_ACCOUNT = "pref_category_account";
public static final String KEY_PREF_SUBSCRIPTION = "pref_subscription";
public static final String KEY_PREF_DELETE_ACCOUNT = "pref_delete_account";
private StatusHeaderView statusHeader;
@ -201,29 +199,9 @@ public class PreferencesActivity extends PreferenceActivity
if (!DavAccountHelper.isUsingOurServers(getBaseContext()))
return;
Preference manageSubscription = findPreference(KEY_PREF_SUBSCRIPTION);
Preference addressbooks = findPreference(KEY_PREF_ADDRESSBOOKS);
PreferenceCategory category = (PreferenceCategory) findPreference(KEY_PREF_CATEGORY_CONTACTS);
if (manageSubscription != null) {
manageSubscription.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Optional<DavAccount> account = DavAccountHelper.getAccount(getBaseContext());
if (account.isPresent()) {
Intent nextIntent = new Intent(getBaseContext(), ManageSubscriptionActivity.class);
nextIntent.putExtra(ManageSubscriptionActivity.KEY_DAV_ACCOUNT_BUNDLE,
account.get().toBundle());
startActivity(nextIntent);
}
return true;
}
});
}
if (addressbooks != null)
category.removePreference(addressbooks);
}
@ -233,13 +211,10 @@ public class PreferencesActivity extends PreferenceActivity
return;
PreferenceCategory accountCategory = (PreferenceCategory) findPreference(KEY_PREF_CATEGORY_ACCOUNT);
Preference manageSubscription = findPreference(KEY_PREF_SUBSCRIPTION);
Preference deleteAccount = findPreference(KEY_PREF_DELETE_ACCOUNT);
if (manageSubscription != null && deleteAccount != null) {
accountCategory.removePreference(manageSubscription);
if (deleteAccount != null)
accountCategory.removePreference(deleteAccount);
}
}
@Override

View File

@ -19,222 +19,31 @@
package org.anhonesteffort.flock;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.support.v4.app.Fragment;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.anhonesteffort.flock.util.guava.Optional;
import org.anhonesteffort.flock.util.PasswordUtil;
import org.apache.commons.lang.StringUtils;
/**
* Programmer: rhodey
*/
public class RegisterAccountFragment extends Fragment {
private static final String TAG = "org.anhonesteffort.flock.RegisterAccountFragment";
private static final String KEY_USERNAME = "KEY_USERNAME";
protected static final int CODE_ACCOUNT_IMPORTED = 9001;
private static MessageHandler messageHandler;
private SetupActivity setupActivity;
private TextWatcher passwordWatcher;
private TextWatcher passwordRepeatWatcher;
private Optional<String> username = Optional.absent();
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null)
username = Optional.fromNullable(savedInstanceState.getString(KEY_USERNAME));
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
EditText usernameView = (EditText) getActivity().findViewById(R.id.account_username);
if (usernameView.getText() != null)
savedInstanceState.putString(KEY_USERNAME, usernameView.getText().toString());
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof SetupActivity)
this.setupActivity = (SetupActivity) activity;
else
throw new ClassCastException(activity.toString() + " not what I expected D: !");
if (messageHandler == null)
messageHandler = new MessageHandler(setupActivity, this);
else {
messageHandler.setupActivity = setupActivity;
messageHandler.importFragment = this;
}
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_register_ows_account, container, false);
initButtons();
initForm(view);
return view;
return inflater.inflate(R.layout.fragment_register_ows_account, container, false);
}
private void initButtons() {
getActivity().findViewById(R.id.button_next).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
registerAccountAsync();
}
});
@Override
public void onResume() {
super.onResume();
getActivity().findViewById(R.id.button_next).setVisibility(View.GONE);
}
private void initForm(View view) {
if (messageHandler != null && messageHandler.serviceStarted) {
getActivity().setProgressBarIndeterminateVisibility(true);
getActivity().setProgressBarVisibility(true);
setupActivity.setNavigationDisabled(true);
}
if (username.isPresent())
((EditText)view.findViewById(R.id.account_username)).setText(username.get());
final EditText passwordTextView = (EditText) view.findViewById(R.id.cipher_passphrase);
final EditText passwordRepeatTextView = (EditText) view.findViewById(R.id.cipher_passphrase_repeat);
final ProgressBar passwordProgressView = (ProgressBar) view.findViewById(R.id.progress_password_strength);
final ProgressBar passwordRepeatProgressView = (ProgressBar) view.findViewById(R.id.progress_password_strength_repeat);
if (passwordWatcher != null)
passwordTextView.removeTextChangedListener(passwordWatcher);
if (passwordRepeatWatcher != null)
passwordRepeatTextView.removeTextChangedListener(passwordRepeatWatcher);
passwordWatcher = PasswordUtil.getPasswordStrengthTextWatcher(getActivity(), passwordProgressView);
passwordRepeatWatcher = PasswordUtil.getPasswordStrengthTextWatcher(getActivity(), passwordRepeatProgressView);
passwordTextView.addTextChangedListener(passwordWatcher);
passwordRepeatTextView.addTextChangedListener(passwordRepeatWatcher);
}
private void handleRegisterComplete() {
Log.d(TAG, "handleRegisterComplete()");
setupActivity.updateFragmentUsingState(SetupActivity.STATE_IMPORT_CONTACTS);
}
private void registerAccountAsync() {
if (messageHandler != null && messageHandler.serviceStarted)
return;
else if (messageHandler == null)
messageHandler = new MessageHandler(setupActivity, this);
Bundle result = new Bundle();
String username = ((EditText) getActivity().findViewById(R.id.account_username)).getText().toString().trim();
String password = ((EditText) getActivity().findViewById(R.id.cipher_passphrase)).getText().toString().trim();
String passwordRepeat = ((EditText) getActivity().findViewById(R.id.cipher_passphrase_repeat)).getText().toString().trim();
if (StringUtils.isEmpty(username)) {
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_EMPTY_ACCOUNT_ID);
ErrorToaster.handleDisplayToastBundledError(getActivity(), result);
return;
}
if (username.contains(" ")) {
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SPACES_IN_USERNAME);
ErrorToaster.handleDisplayToastBundledError(getActivity(), result);
return;
}
if (StringUtils.isEmpty(password)) {
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SHORT_PASSWORD);
ErrorToaster.handleDisplayToastBundledError(getActivity(), result);
((TextView)getActivity().findViewById(R.id.cipher_passphrase)).setText("");
((TextView)getActivity().findViewById(R.id.cipher_passphrase_repeat)).setText("");
return;
}
if (StringUtils.isEmpty(passwordRepeat) || !password.equals(passwordRepeat)) {
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_PASSWORDS_DO_NOT_MATCH);
ErrorToaster.handleDisplayToastBundledError(getActivity(), result);
((TextView)getActivity().findViewById(R.id.cipher_passphrase)).setText("");
((TextView)getActivity().findViewById(R.id.cipher_passphrase_repeat)).setText("");
return;
}
username = DavAccountHelper.correctUsername(getActivity(), username);
Intent importService = new Intent(getActivity(), RegisterAccountService.class);
importService.putExtra(RegisterAccountService.KEY_MESSENGER, new Messenger(messageHandler));
importService.putExtra(RegisterAccountService.KEY_ACCOUNT_ID, username);
importService.putExtra(RegisterAccountService.KEY_MASTER_PASSPHRASE, password);
getActivity().startService(importService);
messageHandler.serviceStarted = true;
setupActivity.setNavigationDisabled(true);
getActivity().setProgressBarIndeterminateVisibility(true);
getActivity().setProgressBarVisibility(true);
}
public static class MessageHandler extends Handler {
public SetupActivity setupActivity;
public RegisterAccountFragment importFragment;
public boolean serviceStarted = false;
public MessageHandler(SetupActivity setupActivity, RegisterAccountFragment importFragment) {
this.setupActivity = setupActivity;
this.importFragment = importFragment;
}
@Override
public void handleMessage(Message message) {
messageHandler = null;
serviceStarted = false;
setupActivity.setNavigationDisabled(false);
setupActivity.setProgressBarIndeterminateVisibility(false);
setupActivity.setProgressBarVisibility(false);
if (message.arg1 == CODE_ACCOUNT_IMPORTED)
importFragment.handleRegisterComplete();
else if (message.arg1 != ErrorToaster.CODE_SUCCESS) {
Bundle errorBundler = new Bundle();
errorBundler.putInt(ErrorToaster.KEY_STATUS_CODE, message.arg1);
ErrorToaster.handleDisplayToastBundledError(setupActivity, errorBundler);
if (importFragment.getView().findViewById(R.id.account_username) != null) {
((TextView)importFragment.getView().findViewById(R.id.account_username)).setText("");
((TextView)importFragment.getView().findViewById(R.id.cipher_passphrase)).setText("");
((TextView)importFragment.getView().findViewById(R.id.cipher_passphrase_repeat)).setText("");
}
}
}
}
}

View File

@ -175,7 +175,6 @@ public class StatusHeaderView extends LinearLayout {
syncStatusText = getContext().getString(R.string.notification_flock_subscription_expired);
syncStatusDrawable = R.drawable.sad_cloud;
if (!subscriptionNotificationShown) {
NotificationDrawer.showSubscriptionExpiredNotification(getContext());
subscriptionNotificationShown = true;
}
}

View File

@ -1,607 +0,0 @@
/*
* Copyright (C) 2014 Open Whisper Systems
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.anhonesteffort.flock;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.anhonesteffort.flock.registration.RegistrationApi;
import org.anhonesteffort.flock.registration.RegistrationApiException;
import org.anhonesteffort.flock.registration.model.GooglePlan;
import org.anhonesteffort.flock.registration.model.SubscriptionPlan;
import org.anhonesteffort.flock.sync.account.AccountStore;
import org.anhonesteffort.flock.sync.account.AccountSyncScheduler;
import org.anhonesteffort.flock.util.Base64;
import org.anhonesteffort.flock.util.TimeUtil;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
/**
* rhodey
*/
public class SubscriptionGoogleFragment extends Fragment {
private static final String TAG = "org.anhonesteffort.flock.SubscriptionGoogleFragment";
private static final int REQUEST_CODE_GOOGLE_FRAGMENT = 1024;
public static final String SKU_YEARLY_SUBSCRIPTION = "flock_subscription_yearly";
public static final String PURCHASE_TOKEN_HACK = "flock_subscription_yearly_hack";
public static final String PRODUCT_TYPE_SUBSCRIPTION = "subs";
private ManageSubscriptionActivity subscriptionActivity;
private AsyncTask asyncTask;
private AsyncTask recurringTask;
private AlertDialog alertDialog;
private final Handler uiHandler = new Handler();
private Timer intervalTimer = new Timer();
private long daysTillNextCharge = -1;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof ManageSubscriptionActivity)
this.subscriptionActivity = (ManageSubscriptionActivity) activity;
else
throw new ClassCastException(activity.toString() + " not what I expected D: !");
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_subscription_google, container, false);
TextView costPerYearView = (TextView) view.findViewById(R.id.cost_per_year);
double costPerYearUsd = (double) getResources().getInteger(R.integer.cost_per_year_usd);
costPerYearView.setText(getString(R.string.usd_per_year, costPerYearUsd));
return view;
}
private void handleStartGoogleSubscription() {
if (subscriptionActivity.activityRequestCode.isPresent() &&
subscriptionActivity.activityRequestCode.get().equals(REQUEST_CODE_GOOGLE_FRAGMENT))
{
if (subscriptionActivity.activityResultCode.isPresent() &&
subscriptionActivity.activityResultData.isPresent())
{
handlePurchaseIntentResult(subscriptionActivity.activityResultCode.get(),
subscriptionActivity.activityResultData.get());
}
else {
Log.e(TAG, "RESULT CODE OR DATA IS MISSING! D:");
subscriptionActivity.handleClearActivityResult();
subscriptionActivity.finish();
}
}
else
handleLoadSkuList(PRODUCT_TYPE_SUBSCRIPTION);
}
private void handleGoogleSubscriptionActive() {
TextView statusView = (TextView) subscriptionActivity.findViewById(R.id.google_subscription_status);
if (daysTillNextCharge >= 0)
statusView.setText(getString(R.string.your_subscription_is_active_well_charge_you_again_in_days, daysTillNextCharge));
else
statusView.setText(getString(R.string.your_subscription_is_active_well_charge_you_again_in_days, -1));
subscriptionActivity.findViewById(R.id.button_cancel).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
handlePromptCancelSubscription();
}
});
}
private void handleUpdateUi() {
if (subscriptionActivity.optionsMenu != null)
subscriptionActivity.optionsMenu.findItem(R.id.button_send_bitcoin).setVisible(true);
int planType = AccountStore.getSubscriptionPlanType(subscriptionActivity);
switch (planType) {
case SubscriptionPlan.PLAN_TYPE_NONE:
handleStartGoogleSubscription();
break;
case SubscriptionPlan.PLAN_TYPE_GOOGLE:
handleGoogleSubscriptionActive();
break;
default:
Log.e(TAG, "active subscription is not google or none, how did we get here?!");
subscriptionActivity.updateFragmentWithPlanType(planType);
break;
}
}
@Override
public void onResume() {
super.onResume();
handleUpdateUi();
handleStartPerpetualRefresh();
}
@Override
public void onPause() {
super.onPause();
if (asyncTask != null && !asyncTask.isCancelled())
asyncTask.cancel(true);
if (recurringTask != null && !recurringTask.isCancelled())
recurringTask.cancel(true);
if (alertDialog != null)
alertDialog.dismiss();
if (intervalTimer != null)
intervalTimer.cancel();
}
private void handleSkuListLoaded(List<String> skuList) {
if (!skuList.contains(SKU_YEARLY_SUBSCRIPTION)) {
Log.e(TAG, "flock yearly subscription sku not in returned sku list.");
Toast.makeText(subscriptionActivity,
R.string.google_play_error_please_update_google_play_services,
Toast.LENGTH_LONG).show();
return;
}
handleRequestPurchaseIntent(PRODUCT_TYPE_SUBSCRIPTION, SKU_YEARLY_SUBSCRIPTION);
}
private void handleStartPurchaseIntent(PendingIntent purchaseIntent) {
try {
subscriptionActivity.startIntentSenderForResult(
purchaseIntent.getIntentSender(), REQUEST_CODE_GOOGLE_FRAGMENT, new Intent(), 0, 0, 0
);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "lol wut?", e);
Toast.makeText(subscriptionActivity,
R.string.google_play_error_please_update_google_play_services,
Toast.LENGTH_LONG).show();
}
}
protected void handlePurchaseIntentResult(int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
if (data.getIntExtra("RESPONSE_CODE", 0) == 0)
handleItemPurchased(data);
else
handleItemPurchaseFailed(data);
}
else
handleItemPurchaseCanceled();
subscriptionActivity.handleClearActivityResult();
}
private void handleItemPurchased(Intent data) {
try {
String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
JSONObject purchaseObject = new JSONObject(purchaseData);
String productSku = purchaseObject.getString("productId");
String purchaseToken = purchaseObject.getString("purchaseToken");
if (productSku != null && purchaseToken != null) {
GooglePlan newGooglePlan = new GooglePlan(
subscriptionActivity.davAccount.getUserId(), SKU_YEARLY_SUBSCRIPTION,
PURCHASE_TOKEN_HACK, Long.MAX_VALUE
);
AccountStore.setSubscriptionPlan(subscriptionActivity, newGooglePlan);
new AccountSyncScheduler(subscriptionActivity).requestSync();
daysTillNextCharge = 365;
Toast.makeText(subscriptionActivity, R.string.thanks, Toast.LENGTH_SHORT).show();
handleUpdateUi();
return;
}
}
catch (JSONException e) {
Log.e(TAG, "no :(", e);
} catch (JsonProcessingException e) {
ErrorToaster.handleShowError(subscriptionActivity, e);
}
Toast.makeText(subscriptionActivity,
R.string.google_play_error_please_update_google_play_services,
Toast.LENGTH_LONG).show();
}
private void handleItemPurchaseFailed(Intent data) {
Log.w(TAG, "handleItemPurchaseFailed :|");
Toast.makeText(subscriptionActivity,
R.string.purchase_failed_check_your_google_wallet,
Toast.LENGTH_LONG).show();
subscriptionActivity.updateFragmentWithPlanType(SubscriptionPlan.PLAN_TYPE_NONE);
}
private void handleItemPurchaseCanceled() {
subscriptionActivity.updateFragmentWithPlanType(SubscriptionPlan.PLAN_TYPE_NONE);
}
private void handlePromptCancelSubscription() {
AlertDialog.Builder builder = new AlertDialog.Builder(subscriptionActivity);
builder.setTitle(R.string.cancel_subscription);
builder.setMessage(R.string.are_you_sure_you_want_to_cancel_your_flock_subscription);
builder.setNegativeButton(R.string.no, null);
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
handleCancelSubscription();
}
});
alertDialog = builder.show();
}
private void handleRequestPurchaseIntent(final String productType,
final String productSku)
{
if (asyncTask != null)
return;
asyncTask = new AsyncTask<Void, Void, Bundle>() {
@Override
protected void onPreExecute() {
Log.d(TAG, "handleRequestPurchaseIntent");
subscriptionActivity.setProgressBarIndeterminateVisibility(true);
subscriptionActivity.setProgressBarVisibility(true);
}
@Override
protected Bundle doInBackground(Void... params) {
Bundle result = new Bundle();
if (subscriptionActivity.billingService == null) {
Log.e(TAG, "billing service is null");
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_GOOGLE_PLAY_ERROR);
return result;
}
String developerPayload = Base64.encodeBytes(
subscriptionActivity.davAccount.getUserId().toUpperCase().getBytes()
);
try {
Bundle intentBundle = subscriptionActivity.billingService
.getBuyIntent(3, SubscriptionGoogleFragment.class.getPackage().getName(),
productSku, productType, developerPayload);
if (intentBundle.getParcelable("BUY_INTENT") != null) {
result.putParcelable("BUY_INTENT", intentBundle.getParcelable("BUY_INTENT"));
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SUCCESS);
return result;
}
Log.e(TAG, "buy intent is null");
} catch (RemoteException e) {
Log.e(TAG, "caught remote exception while getting buy intent", e);
}
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_GOOGLE_PLAY_ERROR);
return result;
}
@Override
protected void onPostExecute(Bundle result) {
asyncTask = null;
subscriptionActivity.setProgressBarIndeterminateVisibility(false);
subscriptionActivity.setProgressBarVisibility(false);
if (result.getInt(ErrorToaster.KEY_STATUS_CODE) == ErrorToaster.CODE_SUCCESS)
handleStartPurchaseIntent((PendingIntent) result.getParcelable("BUY_INTENT"));
else
ErrorToaster.handleDisplayToastBundledError(subscriptionActivity, result);
}
}.execute();
}
private void handleLoadSkuList(final String productType) {
if (asyncTask != null)
return;
asyncTask = new AsyncTask<Void, Void, Bundle>() {
@Override
protected void onPreExecute() {
Log.d(TAG, "handleLoadSkuList");
subscriptionActivity.setProgressBarIndeterminateVisibility(true);
subscriptionActivity.setProgressBarVisibility(true);
}
private ArrayList<String> getSkuDetails(ArrayList<String> skuList,
Bundle result)
throws RemoteException
{
Bundle skuBundle = new Bundle();
skuBundle.putStringArrayList("ITEM_ID_LIST", skuList);
Bundle skuDetails = subscriptionActivity.billingService
.getSkuDetails(3, SubscriptionGoogleFragment.class.getPackage().getName(),
productType, skuBundle);
if (skuDetails.getInt("RESPONSE_CODE") == 0)
return skuDetails.getStringArrayList("DETAILS_LIST");
else {
Log.e(TAG, "sku details response code is " + skuDetails.getInt("RESPONSE_CODE"));
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_GOOGLE_PLAY_ERROR);
return new ArrayList<String>(0);
}
}
@Override
protected Bundle doInBackground(Void... params) {
Bundle result = new Bundle();
if (subscriptionActivity.billingService == null) {
Log.e(TAG, "billing service is null");
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_GOOGLE_PLAY_ERROR);
return result;
}
ArrayList<String> skuQueryList = new ArrayList<String>(2);
skuQueryList.add(SKU_YEARLY_SUBSCRIPTION);
try {
List<String> skuDetails = getSkuDetails(skuQueryList, result);
ArrayList<String> skuList = new ArrayList<String>();
if (result.getInt(ErrorToaster.KEY_STATUS_CODE, -1) != -1)
return result;
for (String thisResponse : skuDetails) {
JSONObject productObject = new JSONObject(thisResponse);
skuList.add(productObject.getString("productId"));
}
result.putStringArrayList("SKU_LIST", skuList);
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SUCCESS);
} catch (JSONException e) {
Log.e(TAG, "error parsing sku details", e);
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_GOOGLE_PLAY_ERROR);
} catch (RemoteException e) {
Log.e(TAG, "error parsing sku details", e);
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_GOOGLE_PLAY_ERROR);
}
return result;
}
@Override
protected void onPostExecute(Bundle result) {
asyncTask = null;
subscriptionActivity.setProgressBarIndeterminateVisibility(false);
subscriptionActivity.setProgressBarVisibility(false);
if (result.getInt(ErrorToaster.KEY_STATUS_CODE) == ErrorToaster.CODE_SUCCESS)
handleSkuListLoaded(result.getStringArrayList("SKU_LIST"));
else
ErrorToaster.handleDisplayToastBundledError(subscriptionActivity, result);
}
}.execute();
}
private void handleCancelSubscription() {
if (asyncTask != null)
return;
asyncTask = new AsyncTask<Void, Void, Bundle>() {
@Override
protected void onPreExecute() {
Log.d(TAG, "handleCancelSubscription()");
subscriptionActivity.setProgressBarIndeterminateVisibility(true);
subscriptionActivity.setProgressBarVisibility(true);
}
@Override
protected Bundle doInBackground(Void... params) {
Bundle result = new Bundle();
try {
RegistrationApi registrationApi = new RegistrationApi(subscriptionActivity);
registrationApi.cancelSubscription(subscriptionActivity.davAccount);
AccountStore.setSubscriptionPlan(subscriptionActivity, SubscriptionPlan.PLAN_NONE);
AccountStore.setAutoRenew(subscriptionActivity, false);
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SUCCESS);
} catch (RegistrationApiException e) {
ErrorToaster.handleBundleError(e, result);
} catch (JsonProcessingException e) {
ErrorToaster.handleBundleError(e, result);
} catch (IOException e) {
ErrorToaster.handleBundleError(e, result);
}
return result;
}
@Override
protected void onPostExecute(Bundle result) {
asyncTask = null;
subscriptionActivity.setProgressBarIndeterminateVisibility(false);
subscriptionActivity.setProgressBarVisibility(false);
if (result.getInt(ErrorToaster.KEY_STATUS_CODE) == ErrorToaster.CODE_SUCCESS) {
Toast.makeText(subscriptionActivity, R.string.subscription_canceled, Toast.LENGTH_SHORT).show();
subscriptionActivity.updateFragmentWithPlanType(SubscriptionPlan.PLAN_TYPE_NONE);
}
else {
ErrorToaster.handleDisplayToastBundledError(subscriptionActivity, result);
handleUpdateUi();
}
}
}.execute();
}
private void handleRefreshDaysTillCharge() {
if (recurringTask != null)
return;
recurringTask = new AsyncTask<Void, Void, Bundle>() {
@Override
protected void onPreExecute() {
Log.d(TAG, "handleRefreshDaysTillCharge");
subscriptionActivity.setProgressBarIndeterminateVisibility(true);
subscriptionActivity.setProgressBarVisibility(true);
}
@Override
protected Bundle doInBackground(Void... params) {
Bundle result = new Bundle();
if (subscriptionActivity.billingService == null) {
Log.e(TAG, "billing service is null");
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_GOOGLE_PLAY_ERROR);
return result;
}
try {
Bundle ownedItems = subscriptionActivity.billingService
.getPurchases(3, SubscriptionGoogleFragment.class.getPackage().getName(),
PRODUCT_TYPE_SUBSCRIPTION, null);
if (ownedItems.getInt("RESPONSE_CODE") != 0) {
Log.e(TAG, "owned items response code is " + ownedItems.getInt("RESPONSE_CODE"));
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_GOOGLE_PLAY_ERROR);
return result;
}
ArrayList<String> purchaseDataList =
ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
for (int i = 0; i < purchaseDataList.size(); ++i) {
JSONObject productObject = new JSONObject(purchaseDataList.get(i));
if (productObject.getString("productId").equals(SKU_YEARLY_SUBSCRIPTION)) {
long purchaseTime = productObject.getLong("purchaseTime");
long msSincePurchase = new Date().getTime() - purchaseTime;
if (msSincePurchase < 0)
msSincePurchase = 0;
daysTillNextCharge = 365 - TimeUtil.millisecondsToDays(msSincePurchase);
if (daysTillNextCharge < 0)
daysTillNextCharge = 0;
}
}
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SUCCESS);
} catch (RemoteException e) {
Log.e(TAG, "error while getting owned items", e);
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_GOOGLE_PLAY_ERROR);
} catch (JSONException e) {
Log.e(TAG, "error while getting owned items", e);
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_GOOGLE_PLAY_ERROR);
}
return result;
}
@Override
protected void onPostExecute(Bundle result) {
recurringTask = null;
subscriptionActivity.setProgressBarIndeterminateVisibility(false);
subscriptionActivity.setProgressBarVisibility(false);
if (result.getInt(ErrorToaster.KEY_STATUS_CODE) == ErrorToaster.CODE_SUCCESS)
handleUpdateUi();
else
ErrorToaster.handleDisplayToastBundledError(subscriptionActivity, result);
}
}.execute();
}
private final Runnable refreshUiRunnable = new Runnable() {
@Override
public void run() {
if (recurringTask == null || recurringTask.isCancelled()) {
if (isAdded())
handleRefreshDaysTillCharge();
}
}
};
private void handleStartPerpetualRefresh() {
intervalTimer = new Timer();
TimerTask uiTask = new TimerTask() {
@Override
public void run() {
uiHandler.post(refreshUiRunnable);
}
};
intervalTimer.schedule(uiTask, 0, 10000);
}
}

View File

@ -1,522 +0,0 @@
/*
* Copyright (C) 2014 Open Whisper Systems
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.anhonesteffort.flock;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.stripe.exception.CardException;
import com.stripe.exception.StripeException;
import com.stripe.model.Token;
import org.anhonesteffort.flock.util.guava.Optional;
import org.anhonesteffort.flock.registration.OwsRegistration;
import org.anhonesteffort.flock.registration.RegistrationApi;
import org.anhonesteffort.flock.registration.RegistrationApiException;
import org.anhonesteffort.flock.registration.model.FlockCardInformation;
import org.anhonesteffort.flock.registration.model.StripePlan;
import org.anhonesteffort.flock.registration.model.SubscriptionPlan;
import org.anhonesteffort.flock.sync.account.AccountStore;
import org.anhonesteffort.flock.sync.account.AccountSyncScheduler;
import org.apache.commons.lang.StringUtils;
import java.io.IOException;
import java.util.HashMap;
/**
* rhodey
*/
public class SubscriptionStripeFragment extends Fragment {
private static final String TAG = "org.anhonesteffort.flock.UnsubscribedFragment";
private ManageSubscriptionActivity subscriptionActivity;
private AsyncTask asyncTask;
private AlertDialog alertDialog;
private Optional<FlockCardInformation> cardInformation;
private TextWatcher cardNumberTextWatcher;
private TextWatcher cardExpirationTextWatcher;
private int lastCardExpirationLength = 0;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof ManageSubscriptionActivity)
this.subscriptionActivity = (ManageSubscriptionActivity) activity;
else
throw new ClassCastException(activity.toString() + " not what I expected D: !");
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_subscription_card, container, false);
TextView costPerYearView = (TextView) view.findViewById(R.id.cost_per_year);
double costPerYearUsd = (double) getResources().getInteger(R.integer.cost_per_year_usd);
costPerYearView.setText(getString(R.string.usd_per_year, costPerYearUsd));
return view;
}
@Override
public void onResume() {
super.onResume();
handleUpdateUi();
}
@Override
public void onPause() {
super.onPause();
if (asyncTask != null && !asyncTask.isCancelled())
asyncTask.cancel(true);
if (alertDialog != null)
alertDialog.dismiss();
((EditText) subscriptionActivity.findViewById(R.id.card_number)).setText("");
((EditText) subscriptionActivity.findViewById(R.id.card_expiration)).setText("");
((EditText) subscriptionActivity.findViewById(R.id.card_cvc)).setText("");
}
private void handleUpdateUiForCreatingSubscription() {
TextView statusView = (TextView) subscriptionActivity.findViewById(R.id.card_subscription_status);
statusView.setVisibility(View.GONE);
Button buttonCancel = (Button) subscriptionActivity.findViewById(R.id.button_card_cancel);
Button buttonStartSubscription = (Button) subscriptionActivity.findViewById(R.id.button_card_action);
buttonCancel.setVisibility(View.GONE);
buttonStartSubscription.setText(R.string.start_subscription);
buttonStartSubscription.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
handleVerifyCardAndPutToServer();
}
});
initCardNumberHelper();
initCardExpirationHelper();
}
private void handleSetupFormForEditingCard() {
TextView statusView = (TextView) subscriptionActivity.findViewById(R.id.card_subscription_status);
statusView.setVisibility(View.VISIBLE);
if (AccountStore.getLastChargeFailed(subscriptionActivity)) {
statusView.setText(R.string.payment_failed);
statusView.setTextColor(getResources().getColor(R.color.error_red));
}
else {
statusView.setText(R.string.subscription_is_active);
statusView.setTextColor(getResources().getColor(R.color.success_green));
}
EditText cardNumberView = (EditText) subscriptionActivity.findViewById(R.id.card_number);
EditText cardExpirationView = (EditText) subscriptionActivity.findViewById(R.id.card_expiration);
if (cardInformation.isPresent()) {
if (StringUtils.isEmpty(cardNumberView.getText().toString()))
cardNumberView.setText("**** **** **** " + cardInformation.get().getCardLastFour());
if (StringUtils.isEmpty(cardExpirationView.getText().toString()))
cardExpirationView.setText(cardInformation.get().getCardExpiration());
}
Button buttonCancel = (Button) subscriptionActivity.findViewById(R.id.button_card_cancel);
Button buttonStartSubscription = (Button) subscriptionActivity.findViewById(R.id.button_card_action);
buttonCancel.setVisibility(View.VISIBLE);
buttonCancel.setText(R.string.cancel_subscription);
buttonCancel.setBackgroundColor(getResources().getColor(R.color.error_red));
buttonCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
handlePromptCancelSubscription();
}
});
buttonStartSubscription.setText(R.string.save_card);
buttonStartSubscription.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
handleVerifyCardAndPutToServer();
}
});
initCardNumberHelper();
initCardExpirationHelper();
}
private void handlePromptCancelSubscription() {
AlertDialog.Builder builder = new AlertDialog.Builder(subscriptionActivity);
builder.setTitle(R.string.cancel_subscription);
builder.setMessage(R.string.are_you_sure_you_want_to_cancel_your_flock_subscription);
builder.setNegativeButton(R.string.no, null);
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
handleCancelSubscription();
}
});
alertDialog = builder.show();
}
private void handleUpdateUi() {
int planType = AccountStore.getSubscriptionPlanType(subscriptionActivity);
cardInformation = AccountStore.getCardInformation(subscriptionActivity);
switch (planType) {
case SubscriptionPlan.PLAN_TYPE_GOOGLE:
Log.e(TAG, "is subscribed using google, how did we get here?");
subscriptionActivity.updateFragmentWithPlanType(SubscriptionPlan.PLAN_TYPE_GOOGLE);
break;
case SubscriptionPlan.PLAN_TYPE_STRIPE:
if (subscriptionActivity.optionsMenu != null)
subscriptionActivity.optionsMenu.findItem(R.id.button_send_bitcoin).setVisible(true);
handleSetupFormForEditingCard();
break;
default:
if (subscriptionActivity.optionsMenu != null)
subscriptionActivity.optionsMenu.findItem(R.id.button_send_bitcoin).setVisible(false);
handleUpdateUiForCreatingSubscription();
break;
}
}
private void initCardNumberHelper() {
final EditText cardNumberView = (EditText) subscriptionActivity.findViewById(R.id.card_number);
final EditText cardExpirationView = (EditText) subscriptionActivity.findViewById(R.id.card_expiration);
cardNumberView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (cardNumberView.getText() != null &&
cardNumberView.getText().toString().contains("*"))
{
cardNumberView.setText("");
}
return false;
}
});
if (cardNumberTextWatcher != null)
cardNumberView.removeTextChangedListener(cardNumberTextWatcher);
cardNumberTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
String cardNumber = s.toString().replace(" ", "");
String formattedCardNumber = "";
for (int i = 0; i < cardNumber.length(); i++) {
if (i > 0 && i % 4 == 0)
formattedCardNumber += " ";
formattedCardNumber += cardNumber.charAt(i);
}
cardNumberView.removeTextChangedListener(this);
cardNumberView.setText(formattedCardNumber);
cardNumberView.setSelection(formattedCardNumber.length());
cardNumberView.addTextChangedListener(this);
if (!cardNumber.contains("*") && cardNumber.length() == 16)
cardExpirationView.requestFocus();
}
};
cardNumberView.addTextChangedListener(cardNumberTextWatcher);
}
private void initCardExpirationHelper() {
final EditText cardExpirationView = (EditText) subscriptionActivity.findViewById(R.id.card_expiration);
final EditText cardCvcView = (EditText) subscriptionActivity.findViewById(R.id.card_cvc);
if (cardExpirationTextWatcher != null)
cardExpirationView.removeTextChangedListener(cardExpirationTextWatcher);
cardExpirationTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
String formattedCardExpiration = s.toString();
if (lastCardExpirationLength <= formattedCardExpiration.length() &&
formattedCardExpiration.length() == 2)
{
formattedCardExpiration = formattedCardExpiration + "/";
}
lastCardExpirationLength = formattedCardExpiration.length();
cardExpirationView.removeTextChangedListener(this);
cardExpirationView.setText(formattedCardExpiration);
cardExpirationView.setSelection(formattedCardExpiration.length());
cardExpirationView.addTextChangedListener(this);
if (formattedCardExpiration.length() == 5)
cardCvcView.requestFocus();
}
};
cardExpirationView.addTextChangedListener(cardExpirationTextWatcher);
}
private void handleVerifyCardAndPutToServer() {
if (asyncTask != null)
return;
asyncTask = new AsyncTask<Void, Void, Bundle>() {
@Override
protected void onPreExecute() {
Log.d(TAG, "handleVerifyCardAndPutToServer()");
subscriptionActivity.setProgressBarIndeterminateVisibility(true);
subscriptionActivity.setProgressBarVisibility(true);
}
private String handleGetStripeCardTokenId(String cardNumber,
String cardExpiration,
String cardCVC)
throws StripeException
{
String[] expiration = cardExpiration.split("/");
Integer expirationMonth = Integer.valueOf(expiration[0]);
Integer expirationYear;
if (expiration[1].length() == 4)
expirationYear = Integer.valueOf(expiration[1]);
else
expirationYear = Integer.valueOf(expiration[1]) + 2000;
java.util.Map<String, Object> cardParams = new HashMap<String, Object>();
java.util.Map<String, Object> tokenParams = new HashMap<String, Object>();
cardParams.put("number", cardNumber.replace(" ", ""));
cardParams.put("exp_month", expirationMonth);
cardParams.put("exp_year", expirationYear);
cardParams.put("cvc", cardCVC);
tokenParams.put("card", cardParams);
return Token.create(tokenParams, OwsRegistration.STRIPE_PUBLIC_KEY).getId();
}
private void handlePutStripeTokenToServer(String stripeTokenId)
throws IOException, RegistrationApiException
{
RegistrationApi registrationApi = new RegistrationApi(subscriptionActivity);
registrationApi.setStripeCard(subscriptionActivity.davAccount, stripeTokenId);
}
private void handleUpdateSubscriptionStore(String cardNumber, String cardExpiration)
throws JsonProcessingException
{
String cardNoSpaces = cardNumber.replace(" ", "");
String lastFour = cardNoSpaces.substring(cardNoSpaces.length() - 4);
FlockCardInformation cardInformation =
new FlockCardInformation(subscriptionActivity.davAccount.getUserId(), lastFour, cardExpiration);
StripePlan newStripePlan =
new StripePlan(subscriptionActivity.davAccount.getUserId(), "nope");
AccountStore.setLastChargeFailed(subscriptionActivity, false);
AccountStore.setSubscriptionPlan(subscriptionActivity, newStripePlan);
AccountStore.setAutoRenew(subscriptionActivity, true);
AccountStore.setCardInformation(subscriptionActivity, Optional.of(cardInformation));
}
@Override
protected Bundle doInBackground(Void... params) {
Bundle result = new Bundle();
String cardNumber = ((TextView) subscriptionActivity.findViewById(R.id.card_number)).getText().toString();
String cardExpiration = ((TextView) subscriptionActivity.findViewById(R.id.card_expiration)).getText().toString();
String cardCVC = ((TextView) subscriptionActivity.findViewById(R.id.card_cvc)).getText().toString();
if (StringUtils.isEmpty(cardNumber) || cardNumber.contains("*")) {
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_CARD_NUMBER_INVALID);
return result;
}
if (StringUtils.isEmpty(cardExpiration) || cardExpiration.split("/").length != 2) {
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_CARD_EXPIRATION_INVALID);
return result;
}
if (StringUtils.isEmpty(cardCVC) || cardCVC.length() < 1) {
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_CARD_CVC_INVALID);
return result;
}
try {
String stripeTokenId = handleGetStripeCardTokenId(cardNumber, cardExpiration, cardCVC);
handlePutStripeTokenToServer(stripeTokenId);
handleUpdateSubscriptionStore(cardNumber, cardExpiration);
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SUCCESS);
} catch (CardException e) {
ErrorToaster.handleBundleError(e, result);
} catch (StripeException e) {
ErrorToaster.handleBundleError(e, result);
} catch (RegistrationApiException e) {
ErrorToaster.handleBundleError(e, result);
} catch (JsonProcessingException e) {
ErrorToaster.handleBundleError(e, result);
} catch (IOException e) {
ErrorToaster.handleBundleError(e, result);
}
return result;
}
@Override
protected void onPostExecute(Bundle result) {
asyncTask = null;
subscriptionActivity.setProgressBarIndeterminateVisibility(false);
subscriptionActivity.setProgressBarVisibility(false);
if (result.getInt(ErrorToaster.KEY_STATUS_CODE) == ErrorToaster.CODE_SUCCESS) {
Toast.makeText(subscriptionActivity, R.string.card_verified_and_saved, Toast.LENGTH_LONG).show();
handleUpdateUi();
}
else {
ErrorToaster.handleDisplayToastBundledError(subscriptionActivity, result);
handleUpdateUi();
}
}
}.execute();
}
private void handleCancelSubscription() {
if (asyncTask != null)
return;
asyncTask = new AsyncTask<Void, Void, Bundle>() {
@Override
protected void onPreExecute() {
Log.d(TAG, "handleCancelSubscription()");
subscriptionActivity.setProgressBarIndeterminateVisibility(true);
subscriptionActivity.setProgressBarVisibility(true);
}
@Override
protected Bundle doInBackground(Void... params) {
Bundle result = new Bundle();
try {
RegistrationApi registrationApi = new RegistrationApi(subscriptionActivity);
registrationApi.cancelSubscription(subscriptionActivity.davAccount);
AccountStore.setLastChargeFailed(subscriptionActivity, false);
AccountStore.setSubscriptionPlan(subscriptionActivity, SubscriptionPlan.PLAN_NONE);
AccountStore.setAutoRenew(subscriptionActivity, false);
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SUCCESS);
} catch (RegistrationApiException e) {
ErrorToaster.handleBundleError(e, result);
} catch (JsonProcessingException e) {
ErrorToaster.handleBundleError(e, result);
} catch (IOException e) {
ErrorToaster.handleBundleError(e, result);
}
return result;
}
@Override
protected void onPostExecute(Bundle result) {
asyncTask = null;
subscriptionActivity.setProgressBarIndeterminateVisibility(false);
subscriptionActivity.setProgressBarVisibility(false);
if (result.getInt(ErrorToaster.KEY_STATUS_CODE) == ErrorToaster.CODE_SUCCESS) {
new AccountSyncScheduler(subscriptionActivity).requestSync();
Toast.makeText(subscriptionActivity, R.string.subscription_canceled, Toast.LENGTH_SHORT).show();
subscriptionActivity.updateFragmentWithPlanType(SubscriptionPlan.PLAN_TYPE_NONE);
}
else {
ErrorToaster.handleDisplayToastBundledError(subscriptionActivity, result);
handleUpdateUi();
}
}
}.execute();
}
}

View File

@ -1,232 +0,0 @@
/*
* Copyright (C) 2014 Open Whisper Systems
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.anhonesteffort.flock;
import android.app.Activity;
import android.content.Intent;
import android.content.SyncResult;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.anhonesteffort.flock.util.guava.Optional;
import org.anhonesteffort.flock.registration.RegistrationApiException;
import org.anhonesteffort.flock.registration.model.SubscriptionPlan;
import org.anhonesteffort.flock.sync.account.AccountStore;
import org.anhonesteffort.flock.sync.account.AccountSyncWorker;
import java.util.Timer;
import java.util.TimerTask;
import de.passsy.holocircularprogressbar.HoloCircularProgressBar;
/**
* rhodey
*/
public class UnsubscribedFragment extends Fragment {
private static final String TAG = "org.anhonesteffort.flock.UnsubscribedFragment";
private final Handler uiHandler = new Handler();
private Timer intervalTimer = new Timer();
private ManageSubscriptionActivity subscriptionActivity;
private AsyncTask asyncTask;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof ManageSubscriptionActivity)
this.subscriptionActivity = (ManageSubscriptionActivity) activity;
else
throw new ClassCastException(activity.toString() + " not what I expected D: !");
}
private void initButtons(View view) {
view.findViewById(R.id.button_google_play).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
subscriptionActivity.updateFragmentWithPlanType(SubscriptionPlan.PLAN_TYPE_GOOGLE);
}
});
view.findViewById(R.id.button_credit_card).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
subscriptionActivity.updateFragmentWithPlanType(SubscriptionPlan.PLAN_TYPE_STRIPE);
}
});
view.findViewById(R.id.button_send_bitcoin).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent sendBitcoin = new Intent(subscriptionActivity, SendBitcoinActivity.class);
sendBitcoin.putExtra(SendBitcoinActivity.KEY_DAV_ACCOUNT_BUNDLE,
subscriptionActivity.davAccount.toBundle());
startActivity(sendBitcoin);
}
});
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_subscription_unsubscribed, container, false);
initButtons(view);
return view;
}
@Override
public void onResume() {
super.onResume();
handleUpdateUi();
handleStartPerpetualRefresh();
}
@Override
public void onPause() {
super.onPause();
if (asyncTask != null && !asyncTask.isCancelled())
asyncTask.cancel(true);
if (intervalTimer != null)
intervalTimer.cancel();
subscriptionActivity.setProgressBarIndeterminateVisibility(false);
subscriptionActivity.setProgressBarVisibility(false);
}
private void handleUpdateDaysRemainingUi(Long daysRemaining) {
TextView daysRemainingView = (TextView)subscriptionActivity.findViewById(R.id.days_remaining);
HoloCircularProgressBar progressBarView = (HoloCircularProgressBar)subscriptionActivity.findViewById(R.id.days_remaining_progress);
Long daysProgress = daysRemaining;
if (daysRemaining < 0) {
daysRemaining = 0L;
daysProgress = 0L;
}
else if (daysRemaining > 365)
daysProgress = 365L;
daysRemainingView.setText(daysRemaining.toString());
progressBarView.setProgress(1.0F - ((float) daysProgress / 365.0F));
}
private void handleUpdateUi() {
int subscriptionType = AccountStore.getSubscriptionPlanType(subscriptionActivity);
Optional<Long> daysRemaining = AccountStore.getDaysRemaining(subscriptionActivity);
switch (subscriptionType) {
case SubscriptionPlan.PLAN_TYPE_GOOGLE:
subscriptionActivity.updateFragmentWithPlanType(SubscriptionPlan.PLAN_TYPE_GOOGLE);
break;
case SubscriptionPlan.PLAN_TYPE_STRIPE:
subscriptionActivity.updateFragmentWithPlanType(SubscriptionPlan.PLAN_TYPE_STRIPE);
break;
default:
if (daysRemaining.isPresent())
handleUpdateDaysRemainingUi(daysRemaining.get());
else
Log.w(TAG, "days remaining not present in SubscriptionStore :|");
break;
}
}
private void handleRefreshSubscriptionStore() {
asyncTask = new AsyncTask<String, Void, Bundle>() {
@Override
protected void onPreExecute() {
Log.d(TAG, "handleRefreshSubscriptionStore()");
subscriptionActivity.setProgressBarIndeterminateVisibility(true);
subscriptionActivity.setProgressBarVisibility(true);
}
@Override
protected Bundle doInBackground(String... params) {
Bundle result = new Bundle();
try {
new AccountSyncWorker(
subscriptionActivity,
subscriptionActivity.davAccount,
null,
new SyncResult()
).run();
} catch (RegistrationApiException e) {
ErrorToaster.handleBundleError(e, result);
return result;
}
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SUCCESS);
return result;
}
@Override
protected void onPostExecute(Bundle result) {
asyncTask = null;
subscriptionActivity.setProgressBarIndeterminateVisibility(false);
subscriptionActivity.setProgressBarVisibility(false);
if (result.getInt(ErrorToaster.KEY_STATUS_CODE) == ErrorToaster.CODE_SUCCESS)
handleUpdateUi();
else
ErrorToaster.handleDisplayToastBundledError(subscriptionActivity, result);
}
}.execute();
}
private final Runnable refreshUiRunnable = new Runnable() {
@Override
public void run() {
if (asyncTask == null || asyncTask.isCancelled())
handleRefreshSubscriptionStore();
}
};
private void handleStartPerpetualRefresh() {
intervalTimer = new Timer();
TimerTask uiTask = new TimerTask() {
@Override
public void run() {
uiHandler.post(refreshUiRunnable);
}
};
intervalTimer.schedule(uiTask, 0, 15000);
}
}

View File

@ -22,7 +22,6 @@ package org.anhonesteffort.flock.sync;
import android.accounts.Account;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.OperationApplicationException;
import android.database.Cursor;
@ -33,27 +32,25 @@ import android.util.Pair;
import org.anhonesteffort.flock.util.guava.Optional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
/**
* Programmer: rhodey
* Date: 2/4/14
* rhodey
*/
// TODO: I really don't think we need all of these null cursor checks...
public abstract class AbstractLocalComponentCollection<T> implements LocalComponentCollection<T> {
private static final String TAG = "org.anhonesteffort.flock.sync.AbstractLocalComponentCollection";
protected ContentProviderClient client;
protected Account account;
protected String remotePath;
protected final Long localId;
protected final ContentProviderClient client;
protected final ContentProviderOperationQueue operationQueue;
protected ArrayList<ContentProviderOperation> pendingOperations;
protected final Account account;
protected final String remotePath;
protected final Long localId;
public AbstractLocalComponentCollection(ContentProviderClient client,
Account account,
@ -61,11 +58,10 @@ public abstract class AbstractLocalComponentCollection<T> implements LocalCompon
Long localId)
{
this.client = client;
operationQueue = new ContentProviderOperationQueue(client);
this.account = account;
this.remotePath = remotePath;
this.localId = localId;
pendingOperations = new ArrayList<ContentProviderOperation>();
}
public Account getAccount() {
@ -92,14 +88,12 @@ public abstract class AbstractLocalComponentCollection<T> implements LocalCompon
protected abstract String getColumnNameDirty();
protected abstract String getColumnNameDeleted();
protected abstract String getColumnNameQueuedForMigration();
protected abstract String getColumnNameAccountType();
public List<Long> getNewComponentIds() throws RemoteException {
final String[] PROJECTION = new String[]{getColumnNameComponentLocalId(), getColumnNameComponentUid()};
final String SELECTION = "(" + getColumnNameComponentUid() + " IS NULL OR " +
getColumnNameQueuedForMigration() + "=1) AND " +
getColumnNameCollectionLocalId() + "=" + localId;
final String SELECTION = getColumnNameComponentUid() + " IS NULL AND " +
getColumnNameCollectionLocalId() + "=" + localId;
Cursor cursor = client.query(getUriForComponents(), PROJECTION, SELECTION, null, null);
List<Long> newIds = new LinkedList<Long>();
@ -162,8 +156,7 @@ public abstract class AbstractLocalComponentCollection<T> implements LocalCompon
final String[] PROJECTION = new String[]{};
final String SELECTION = "(" + getColumnNameComponentUid() + " IS NULL OR " +
getColumnNameDirty() + "=1 OR " +
getColumnNameDeleted() + "=1 OR " +
getColumnNameQueuedForMigration() + "=1) AND " +
getColumnNameDeleted() + "=1) AND " +
getColumnNameCollectionLocalId() + "=" + localId;
Cursor cursor = client.query(getUriForComponents(), PROJECTION, SELECTION, null, null);
@ -287,7 +280,7 @@ public abstract class AbstractLocalComponentCollection<T> implements LocalCompon
String rand = UUID.randomUUID().toString();
Log.d(TAG, "populateComponentUid() gonna populate " + localId + " with " + rand);
pendingOperations.add(ContentProviderOperation
operationQueue.queue(ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(getUriForComponents(), localId))
.withValue(getColumnNameComponentUid(), rand)
.withYieldAllowed(false)
@ -300,7 +293,7 @@ public abstract class AbstractLocalComponentCollection<T> implements LocalCompon
public void removeComponent(Long localId) {
Log.d(TAG, "removeComponent() localId " + localId);
pendingOperations.add(ContentProviderOperation
operationQueue.queue(ContentProviderOperation
.newDelete(ContentUris.withAppendedId(getUriForComponents(), localId))
.withYieldAllowed(true)
.build());
@ -312,7 +305,7 @@ public abstract class AbstractLocalComponentCollection<T> implements LocalCompon
getColumnNameCollectionLocalId() + "=" + localId;
final String[] SELECTION_ARGS = new String[]{remoteUId};
pendingOperations.add(ContentProviderOperation
operationQueue.queue(ContentProviderOperation
.newDelete(getUriForComponents())
.withSelection(SELECTION, SELECTION_ARGS)
.withYieldAllowed(true)
@ -325,7 +318,7 @@ public abstract class AbstractLocalComponentCollection<T> implements LocalCompon
final Uri COMPONENT_URI = getUriForComponents().buildUpon().clearQuery().build();
final Uri CONTENT_URI = handleAddAccountQueryParams(COMPONENT_URI);
pendingOperations.add(ContentProviderOperation
operationQueue.queue(ContentProviderOperation
.newDelete(CONTENT_URI)
.withSelection(SELECTION, null)
.withYieldAllowed(true)
@ -356,17 +349,16 @@ public abstract class AbstractLocalComponentCollection<T> implements LocalCompon
public void cleanComponent(Long localId) {
Log.d(TAG, "cleanComponent() localId " + localId);
pendingOperations.add(ContentProviderOperation
operationQueue.queue(ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(getUriForComponents(), localId))
.withValue(getColumnNameDirty(), 0)
.withValue(getColumnNameQueuedForMigration(), 0)
.build());
}
public void dirtyComponent(Long localId) {
Log.d(TAG, "dirtyComponent() localId " + localId);
pendingOperations.add(ContentProviderOperation
operationQueue.queue(ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(getUriForComponents(), localId))
.withValue(getColumnNameDirty(), 1).build());
}
@ -374,32 +366,16 @@ public abstract class AbstractLocalComponentCollection<T> implements LocalCompon
public void setUidToNull(Long localId) {
Log.d(TAG, "setUidToNull() localId " + localId);
pendingOperations.add(ContentProviderOperation
operationQueue.queue(ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(getUriForComponents(), localId))
.withValue(getColumnNameComponentUid(), null)
.build());
}
public void queueForMigration(Long localId)
throws RemoteException
{
pendingOperations.add(ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(getUriForComponents(), localId))
.withValue(getColumnNameQueuedForMigration(), 1)
.withYieldAllowed(false)
.build());
}
public int commitPendingOperations()
throws OperationApplicationException, RemoteException
{
ContentProviderResult[] result = new ContentProviderResult[0];
if (!pendingOperations.isEmpty())
result = client.applyBatch(pendingOperations);
pendingOperations.clear();
return result.length;
return operationQueue.commit();
}
}

View File

@ -185,8 +185,6 @@ public abstract class AbstractSyncAdapter extends AbstractThreadedSyncAdapter {
{
NotificationDrawer.handleInvalidatePasswordAndShowAuthNotification(getContext());
}
if (result.stats.numSkippedEntries > 0)
NotificationDrawer.showSubscriptionExpiredNotification(getContext());
if (NotificationDrawer.isAuthNotificationDisabled(getContext(), getSyncScheduler().getAuthority()))
NotificationDrawer.enableAuthNotifications(getContext(), getSyncScheduler().getAuthority());

View File

@ -0,0 +1,89 @@
/*
* Copyright (C) 2015 Open Whisper Systems
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.anhonesteffort.flock.sync;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.OperationApplicationException;
import android.os.RemoteException;
import java.util.ArrayList;
import java.util.List;
/**
* rhodey
*/
public class ContentProviderOperationQueue {
private static final Integer MAX_QUEUE_SIZE = 100;
private static final Long MAX_QUEUE_SIZE_BYTES = 512000L;
private final ContentProviderClient client;
private final ArrayList<ContentProviderOperation> operations;
private Long queueSizeBytes;
public ContentProviderOperationQueue(ContentProviderClient client) {
this.client = client;
operations = new ArrayList<>();
queueSizeBytes = 0L;
}
public int size() {
return operations.size();
}
public boolean hasSpace() {
return size() < MAX_QUEUE_SIZE && queueSizeBytes < MAX_QUEUE_SIZE_BYTES;
}
public void queue(ContentProviderOperation operation, Integer operationSizeBytes) {
operations.add(operation);
queueSizeBytes += operationSizeBytes;
}
public void queue(ContentProviderOperation operation) {
queue(operation, 0);
}
public void queueAll(List<ContentProviderOperation> operationList, Integer operationsSizeBytes) {
operations.addAll(operationList);
queueSizeBytes += operationsSizeBytes;
}
public void queueAll(List<ContentProviderOperation> operationList) {
queueAll(operationList, 0);
}
public int commit() throws OperationApplicationException, RemoteException {
ContentProviderResult[] result = new ContentProviderResult[0];
try {
if (!operations.isEmpty())
result = client.applyBatch(operations);
} finally {
operations.clear();
queueSizeBytes = 0L;
}
return result.length;
}
}

View File

@ -18,15 +18,11 @@
package org.anhonesteffort.flock.sync.account;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import com.android.vending.billing.IInAppBillingService;
import org.anhonesteffort.flock.DavAccountHelper;
import org.anhonesteffort.flock.crypto.InvalidMacException;
import org.anhonesteffort.flock.registration.RegistrationApiException;
@ -45,7 +41,6 @@ import java.util.List;
*/
public class AccountSyncService extends Service {
protected IInAppBillingService billingService = null;
private static AccountSyncAdapter sSyncAdapter = null;
private static final Object sSyncAdapterLock = new Object();
@ -55,10 +50,6 @@ public class AccountSyncService extends Service {
if (sSyncAdapter == null)
sSyncAdapter = new AccountSyncAdapter(getApplicationContext());
}
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
@Override
@ -97,7 +88,7 @@ public class AccountSyncService extends Service {
List<SyncWorker> workers = new LinkedList<>();
if (DavAccountHelper.isUsingOurServers(davAccount))
workers.add(new AccountSyncWorker(getContext(), davAccount, billingService, syncResult));
workers.add(new AccountSyncWorker(getContext(), davAccount, syncResult));
return workers;
}
@ -110,26 +101,4 @@ public class AccountSyncService extends Service {
}
}
@Override
public void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
billingService = null;
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
billingService = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
billingService = IInAppBillingService.Stub.asInterface(service);
}
};
}

View File

@ -19,34 +19,19 @@ package org.anhonesteffort.flock.sync.account;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import com.android.vending.billing.IInAppBillingService;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.anhonesteffort.flock.util.guava.Optional;
import org.anhonesteffort.flock.SubscriptionGoogleFragment;
import org.anhonesteffort.flock.auth.DavAccount;
import org.anhonesteffort.flock.registration.PaymentRequiredException;
import org.anhonesteffort.flock.registration.RegistrationApi;
import org.anhonesteffort.flock.registration.RegistrationApiException;
import org.anhonesteffort.flock.registration.ResourceAlreadyExistsException;
import org.anhonesteffort.flock.registration.model.AugmentedFlockAccount;
import org.anhonesteffort.flock.registration.model.GooglePlan;
import org.anhonesteffort.flock.registration.model.SubscriptionPlan;
import org.anhonesteffort.flock.sync.SyncWorker;
import org.anhonesteffort.flock.sync.SyncWorkerUtil;
import org.anhonesteffort.flock.util.Base64;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* rhodey
@ -57,237 +42,21 @@ public class AccountSyncWorker implements SyncWorker {
private final Context context;
private final DavAccount account;
private final IInAppBillingService billingService;
private final SyncResult result;
private final RegistrationApi registration;
private boolean billingServiceHasErrors = false;
public AccountSyncWorker(Context context,
DavAccount account,
IInAppBillingService billingService,
SyncResult syncResult)
throws RegistrationApiException
{
this.context = context;
this.account = account;
this.result = syncResult;
this.billingService = billingService;
registration = new RegistrationApi(context);
}
private List<JSONObject> getPurchasedGoogleSubscriptions() {
List<JSONObject> subscriptions = new LinkedList<>();
if (billingService == null) {
Log.e(TAG, "billing service is null");
billingServiceHasErrors = true;
return subscriptions;
}
try {
Bundle ownedItems = billingService
.getPurchases(3, SubscriptionGoogleFragment.class.getPackage().getName(),
SubscriptionGoogleFragment.PRODUCT_TYPE_SUBSCRIPTION, null);
if (ownedItems.getInt("RESPONSE_CODE") != 0) {
Log.e(TAG, "owned items response code is " + ownedItems.getInt("RESPONSE_CODE"));
billingServiceHasErrors = true;
return subscriptions;
}
ArrayList<String> purchaseDataList =
ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
for (int i = 0; i < purchaseDataList.size(); ++i) {
JSONObject productObject = new JSONObject(purchaseDataList.get(i));
if (productObject.getString("productId") != null)
subscriptions.add(productObject);
}
} catch (RemoteException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (JSONException e) {
SyncWorkerUtil.handleException(context, e, result);
}
return subscriptions;
}
private void handleCancelSubscriptionWithServerIfNotRenewing() {
Log.d(TAG, "handleCancelSubscriptionWithServerIfNotRenewing");
try {
if (!registration.getIsPlanAutoRenewing(account)) {
Log.d(TAG, "server says our plan is not auto renewing, must have been canceled through" +
" google play store, will now cancel with registration server.");
registration.cancelSubscription(account);
AccountStore.setSubscriptionPlan(context, SubscriptionPlan.PLAN_NONE);
AccountStore.setAutoRenew(context, false);
}
else
Log.w(TAG, "active subscription is GOOGLE but google play returned no matching purchases, why?");
} catch (RegistrationApiException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (JsonProcessingException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
private void handleLocallyCanceledGoogleSubscriptions() {
Log.d(TAG, "handleLocallyCanceledGoogleSubscriptions");
if (AccountStore.getSubscriptionPlanType(context) != SubscriptionPlan.PLAN_TYPE_GOOGLE)
return;
boolean havePlanWithPlayServices = false;
for (JSONObject googleSubscription : getPurchasedGoogleSubscriptions()) {
try {
String subscriptionSku = googleSubscription.getString("productId");
if (subscriptionSku.equals(SubscriptionGoogleFragment.SKU_YEARLY_SUBSCRIPTION)) {
havePlanWithPlayServices = true;
break;
}
} catch (JSONException e) {
SyncWorkerUtil.handleException(context, e, result);
return;
}
}
if (!havePlanWithPlayServices && !billingServiceHasErrors)
handleCancelSubscriptionWithServerIfNotRenewing();
}
private void handlePutGoogleSubscriptionToServer(JSONObject subscription) {
Log.d(TAG, "handlePutGoogleSubscriptionToServer");
try {
String productSku = subscription.getString("productId");
String purchaseToken = subscription.getString("purchaseToken");
GooglePlan plan = new GooglePlan(
account.getUserId(), productSku, purchaseToken, Long.MAX_VALUE
);
try {
registration.putNewGooglePlan(account, productSku, purchaseToken);
AccountStore.setSubscriptionPlan(context, plan);
} catch (ResourceAlreadyExistsException e) {
Log.w(TAG, "thought we were putting new google plan but one already exists.");
}
AccountStore.setAutoRenew(context, true);
} catch (RegistrationApiException e) {
if (e instanceof PaymentRequiredException)
Log.w(TAG, "thought we were putting new google plan but server says it is bad", e);
else
SyncWorkerUtil.handleException(context, e, result);
} catch (JSONException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (JsonProcessingException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
private void handleNewGoogleSubscriptions() {
Log.d(TAG, "handleNewGoogleSubscriptions");
try {
JSONObject newGooglePlan = null;
SubscriptionPlan currentPlan = AccountStore.getSubscriptionPlan(context);
if (currentPlan.getPlanType() != SubscriptionPlan.PLAN_TYPE_GOOGLE &&
currentPlan.getPlanType() != SubscriptionPlan.PLAN_TYPE_NONE)
{
return;
}
for (JSONObject purchasedGoogleSubscription : getPurchasedGoogleSubscriptions()) {
try {
String productSku = purchasedGoogleSubscription.getString("productId");
String encodedDeveloperPayload = purchasedGoogleSubscription.getString("developerPayload");
Log.d(TAG, "GOOGLE THINKS WE PURCHASED THIS >> " + " - " + productSku);
if (productSku.equals(SubscriptionGoogleFragment.SKU_YEARLY_SUBSCRIPTION)) {
if (encodedDeveloperPayload != null) {
String developerPayload = new String(Base64.decode(encodedDeveloperPayload));
if (developerPayload.toUpperCase().equals(account.getUserId().toUpperCase())) {
switch (currentPlan.getPlanType()) {
case SubscriptionPlan.PLAN_TYPE_NONE:
newGooglePlan = purchasedGoogleSubscription;
break;
case SubscriptionPlan.PLAN_TYPE_GOOGLE:
GooglePlan currentGooglePlan = (GooglePlan) currentPlan;
if (currentGooglePlan.getPurchaseToken().equals(SubscriptionGoogleFragment.PURCHASE_TOKEN_HACK))
newGooglePlan = purchasedGoogleSubscription;
break;
}
}
else
Log.w(TAG, "found google play subscription belonging to account other than this, won't put to server.");
}
else
Log.w(TAG, "found google play subscription with null developer payload, won't put to server.");
}
} catch (JSONException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
if (newGooglePlan != null)
handlePutGoogleSubscriptionToServer(newGooglePlan);
} catch (JsonParseException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
private void handleMigrateToStripePlanIfAutoRenewAndNoPlan(AugmentedFlockAccount flockAccount) {
if (!flockAccount.getAutoRenewEnabled() ||
!flockAccount.getSubscriptionPlan().getPlanType().equals(SubscriptionPlan.PLAN_TYPE_NONE))
{
return;
}
try {
registration.migrateBillingToStripeSubscriptionModel(account);
} catch (PaymentRequiredException e) {
Log.e(TAG, "tried to migrate account to stripe plan and got CardException", e);
SyncWorkerUtil.handleException(context, e, result);
} catch (RegistrationApiException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
private Optional<AugmentedFlockAccount> handleUpdateFlockAccountCache() {
Log.d(TAG, "handleUpdateFlockAccountCache");
@ -309,31 +78,9 @@ public class AccountSyncWorker implements SyncWorker {
return Optional.fromNullable(flockAccount);
}
private void handleUpdateCardInformationCache() {
Log.d(TAG, "handleUpdateCardInformationCache");
try {
AccountStore.setCardInformation(context, registration.getCard(account));
} catch (RegistrationApiException e) {
SyncWorkerUtil.handleException(context, e, result);
} catch (IOException e) {
SyncWorkerUtil.handleException(context, e, result);
}
}
@Override
public void run() {
handleLocallyCanceledGoogleSubscriptions();
handleNewGoogleSubscriptions();
Optional<AugmentedFlockAccount> flockAccount = handleUpdateFlockAccountCache();
if (flockAccount.isPresent())
handleMigrateToStripePlanIfAutoRenewAndNoPlan(flockAccount.get());
handleUpdateCardInformationCache();
handleUpdateFlockAccountCache();
}
@Override

View File

@ -144,12 +144,12 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
@Override
protected String getColumnNameComponentUid() {
return ContactsContract.RawContacts.SOURCE_ID;
return ContactFactory.COLUMN_NAME_CONTACT_UID;
}
@Override
protected String getColumnNameComponentETag() {
return ContactsContract.RawContacts.SYNC1;
return ContactFactory.COLUMN_NAME_CONTACT_ETAG;
}
@Override
@ -162,11 +162,6 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
return ContactsContract.RawContacts.DELETED;
}
@Override
protected String getColumnNameQueuedForMigration() {
return ContactsContract.RawContacts.SYNC2;
}
@Override
protected String getColumnNameAccountType() {
return ContactsContract.RawContacts.ACCOUNT_TYPE;
@ -222,7 +217,7 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
}
private void addPhoneNumbers(Long rawContactId, VCard vCard)
throws InvalidLocalComponentException, RemoteException
throws RemoteException
{
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
@ -239,13 +234,13 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
while (cursor.moveToNext()) {
ContentValues phoneNumberValues = ContactFactory.getValuesForPhoneNumber(cursor);
ContactFactory.addPhoneNumber(getPath(), vCard, phoneNumberValues);
ContactFactory.addPhoneNumber(vCard, phoneNumberValues);
}
cursor.close();
}
private void addEmailAddresses(Long rawContactId, VCard vCard)
throws InvalidLocalComponentException, RemoteException
throws RemoteException
{
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
@ -262,13 +257,13 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
while (cursor.moveToNext()) {
ContentValues emailAddressValues = ContactFactory.getValuesForEmailAddress(cursor);
ContactFactory.addEmailAddress(getPath(), vCard, emailAddressValues);
ContactFactory.addEmailAddress(vCard, emailAddressValues);
}
cursor.close();
}
private Optional<Photo> getDisplayPhoto(Long rawContactId)
throws InvalidLocalComponentException, RemoteException
throws RemoteException
{
try {
@ -312,7 +307,7 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
}
private void addPhotos(Long rawContactId, VCard vCard)
throws InvalidLocalComponentException, RemoteException
throws RemoteException
{
Optional<Photo> photo = getDisplayPhoto(rawContactId);
if (!photo.isPresent())
@ -338,13 +333,13 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
while (cursor.moveToNext()) {
ContentValues organizationValues = ContactFactory.getValuesForOrganization(cursor);
ContactFactory.addOrganizer(vCard, organizationValues);
ContactFactory.addOrganization(vCard, organizationValues);
}
cursor.close();
}
private void addInstantMessaging(Long rawContactId, VCard vCard)
throws InvalidLocalComponentException, RemoteException
throws RemoteException
{
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
@ -361,7 +356,7 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
while (cursor.moveToNext()) {
ContentValues instantMessagingValues = ContactFactory.getValuesForInstantMessaging(cursor);
ContactFactory.addInstantMessaging(getPath(), vCard, instantMessagingValues);
ContactFactory.addInstantMessaging(vCard, instantMessagingValues);
}
cursor.close();
}
@ -409,7 +404,7 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
}
private void addPostalAddresses(Long rawContactId, VCard vCard)
throws InvalidLocalComponentException, RemoteException
throws RemoteException
{
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
@ -426,13 +421,13 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
while (cursor.moveToNext()) {
ContentValues postalAddressValues = ContactFactory.getValuesForPostalAddress(cursor);
ContactFactory.addPostalAddress(getPath(), vCard, postalAddressValues);
ContactFactory.addPostalAddress(vCard, postalAddressValues);
}
cursor.close();
}
private void addWebsites(Long rawContactId, VCard vCard)
throws InvalidLocalComponentException, RemoteException
throws RemoteException
{
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
@ -449,7 +444,7 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
while (cursor.moveToNext()) {
ContentValues websiteValues = ContactFactory.getValuesForWebsite(cursor);
ContactFactory.addWebsite(getPath(), vCard, websiteValues);
ContactFactory.addWebsite(vCard, websiteValues);
}
cursor.close();
}
@ -580,7 +575,7 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
String[] PROJECTION = new String[] {
ContactsContract.RawContacts.ACCOUNT_NAME,
ContactsContract.RawContacts.ACCOUNT_TYPE,
ContactsContract.RawContacts.SOURCE_ID
ContactFactory.COLUMN_NAME_CONTACT_UID
};
String SELECTION = ContactsContract.RawContacts._ID + "=? ";
String[] SELECTION_ARGS = new String[] { rawContactId.toString() };
@ -681,7 +676,6 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
String SELECTION = getColumnNameComponentUid() + "=?";
String[] SELECTION_ARGS = new String[]{uid};
Cursor cursor = client.query(getUriForComponents(),
ContactFactory.getProjectionForRawContact(),
SELECTION,
@ -784,115 +778,142 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
@Override
public int addComponent(ComponentETagPair<VCard> vCard) throws RemoteException {
int rawContactOpIndex = operationQueue.size();
ContentValues rawContactValues = ContactFactory.getValuesForRawContact(vCard);
int raw_contact_op_index = pendingOperations.size();
pendingOperations.add(ContentProviderOperation.newInsert(getUriForComponents())
.withValues(rawContactValues)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForComponents())
.withValues(rawContactValues)
.build(),
128
);
Optional<ContentValues> structuredName = ContactFactory.getValuesForStructuredName(vCard.getComponent());
if (structuredName.isPresent()) {
pendingOperations.add(ContentProviderOperation.newInsert(getUriForData())
.withValues(structuredName.get())
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, raw_contact_op_index)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(structuredName.get())
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
List<ContentValues> phoneNumbers = ContactFactory.getValuesForPhoneNumbers(vCard.getComponent());
for (ContentValues phoneNumber : phoneNumbers) {
pendingOperations.add(ContentProviderOperation.newInsert(getUriForData())
.withValues(phoneNumber)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, raw_contact_op_index)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(phoneNumber)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
128);
}
List<ContentValues> emailAddresses = ContactFactory.getValuesForEmailAddresses(vCard.getComponent());
for (ContentValues emailAddress : emailAddresses) {
pendingOperations.add(ContentProviderOperation.newInsert(getUriForData())
.withValues(emailAddress)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, raw_contact_op_index)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(emailAddress)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
Optional<ContentValues> picture = ContactFactory.getValuesForPhoto(vCard.getComponent());
if (picture.isPresent()) {
pendingOperations.add(ContentProviderOperation.newInsert(getUriForData())
.withValues(picture.get())
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, raw_contact_op_index)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(picture.get())
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
ContactFactory.getSizeOfPhotoInBytes(picture.get()));
}
List<ContentValues> organizations = ContactFactory.getValuesForOrganization(vCard.getComponent());
List<ContentValues> organizations = ContactFactory.getValuesForOrganizations(vCard.getComponent());
for (ContentValues organization : organizations) {
pendingOperations.add(ContentProviderOperation.newInsert(getUriForData())
.withValues(organization)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, raw_contact_op_index)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(organization)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
List<ContentValues> instantMessaging = ContactFactory.getValuesForInstantMessaging(vCard.getComponent());
for (ContentValues instantMessenger : instantMessaging) {
pendingOperations.add(ContentProviderOperation.newInsert(getUriForData())
.withValues(instantMessenger)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, raw_contact_op_index)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(instantMessenger)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
List<ContentValues> nickNames = ContactFactory.getValuesForNickName(vCard.getComponent());
List<ContentValues> nickNames = ContactFactory.getValuesForNickNames(vCard.getComponent());
for (ContentValues nickName : nickNames) {
pendingOperations.add(ContentProviderOperation.newInsert(getUriForData())
.withValues(nickName)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, raw_contact_op_index)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(nickName)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
List<ContentValues> notes = ContactFactory.getValuesForNote(vCard.getComponent());
List<ContentValues> notes = ContactFactory.getValuesForNotes(vCard.getComponent());
for (ContentValues note : notes) {
pendingOperations.add(ContentProviderOperation.newInsert(getUriForData())
.withValues(note)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, raw_contact_op_index)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(note)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
ContactFactory.getSizeOfNoteInBytes(note));
}
List<ContentValues> postalAddresses = ContactFactory.getValuesForPostalAddresses(vCard.getComponent());
for (ContentValues postalAddress : postalAddresses) {
pendingOperations.add(ContentProviderOperation.newInsert(getUriForData())
.withValues(postalAddress)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, raw_contact_op_index)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(postalAddress)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
List<ContentValues> websites = ContactFactory.getValuesForWebsites(vCard.getComponent());
for (ContentValues website : websites) {
pendingOperations.add(ContentProviderOperation.newInsert(getUriForData())
.withValues(website)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, raw_contact_op_index)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(website)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
List<ContentValues> events = ContactFactory.getValuesForEvents(vCard.getComponent());
for (ContentValues event : events) {
pendingOperations.add(ContentProviderOperation.newInsert(getUriForData())
.withValues(event)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, raw_contact_op_index)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(event)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
List<ContentValues> sipAddresses = ContactFactory.getValuesForSipAddresses(vCard.getComponent());
for (ContentValues sipAddress : sipAddresses) {
pendingOperations.add(ContentProviderOperation.newInsert(getUriForData())
.withValues(sipAddress)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, raw_contact_op_index)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(sipAddress)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
pendingOperations.addAll(
getOperationsForAggregationExceptions(vCard.getComponent(), raw_contact_op_index)
operationQueue.queueAll(
getOperationsForAggregationExceptions(vCard.getComponent(), rawContactOpIndex),
256
);
return pendingOperations.size() - raw_contact_op_index;
return operationQueue.size() - rawContactOpIndex;
}
@Override
@ -915,43 +936,40 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
ContactCopiedListener listener,
boolean forceFull)
{
int operationSum = 0;
for (int operationCount : contactOperationCounts)
operationSum += operationCount;
if (toCollection.operationQueue.hasSpace() && !forceFull)
return false;
if (operationSum >= 100 || forceFull) {
try {
try {
int pendingCount = toCollection.pendingOperations.size();
int successCount = toCollection.commitPendingOperations();
int failCount = pendingCount - successCount;
int pendingCount = toCollection.operationQueue.size();
int successCount = toCollection.commitPendingOperations();
int failCount = pendingCount - successCount;
Log.d(TAG, pendingCount + " were pending " + successCount + " were committed");
Log.d(TAG, pendingCount + " were pending " + successCount + " were committed");
for (int operationCount : contactOperationCounts)
listener.onContactCopied(account, toCollection.getAccount());
int contactCount = 0;
while (contactCount++ < contactOperationCounts.size())
listener.onContactCopied(account, toCollection.getAccount());
if (failCount > 0)
Log.e(TAG, "failed to commit " + failCount + "" +
"operations but no idea which contacts they're from!");
if (failCount > 0)
Log.e(TAG, "failed to commit " + failCount + " " +
"operations but no idea which contacts they're from!");
} catch (OperationApplicationException e) {
} catch (OperationApplicationException e) {
for (int operationCount : contactOperationCounts)
listener.onContactCopyFailed(e, account, toCollection.getAccount());
toCollection.pendingOperations.clear();
int contactCount = 0;
while (contactCount++ < contactOperationCounts.size())
listener.onContactCopyFailed(e, account, toCollection.getAccount());
} catch (RemoteException e) {
} catch (RemoteException e) {
for (int operationCount : contactOperationCounts)
listener.onContactCopyFailed(e, account, toCollection.getAccount());
toCollection.pendingOperations.clear();
int contactCount = 0;
while (contactCount++ < contactOperationCounts.size())
listener.onContactCopyFailed(e, account, toCollection.getAccount());
}
return true;
}
return false;
return true;
}
public void copyToAccount(Account toAccount, ContactCopiedListener listener)
@ -992,7 +1010,7 @@ public class LocalContactCollection extends AbstractLocalComponentCollection<VCa
}
}
if (toCollection.pendingOperations.size() > 0)
if (toCollection.operationQueue.size() > 0)
handleCommitPendingIfFull(toCollection, contactOperationCounts, listener, true);
}
}

View File

@ -98,6 +98,10 @@ public class EventFactory {
private static final String TAG = "org.anhonesteffort.flock.sync.calendar.EventFactory";
protected static final String COLUMN_NAME_EVENT_UID = CalendarContract.Events._SYNC_ID;
protected static final String COLUMN_NAME_EVENT_ETAG = CalendarContract.Events.SYNC_DATA1;
protected static final String COLUMN_NAME_COPIED_EVENT_ID = CalendarContract.Events.SYNC_DATA2;
private static final String PROPERTY_NAME_FLOCK_ALL_DAY = "X-FLOCK-ALL-DAY";
private static final String PROPERTY_NAME_FLOCK_ORIGINAL_SYNC_ID = "X-FLOCK-ORIGINAL-SYNC-ID";
private static final String PROPERTY_NAME_FLOCK_ORIGINAL_INSTANCE_TIME = "X-FLOCK-ORIGINAL-INSTANCE-TIME";
@ -150,9 +154,9 @@ public class EventFactory {
CalendarContract.Events.ORIGINAL_ALL_DAY, // 21
CalendarContract.Events.AVAILABILITY, // 22
CalendarContract.Events.STATUS, // 23
CalendarContract.Events._SYNC_ID, // 24 UID
CalendarContract.Events.SYNC_DATA1, // 25 ETag
CalendarContract.Events.SYNC_DATA2 // 26 copies event id
COLUMN_NAME_EVENT_UID, // 24
COLUMN_NAME_EVENT_ETAG, // 25
COLUMN_NAME_COPIED_EVENT_ID // 26
};
}
@ -183,9 +187,9 @@ public class EventFactory {
values.put(CalendarContract.Events.ORIGINAL_ALL_DAY, (cursor.getInt(21) != 0));
values.put(CalendarContract.Events.AVAILABILITY, cursor.getInt(22));
values.put(CalendarContract.Events.STATUS, cursor.getInt(23));
values.put(CalendarContract.Events._SYNC_ID, cursor.getString(24));
values.put(CalendarContract.Events.SYNC_DATA1, cursor.getString(25));
values.put(CalendarContract.Events.SYNC_DATA2, cursor.getLong(26));
values.put(COLUMN_NAME_EVENT_UID, cursor.getString(24));
values.put(COLUMN_NAME_EVENT_ETAG, cursor.getString(25));
values.put(COLUMN_NAME_COPIED_EVENT_ID, cursor.getLong(26));
return values;
}
@ -224,7 +228,7 @@ public class EventFactory {
private static void handleAttachPropertiesForCopiedRecurrenceWithExceptions(ContentValues values,
VEvent event)
{
Long copiedEventId = values.getAsLong(CalendarContract.Events.SYNC_DATA2);
Long copiedEventId = values.getAsLong(COLUMN_NAME_COPIED_EVENT_ID);
if (copiedEventId != null && copiedEventId > 0)
event.getProperties().add(new XProperty(PROPERTY_NAME_FLOCK_COPY_EVENT_ID,
@ -237,7 +241,7 @@ public class EventFactory {
Property copyIdProp = event.getProperty(PROPERTY_NAME_FLOCK_COPY_EVENT_ID);
if (copyIdProp != null)
values.put(CalendarContract.Events.SYNC_DATA2, Long.valueOf(copyIdProp.getValue()));
values.put(COLUMN_NAME_COPIED_EVENT_ID, Long.valueOf(copyIdProp.getValue()));
}
protected static void handleReplaceOriginalSyncId(String path, String syncId, VEvent event)
@ -376,12 +380,12 @@ public class EventFactory {
values.put(CalendarContract.Events.CALENDAR_ID, calendarId);
if (vEvent.getUid() != null && vEvent.getUid().getValue() != null)
values.put(CalendarContract.Events._SYNC_ID, vEvent.getUid().getValue());
values.put(COLUMN_NAME_EVENT_UID, vEvent.getUid().getValue());
else
values.putNull(CalendarContract.Events._SYNC_ID);
values.putNull(COLUMN_NAME_EVENT_UID);
if (component.getETag().isPresent())
values.put(CalendarContract.Events.SYNC_DATA1, component.getETag().get());
values.put(COLUMN_NAME_EVENT_ETAG, component.getETag().get());
DtStart dtStart = vEvent.getStartDate();
if (dtStart != null && dtStart.getDate() != null) {
@ -503,7 +507,7 @@ public class EventFactory {
Long originalLocalId = eventValues.getAsLong(CalendarContract.Events.ORIGINAL_ID);
String originalSyncId = eventValues.getAsString(CalendarContract.Events.ORIGINAL_SYNC_ID);
String syncId = eventValues.getAsString(CalendarContract.Events._SYNC_ID);
String syncId = eventValues.getAsString(COLUMN_NAME_EVENT_UID);
if (TextUtils.isEmpty(originalSyncId))
throw new InvalidLocalComponentException("original sync id required on recurring event deletion exceptions",
@ -532,7 +536,7 @@ public class EventFactory {
Long originalLocalId = eventValues.getAsLong(CalendarContract.Events.ORIGINAL_ID);
String originalSyncId = eventValues.getAsString(CalendarContract.Events.ORIGINAL_SYNC_ID);
Long originalInstanceTime = eventValues.getAsLong(CalendarContract.Events.ORIGINAL_INSTANCE_TIME);
String syncId = eventValues.getAsString(CalendarContract.Events._SYNC_ID);
String syncId = eventValues.getAsString(COLUMN_NAME_EVENT_UID);
if (TextUtils.isEmpty(originalSyncId))
throw new InvalidLocalComponentException("original sync id required on recurring event edit exceptions",
@ -562,7 +566,7 @@ public class EventFactory {
calendar.getProperties().add(Version.VERSION_2_0);
handleAttachPropertiesForCopiedRecurrenceWithExceptions(eventValues, vEvent);
String uidText = eventValues.getAsString(CalendarContract.Events._SYNC_ID);
String uidText = eventValues.getAsString(COLUMN_NAME_EVENT_UID);
if (!StringUtils.isEmpty(uidText)) {
Uid eventUid = new Uid(uidText);
vEvent.getProperties().add(eventUid);
@ -710,12 +714,12 @@ public class EventFactory {
}
calendar.getComponents().add(vEvent);
Optional<String> eTag = Optional.fromNullable(eventValues.getAsString(CalendarContract.Events.SYNC_DATA1));
Optional<String> eTag = Optional.fromNullable(eventValues.getAsString(COLUMN_NAME_EVENT_ETAG));
return new ComponentETagPair<Calendar>(calendar, eTag);
}
protected static String[] getProjectionForAttendee() {
public static String[] getProjectionForAttendee() {
return new String[] {
CalendarContract.Attendees.EVENT_ID, // 00
CalendarContract.Attendees.ATTENDEE_EMAIL, // 01
@ -728,7 +732,7 @@ public class EventFactory {
};
}
protected static ContentValues getValuesForAttendee(Cursor cursor) {
public static ContentValues getValuesForAttendee(Cursor cursor) {
ContentValues values = new ContentValues(8);
values.put(CalendarContract.Attendees.EVENT_ID, cursor.getLong(0));
@ -743,7 +747,7 @@ public class EventFactory {
return values;
}
protected static List<ContentValues> getValuesForAttendees(Calendar component) {
public static List<ContentValues> getValuesForAttendees(Calendar component) {
List<ContentValues> valuesList = new LinkedList<ContentValues>();
VEvent vEvent = (VEvent) component.getComponent(VEvent.VEVENT);
@ -823,7 +827,7 @@ public class EventFactory {
return valuesList;
}
protected static void addAttendee(String path, Calendar component, ContentValues attendeeValues)
public static void addAttendee(String path, Calendar component, ContentValues attendeeValues)
throws InvalidLocalComponentException
{
VEvent vEvent = (VEvent) component.getComponent(VEvent.VEVENT);
@ -840,14 +844,13 @@ public class EventFactory {
Integer status = attendeeValues.getAsInteger(CalendarContract.Attendees.ATTENDEE_STATUS);
if (StringUtils.isEmpty(email)) {
Log.e(TAG, "attendee email is null or empty");
throw new InvalidLocalComponentException("attendee email is null or empty",
CalDavConstants.CALDAV_NAMESPACE, path, getUid(vEvent));
Log.w(TAG, "attendee email is null or empty, not going to add anything");
return;
}
try {
Attendee attendee = new Attendee(new URI("mailto", email, null));
Attendee attendee = new Attendee(new URI("mailto", email, null));
ParameterList attendeeParams = attendee.getParameters();
attendeeParams.add(CuType.INDIVIDUAL);
@ -890,7 +893,7 @@ public class EventFactory {
}
}
protected static String [] getProjectionForReminder() {
public static String [] getProjectionForReminder() {
return new String[] {
CalendarContract.Reminders.EVENT_ID, // 00
CalendarContract.Reminders.MINUTES, // 01
@ -898,27 +901,23 @@ public class EventFactory {
};
}
protected static ContentValues getValuesForReminder(String path, Cursor cursor)
throws InvalidLocalComponentException
{
if (!cursor.isNull(0) && !cursor.isNull(1)) {
ContentValues values = new ContentValues(3);
public static Optional<ContentValues> getValuesForReminder(Cursor cursor) {
if (cursor.isNull(0) || cursor.isNull(1))
return Optional.absent();
values.put(CalendarContract.Reminders.EVENT_ID, cursor.getLong(0));
values.put(CalendarContract.Reminders.MINUTES, cursor.getInt(1));
values.put(CalendarContract.Reminders.METHOD, cursor.getInt(2));
return values;
}
ContentValues values = new ContentValues(3);
Log.e(TAG, "reminder event id or minutes is null");
throw new InvalidLocalComponentException("reminder event id or minutes is null",
CalDavConstants.CALDAV_NAMESPACE, path);
values.put(CalendarContract.Reminders.EVENT_ID, cursor.getLong(0));
values.put(CalendarContract.Reminders.MINUTES, cursor.getInt(1));
values.put(CalendarContract.Reminders.METHOD, cursor.getInt(2));
return Optional.of(values);
}
protected static List<ContentValues> getValuesForReminders(Calendar component) {
public static List<ContentValues> getValuesForReminders(Calendar component) {
List<ContentValues> valueList = new LinkedList<ContentValues>();
VEvent vEvent = (VEvent) component.getComponent(VEvent.VEVENT);
VToDo vToDo = (VToDo) component.getComponent(VToDo.VTODO);
VEvent vEvent = (VEvent) component.getComponent(VEvent.VEVENT);
VToDo vToDo = (VToDo) component.getComponent(VToDo.VTODO);
ComponentList vAlarms;
if (vEvent != null)
@ -936,7 +935,7 @@ public class EventFactory {
if (trigger != null && trigger.getDuration() != null) {
ContentValues values = new ContentValues();
values.put(CalendarContract.Reminders.MINUTES, (trigger.getDuration().getMinutes()));
values.put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT);
values.put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT);
valueList.add(values);
}
}
@ -946,33 +945,32 @@ public class EventFactory {
}
// TODO: can we support more alarm types?
protected static void addReminder(String path, Calendar component, ContentValues reminderValues)
throws InvalidLocalComponentException
{
public static void addReminder(Calendar component, ContentValues reminderValues) {
Integer minutes = reminderValues.getAsInteger(CalendarContract.Reminders.MINUTES);
if (minutes != null) {
VAlarm vAlarm = new VAlarm(new Dur(0, 0, -minutes, 0));
PropertyList alarmProps = vAlarm.getProperties();
alarmProps.add(Action.DISPLAY);
VEvent vEvent = (VEvent) component.getComponent(VEvent.VEVENT);
VToDo vToDo = (VToDo) component.getComponent(VEvent.VTODO);
if (vEvent != null && vEvent.getSummary() != null) {
alarmProps.add(new Description(vEvent.getSummary().getValue()));
vEvent.getAlarms().add(vAlarm);
}
else if (vToDo != null && vToDo.getSummary() != null) {
alarmProps.add(new Description(vToDo.getSummary().getValue()));
vToDo.getAlarms().add(vAlarm);
}
if (minutes == null) {
Log.w(TAG, "reminder minutes is null, nothing to add.");
return;
}
else {
Log.e(TAG, "reminder minutes is null");
throw new InvalidLocalComponentException("reminder minutes is null",
CalDavConstants.CALDAV_NAMESPACE, path, getUid(component));
VAlarm vAlarm = new VAlarm(new Dur(0, 0, -minutes, 0));
PropertyList alarmProps = vAlarm.getProperties();
alarmProps.add(Action.DISPLAY);
VEvent vEvent = (VEvent) component.getComponent(VEvent.VEVENT);
VToDo vToDo = (VToDo) component.getComponent(VEvent.VTODO);
if (vEvent != null) {
if (vEvent.getSummary() != null)
alarmProps.add(new Description(vEvent.getSummary().getValue()));
vEvent.getAlarms().add(vAlarm);
}
else if (vToDo != null) {
if (vToDo.getSummary() != null)
alarmProps.add(new Description(vToDo.getSummary().getValue()));
vToDo.getAlarms().add(vAlarm);
}
}
}

View File

@ -137,12 +137,12 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
@Override
protected String getColumnNameComponentUid() {
return CalendarContract.Events._SYNC_ID;
return EventFactory.COLUMN_NAME_EVENT_UID;
}
@Override
protected String getColumnNameComponentETag() {
return CalendarContract.Events.SYNC_DATA1;
return EventFactory.COLUMN_NAME_EVENT_ETAG;
}
@Override
@ -155,11 +155,6 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
return CalendarContract.Events.DELETED;
}
@Override
protected String getColumnNameQueuedForMigration() {
return CalendarContract.Events.SYNC_DATA3;
}
@Override
protected String getColumnNameAccountType() {
return CalendarContract.Events.ACCOUNT_TYPE;
@ -168,10 +163,9 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
@Override
public List<Long> getNewComponentIds() throws RemoteException {
final String[] PROJECTION = new String[]{getColumnNameComponentLocalId()};
final String SELECTION = "(" + getColumnNameComponentUid() + " IS NULL OR " +
CalendarContract.Events.SYNC_DATA2 + " > 0 OR " +
getColumnNameQueuedForMigration() + "=1) AND " +
getColumnNameCollectionLocalId() + "=" + localId;
final String SELECTION = "(" + getColumnNameComponentUid() + " IS NULL OR " +
EventFactory.COLUMN_NAME_COPIED_EVENT_ID + " > 0) AND " +
getColumnNameCollectionLocalId() + "=" + localId;
Cursor cursor = client.query(getUriForComponents(), PROJECTION, SELECTION, null, null);
List<Long> newIds = new LinkedList<Long>();
@ -191,11 +185,10 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
@Override
public boolean hasChanges() throws RemoteException {
final String[] PROJECTION = new String[]{};
final String SELECTION = "(" + getColumnNameComponentUid() + " IS NULL OR " +
getColumnNameDirty() + "=1 OR " +
getColumnNameDeleted() + "=1 OR " +
getColumnNameQueuedForMigration() + "=1 OR " +
CalendarContract.Events.SYNC_DATA2 + "> 0) AND " +
final String SELECTION = "(" + getColumnNameComponentUid() + " IS NULL OR " +
getColumnNameDirty() + "=1 OR " +
getColumnNameDeleted() + "=1 OR " +
EventFactory.COLUMN_NAME_COPIED_EVENT_ID + "> 0) AND " +
getColumnNameCollectionLocalId() + "=" + localId;
Cursor cursor = client.query(getUriForComponents(), PROJECTION, SELECTION, null, null);
@ -209,10 +202,10 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
public void cleanComponent(Long localId) {
Log.d(TAG, "cleanComponent() localId " + localId);
pendingOperations.add(ContentProviderOperation
operationQueue.queue(ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(getUriForComponents(), localId))
.withValue(getColumnNameDirty(), 0)
.withValue(CalendarContract.Events.SYNC_DATA2, null)
.withValue(EventFactory.COLUMN_NAME_COPIED_EVENT_ID, null)
.build());
}
@ -234,16 +227,18 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
}
public void setVisible(Boolean isVisible) {
pendingOperations.add(ContentProviderOperation.newUpdate(getCollectionUri())
operationQueue.queue(ContentProviderOperation.newUpdate(getCollectionUri())
.withValue(CalendarContract.Calendars.VISIBLE, isVisible ? 1 : 0)
.build());
}
@Override
public void setDisplayName(String displayName) {
pendingOperations.add(ContentProviderOperation.newUpdate(getCollectionUri())
.withValue(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, displayName)
.build());
operationQueue.queue(
ContentProviderOperation.newUpdate(getCollectionUri())
.withValue(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, displayName)
.build(),
displayName.getBytes().length);
}
public Optional<Integer> getColor() throws RemoteException {
@ -266,7 +261,7 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
}
public void setColor(int color) {
pendingOperations.add(ContentProviderOperation.newUpdate(getCollectionUri())
operationQueue.queue(ContentProviderOperation.newUpdate(getCollectionUri())
.withValue(CalendarContract.Calendars.CALENDAR_COLOR, color)
.build());
}
@ -290,9 +285,11 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
@Override
public void setCTag(String cTag) throws RemoteException {
pendingOperations.add(ContentProviderOperation.newUpdate(getCollectionUri())
.withValue(COLUMN_NAME_COLLECTION_C_TAG, cTag)
.build());
operationQueue.queue(
ContentProviderOperation.newUpdate(getCollectionUri())
.withValue(COLUMN_NAME_COLLECTION_C_TAG, cTag)
.build(),
cTag.getBytes().length);
}
public Optional<Calendar> getTimeZone() throws RemoteException {
@ -326,7 +323,7 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
VTimeZone vTimeZone = (VTimeZone) timezone.getComponent(VTimeZone.VTIMEZONE);
if (vTimeZone != null && vTimeZone.getTimeZoneId() != null) {
pendingOperations.add(ContentProviderOperation.newUpdate(getCollectionUri())
operationQueue.queue(ContentProviderOperation.newUpdate(getCollectionUri())
.withValue(CalendarContract.Calendars.CALENDAR_TIME_ZONE, vTimeZone.getTimeZoneId().getValue())
.build());
}
@ -352,7 +349,7 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
}
public void setOrder(Integer order) {
pendingOperations.add(ContentProviderOperation.newUpdate(getCollectionUri())
operationQueue.queue(ContentProviderOperation.newUpdate(getCollectionUri())
.withValue(COLUMN_NAME_COLLECTION_ORDER, order)
.build());
}
@ -389,8 +386,9 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
null);
while (cursor.moveToNext()) {
ContentValues reminderValues = EventFactory.getValuesForReminder(getPath(), cursor);
EventFactory.addReminder(getPath(), component, reminderValues);
Optional<ContentValues> reminderValues = EventFactory.getValuesForReminder(cursor);
if (reminderValues.isPresent())
EventFactory.addReminder(component, reminderValues.get());
}
cursor.close();
}
@ -501,30 +499,36 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
public int addComponent(ComponentETagPair<Calendar> component)
throws RemoteException, InvalidRemoteComponentException
{
ContentValues eventValues = EventFactory.getValuesForEvent(this, localId, component);
int event_op_index = pendingOperations.size();
int eventOpIndex = operationQueue.size();
pendingOperations.add(ContentProviderOperation.newInsert(getUriForComponents())
.withValues(eventValues)
.build());
ContentValues eventValues = EventFactory.getValuesForEvent(this, localId, component);
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForComponents())
.withValues(eventValues)
.build(),
512);
List<ContentValues> attendeeValues = EventFactory.getValuesForAttendees(component.getComponent());
for (ContentValues attendee : attendeeValues) {
pendingOperations.add(ContentProviderOperation.newInsert(getUriForAttendees())
.withValues(attendee)
.withValueBackReference(CalendarContract.Attendees.EVENT_ID, event_op_index)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForAttendees())
.withValues(attendee)
.withValueBackReference(CalendarContract.Attendees.EVENT_ID, eventOpIndex)
.build(),
256);
}
List<ContentValues> reminderValues = EventFactory.getValuesForReminders(component.getComponent());
for (ContentValues reminder : reminderValues) {
pendingOperations.add(ContentProviderOperation.newInsert(getUriForReminders())
.withValues(reminder)
.withValueBackReference(CalendarContract.Reminders.EVENT_ID, event_op_index)
.build());
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForReminders())
.withValues(reminder)
.withValueBackReference(CalendarContract.Reminders.EVENT_ID, eventOpIndex)
.build(),
256);
}
return pendingOperations.size() - event_op_index;
return operationQueue.size() - eventOpIndex;
}
@Override
@ -533,19 +537,19 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
final String[] SELECTION_ARGS = new String[]{remoteUId};
final Optional<Long> LOCAL_ID = getLocalIdForUid(remoteUId);
pendingOperations.add(ContentProviderOperation
operationQueue.queue(ContentProviderOperation
.newDelete(getUriForComponents())
.withSelection(SELECTION, SELECTION_ARGS)
.withYieldAllowed(true)
.build());
if (LOCAL_ID.isPresent()) {
pendingOperations.add(ContentProviderOperation
operationQueue.queue(ContentProviderOperation
.newDelete(ContentUris.withAppendedId(getUriForAttendees(), LOCAL_ID.get()))
.withYieldAllowed(true)
.build());
pendingOperations.add(ContentProviderOperation
operationQueue.queue(ContentProviderOperation
.newDelete(ContentUris.withAppendedId(getUriForReminders(), LOCAL_ID.get()))
.withYieldAllowed(true)
.build());
@ -620,7 +624,7 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
private Optional<String> getUidForCopiedEventLocalId(Long copiedEventId) throws RemoteException {
final String[] PROJECTION = new String[]{getColumnNameComponentUid()};
final String SELECTION = CalendarContract.Events.SYNC_DATA2 + "=" + copiedEventId;
final String SELECTION = EventFactory.COLUMN_NAME_COPIED_EVENT_ID + "=" + copiedEventId;
Cursor cursor = client.query(getUriForComponents(), PROJECTION, SELECTION, null, null);
if (cursor == null)
@ -637,10 +641,6 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
private void handleCorrectOrganizersAndAttendees(VEvent vEvent, Account toAccount)
throws InvalidLocalComponentException
{
Uid uid = vEvent.getUid();
if (uid != null)
uid.setValue(null);
Organizer oldOrganizer = vEvent.getOrganizer();
if (oldOrganizer != null)
vEvent.getProperties().remove(oldOrganizer);
@ -673,43 +673,40 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
CalendarCopiedListener listener,
boolean forceFull)
{
int operationSum = 0;
for (int operationCount : eventOperationCounts)
operationSum += operationCount;
if (toCollection.operationQueue.hasSpace() && !forceFull)
return false;
if (operationSum >= 100 || forceFull) {
try {
try {
int pendingCount = toCollection.pendingOperations.size();
int successCount = toCollection.commitPendingOperations();
int failCount = pendingCount - successCount;
int pendingCount = toCollection.operationQueue.size();
int successCount = toCollection.commitPendingOperations();
int failCount = pendingCount - successCount;
Log.d(TAG, pendingCount + " were pending " + successCount + " were committed");
Log.d(TAG, pendingCount + " were pending " + successCount + " were committed");
for (int operationCount : eventOperationCounts)
listener.onEventCopied(account, toCollection.getAccount(), localId);
int eventCount = 0;
while (eventCount++ < eventOperationCounts.size())
listener.onEventCopied(account, toCollection.getAccount(), localId);
if (failCount > 0)
Log.e(TAG, "failed to commit " + failCount + "" +
"operations but no idea which events they're from!");
if (failCount > 0)
Log.e(TAG, "failed to commit " + failCount + "" +
"operations but no idea which events they're from!");
} catch (OperationApplicationException e) {
} catch (OperationApplicationException e) {
for (int operationCount : eventOperationCounts)
listener.onEventCopyFailed(e, account, toCollection.getAccount(), localId);
toCollection.pendingOperations.clear();
int eventCount = 0;
while (eventCount++ < eventOperationCounts.size())
listener.onEventCopyFailed(e, account, toCollection.getAccount(), localId);
} catch (RemoteException e) {
} catch (RemoteException e) {
for (int operationCount : eventOperationCounts)
listener.onEventCopyFailed(e, account, toCollection.getAccount(), localId);
toCollection.pendingOperations.clear();
int eventCount = 0;
while (eventCount++ < eventOperationCounts.size())
listener.onEventCopyFailed(e, account, toCollection.getAccount(), localId);
}
return true;
}
return false;
return true;
}
private void handleCopyRecurrenceExceptions(Account toAccount,
@ -771,7 +768,7 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
}
}
if (toCollection.pendingOperations.size() > 0)
if (toCollection.operationQueue.size() > 0)
handleCommitPendingIfFull(toCollection, eventOperationCounts, listener, true);
}
@ -860,7 +857,7 @@ public class LocalEventCollection extends AbstractLocalComponentCollection<Calen
}
}
if (toCollection.get().pendingOperations.size() > 0)
if (toCollection.get().operationQueue.size() > 0)
handleCommitPendingIfFull(toCollection.get(), eventOperationCounts, listener, true);
handleCopyRecurrenceExceptions(toAccount, toCollection.get(), listener);

View File

@ -44,7 +44,8 @@
android:fontFamily="sans-serif-light"
android:text="@string/welcome_to_flock"/>
<TextView android:layout_width="wrap_content"
<TextView android:id="@+id/flock_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"

View File

@ -43,7 +43,8 @@
android:fontFamily="sans-serif-light"
android:text="@string/welcome_to_flock"/>
<TextView android:layout_width="wrap_content"
<TextView android:id="@+id/flock_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"

View File

@ -38,7 +38,8 @@
android:fontFamily="sans-serif-light"
android:text="@string/welcome_to_flock"/>
<TextView android:layout_width="wrap_content"
<TextView android:id="@+id/flock_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-light"

View File

@ -13,16 +13,6 @@
android:layout_marginRight="16dip"
android:layout_weight="1">
<EditText android:id="@+id/cipher_passphrase"
android:singleLine="true"
android:maxLines="1"
android:layout_width="match_parent"
android:layout_height="48dip"
android:inputType="textPassword"
android:layout_marginTop="8dip"
android:hint="@string/hint_current_password"
android:layout_marginBottom="32dp"/>
<EditText android:id="@+id/new_cipher_passphrase"
android:singleLine="true"
android:maxLines="1"

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ /**
~ * Copyright (C) Open 2015 Whisper Systems
~ *
~ * This program 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.
~ *
~ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
~ */
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingTop="8dp"
android:paddingRight="16dp"
android:paddingLeft="16dp">
<LinearLayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_gravity="center_horizontal"
android:src="@drawable/big_flock_icon"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/eol_paragraph_one"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:textAppearance="?android:textAppearanceMedium"
android:text="@string/eol_paragraph_two"/>
</LinearLayout>
</ScrollView>
<View android:background="?android:attr/dividerHorizontal"
android:layout_height="1dp"
android:layout_width="match_parent" />
<LinearLayout style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button android:id="@+id/button_export"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/export" />
</LinearLayout>
</LinearLayout>

View File

@ -30,49 +30,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/chose_a_username_for_your_account_and_a_strong_password_to_encrypt_your_data"/>
<EditText android:id="@+id/account_username"
android:layout_marginTop="8dp"
android:singleLine="true"
android:maxLines="1"
android:layout_width="match_parent"
android:layout_height="48dip"
android:inputType="textEmailAddress"
android:hint="@string/hint_username"/>
<EditText android:id="@+id/cipher_passphrase"
android:singleLine="true"
android:maxLines="1"
android:layout_width="match_parent"
android:layout_height="48dip"
android:layout_marginTop="4dp"
android:inputType="textPassword"
android:hint="@string/hint_password" />
<ProgressBar android:id="@+id/progress_password_strength"
android:layout_width="match_parent"
android:layout_height="4dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:visibility="invisible"
style="?android:attr/progressBarStyleHorizontal"/>
<EditText android:id="@+id/cipher_passphrase_repeat"
android:singleLine="true"
android:maxLines="1"
android:layout_width="match_parent"
android:layout_height="48dip"
android:inputType="textPassword"
android:hint="@string/hint_repeat_password" />
<ProgressBar android:id="@+id/progress_password_strength_repeat"
android:layout_width="match_parent"
android:layout_height="4dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:visibility="invisible"
style="?android:attr/progressBarStyleHorizontal"/>
android:text="@string/flock_is_shutting_down_permanently_account_registration_no_longer_allowed"/>
<LinearLayout
android:layout_width="match_parent"

View File

@ -1,21 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?>
<!--
~ Copyright (C) 2015 Open Whisper Systems
~
~ This program 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.
~
~ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="app_name">Flock</string>
<string name="at_flock_sync">\@flock</string>
@ -49,6 +32,7 @@
<string name="notification_tap_to_correct_encryption_password">Antippen zur Korrektur des Verschlüsselungspassworts.</string>
<string name="notification_flock_subscription_expired">Flock-Abonnement abgelaufen</string>
<string name="notification_tap_to_update_subscription">Antippen zur Aktualisierung des Abonnements.</string>
<string name="account_will_be_deleted_in_days_tap_to_update_subscription">Ihr Benutzerkonto wird in %1$d Tagen gelöscht. Antippen zur Aktualisierung des Abonnements.</string>
<!--status header stuff-->
<string name="status_header_status_good">Alles funktioniert!</string>
<string name="status_header_status_account_login_failed">Anmeldefehler</string>
@ -66,38 +50,38 @@
<!--SetupActivity, select service provider-->
<string name="title_chose_sync_service">Sychronisationsdienst auswählen</string>
<string name="flock_sync">Flock Sync</string>
<string name="privacy_and_terms_of_service"><![CDATA[<a href="https://whispersystems.org/flock/legal.html">Datenschutzerklärung und AGB</a>]]></string>
<string name="flock_sync_is_a_service_run_by_open_whisper_systems_available">Flock Sync ist ein Dienst von Open Whisper Systems, der für %.2f USD/Jahr zur Verfügung steht. Der Dienst kann 30 Tage ohne Angabe von Zahlungsinformationen getestet werden.</string>
<string name="privacy_and_terms_of_service"><![CDATA[<a href=\"https://whispersystems.org/flock/legal.html\">Datenschutzerklärung und AGB</a>]]></string>
<string name="flock_sync_is_a_service_run_by_open_whisper_systems_available">Flock Sync ist ein Dienst von Open Whisper Systems, der für %.2f USD/Jahr zur Verfügung steht. Der Dienst kann 30 Tage ohne Angabe von Zahlungsinformationen getestet werden. </string>
<string name="host_my_own">Eigener Server</string>
<string name="you_may_chose_to_run_and_configure_your_own_webdav_compliant_server">Sie können einen eigenen, WebDAV-kompatiblen Server als Synchronisationsdienst verwenden. Vor der Nutzung mit Flock wird der Server auf Kompatibilität überprüft.</string>
<!--SetupActivity, other server test-->
<string name="title_server_tests">Serverüberprüfung</string>
<string name="tests_not_yet_started">Bitte Überprüfung starten…</string>
<string name="tests_interrupted">Die Überprüfung wurde abgebrochen…</string>
<string name="tests_not_yet_started">Überprüfung steht aus...</string>
<string name="tests_interrupted">Überprüfung abgebrochen…</string>
<string name="stop_tests">Überprüfung beenden</string>
<string name="restart_tests">Erneut prüfen</string>
<string name="tests_completed_successfully">Die Überprüfung wurde erfolgreich beendet!</string>
<string name="tests_completed_successfully">Überprüfung erfolgreich abgeschlossen!</string>
<string name="test_dav_current_user_principal">DAV: Zugriffskontrolle</string>
<string name="test_error_carddav_current_user_principal">Keine Serverunterstützung für \"CardDAV: Well-Known URI\" oder \"DAV: Zugriffskontrolle\".</string>
<string name="test_error_caldav_current_user_principal">Keine Serverunterstützung für \"CalDAV: Well-Known URI\" oder \"DAV: Zugriffskontrolle\".</string>
<string name="test_carddav_addressbook_homeset">CardDAV: Adressbuchabfrage</string>
<string name="test_error_carddav_addressbook_homeset">Der Server unterstützt keine CardDAV-Adressbuchabfrage.</string>
<string name="test_error_carddav_addressbook_homeset">Server unterstützt keine CardDAV-Adressbuchabfrage.</string>
<string name="test_caldav_calendar_homeset">CalDAV: Kalenderabfrage</string>
<string name="test_error_caldav_calendar_homeset">Der Server unterstützt keine CalDAV-Kalenderabfrage.</string>
<string name="test_error_caldav_calendar_homeset">Server unterstützt keine CalDAV-Kalenderabfrage.</string>
<string name="test_caldav_create_and_delete_collections">CalDAV: Kalenderverwaltung</string>
<string name="test_error_caldav_create_and_delete_collections">Der Server unterstützt nicht das Erstellen und Löschen von Kalendern.</string>
<string name="test_error_caldav_create_and_delete_collections">Server unterstützt nicht das Erstellen und Löschen von Kalendern.</string>
<string name="test_caldav_create_and_edit_collection_properties">DAV: Eigenschaften</string>
<string name="test_error_caldav_create_and_edit_collection_properties">Der Server unterstützt nicht das Erstellen und Bearbeiten von DAV-Eigenschaften.</string>
<string name="test_error_caldav_create_and_edit_collection_properties">Server unterstützt nicht das Erstellen und Bearbeiten von DAV-Eigenschaften.</string>
<string name="test_carddav_create_and_delete_contacts">CardDAV: Kontaktverwaltung</string>
<string name="test_error_carddav_create_and_delete_contacts">Der Server unterstützt nicht das Erstellen und Löschen von Kontakten.</string>
<string name="test_error_carddav_create_and_delete_contacts">Server unterstützt nicht das Erstellen und Löschen von Kontakten.</string>
<string name="test_caldav_create_and_delete_events">CalDAV: Terminverwaltung</string>
<string name="test_error_caldav_create_and_delete_events">Der Server unterstützt nicht das Erstellen und Löschen von Kalenderterminen.</string>
<string name="test_error_caldav_create_and_delete_events">Server unterstützt nicht das Erstellen und Löschen von Kalenderterminen.</string>
<!--SetupActivity, setup account-->
<string name="title_import_account">Konto importieren</string>
<string name="title_register_account">Konto anlegen</string>
<string name="title_setup_account">Konto einrichten</string>
<string name="chose_a_username_for_your_account_and_a_strong_password_to_encrypt_your_data">Wählen Sie einen Benutzernamen und ein starkes Passwort für die Verschlüsselung Ihrer Benutzerdaten.</string>
<string name="do_you_have_a_flock_account">Haben Sie bereits ein Flock-Konto?</string>
<string name="title_import_account">Benutzerkonto importieren</string>
<string name="title_register_account">Benutzerkonto erstellen</string>
<string name="title_setup_account">Benutzerkonto einrichten</string>
<string name="chose_a_username_for_your_account_and_a_strong_password_to_encrypt_your_data">Wählen Sie einen Benutzernamen und ein starkes Passwort für die Verschlüsselung Ihrer Daten. Ein vergessenes Passwort kann nicht zurückgesetzt werden!</string>
<string name="do_you_have_a_flock_account">Haben Sie bereits ein Flock-Benutzerkonto?</string>
<string name="yes_log_me_in">Ja</string>
<string name="no_register_me">Nein</string>
<string name="text_setup_account_with_other">Geben Sie die URL und die Zugangsdaten für Ihren WebDAV-Host ein und wählen Sie dann ein Passwort für die Verschlüsselung.</string>
@ -108,8 +92,8 @@
<string name="hint_href_webdav_host">https://beispiel.de</string>
<string name="hint_encryption_password">Verschlüsselungspasswort</string>
<string name="hint_repeat_encryption_password">Verschlüsselungspasswort wiederholen</string>
<string name="registering_account">Konto wird angelegt...</string>
<string name="account_register_failed">Das Anlegen des Kontos ist fehlgeschlagen.</string>
<string name="registering_account">Benutzerkonto wird erstellt...</string>
<string name="account_register_failed">Kontoerstellung fehlgeschlagen.</string>
<string name="importing_contacts_and_calendars">Kontakte und Kalender werden importiert…</string>
<string name="account_import_completed_with_errors">Kontoimport mit Fehlern abgeschlossen.</string>
<string name="account_import_failed">Kontoimport fehlgeschlagen.</string>
@ -134,12 +118,12 @@
<string name="hint_display_name">Anzeigename</string>
<string name="heading_display_name">Anzeigename</string>
<string name="heading_sync">Synchronisation</string>
<string name="display_name_cannot_be_empty">Der Anzeigename darf nicht leer sein.</string>
<string name="display_name_cannot_be_empty">Anzeigename darf nicht leer sein.</string>
<!--My addressbooks-->
<string name="title_my_addressbooks">Adressbücher</string>
<string name="title_edit_selected_addressbooks">Ausgewählte Adressbücher bearbeiten</string>
<string name="title_addressbook_properties">Adressbucheigenschaften</string>
<string name="addressbooks_selected">Ausgewählte Adressbücher</string>
<string name="addressbooks_selected">Adressbücher ausgewählt</string>
<string name="are_you_sure_you_want_to_delete_selected_addressbooks">Sollen die ausgewählten Adressbücher wirklich gelöscht werden?</string>
<string name="you_cannot_edit_addressbooks_that_have_not_been_synced">Unsynchronisierte Adressbücher können nicht bearbeitet werden.</string>
<!--My calendars-->
@ -147,31 +131,31 @@
<string name="title_edit_selected_calendars">Ausgewählte Kalender bearbeiten</string>
<string name="title_calendar_properties">Kalendereigenschaften</string>
<string name="title_delete_selected_calendars_dialog">Ausgewählte Kalender löschen?</string>
<string name="calendars_selected">Ausgewählte Kalender</string>
<string name="calendars_selected">Kalender ausgewählt</string>
<string name="are_you_sure_you_want_to_delete_selected_calendars">Sollen die ausgewählten Kalender wirklich gelöscht werden?</string>
<string name="you_cannot_edit_calendars_that_have_not_been_synced">Unsychronisierte Kalender können nicht bearbeitet werden.</string>
<!--PreferencesActivity-->
<string name="title_flock_settings">Flock-Einstellungen</string>
<string name="preference_group_sync">Synchronisation</string>
<string name="preference_title_sync_frequency">Synchronisationshäufigkeit</string>
<string name="preference_title_sync_on_content_change">Synchronisation bei Veränderung</string>
<string name="preference_description_sync_on_content_change">Kontakte und Kalender synchronisieren sobald diese verändert werden.</string>
<string name="preference_title_sync_on_content_change">Synchronisation bei Änderung</string>
<string name="preference_description_sync_on_content_change">Kontakte und Kalender bei Veränderung sofort synchronisieren</string>
<string name="preference_title_sync_now">Jetzt synchronisieren</string>
<string name="sync_requested_will_begin_when_possible">Die Synchronisation wird sobald wie möglich beginnen</string>
<string name="sync_requested_will_begin_when_possible">Die Synchronisation beginnt so bald wie möglich</string>
<string name="preference_group_contacts">Kontakte</string>
<string name="preference_group_calendars">Kalender</string>
<string name="preference_title_default_calendar_color">Standardkalenderfarbe</string>
<string name="minutes">Minuten</string>
<string name="new_calendars_will_be">Neue Kalender werden %1$s sein</string>
<string name="preference_group_account">Konto</string>
<string name="preference_group_account">Benutzerkonto</string>
<!--ChangeEncryptionPasswordActivity-->
<string name="title_change_encryption_password">Verschlüsselungspasswort ändern</string>
<string name="button_save_password">Passwort speichern</string>
<string name="hint_new_password">Neues Passwort</string>
<string name="hint_new_password_repeat">Neues Passwort wiederholen</string>
<string name="encryption_password_saved">Verschlüsselungspasswort wurde gespeichert!</string>
<string name="encryption_password_saved">Verschlüsselungspasswort gespeichert!</string>
<string name="updating_encryption_secrets">Verschlüsselungsmerkmale werden aktualisiert…</string>
<string name="password_change_failed">Der Passwortwechsel ist fehlgeschlagen.</string>
<string name="password_change_failed">Passwortänderung fehlgeschlagen.</string>
<!--ManageSubscriptionActivity-->
<string name="title_manage_subscription">Abonnement verwalten</string>
<string name="days_remain">verbleibende Tage</string>
@ -184,12 +168,12 @@
<string name="cancel_subscription">Abonnement kündigen</string>
<string name="are_you_sure_you_want_to_cancel_your_flock_subscription">Sind Sie sicher, dass Sie Ihr Abonnement für Flock kündigen möchten?</string>
<string name="your_subscription_is_active_well_charge_you_again_in_days">Ihr Abonnement ist aktiv. Die nächste Abbuchung wird in %1$d Tagen stattfinden.</string>
<string name="google_play_error_please_update_google_play_services">Fehler bei Google Play. Bitte aktualisieren Sie die Google Play Services.</string>
<string name="purchase_failed_check_your_google_wallet">Kauf fehlgeschlagen. Bitte überprüfen Sie Ihr Google Wallet</string>
<string name="google_play_error_please_update_google_play_services">Fehler bei Google Play. Bitte Google Play Services aktualisieren.</string>
<string name="purchase_failed_check_your_google_wallet">Kauf fehlgeschlagen. Bitte Google Wallet überprüfen.</string>
<!--EditAutoRenewActivity-->
<string name="usd_per_year">%.2f USD/Jahr</string>
<string name="save_card">Karte speichern</string>
<string name="card_verified_and_saved">Die Karte wurde überprüft und gespeichert!</string>
<string name="card_verified_and_saved">Karte überprüft und gespeichert!</string>
<string name="subscription_canceled">Abonnement gekündigt</string>
<string name="hint_card_cvc">CVC</string>
<string name="hint_card_expiration">MM/JJ</string>
@ -197,18 +181,18 @@
<!--SendBitcoinActivity-->
<string name="title_send_bitcoin">Bitcoin senden</string>
<string name="mBTC_to">mBTC an</string>
<string name="address_copied_to_clipboard">Adresse wurde in der Zwischenablage gespeichert.</string>
<string name="bitcoin_received">Bitcoins wurden empfangen!</string>
<string name="minutes_until_address_expires">Minuten bis die Adresse abläuft</string>
<string name="address_copied_to_clipboard">Adresse in der Zwischenablage gespeichert.</string>
<string name="bitcoin_received">Bitcoins empfangen!</string>
<string name="minutes_until_address_expires">Minuten bis zum Ablauf der Adresse</string>
<!--DeleteAccountActivity-->
<string name="title_unregister_account">Konto löschen</string>
<string name="if_you_chose_to_unregister_your_account_all_flock">Bei Löschung des Kontos werden alle Flock-Kontakte und -Kalender sowie die Zahlungshistorie unwiderruflich gelöscht.</string>
<string name="are_you_sure_you_want_to_unregister_your_account">Möchten Sie das Konto wirklich löschen?</string>
<string name="account_has_been_unregistered">Das Konto wurde gelöscht.</string>
<string name="title_unregister_account">Benutzerkonto löschen</string>
<string name="if_you_chose_to_unregister_your_account_all_flock">Bei Löschung des Benutzerkontos werden alle Flock-Kontakte und -Kalender sowie der Zahlungsverlauf unwiderruflich gelöscht.</string>
<string name="are_you_sure_you_want_to_unregister_your_account">Möchten Sie das Benutzerkonto wirklich löschen?</string>
<string name="account_has_been_unregistered">Benutzerkonto gelöscht.</string>
<!--DeleteAllContactsActivity-->
<string name="title_delete_all_contacts">Alle Kontakte löschen</string>
<string name="all_contacts_have_been_deleted">Es wurden alle Kontakte gelöscht.</string>
<string name="if_you_chose_to_delete_your_flock_contacts">Bei Löschung von Flock-Kontakten werden diese auch von allen anderen Geräten mit diesem Flock-Konto gelöscht.</string>
<string name="all_contacts_have_been_deleted">Alle Kontakte gelöscht.</string>
<string name="if_you_chose_to_delete_your_flock_contacts">Bei Löschung von Flock-Kontakten werden diese auch von allen anderen mit diesem Flock-Konto verbundenen Geräten gelöscht.</string>
<string name="are_you_sure_you_want_to_delete_all_flock_contacts">Möchten Sie wirklich alle Flock-Kontakte löschen?</string>
<!--CorrectPasswordActivity-->
<string name="title_correct_sync_password">Synchronisationspasswort korrigieren</string>
@ -217,7 +201,7 @@
<!--CorrectEncryptionPasswordActivity-->
<string name="title_correct_encryption_password">Verschlüsselungspasswort korrigieren</string>
<string name="encryption_password_is_invalid_please_correct_your_encryption_password">Das Verschlüsselungspasswort ist ungültig. Bitte korrigieren Sie es.</string>
<string name="password_corrected">Das Passwort wurde aktualisiert!</string>
<string name="password_corrected">Passwort aktualisiert!</string>
<!--SendDebugLogActivity-->
<string name="send_debug_log">Diagnoseprotokoll senden</string>
<string name="flock_debug_log">Diagnoseprotokoll</string>
@ -225,7 +209,7 @@
<string name="dont_ask_again">Nicht erneut fragen</string>
<string name="send_log">Protokoll senden</string>
<!--ErrorToaster messages-->
<string name="error_login_unauthorized">Die Anmeldung ist fehlgeschlagen. Ungültige Zugangsdaten</string>
<string name="error_login_unauthorized">Anmeldung fehlgeschlagen. Ungültige Zugangsdaten.</string>
<string name="error_registration_api_server_error">Fehler bei unseren Registrierungsservern. Entschuldigung.</string>
<string name="error_registration_api_client_error">API-Fehler bei der Registrierung. Bitte ein Diagnoseprotokoll senden.</string>
<string name="error_our_dav_server_error">Fehler bei unseren Synchronisationsservern. Entschuldigung.</string>
@ -238,21 +222,21 @@
<string name="error_invalid_encryption_password">Ungültiges Verschlüsselungspasswort.</string>
<string name="error_invalid_mac_error">Entschlüsselungsfehler. Bitte ein Diagnoseprotokoll senden.</string>
<string name="error_unknown_crypto_error">Unbekannter Kryptografiefehler Bitte ein Diagnoseprotokoll senden.</string>
<string name="error_url_cannot_be_empty">Die URL darf nicht leer sein</string>
<string name="error_username_empty">Der Benutzername darf nicht leer sein.</string>
<string name="error_spaces_in_username">Der Benutzername darf keine Leerzeichen enthalten.</string>
<string name="error_username_illegal">Der Benutzername muss ein \"@\" enthalten und aus mindestens 5 Zeichen bestehen.</string>
<string name="error_passwords_do_not_match">Die Passwörter stimmen nicht überein.</string>
<string name="error_password_too_short">Das Passwort ist zu kurz!</string>
<string name="error_username_already_registered">Der Benutzername ist bereits registriert. Bitte einen anderen wählen.</string>
<string name="error_card_number_could_not_be_verified">Die Kreditkartennummer konnte nicht überprüft werden</string>
<string name="error_card_expiration_could_not_be_verified">Das Ablaufdatum der Kreditkarte konnte nicht überprüft werden</string>
<string name="error_card_security_code_could_not_be_verified">Der Sicherheitscode der Kreditkarte konnte nicht überprüft werden</string>
<string name="error_url_cannot_be_empty">URL darf nicht leer sein.</string>
<string name="error_username_empty">Benutzername darf nicht leer sein.</string>
<string name="error_spaces_in_username">Benutzername darf keine Leerzeichen enthalten.</string>
<string name="error_username_illegal">Benutzername muss ein \"@\" enthalten und aus mindestens 5 Zeichen bestehen.</string>
<string name="error_passwords_do_not_match">Passwörter stimmen nicht überein.</string>
<string name="error_password_too_short">Passwort ist zu kurz!</string>
<string name="error_username_already_registered">Benutzername ist bereits registriert. Bitte einen anderen wählen.</string>
<string name="error_card_number_could_not_be_verified">Kreditkartennummer konnte nicht überprüft werden</string>
<string name="error_card_expiration_could_not_be_verified">Ablaufdatum der Kreditkarte konnte nicht überprüft werden</string>
<string name="error_card_security_code_could_not_be_verified">Sicherheitscode der Kreditkarte konnte nicht überprüft werden</string>
<string name="error_your_card_could_not_be_verified">Ihre Kreditkarte konnte nicht überprüft werden</string>
<string name="error_stripe_api_error">Unbekannter Fehler bei der Zahlung. Bitte erneut versuchen oder ein Diagnoseprotokoll senden.</string>
<string name="error_android_account_manager_error">Unerwarteter Fehler in Androids Kontenverwaltung!</string>
<!--misc errors-->
<string name="error_multiple_accounts_not_allowed">Flock-Nutzer können nur ein Konto haben.</string>
<string name="error_no_account_is_registered">Mit diesem Gerät ist kein Konto verbunden.</string>
<string name="error_password_unavailable_please_login">Das Passwort ist nicht verfügbar. Bitte anmelden.</string>
<string name="error_multiple_accounts_not_allowed">Flock-Nutzer können nur ein Benutzerkonto haben.</string>
<string name="error_no_account_is_registered">Dieses Gerät ist mit keinem Benutzerkonto verbunden.</string>
<string name="error_password_unavailable_please_login">Passwort ist nicht verfügbar. Bitte anmelden.</string>
</resources>

View File

@ -0,0 +1,242 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">Flock</string>
<string name="at_flock_sync">\@flock</string>
<string name="support_email_address">rhodey@anhonesteffort.org</string>
<string name="begin">Aloita</string>
<string name="next">Seuraava</string>
<string name="skip">Ohita</string>
<string name="previous">Edellinen</string>
<string name="ok">OK</string>
<string name="cancel">Peruuta</string>
<string name="thanks">kiitos!</string>
<string name="yes">Kyllä</string>
<string name="no">Ei</string>
<string name="days">päivää</string>
<string name="addressbook">osoitekirja</string>
<string name="display_name_missing">Nimi puuttuu</string>
<!--Notifications-->
<string name="notification_contact_import">Tuo yhteystietoja</string>
<string name="notification_importing_contacts">Tuodaan yhteystietoja.</string>
<string name="notification_importing_contacts_from">Tuodaan yhteystietoja tililtä %1$s</string>
<string name="notification_import_complete_copied_contacts">Tuonti valmis, kopioitiin %1$d yhteystietoa.</string>
<string name="notification_import_complete_copied_contacts_failed">Tuonti valmis, kopioitiin %1$d yhteystietoa, %2$d epäonnistui.</string>
<string name="notification_calendar_import">Tuo kalenteri</string>
<string name="notification_importing_calendars">Tuodaan kalentereita.</string>
<string name="notification_importing_events_from">Tuodaan tapahtumia kalenterista %1$s</string>
<string name="notification_import_complete_copied_events">Tuonti valmis, kopioitiin %1$d tapahtumaa.</string>
<string name="notification_import_complete_copied_events_failed">Tuonti valmis, kopioitiin %1$d tapahtumaa, %2$d epäonnistui.</string>
<string name="notification_flock_login_error">Flock sisäänkirjautumisvirhe</string>
<string name="notification_tap_to_correct_password">Näpäytä korjataksesi salasanan.</string>
<string name="notification_flock_encryption_error">Flock salausvirhe</string>
<string name="notification_tap_to_correct_encryption_password">Näpäytä korjataksesi salausavaimen.</string>
<string name="notification_flock_subscription_expired">Flock-tilaus vanhentunut</string>
<string name="notification_tap_to_update_subscription">Näpäytä uudistaaksesi tilaus.</string>
<string name="account_will_be_deleted_in_days_tap_to_update_subscription">Tilisi poistetaan %1$d päivän kuluttua, näpäytä uudistaaksesi tilaus.</string>
<!--status header stuff-->
<string name="status_header_status_good">Kaikki on kunnossa!</string>
<string name="status_header_status_account_login_failed">Sisäänkirjautuminen epäonnistui</string>
<string name="status_header_status_encryption_password_incorrect">Salausavain on väärä</string>
<string name="status_header_status_sync_disabled">Android sync on pois päältä!</string>
<string name="status_header_status_auto_renew_error">Tilauksen automaattinen uusiminen epäonnistui</string>
<string name="status_header_status_our_sync_service_error">Synkronointi väliaikaisesti poissa käytöstä...</string>
<string name="status_header_status_their_sync_service_error">WebDAV-palvelimella tapahtui virhe</string>
<string name="status_header_status_registration_service_error">Rekisteröintipalvelu on väliaikaisesti poissa käytöstä...</string>
<string name="status_header_sync_time">Viimeksi synkroinoitu %1$s</string>
<string name="status_header_sync_in_progress">Synkronoidaan...</string>
<!--SetupActivity, intro-->
<string name="welcome_to_flock">Tervetuloa Flock:n!</string>
<string name="flock_syncs_your_contacts_and_calendars_between_multiple_devices">Flock synkronoi yhteystietosi ja kalenterisi useiden laitteiden välillä pitäen samalla huolta yksityisyydestäsi vahvan salauksen avulla.</string>
<!--SetupActivity, select service provider-->
<string name="title_chose_sync_service">Valitse synkronointipalvelu</string>
<string name="flock_sync">Flock Synkronointi</string>
<string name="privacy_and_terms_of_service"><![CDATA[<a href=\"https://whispersystems.org/flock/legal.html\">Yksityisyys ja käyttöehdot</a>]]></string>
<string name="flock_sync_is_a_service_run_by_open_whisper_systems_available">Flock Synkronointipalvelun tarjoaa Open Whisper Systems hintaan $%.2f/vuosi. Kokeile 30 päivää ilmaiseksi, ilman maksutietoja.</string>
<string name="host_my_own">Ylläpidä omaa palvelinta</string>
<string name="you_may_chose_to_run_and_configure_your_own_webdav_compliant_server">Voit ajaa omaa WebDAV-yhteensopivaa palvelinta ja konfiguroida sen synkronointia varten. Serverin on läpäistävä sarja testejä ennen kuin sitä voidaan käyttää Flock:n kanssa.</string>
<!--SetupActivity, other server test-->
<string name="title_server_tests">Palvelintestit</string>
<string name="tests_not_yet_started">Testien ajoa ei ole aloitettu...</string>
<string name="tests_interrupted">Testien ajo keskeytetty...</string>
<string name="stop_tests">Keskeytä testien ajo</string>
<string name="restart_tests">Uudelleenkäynnistä testien ajo</string>
<string name="tests_completed_successfully">Testit ajettu onnistuneesti!</string>
<string name="test_dav_current_user_principal">DAV: current-user-principal</string>
<string name="test_error_carddav_current_user_principal">Palvelimella ei ole tukea CardDAV well-known URI:lle tai DAV: current-user-principal:lle.</string>
<string name="test_error_caldav_current_user_principal">Palvelimella ei ole tukea CalDAV well-known URI:lle tai DAV: current-user-principal:lle.</string>
<string name="test_carddav_addressbook_homeset">CardDAV: addressbook-homeset</string>
<string name="test_error_carddav_addressbook_homeset">Palvelimella ei ole tukea CardDAV: addressbook-homeset:lle.</string>
<string name="test_caldav_calendar_homeset">CalDAV: calendar-homeset</string>
<string name="test_error_caldav_calendar_homeset">Palvelimella ei ole tukea CalDAV: calendar-homeset:lle.</string>
<string name="test_caldav_create_and_delete_collections">DAV: luo ja poista kokoelma</string>
<string name="test_error_caldav_create_and_delete_collections">Palvelin ei tue kalenterikokoelmien luomista ja poistamista.</string>
<string name="test_caldav_create_and_edit_collection_properties">DAV: muokkaa kokoelman ominaisuuksia</string>
<string name="test_error_caldav_create_and_edit_collection_properties">Palvelin ei tue DAV-kokoelmien ominaisuuksien luomista.</string>
<string name="test_carddav_create_and_delete_contacts">CardDAV: luo ja poista yhteystietoja</string>
<string name="test_error_carddav_create_and_delete_contacts">Palvelin ei tue yhteystietojen luomista ja poistamista.</string>
<string name="test_caldav_create_and_delete_events">CalDAV: luo ja poista tapahtumia</string>
<string name="test_error_caldav_create_and_delete_events">Palvelin ei tue kalenteritapahtumien luomista ja poistamista.</string>
<!--SetupActivity, setup account-->
<string name="title_import_account">Tuo tili</string>
<string name="title_register_account">Rekisteröi tili</string>
<string name="title_setup_account">Määrittele tilin asetukset</string>
<string name="chose_a_username_for_your_account_and_a_strong_password_to_encrypt_your_data">Valitse käyttäjänimi ja vahva salasana, jolla tietosi salataan. Jos unohdat salasanasi, tietojasi ei voida palauttaa.</string>
<string name="do_you_have_a_flock_account">Onko sinulla Flock-tili?</string>
<string name="yes_log_me_in">Kyllä, kirjaudun sisään</string>
<string name="no_register_me">Ei, luon tilin</string>
<string name="text_setup_account_with_other">Aseta URL ja kirjautumistietosi WebDAV-palvelimelle. Tämän jälkeen valitse erillinen sanasala salausta varten.</string>
<string name="hint_username">käyttäjänimi</string>
<string name="hint_password">salasana</string>
<string name="hint_current_password">nykyinen salasana</string>
<string name="hint_repeat_password">toista salasana</string>
<string name="hint_href_webdav_host">https://example.com</string>
<string name="hint_encryption_password">salausavain</string>
<string name="hint_repeat_encryption_password">toista salausavain</string>
<string name="registering_account">rekisteröidään tiliä...</string>
<string name="account_register_failed">Tilin rekisteröiminen epäonnistui.</string>
<string name="importing_contacts_and_calendars">Tuodaan yhteystietoja ja kalentereja...</string>
<string name="account_import_completed_with_errors">Tilin tuominen valmistui virheillä.</string>
<string name="account_import_failed">Tilin tuominen epäonnistui.</string>
<string name="importing_encryption_secrets">Tuodaan salattuja tietoja...</string>
<!--SetupActivity, import-->
<string name="button_import">Tuo</string>
<string name="local_storage">Lokaali varasto</string>
<!--SetupActivity, import contacts-->
<string name="select_accounts_to_import_contacts_from">Valitse tilit joista yhteystietoja tuodaan</string>
<string name="contacts">yhteystiedot</string>
<string name="title_import_contacts">Tuo yhteystietoja</string>
<string name="started_background_import_of_contacts">Aloitettiin tuonti tausta-ajona %1$d yhteystiedolle</string>
<!--SetupActivity, import calendars-->
<string name="select_calendars_to_import">Valitse tuotavat kalenterit</string>
<string name="title_import_calendars">Tuo kalentereja</string>
<string name="started_background_import_of_calendars">Aloitettiin tuonti tausta-ajona %1$d kalenterille</string>
<!--SetupActivity, select remote addressbook and calendars-->
<string name="select_a_single_remote_addressbook_in_which_to_store">Valitse yksi osoitekirja Flock-yhteystietojen tallennusta varten.</string>
<string name="select_the_calendars_you_would_like_to_sync_with_this_device">Valitse kalenterit, jotka haluat synkronoida tälle laitteelle.</string>
<string name="setup_complete">Asennus valmis!</string>
<!--My calendars and my addressbooks...-->
<string name="hint_display_name">nimi</string>
<string name="heading_display_name">nimi</string>
<string name="heading_sync">Synkronointi</string>
<string name="display_name_cannot_be_empty">Nimi ei voi olla tyhjä.</string>
<!--My addressbooks-->
<string name="title_my_addressbooks">Osoitekirjani</string>
<string name="title_edit_selected_addressbooks">Muokkaa valittuja osoitekirjoja</string>
<string name="title_addressbook_properties">Osoitekirjan ominaisuudet</string>
<string name="addressbooks_selected">osoitekirjaa valittu</string>
<string name="are_you_sure_you_want_to_delete_selected_addressbooks">Oletko varma että haluat poistaa valitut osoitekirjat?</string>
<string name="you_cannot_edit_addressbooks_that_have_not_been_synced">Et voi muokata synkronoimattomia osoitekirjoja.</string>
<!--My calendars-->
<string name="title_my_calendars">Kalenterini</string>
<string name="title_edit_selected_calendars">Muokkaa valittuja kalentereja</string>
<string name="title_calendar_properties">Kalenterin ominaisuudet</string>
<string name="title_delete_selected_calendars_dialog">Poista valitut kalenterit?</string>
<string name="calendars_selected">kalenteria valittu</string>
<string name="are_you_sure_you_want_to_delete_selected_calendars">Oletko varma että haluat poistaa valitut kalenterit?</string>
<string name="you_cannot_edit_calendars_that_have_not_been_synced">Et voi muokata synkronoimattomia kalentereja.</string>
<!--PreferencesActivity-->
<string name="title_flock_settings">Flock asetukset</string>
<string name="preference_group_sync">Synkronointi</string>
<string name="preference_title_sync_frequency">Synkronointitiheys</string>
<string name="preference_title_sync_on_content_change">Synkronoi muuttuessa</string>
<string name="preference_description_sync_on_content_change">Synkronoi yhteystiedot ja kalenterit niiden muuttuessa</string>
<string name="preference_title_sync_now">Synkronoi nyt</string>
<string name="sync_requested_will_begin_when_possible">Synkronointipyyntö lähetetty, aloitetaan heti kuin mahdollista</string>
<string name="preference_group_contacts">Yhteystiedot</string>
<string name="preference_group_calendars">Kalenterit</string>
<string name="preference_title_default_calendar_color">Kalenterin oletusväri</string>
<string name="minutes">minuuttia</string>
<string name="new_calendars_will_be">Uudet kalenterit ovat %1$s</string>
<string name="preference_group_account">Tili</string>
<!--ChangeEncryptionPasswordActivity-->
<string name="title_change_encryption_password">Muuta salausavainta</string>
<string name="button_save_password">Tallenna salasana</string>
<string name="hint_new_password">uusi salasana</string>
<string name="hint_new_password_repeat">toista uusi salasana</string>
<string name="encryption_password_saved">Salausavain tallennettu!</string>
<string name="updating_encryption_secrets">päivitetään salausavaimia...</string>
<string name="password_change_failed">salasanan vaihtaminen epäonnstui.</string>
<!--ManageSubscriptionActivity-->
<string name="title_manage_subscription">Hallitse tilausta</string>
<string name="days_remain">päivää jäljellä</string>
<string name="google_play">Google Play</string>
<string name="credit_card">Luottokortti</string>
<string name="when_subscribed_your_card_will_be_charged_once_per_year">Tilauksen ollessa voimassa luottokorttiasi veloitetaan kerran vuodessa. Voit peruttaa tilauksen koska tahansa.</string>
<string name="subscription_is_active">Tilaus on voimassa</string>
<string name="payment_failed">Maksaminen epäonnistui</string>
<string name="start_subscription">Aloita tilaus</string>
<string name="cancel_subscription">Peruuta tilaus</string>
<string name="are_you_sure_you_want_to_cancel_your_flock_subscription">Oletko varma, että haluat peruuttaa Flock-tilausen?</string>
<string name="your_subscription_is_active_well_charge_you_again_in_days">Tilauksesi on voimassa, laskutamme sinua uudelleen %1$d päivän kuluttua.</string>
<string name="google_play_error_please_update_google_play_services">Google Play-virhe, ole hyvä ja päivitä Google Play Services.</string>
<string name="purchase_failed_check_your_google_wallet">osto epäonnistui, tarkista Google Wallet</string>
<!--EditAutoRenewActivity-->
<string name="usd_per_year">$%.2f/vuosi</string>
<string name="save_card">Tallenna kortti</string>
<string name="card_verified_and_saved">Luottokortti varmistettu ja tallennettu!</string>
<string name="subscription_canceled">Tilaus peruutettu</string>
<string name="hint_card_cvc">CVC</string>
<string name="hint_card_expiration">KK/VV</string>
<string name="hint_card_number">5555 5555 5555 5555</string>
<!--SendBitcoinActivity-->
<string name="title_send_bitcoin">Lähetä bitcoineja</string>
<string name="mBTC_to">mBTC osoitteeseen</string>
<string name="address_copied_to_clipboard">Osoite kopiotu leikepöydälle.</string>
<string name="bitcoin_received">Bitcoin vastaanotettu!</string>
<string name="minutes_until_address_expires">minuuttia kunnes osoite vanhentuu</string>
<!--DeleteAccountActivity-->
<string name="title_unregister_account">Poista tili</string>
<string name="if_you_chose_to_unregister_your_account_all_flock">Jos päätit poistaa tilisi, kaikki Flock yhteystiedot, kalenterit sekä tilaushistoria poistetaan pysyvästi.</string>
<string name="are_you_sure_you_want_to_unregister_your_account">Oletko varma, että haluat poistaa tilisi?</string>
<string name="account_has_been_unregistered">Tili poistettu.</string>
<!--DeleteAllContactsActivity-->
<string name="title_delete_all_contacts">Poista kaikki yhteystiedot</string>
<string name="all_contacts_have_been_deleted">Kaikki yhteystiedot on poistettu.</string>
<string name="if_you_chose_to_delete_your_flock_contacts">Jos päätit poistaa Flock-yhteystietosi, ne tullaan poistamaan kaikilta laitteita jotka käyttävät tätä Flock-tiliä.</string>
<string name="are_you_sure_you_want_to_delete_all_flock_contacts">Oletko varma, että haluat poistaa kaikki Flock-yhteystietosi?</string>
<!--CorrectPasswordActivity-->
<string name="title_correct_sync_password">Oikea synkronointisalasana</string>
<string name="unable_to_login_to_sync_service_please_correct_your_password">Sisäänkirjautuminen epäonnistui, ole hyvä ja korjaa salasanasi.</string>
<string name="login_successful">Sisäänkirjautuminen onnistui!</string>
<!--CorrectEncryptionPasswordActivity-->
<string name="title_correct_encryption_password">Oikea salausavain</string>
<string name="encryption_password_is_invalid_please_correct_your_encryption_password">Salausavain on väärä, ole hyvä ja korjaa salausavaimesi.</string>
<string name="password_corrected">Salasana korjattu!</string>
<!--SendDebugLogActivity-->
<string name="send_debug_log">Lähetä debug-loki</string>
<string name="flock_debug_log">Flock debug-loki</string>
<string name="something_strange_happened_send_us_your_debug_log">Jotain kummallista on tapahtunut, lähetä meille debug-lokisi ja me korjaamme vian.</string>
<string name="dont_ask_again">Älä kysy uudestaan</string>
<string name="send_log">Lähetä loki</string>
<!--ErrorToaster messages-->
<string name="error_login_unauthorized">Sisäänkirjautuminen epäonnistui. Virheellinen käyttäjätunnus tai salasana</string>
<string name="error_registration_api_server_error">Rekisteröintipalvelimella on ongelmia, olemme pahoillamme.</string>
<string name="error_registration_api_client_error">Asiakasohjelman rekisteröinnin API-virhe, ole hyvä ja lähetä debug-lokit.</string>
<string name="error_our_dav_server_error">Synkronointipalvelimella on onglemia, olemme pahoillamme.</string>
<string name="error_their_dav_server_error">Synkronointipalvelin käyttäytyy kummallisesti, tarkista debug-lokit.</string>
<string name="error_dav_client_error">Virhe lokaalissa varastossa, ole hyvä ja lähetä debug-lokit.</string>
<string name="error_connection_error">Yhteysvirhe, ole hyvä ja tarkista internetyhteys.</string>
<string name="error_our_certificate_validation">Salaamaton synkronointiyhteys, ole hyvä ja lähetä debug-lokit.</string>
<string name="error_their_certificate_validation">Sertifikaatin varmentaminen epäonnistui, tarkista debug-lokit.</string>
<string name="error_unknown_io_error">Tuntematon IO-virhe, ole hyvä ja lähetä debug-lokit.</string>
<string name="error_invalid_encryption_password">Salausavain on väärä.</string>
<string name="error_invalid_mac_error">Salauksen purussa tapahtui virhe, ole hyvä ja lähetä debug-lokit.</string>
<string name="error_unknown_crypto_error">Tuntematon kryptografinen virhe, ole hyvä ja lähetä debug-lokit.</string>
<string name="error_url_cannot_be_empty">URL ei voi olla tyhjä</string>
<string name="error_username_empty">Käyttäjätunnus ei voi olla tyhjä.</string>
<string name="error_spaces_in_username">Käyttäjätunnus ei saa sisältää välilyöntejä.</string>
<string name="error_username_illegal">Käyttäjätunnuksen tulee sisältää \'@\' ja olla ainakin 5 merkkiä pitkä.</string>
<string name="error_passwords_do_not_match">Salasanat eivät täsmää.</string>
<string name="error_password_too_short">Salasana on liian lyhyt!</string>
<string name="error_username_already_registered">Käyttäjätunnus on jo käytössä, yritä toista.</string>
<string name="error_card_number_could_not_be_verified">Luottokortin numeroa ei voitu varmentaa</string>
<string name="error_card_expiration_could_not_be_verified">Luottokortin viimeistä voimassaolopäivää ei voitu varmentaa</string>
<string name="error_card_security_code_could_not_be_verified">Luottokortin CVC-koodia ei voitu varmentaa</string>
<string name="error_your_card_could_not_be_verified">Luottokorttiasi ei voitu varmentaa</string>
<string name="error_stripe_api_error">Tuntematon maksutapahtumavirhe, yritä uudestaan tai lähetä debug-lokit.</string>
<string name="error_android_account_manager_error">Odottamaton Android AccountManager-virhe!</string>
<!--misc errors-->
<string name="error_multiple_accounts_not_allowed">Flock-käyttäjällä ei voi olla useita tilejä.</string>
<string name="error_no_account_is_registered">Tiliä ei ole rekisteröity tälle laitteelle.</string>
<string name="error_password_unavailable_please_login">Salasana ei ole saatavilla, ole hyvä ja kirjaudu sisään.</string>
</resources>

View File

@ -49,6 +49,7 @@
<string name="notification_tap_to_correct_encryption_password">タップして暗号化パスワードを修正してください。</string>
<string name="notification_flock_subscription_expired">Flock のサブスクリプションが期限切れです</string>
<string name="notification_tap_to_update_subscription">タップしてサブスクリプションを更新してください。</string>
<string name="account_will_be_deleted_in_days_tap_to_update_subscription">あなたのアカウントは %1$d 日で削除されます。タップすると、サブスクリプションを更新します。</string>
<!--status header stuff-->
<string name="status_header_status_good">すべて動作しています!</string>
<string name="status_header_status_account_login_failed">アカウントのログインに失敗しました</string>
@ -67,7 +68,7 @@
<string name="title_chose_sync_service">同期サービスを選択</string>
<string name="flock_sync">Flock 同期</string>
<string name="privacy_and_terms_of_service"><![CDATA[<a href="https://whispersystems.org/flock/legal.html">プライバシーと利用規約</a>]]></string>
<string name="flock_sync_is_a_service_run_by_open_whisper_systems_available">Flock 同期は Open Whisper Systems により実行される $%.2f/年 で利用できるサービスです。無償で30日間評価してください。支払情報は必要ありません。</string>
<string name="flock_sync_is_a_service_run_by_open_whisper_systems_available">Flock 同期は Open Whisper Systems が運営するサービスで $%.2f/年 で利用できます。 30 日間無償で評価してください。支払情報は必要ありません。</string>
<string name="host_my_own">自分独自のホスト</string>
<string name="you_may_chose_to_run_and_configure_your_own_webdav_compliant_server">あなたは同期サービスとして独自に WebDAV 準拠のサーバーを実行および構成することを選択することができます。Flock で使用する前に、サーバーが一連のテストに合格する必要があります。</string>
<!--SetupActivity, other server test-->
@ -96,7 +97,7 @@
<string name="title_import_account">アカウントのインポート</string>
<string name="title_register_account">アカウントの登録</string>
<string name="title_setup_account">アカウントのセットアップ</string>
<string name="chose_a_username_for_your_account_and_a_strong_password_to_encrypt_your_data">あなたのアカウントのユーザー名と、あなたのデータを暗号化するための強力なパスワードを選択してください。</string>
<string name="chose_a_username_for_your_account_and_a_strong_password_to_encrypt_your_data">データを暗号化するため、ユーザー名と強力なパスワードを選択してください。パスワードを忘れた場合はリセットできません</string>
<string name="do_you_have_a_flock_account">Flock アカウントをお持ちですか?</string>
<string name="yes_log_me_in">はい、ログインします</string>
<string name="no_register_me">いいえ、登録します</string>

View File

@ -0,0 +1,242 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">Flock</string>
<string name="at_flock_sync">\@flock</string>
<string name="support_email_address">rhodey@anhonesteffort.org</string>
<string name="begin">Начало</string>
<string name="next">Далее</string>
<string name="skip">Пропустить</string>
<string name="previous">Назад</string>
<string name="ok">OK</string>
<string name="cancel">Отмена</string>
<string name="thanks">спасибо!</string>
<string name="yes">Да</string>
<string name="no">Нет</string>
<string name="days">дней</string>
<string name="addressbook">адресная книга</string>
<string name="display_name_missing">Отображаемое имя отсутствует</string>
<!--Notifications-->
<string name="notification_contact_import">Импорт контактов</string>
<string name="notification_importing_contacts">Импортируются контакты.</string>
<string name="notification_importing_contacts_from">Импортируются контакты из %1$s</string>
<string name="notification_import_complete_copied_contacts">Импорт завершен, скопировано контактов: %1$d.</string>
<string name="notification_import_complete_copied_contacts_failed">Импорт завершен, скопировано контактов: %1$d, неудачно: %2$d.</string>
<string name="notification_calendar_import">Импорт календаря</string>
<string name="notification_importing_calendars">Импортируются календари.</string>
<string name="notification_importing_events_from">Импортируются события из %1$s</string>
<string name="notification_import_complete_copied_events">Импорт завершен, скопировано событий: %1$d.</string>
<string name="notification_import_complete_copied_events_failed">Импорт завершен, скопировано событий: %1$d, неудачно: %2$d.</string>
<string name="notification_flock_login_error">Ошибка входа Flock</string>
<string name="notification_tap_to_correct_password">Коснитесь, чтобы исправить пароль.</string>
<string name="notification_flock_encryption_error">Ошибка шифрования Flock</string>
<string name="notification_tap_to_correct_encryption_password">Коснитесь, чтобы исправить пароль шифрования.</string>
<string name="notification_flock_subscription_expired">Подписка Flock истекла</string>
<string name="notification_tap_to_update_subscription">Коснитесь, чтобы продлить подписку.</string>
<string name="account_will_be_deleted_in_days_tap_to_update_subscription">Ваш аккаунт будет удален через %1$d дней, коснитесь для обновления подписки.</string>
<!--status header stuff-->
<string name="status_header_status_good">Все работает!</string>
<string name="status_header_status_account_login_failed">Не удалось войти в аккаунт</string>
<string name="status_header_status_encryption_password_incorrect">Неверный пароль шифрования</string>
<string name="status_header_status_sync_disabled">Синхронизация Android отключена!</string>
<string name="status_header_status_auto_renew_error">Ошибка автопродления подписки</string>
<string name="status_header_status_our_sync_service_error">Синхронизация временно недоступна…</string>
<string name="status_header_status_their_sync_service_error">WebDAV-сервер содержит ошибки</string>
<string name="status_header_status_registration_service_error">Сервис регистрации временно недоступен…</string>
<string name="status_header_sync_time">Последняя синхронизация в %1$s</string>
<string name="status_header_sync_in_progress">Производится синхронизация…</string>
<!--SetupActivity, intro-->
<string name="welcome_to_flock">Добро пожаловать в Flock!</string>
<string name="flock_syncs_your_contacts_and_calendars_between_multiple_devices">Flock синхронизирует Ваши контакты и календари между разными устройствами, защишая Вашу приватность с помощью стойкой криптографии.</string>
<!--SetupActivity, select service provider-->
<string name="title_chose_sync_service">Выберите сервис синхронизации</string>
<string name="flock_sync">Flock Sync</string>
<string name="privacy_and_terms_of_service"><![CDATA[<a href=\"https://whispersystems.org/flock/legal.html\">Приватность и Условия обслуживания</a>]]></string>
<string name="flock_sync_is_a_service_run_by_open_whisper_systems_available">Flock Sync - это сервис компании Open Whisper Systems, доступный за $%.2f/год. Испытайте бесплатно в течение 30 дней, информация об оплате не требуется. </string>
<string name="host_my_own">Использовать собственный</string>
<string name="you_may_chose_to_run_and_configure_your_own_webdav_compliant_server">Вы можете выбрать свой собственный WebDAV-совместимый сервер в качестве сервиса синхронизации. Перед использованием в Flock сервер должен пройти серию тестов.</string>
<!--SetupActivity, other server test-->
<string name="title_server_tests">Тесты сервера</string>
<string name="tests_not_yet_started">Тесты еще не запущены…</string>
<string name="tests_interrupted">Тесты прерваны…</string>
<string name="stop_tests">Остановить тесты</string>
<string name="restart_tests">Перезапустить тесты</string>
<string name="tests_completed_successfully">Тесты успешно завершены!</string>
<string name="test_dav_current_user_principal">DAV: current-user-principal</string>
<string name="test_error_carddav_current_user_principal">Сервер не поддерживает CardDAV well-known URI или DAV: current-user-principal.</string>
<string name="test_error_caldav_current_user_principal">Сервер не поддерживает CalDAV well-known URI или DAV: current-user-principal.</string>
<string name="test_carddav_addressbook_homeset">CardDAV: addressbook-homeset</string>
<string name="test_error_carddav_addressbook_homeset">Сервер не поддерживает CardDAV: addressbook-homeset.</string>
<string name="test_caldav_calendar_homeset">CalDAV: calendar-homeset</string>
<string name="test_error_caldav_calendar_homeset">Сервер не поддерживает CalDAV: calendar-homeset.</string>
<string name="test_caldav_create_and_delete_collections">DAV: создание и удаление коллекции</string>
<string name="test_error_caldav_create_and_delete_collections">Сервер не поддерживает создание и удаление коллекции календарей.</string>
<string name="test_caldav_create_and_edit_collection_properties">DAV: редактирование свойств коллекции</string>
<string name="test_error_caldav_create_and_edit_collection_properties">Сервер не поддерживает создание свойств коллекции.</string>
<string name="test_carddav_create_and_delete_contacts">CardDAV: создание и удаление контактов</string>
<string name="test_error_carddav_create_and_delete_contacts">Сервер не поддерживает создание и удаление контактов.</string>
<string name="test_caldav_create_and_delete_events">CalDAV: создание и удаление событий</string>
<string name="test_error_caldav_create_and_delete_events">Сервер не поддерживает создание и удаление событий календарей.</string>
<!--SetupActivity, setup account-->
<string name="title_import_account">Импортировать аккаунт</string>
<string name="title_register_account">Зарегистрировать аккаунт</string>
<string name="title_setup_account">Настроить аккаунт</string>
<string name="chose_a_username_for_your_account_and_a_strong_password_to_encrypt_your_data">Выберите имя пользователя для вашего аккаунта и строгий пароль для шифрования ваших данных. Если Вы забудете пароль, его невозможно будет восстановить.</string>
<string name="do_you_have_a_flock_account">У Вас есть аккаунт Flock?</string>
<string name="yes_log_me_in">Да, авторизуйте меня</string>
<string name="no_register_me">Нет, зарегистрируйте меня</string>
<string name="text_setup_account_with_other">Введите URL и учетные данные Вашего WebDAV-хоста, затем выберите отдельный пароль для шифрования. </string>
<string name="hint_username">имя пользователя</string>
<string name="hint_password">пароль</string>
<string name="hint_current_password">текущий пароль</string>
<string name="hint_repeat_password">повторите пароль</string>
<string name="hint_href_webdav_host">https://example.com</string>
<string name="hint_encryption_password">пароль шифрования</string>
<string name="hint_repeat_encryption_password">повторите пароль шифрования</string>
<string name="registering_account">регистрация аккаунта…</string>
<string name="account_register_failed">Не удалось зарегистрировать аккаунт.</string>
<string name="importing_contacts_and_calendars">Импорт контактов и календарей…</string>
<string name="account_import_completed_with_errors">Импорт аккаунта завершен с ошибками.</string>
<string name="account_import_failed">Не удалось импортировать аккаунт.</string>
<string name="importing_encryption_secrets">Импорт ключей шифрования…</string>
<!--SetupActivity, import-->
<string name="button_import">Импорт</string>
<string name="local_storage">Локальное хранилище</string>
<!--SetupActivity, import contacts-->
<string name="select_accounts_to_import_contacts_from">Выберите аккаунты для импорта контактов </string>
<string name="contacts">контакты</string>
<string name="title_import_contacts">Импорт контактов</string>
<string name="started_background_import_of_contacts">Начат фоновый импорт контактов %1$d</string>
<!--SetupActivity, import calendars-->
<string name="select_calendars_to_import">Выберите календари для импорта </string>
<string name="title_import_calendars">Импорт календарей</string>
<string name="started_background_import_of_calendars">Начат фоновый импорт календарей %1$d</string>
<!--SetupActivity, select remote addressbook and calendars-->
<string name="select_a_single_remote_addressbook_in_which_to_store">Выберите едиственную удаленную адресную книгу для хранения контактов Flock.</string>
<string name="select_the_calendars_you_would_like_to_sync_with_this_device">Выберите календари, которые Вы бы хотели синронизировать с этим устройством. </string>
<string name="setup_complete">Настройка завершена!</string>
<!--My calendars and my addressbooks...-->
<string name="hint_display_name">отображаемое имя</string>
<string name="heading_display_name">Отображаемое имя</string>
<string name="heading_sync">Синхронизация</string>
<string name="display_name_cannot_be_empty">Отображаемое имя не может быть пустым.</string>
<!--My addressbooks-->
<string name="title_my_addressbooks">Мои адресные книги</string>
<string name="title_edit_selected_addressbooks">Редактировать выбранные адресные книги</string>
<string name="title_addressbook_properties">Параметры адресных книг</string>
<string name="addressbooks_selected">ресные книги выбраны</string>
<string name="are_you_sure_you_want_to_delete_selected_addressbooks">Вы действительно хотите удалить выбранные адресные книги? </string>
<string name="you_cannot_edit_addressbooks_that_have_not_been_synced">Вы не можете редактировать несинхронизированные адресные книги. </string>
<!--My calendars-->
<string name="title_my_calendars">Мои календари</string>
<string name="title_edit_selected_calendars">Редактировать выбранные календари</string>
<string name="title_calendar_properties">Параметры календарей</string>
<string name="title_delete_selected_calendars_dialog">Удалить выбранные календари?</string>
<string name="calendars_selected">календари выбраны</string>
<string name="are_you_sure_you_want_to_delete_selected_calendars">Вы действительно хотите удалить выбранные календари? </string>
<string name="you_cannot_edit_calendars_that_have_not_been_synced">Вы не можете редактировать несинхронизированные календари. </string>
<!--PreferencesActivity-->
<string name="title_flock_settings">Настройки Flock</string>
<string name="preference_group_sync">Синхронизация</string>
<string name="preference_title_sync_frequency">Частота синхронизации</string>
<string name="preference_title_sync_on_content_change">Синхронизировать при изменении</string>
<string name="preference_description_sync_on_content_change">Синхронизировать контакты и календари как только они изменяются</string>
<string name="preference_title_sync_now">Синхронизировать сейчас</string>
<string name="sync_requested_will_begin_when_possible">Запрошена синхронизация, начнется при возможности</string>
<string name="preference_group_contacts">Контакты</string>
<string name="preference_group_calendars">Календари</string>
<string name="preference_title_default_calendar_color">Цвет календаря по умолчанию</string>
<string name="minutes">минут</string>
<string name="new_calendars_will_be">Новые календари будут %1$s</string>
<string name="preference_group_account">Аккаунт</string>
<!--ChangeEncryptionPasswordActivity-->
<string name="title_change_encryption_password">Изменить пароль шифрования</string>
<string name="button_save_password">Сохранить пароль</string>
<string name="hint_new_password">новый пароль</string>
<string name="hint_new_password_repeat">повторить новый пароль</string>
<string name="encryption_password_saved">Пароль шифрования сохранен!</string>
<string name="updating_encryption_secrets">обновление ключей шифрования…</string>
<string name="password_change_failed">не удалось сменить пароль.</string>
<!--ManageSubscriptionActivity-->
<string name="title_manage_subscription">Управление подпиской</string>
<string name="days_remain">дней осталось</string>
<string name="google_play">Google Play</string>
<string name="credit_card">Банковская карта</string>
<string name="when_subscribed_your_card_will_be_charged_once_per_year">При подписке оплата будет списываться с Вашей карты раз в год. Вы можете отменить это в любое время.</string>
<string name="subscription_is_active">Подписка активна</string>
<string name="payment_failed">Сбой оплаты</string>
<string name="start_subscription">Начать подписку</string>
<string name="cancel_subscription">Отменить подписку</string>
<string name="are_you_sure_you_want_to_cancel_your_flock_subscription">Вы уверены, что хотите отменить Вашу подписку Flock?</string>
<string name="your_subscription_is_active_well_charge_you_again_in_days">Ваша подписка активна, мы спишем деньги вновь через %1$d дней.</string>
<string name="google_play_error_please_update_google_play_services">Ошибка Google Play, пожалуйста, обновите Службы Google Play.</string>
<string name="purchase_failed_check_your_google_wallet">ошибка покупки, проверьте Ваш Кошелек Google</string>
<!--EditAutoRenewActivity-->
<string name="usd_per_year">$%.2f/год</string>
<string name="save_card">Сохранить карту</string>
<string name="card_verified_and_saved">Карта проверена и сохранена!</string>
<string name="subscription_canceled">Подписка отменена</string>
<string name="hint_card_cvc">CVC</string>
<string name="hint_card_expiration">MM/YY</string>
<string name="hint_card_number">5555 5555 5555 5555</string>
<!--SendBitcoinActivity-->
<string name="title_send_bitcoin">Отправить bitcoin</string>
<string name="mBTC_to">mBTC на</string>
<string name="address_copied_to_clipboard">Адрес скопирован в буфер обмена.</string>
<string name="bitcoin_received">Bitcoin получены!</string>
<string name="minutes_until_address_expires">минут до истечения адреса</string>
<!--DeleteAccountActivity-->
<string name="title_unregister_account">Отменить регистрацию</string>
<string name="if_you_chose_to_unregister_your_account_all_flock">Если Вы выберите отмену регистрации, все контакты, календари Flock, а также история подписок будут полностью удалены. </string>
<string name="are_you_sure_you_want_to_unregister_your_account">Вы действительно хотите отменить регистрацию аккаунта? </string>
<string name="account_has_been_unregistered">Регистрация аккаунта отменена.</string>
<!--DeleteAllContactsActivity-->
<string name="title_delete_all_contacts">Удалить все контакты</string>
<string name="all_contacts_have_been_deleted">Все контакты были удалены.</string>
<string name="if_you_chose_to_delete_your_flock_contacts">Если Вы выберите удаление контактов Flock, они будут удалены со всех устройств, использующих этот аккаунт Flock.</string>
<string name="are_you_sure_you_want_to_delete_all_flock_contacts">Вы действительно хотите удалить все контакты Flock?</string>
<!--CorrectPasswordActivity-->
<string name="title_correct_sync_password">Исправление пароля синхронизации</string>
<string name="unable_to_login_to_sync_service_please_correct_your_password">Невозможно войти в службу синхронизации, пожалуйста, исправьте пароль. </string>
<string name="login_successful">Успешный вход!</string>
<!--CorrectEncryptionPasswordActivity-->
<string name="title_correct_encryption_password">Исправление пароля шифрования</string>
<string name="encryption_password_is_invalid_please_correct_your_encryption_password">Неверный пароль шифрования, пожалуйста, исправьте пароль. </string>
<string name="password_corrected">Пароль исправлен!</string>
<!--SendDebugLogActivity-->
<string name="send_debug_log">Отправить журнал отладки</string>
<string name="flock_debug_log">Журнал отладки Flock</string>
<string name="something_strange_happened_send_us_your_debug_log">Случилось что-то странной, отправьте нам журнал отладки, и мы исправим это.</string>
<string name="dont_ask_again">Больше не спрашивать</string>
<string name="send_log">Отправить журнал</string>
<!--ErrorToaster messages-->
<string name="error_login_unauthorized">Не удалось выполнить вход. Неверное имя или пароль</string>
<string name="error_registration_api_server_error">Извините, есть проблемы с нашими серверами регистрации.</string>
<string name="error_registration_api_client_error">Ошибка API клиента регистрации, пожалуйста, отправьте журналы отладки.</string>
<string name="error_our_dav_server_error">Извините, есть проблемы с нашими серверами синхронизации.</string>
<string name="error_their_dav_server_error">Ваш сервер синхронизации не работает как ожидается, проверьте журналы отладки.</string>
<string name="error_dav_client_error">Ошибка локального хранилища, пожалуйста, отправьте журналы отладки.</string>
<string name="error_connection_error">Ошибка соединения, пожалуйста, проверьте соединение с Интернетом.</string>
<string name="error_our_certificate_validation">Незащищенное соединение синхронизации, пожалуйста, отправьте журналы отладки.</string>
<string name="error_their_certificate_validation">Ошибка проверки сертификата, проверьте журналы отладки.</string>
<string name="error_unknown_io_error">Неизвестная ошибка ввода-вывода, пожалуйста, отправьте журналы отладки.</string>
<string name="error_invalid_encryption_password">Неверный пароль шифрования.</string>
<string name="error_invalid_mac_error">Ошибка расшифровки, пожалуйста, отправьте журналы отладки.</string>
<string name="error_unknown_crypto_error">Неизвестная криптографическая ошибка, пожалуйста, отправьте журналы отладки.</string>
<string name="error_url_cannot_be_empty">Url не может быть пуст</string>
<string name="error_username_empty">Имя пользователя не должно быть пустым.</string>
<string name="error_spaces_in_username">Имя пользователя не должно содержать пробелы.</string>
<string name="error_username_illegal">Имя пользователя должно содержать \'@\' и состоять не менее, чем из 5 символов.</string>
<string name="error_passwords_do_not_match">Пароли не совпадают.</string>
<string name="error_password_too_short">Пароль слишком короткий!</string>
<string name="error_username_already_registered">Пользователь уже зарегистрирован, попробуйте другое имя.</string>
<string name="error_card_number_could_not_be_verified">Номер карты не может быть проверен</string>
<string name="error_card_expiration_could_not_be_verified">Срок действия карты не может быть проверен</string>
<string name="error_card_security_code_could_not_be_verified">Код безопасности карты не может быть проверен</string>
<string name="error_your_card_could_not_be_verified">Ваша карта не может быть проверена</string>
<string name="error_stripe_api_error">Неизвестная ошибка обработки платежа, попробуйте еще раз или отправьте журналы отладки.</string>
<string name="error_android_account_manager_error">Неожиданная ошибка Android AccountManager!</string>
<!--misc errors-->
<string name="error_multiple_accounts_not_allowed">Пользователи Flock не могут иметь нескольких аккаунтов.</string>
<string name="error_no_account_is_registered">Нет аккаунтов, зарегистрированных с данным устройством.</string>
<string name="error_password_unavailable_please_login">Пароль недоступен, пожалуйста, войдите.</string>
</resources>

View File

@ -0,0 +1,242 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">Flock</string>
<string name="at_flock_sync">\@flock</string>
<string name="support_email_address">rhodey@anhonesteffort.org</string>
<string name="begin">Börja</string>
<string name="next">Nästa</string>
<string name="skip">Hoppa över</string>
<string name="previous">Föregående</string>
<string name="ok">OK</string>
<string name="cancel">Avbryt</string>
<string name="thanks">tack!</string>
<string name="yes">Ja</string>
<string name="no">Nej</string>
<string name="days">dagar</string>
<string name="addressbook">adressbok</string>
<string name="display_name_missing">Visningsnamn saknas</string>
<!--Notifications-->
<string name="notification_contact_import">Importera kontakter</string>
<string name="notification_importing_contacts">Importerar kontakter.</string>
<string name="notification_importing_contacts_from">Importerar kontakter från %1$s</string>
<string name="notification_import_complete_copied_contacts">Import färdig, kopierade %1$d kontakter.</string>
<string name="notification_import_complete_copied_contacts_failed">Import färdig, kopierade %1$d kontakter, %2$d misslyckades.</string>
<string name="notification_calendar_import">Importera kalender</string>
<string name="notification_importing_calendars">Importerar kalendrar.</string>
<string name="notification_importing_events_from">Importerar händelser från %1$s</string>
<string name="notification_import_complete_copied_events">Import färdig, kopierade %1$d händelser.</string>
<string name="notification_import_complete_copied_events_failed">Import färdig, kopierade %1$d händelser, %2$d misslyckades.</string>
<string name="notification_flock_login_error">Flock inloggningsfel</string>
<string name="notification_tap_to_correct_password">Tryck för att ange lösenord.</string>
<string name="notification_flock_encryption_error">Flock krypteringsfel</string>
<string name="notification_tap_to_correct_encryption_password">Tryck för att ange krypteringslösenord.</string>
<string name="notification_flock_subscription_expired">Flock-prenumeration utgången</string>
<string name="notification_tap_to_update_subscription">Tryck för att uppdatera prenumeration.</string>
<string name="account_will_be_deleted_in_days_tap_to_update_subscription">Ditt konto kommer att tas bort om %1$d dagar, klicka för att uppdatera prenumeration.</string>
<!--status header stuff-->
<string name="status_header_status_good">Allting fungerar!</string>
<string name="status_header_status_account_login_failed">Kontoinloggning misslyckades</string>
<string name="status_header_status_encryption_password_incorrect">Krypteringslösenord felaktigt</string>
<string name="status_header_status_sync_disabled">Android synk inaktiverad!</string>
<string name="status_header_status_auto_renew_error">Fel med automatisk förnyelse av prenumeration</string>
<string name="status_header_status_our_sync_service_error">Synk tillfälligt otillgänglig...</string>
<string name="status_header_status_their_sync_service_error">Fel i WebDAB-servern</string>
<string name="status_header_status_registration_service_error">Registreringstjänsten tillfälligt otillgänglig...</string>
<string name="status_header_sync_time">Synkroniserades senast den %1$s</string>
<string name="status_header_sync_in_progress">Synk pågår...</string>
<!--SetupActivity, intro-->
<string name="welcome_to_flock">Välkommen till Flock!</string>
<string name="flock_syncs_your_contacts_and_calendars_between_multiple_devices">Flock synkroniserar dina kontakter och kalendrar mellan flera enheter och skyddar samtidigt din integritet med stark kryptering.</string>
<!--SetupActivity, select service provider-->
<string name="title_chose_sync_service">Välj synktjänst</string>
<string name="flock_sync">Flock Synk</string>
<string name="privacy_and_terms_of_service"><![CDATA[<a href=\"https://whispersystems.org/flock/legal.html\">Integritet och villkor för tjänsten</a>]]></string>
<string name="flock_sync_is_a_service_run_by_open_whisper_systems_available">Flock Synk är en tjänst från Open Whispter Systems tillgänglig för $%.2f/år. Utvärdera kostnadsfritt i 30 dagar, ingen betalningsinformation krävs.</string>
<string name="host_my_own">Använd min egen server</string>
<string name="you_may_chose_to_run_and_configure_your_own_webdav_compliant_server">Du kan välja att köra och konfigurera din egen WebDAV-kompaitbla server som synkroniseringstjänst. Innan servern används med Flock måste den klara en serie av tester.</string>
<!--SetupActivity, other server test-->
<string name="title_server_tests">Servertester</string>
<string name="tests_not_yet_started">Tester har inte startats ännu...</string>
<string name="tests_interrupted">Tester avbrutna...</string>
<string name="stop_tests">Stoppa tester</string>
<string name="restart_tests">Starta tester</string>
<string name="tests_completed_successfully">Testerna lyckades!</string>
<string name="test_dav_current_user_principal">DAV: current-user-principal</string>
<string name="test_error_carddav_current_user_principal">Servern stöder inte CardDAV well-known URI eller DAV: current-user-principal.</string>
<string name="test_error_caldav_current_user_principal">Servern stöder inte CalDAV well-known URI eller DAV: current-user-principal.</string>
<string name="test_carddav_addressbook_homeset">CardDAV: addressbook-homeset</string>
<string name="test_error_carddav_addressbook_homeset">Servern stöder inte CardDAV: addressbook-homeset.</string>
<string name="test_caldav_calendar_homeset">CalDAV: calendar-homeset</string>
<string name="test_error_caldav_calendar_homeset">Servern stöder inte CalDAV: calendar-homeset.</string>
<string name="test_caldav_create_and_delete_collections">DAV: skapa och radera samling</string>
<string name="test_error_caldav_create_and_delete_collections">Servern stöder inte att skapa och radera kalendersamlingar.</string>
<string name="test_caldav_create_and_edit_collection_properties">DAV: redigera samlingsegenskaper</string>
<string name="test_error_caldav_create_and_edit_collection_properties">Servern stöder inte skapande av DAV samlingsegenskaper.</string>
<string name="test_carddav_create_and_delete_contacts">CardDAV: skapa och radera kontakter</string>
<string name="test_error_carddav_create_and_delete_contacts">Servern stöder inte skapande och borttagning av kontakter.</string>
<string name="test_caldav_create_and_delete_events">CalDAV: skapa och ta bort händelser</string>
<string name="test_error_caldav_create_and_delete_events">Servern stöder inte skapande och borttagning av kalenderhändelser.</string>
<!--SetupActivity, setup account-->
<string name="title_import_account">Importera konto</string>
<string name="title_register_account">Registrera konto</string>
<string name="title_setup_account">Konfigurera konto</string>
<string name="chose_a_username_for_your_account_and_a_strong_password_to_encrypt_your_data">Välj ett användarnamn och ett starkt lösenord att kryptera din data med. Om du glömmer ditt lösenord kan det inte återställas.</string>
<string name="do_you_have_a_flock_account">Har du ett Flockkonto?</string>
<string name="yes_log_me_in">Ja, logga in mig</string>
<string name="no_register_me">Nej, registrera mig</string>
<string name="text_setup_account_with_other">Ange URL och uppgifter till din WebDAV-server och välj sen ett separat lösenord för kryptering.</string>
<string name="hint_username">användarnamn</string>
<string name="hint_password">lösenord</string>
<string name="hint_current_password">nuvarande lösenord</string>
<string name="hint_repeat_password">upprepa lösenord</string>
<string name="hint_href_webdav_host">https://example.com</string>
<string name="hint_encryption_password">krypteringslösenord</string>
<string name="hint_repeat_encryption_password">upprepa krypteringslösenord</string>
<string name="registering_account">registrera konto...</string>
<string name="account_register_failed">Kontoregistrering misslyckades.</string>
<string name="importing_contacts_and_calendars">Importerar kontakter och kalendrar...</string>
<string name="account_import_completed_with_errors">Import av konton avslutades med fel.</string>
<string name="account_import_failed">Import av konto misslyckades.</string>
<string name="importing_encryption_secrets">Importerar krypteringshemligheter...</string>
<!--SetupActivity, import-->
<string name="button_import">Importera</string>
<string name="local_storage">Lokal lagring</string>
<!--SetupActivity, import contacts-->
<string name="select_accounts_to_import_contacts_from">Välj konton att importera kontakter från</string>
<string name="contacts">kontakter</string>
<string name="title_import_contacts">Importera kontakter</string>
<string name="started_background_import_of_contacts">Påbörjade bakgrundsimport av %1$d kontakter</string>
<!--SetupActivity, import calendars-->
<string name="select_calendars_to_import">Välj kalendrar att importera</string>
<string name="title_import_calendars">Importera kalendrar</string>
<string name="started_background_import_of_calendars">Påbörjade bakgrundsimport av %1$d kalendrar</string>
<!--SetupActivity, select remote addressbook and calendars-->
<string name="select_a_single_remote_addressbook_in_which_to_store">Välj en enskild fjärradressbok att lagra Flock-kontakter i.</string>
<string name="select_the_calendars_you_would_like_to_sync_with_this_device">Välj de kalendrar du vill synka med den här enheten.</string>
<string name="setup_complete">Installation färdig!</string>
<!--My calendars and my addressbooks...-->
<string name="hint_display_name">visningsnamn</string>
<string name="heading_display_name">Visningsnamn</string>
<string name="heading_sync">Synk</string>
<string name="display_name_cannot_be_empty">Visningsnamn kan inte vara tomt.</string>
<!--My addressbooks-->
<string name="title_my_addressbooks">Mina adressböcker</string>
<string name="title_edit_selected_addressbooks">Redigera valda adressböcker</string>
<string name="title_addressbook_properties">Egenskaper för adressbok</string>
<string name="addressbooks_selected">valda adressböcker</string>
<string name="are_you_sure_you_want_to_delete_selected_addressbooks">Är det säkert att du vill radera valda adressböcker?</string>
<string name="you_cannot_edit_addressbooks_that_have_not_been_synced">Du kan inte redigera adressböcker som inte har synkroniserats.</string>
<!--My calendars-->
<string name="title_my_calendars">Mina kalendrar</string>
<string name="title_edit_selected_calendars">Redigera valda kalendrar</string>
<string name="title_calendar_properties">Egenskaper för kalender</string>
<string name="title_delete_selected_calendars_dialog">Radera valda kalendrar?</string>
<string name="calendars_selected">valda kalendrar</string>
<string name="are_you_sure_you_want_to_delete_selected_calendars">Är det säkert att du vill radera valda kalendrar?</string>
<string name="you_cannot_edit_calendars_that_have_not_been_synced">Du kan inte redigera kalendrar som inte har synkroniserats.</string>
<!--PreferencesActivity-->
<string name="title_flock_settings">Flock inställningar</string>
<string name="preference_group_sync">Synk</string>
<string name="preference_title_sync_frequency">Synkfrekvens</string>
<string name="preference_title_sync_on_content_change">Synka vid förändring</string>
<string name="preference_description_sync_on_content_change">Synkronisera kontakter och kalendrar så fort de förändras</string>
<string name="preference_title_sync_now">Synka nu</string>
<string name="sync_requested_will_begin_when_possible">Synk begärd, kommer att börja när det är möjligt</string>
<string name="preference_group_contacts">Kontakter</string>
<string name="preference_group_calendars">Kalendrar</string>
<string name="preference_title_default_calendar_color">Standardfärg på kalender</string>
<string name="minutes">minuter</string>
<string name="new_calendars_will_be">Nya kalendrar kommer att bli %1$s</string>
<string name="preference_group_account">Konto</string>
<!--ChangeEncryptionPasswordActivity-->
<string name="title_change_encryption_password">Ändra krypteringslösenord</string>
<string name="button_save_password">Spara lösenord</string>
<string name="hint_new_password">nytt lösenord</string>
<string name="hint_new_password_repeat">upprepa nytt lösenord</string>
<string name="encryption_password_saved">Krypteringslösenord sparat!</string>
<string name="updating_encryption_secrets">uppdaterar krypteringshemligheter...</string>
<string name="password_change_failed">lösenordsbyte misslyckades.</string>
<!--ManageSubscriptionActivity-->
<string name="title_manage_subscription">Hantera prenumerationer</string>
<string name="days_remain">dagar återstår</string>
<string name="google_play">Google Play</string>
<string name="credit_card">Kreditkort</string>
<string name="when_subscribed_your_card_will_be_charged_once_per_year">När du prenumererar kommer ditt kort att belastas en gång per år och du kan avbryta när som helst.</string>
<string name="subscription_is_active">Prenumerationen är aktiv</string>
<string name="payment_failed">Betalning misslyckades</string>
<string name="start_subscription">Starta prenumeration</string>
<string name="cancel_subscription">Avsluta prenumeration</string>
<string name="are_you_sure_you_want_to_cancel_your_flock_subscription">Är du säker på att du vill avsluta din Flockprenumeration?</string>
<string name="your_subscription_is_active_well_charge_you_again_in_days">Din prenumeration är aktiv, vi kommer att debitera dig igen om %1$d dagar.</string>
<string name="google_play_error_please_update_google_play_services">Google Play-fel, vänligen uppdatera Google Play Services.</string>
<string name="purchase_failed_check_your_google_wallet">köp misslyckades, kontrollera din Google Wallet</string>
<!--EditAutoRenewActivity-->
<string name="usd_per_year">$%.2f/år</string>
<string name="save_card">Spara kort</string>
<string name="card_verified_and_saved">Kort verifierat och sparat!</string>
<string name="subscription_canceled">Prenumeration avslutad</string>
<string name="hint_card_cvc">CVC</string>
<string name="hint_card_expiration">MM/ÅÅ</string>
<string name="hint_card_number">5555 5555 5555 5555</string>
<!--SendBitcoinActivity-->
<string name="title_send_bitcoin">Skicka bitcoin</string>
<string name="mBTC_to">mBTC till</string>
<string name="address_copied_to_clipboard">Adress kopierad till urklipp.</string>
<string name="bitcoin_received">Bitcoin mottagna!</string>
<string name="minutes_until_address_expires">minuter innan adressen upphör</string>
<!--DeleteAccountActivity-->
<string name="title_unregister_account">Avregistrera konto</string>
<string name="if_you_chose_to_unregister_your_account_all_flock">Om du väljer att avregistrera ditt konto kommer alla Flock-kontakter, kalendrar och prenumerationshistorik att raderas permanent.</string>
<string name="are_you_sure_you_want_to_unregister_your_account">Är det säkert att du vill avregistrera ditt konto?</string>
<string name="account_has_been_unregistered">Kontot har avregistrerats.</string>
<!--DeleteAllContactsActivity-->
<string name="title_delete_all_contacts">Radera alla kontakter</string>
<string name="all_contacts_have_been_deleted">Alla kontakter har raderats.</string>
<string name="if_you_chose_to_delete_your_flock_contacts">Om du väljer att radera dina Flock-kontakter kommer de att tas bort från alla enheter som använder detta Flock-konto.</string>
<string name="are_you_sure_you_want_to_delete_all_flock_contacts">Är det säkert att du vill radera alla Flock-kontakter?</string>
<!--CorrectPasswordActivity-->
<string name="title_correct_sync_password">Rätta till synklösenord</string>
<string name="unable_to_login_to_sync_service_please_correct_your_password">Kan inte logga in till synktjänsten, vänligen rätta till ditt lösenord.</string>
<string name="login_successful">Inloggning lyckades!</string>
<!--CorrectEncryptionPasswordActivity-->
<string name="title_correct_encryption_password">Rätta till krypteringslösenord</string>
<string name="encryption_password_is_invalid_please_correct_your_encryption_password">Krypteringslösenord är felaktigt, vänligen rätta till ditt krypteringslösenord.</string>
<string name="password_corrected">Lösenordet har rättats till!</string>
<!--SendDebugLogActivity-->
<string name="send_debug_log">Skicka felsökningslogg</string>
<string name="flock_debug_log">Flock felsökningslogg</string>
<string name="something_strange_happened_send_us_your_debug_log">Någonting konstigt hände, skicka oss din felsökningslogg så kan vi fixa det.</string>
<string name="dont_ask_again">Fråga inte igen</string>
<string name="send_log">Skicka logg</string>
<!--ErrorToaster messages-->
<string name="error_login_unauthorized">Inloggning misslyckades. Felaktigt användarnamn eller lösenord</string>
<string name="error_registration_api_server_error">Våra registreringsservrar har problem, förlåt.</string>
<string name="error_registration_api_client_error">Klientregistrering API-fel, vänligen skicka felsökningsloggar.</string>
<string name="error_our_dav_server_error">Våra synkservrar har problem, förlåt.</string>
<string name="error_their_dav_server_error">Din synkserver beter sig inte som förväntat, kontrollera felsökningsloggar.</string>
<string name="error_dav_client_error">Fel med lokal lagring, vänligen skicka felsökningsloggar.</string>
<string name="error_connection_error">Anslutningsfel, vänligen kontrollera internetanslutningen.</string>
<string name="error_our_certificate_validation">Osäker synkanslutning, vänligen skicka felsökningsloggar.</string>
<string name="error_their_certificate_validation">Certifikatvalideringsfel, kontrollera felsökningsloggar.</string>
<string name="error_unknown_io_error">Okänt IO fel, vänligen skicka felsökningsloggar.</string>
<string name="error_invalid_encryption_password">Felaktigt krypteringslösenord.</string>
<string name="error_invalid_mac_error">Dekrypteringsfel, vänligen skicka felsökningsloggar.</string>
<string name="error_unknown_crypto_error">Okänt kryptografifel, vänligen skicka felsökningsloggar.</string>
<string name="error_url_cannot_be_empty">URL kan inte vara tom</string>
<string name="error_username_empty">Användarnamn kan inte vara tomt.</string>
<string name="error_spaces_in_username">Användarnamn får inte innehålla mellanslag.</string>
<string name="error_username_illegal">Användarnamn måste innehålla \'@\' och vara minst 5 tecken långt.</string>
<string name="error_passwords_do_not_match">Lösenorden matchar inte.</string>
<string name="error_password_too_short">Lösenordet är för kort!</string>
<string name="error_username_already_registered">Användarnamnet är redan registrerat, prova med ett annat.</string>
<string name="error_card_number_could_not_be_verified">Kortnumret kunde inte verifieras</string>
<string name="error_card_expiration_could_not_be_verified">Kortets utgångsdatum kunde inte verifieras</string>
<string name="error_card_security_code_could_not_be_verified">Kortets säkerhetskod kunde inte verifieras</string>
<string name="error_your_card_could_not_be_verified">Ditt kort kunde inte verifieras</string>
<string name="error_stripe_api_error">Okänt fel med betalningshanteringen, försök igen eller skicka felsökningsloggar.</string>
<string name="error_android_account_manager_error">Oväntat fel med Android AccountManager!</string>
<!--misc errors-->
<string name="error_multiple_accounts_not_allowed">Flockanvändare kan inte ha flera konton.</string>
<string name="error_no_account_is_registered">Inget konto är registrerat med den här enheten.</string>
<string name="error_password_unavailable_please_login">Lösenord otillgängligt, var god logga in.</string>
</resources>

View File

@ -2,7 +2,7 @@
<resources>
<integer name="cost_per_year_usd">5</integer>
<integer name="auto_renew_trigger_days_remaining">7</integer>
<integer name="limit_days_expired">20</integer>
<integer name="tag_view_holder">1337</integer>
<integer name="tag_collection_path">100</integer>

View File

@ -60,6 +60,7 @@
<string name="notification_tap_to_correct_encryption_password">Tap to correct encryption password.</string>
<string name="notification_flock_subscription_expired">Flock subscription expired</string>
<string name="notification_tap_to_update_subscription">Tap to update subscription.</string>
<string name="account_will_be_deleted_in_days_tap_to_update_subscription">Your account will be deleted in %1$d days, tap to update subscription.</string>
<!-- status header stuff -->
@ -86,7 +87,7 @@
<string name="privacy_and_terms_of_service"><![CDATA[<a href="https://whispersystems.org/flock/legal.html">Privacy and Terms of Service</a>]]></string>
<string name="flock_sync_is_a_service_run_by_open_whisper_systems_available">Flock Sync is a service run by Open Whisper Systems available for $%.2f/year. Evaluate at no charge for 30 days, no payment information required.</string>
<string name="host_my_own">Host my own</string>
<string name="you_may_chose_to_run_and_configure_your_own_webdav_compliant_server">You may chose to run and configure your own WebDAV-compliant server as a sync service. Before use with Flock the server must pass a series of tests.</string>
<string name="you_may_chose_to_run_and_configure_your_own_webdav_compliant_server">You may choose to run and configure your own WebDAV-compliant server as a sync service. Before use with Flock the server must pass a series of tests.</string>
<!-- SetupActivity, other server test -->
@ -124,12 +125,13 @@
<string name="title_import_account">Import Account</string>
<string name="title_register_account">Register Account</string>
<string name="title_setup_account">Setup account</string>
<string name="chose_a_username_for_your_account_and_a_strong_password_to_encrypt_your_data">Choose a username for your account and a strong password to encrypt your data.</string>
<string name="chose_a_username_for_your_account_and_a_strong_password_to_encrypt_your_data">Choose a username and a strong password to encrypt your data, If your password is forgotten it cannot be reset.</string>
<string name="flock_is_shutting_down_permanently_account_registration_no_longer_allowed">Flock is shutting down permanently, account registration no longer allowed.</string>
<string name="do_you_have_a_flock_account">Do you have a Flock account?</string>
<string name="yes_log_me_in">Yes, log me in</string>
<string name="no_register_me">No, register me</string>
<string name="text_setup_account_with_other">Enter the URL and credentials for your WebDAV host then chose a separate password for encryption.</string>
<string name="text_setup_account_with_other">Enter the URL and credentials for your WebDAV host then choose a separate password for encryption.</string>
<string name="hint_username">username</string>
<string name="hint_password">password</string>
@ -265,7 +267,7 @@
<!-- DeleteAccountActivity -->
<string name="title_unregister_account">Unregister account</string>
<string name="if_you_chose_to_unregister_your_account_all_flock">If you chose to unregister your account, all Flock contacts, calendars, and subscription history will be permanently deleted.</string>
<string name="if_you_chose_to_unregister_your_account_all_flock">If you choose to unregister your account, all Flock contacts, calendars, and subscription history will be permanently deleted.</string>
<string name="are_you_sure_you_want_to_unregister_your_account">Are you sure you want to unregister your account?</string>
<string name="account_has_been_unregistered">Account has been unregistered.</string>
@ -273,7 +275,7 @@
<!-- DeleteAllContactsActivity -->
<string name="title_delete_all_contacts">Delete all contacts</string>
<string name="all_contacts_have_been_deleted">All contacts have been deleted.</string>
<string name="if_you_chose_to_delete_your_flock_contacts">If you chose to delete your Flock contacts they will be removed from all devices using this Flock account.</string>
<string name="if_you_chose_to_delete_your_flock_contacts">If you choose to delete your Flock contacts they will be removed from all devices using this Flock account.</string>
<string name="are_you_sure_you_want_to_delete_all_flock_contacts">Are you sure you want to delete all Flock contacts?</string>
<!-- CorrectPasswordActivity -->
@ -295,6 +297,25 @@
<string name="dont_ask_again">Don\'t ask again</string>
<string name="send_log">Send log</string>
<!-- Eol -->
<string name="flock_is_shutting_down">Flock is shutting down</string>
<string name="tap_for_important_details">Tap for important details</string>
<string name="shutting_down">Shutting down</string>
<string name="eol_paragraph_one">Flock will shutdown permanently on October 1st, 2015. Upon shutdown, all active subscriptions will be refunded and account information erased. We apologize for the inconvenience and thank you for supporting the project.</string>
<string name="eol_paragraph_two">Tap \'Export\' and your contacts and calendars will be copied to external storage in standard \'.vcf\' and \'.ics\' files, after this you may uninstall Flock.</string>
<string name="export">Export</string>
<string name="export_data">Export data</string>
<string name="export_started">Export started</string>
<string name="exporting_contacts_and_calendars">Exporting contacts and calendars</string>
<string name="export_complete">Export complete</string>
<string name="export_completed_successfully">Export completed successfully</string>
<string name="failed_to_copy_contacts_and_events">Failed to copy %1$d contacts and %2$d events</string>
<string name="export_failed">Export failed</string>
<string name="tap_to_login_then_retry_export">Tap to login then retry export</string>
<string name="try_making_more_storage_space_available">Try making more storage space available</string>
<string name="try_a_separate_export_app_if_error_continues">Try a separate export app if error continues</string>
<string name="flock_contacts_vcf">flock-contacts.vcf</string>
<string name="flock_calendar_ical">flock-calendar-%1$d.ics</string>
<!-- ErrorToaster messages -->
<string name="error_login_unauthorized">Login failed. Invalid username or password</string>

View File

@ -57,9 +57,8 @@
<intent android:action="org.anhonesteffort.flock.ChangeCipherPassphraseActivity"/>
</Preference>
<Preference android:title="@string/title_manage_subscription"
android:key="pref_subscription">
<intent android:action="org.anhonesteffort.flock.ManageSubscriptionActivity"/>
<Preference android:title="@string/export_data">
<intent android:action="org.anhonesteffort.flock.EolActivity"/>
</Preference>
<Preference android:title="@string/title_unregister_account"

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2015 Open Whisper Systems
~
~ This program 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.
~
~ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="privacy_and_terms_of_service"><![CDATA[<a href="http://anhonesteffort.org/flock/legal.html">Yksityisyys ja käyttöehdot</a>]]></string>
<string name="flock_syncs_your_contacts_and_calendars_between_multiple_devices">Flock synkronoi yhteystietosi ja kalenterisi useiden laitteiden välillä pitäen samalla huolta yksityisyydestäsi vahvan salauksen avulla. <![CDATA[<a href="http://anhonesteffort.org/flock/legal.html">Yksityisyys ja käyttöehdot</a>]]></string>
</resources>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2015 Open Whisper Systems
~
~ This program 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.
~
~ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="privacy_and_terms_of_service"><![CDATA[<a href="http://anhonesteffort.org/flock/legal.html">Приватность и Условия обслуживания</a>]]></string>
<string name="flock_syncs_your_contacts_and_calendars_between_multiple_devices">Flock синхронизирует Ваши контакты и календари между разными устройствами, защишая Вашу приватность с помощью стойкой криптографии. <![CDATA[<a href="http://anhonesteffort.org/flock/legal.html">Приватность и Условия обслуживания</a>]]></string>
</resources>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2015 Open Whisper Systems
~
~ This program 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.
~
~ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="privacy_and_terms_of_service"><![CDATA[<a href="http://anhonesteffort.org/flock/legal.html">Integritet och villkor för tjänsten</a>]]></string>
<string name="flock_syncs_your_contacts_and_calendars_between_multiple_devices">Flock synkroniserar dina kontakter och kalendrar mellan flera enheter och skyddar samtidigt din integritet med stark kryptering. <![CDATA[<a href="http://anhonesteffort.org/flock/legal.html">Integritet och villkor för tjänsten</a>]]></string>
</resources>