/*
* *
* 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.ContentResolver;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.anhonesteffort.flock.util.guava.Optional;
import org.anhonesteffort.flock.auth.DavAccount;
import org.anhonesteffort.flock.crypto.InvalidCipherVersionException;
import org.anhonesteffort.flock.crypto.KeyHelper;
import org.anhonesteffort.flock.registration.RegistrationApi;
import org.anhonesteffort.flock.registration.RegistrationApiException;
import org.anhonesteffort.flock.sync.addressbook.AddressbookSyncScheduler;
import org.anhonesteffort.flock.sync.calendar.CalendarsSyncScheduler;
import org.anhonesteffort.flock.webdav.PropertyParseException;
import org.apache.jackrabbit.webdav.DavException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.text.DateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class StatusHeaderView extends LinearLayout {
private final static String TAG = StatusHeaderView.class.getSimpleName();
private final Handler uiHandler = new Handler();
private Timer intervalTimer = new Timer();
private final TextView timeLastSyncView;
private final TextView syncStatusView;
private Optional<DavAccount> account;
private AsyncTask asyncTaskSubscription;
private AsyncTask asyncTaskCard;
private AsyncTask asyncTaskMasterPassphrase;
private long timeLastSync = -1;
private boolean syncInProgress = false;
private boolean subscriptionIsValid = true;
private boolean cardIsValid = true;
private boolean cipherPassphraseIsValid = true;
private boolean authNotificationShown = false;
private boolean subscriptionNotificationShown = false;
private boolean syncServerHasError = false;
private boolean registrationServerHasError = false;
private boolean syncServerErrorNotificationShown = false;
private boolean registrationServerErrorNotificationShown = false;
public StatusHeaderView(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.status_header_view, this);
account = DavAccountHelper.getAccount(context);
timeLastSyncView = (TextView) getRootView().findViewById(R.id.last_sync_time);
syncStatusView = (TextView) getRootView().findViewById(R.id.sync_status);
}
public void hackOnPause() {
if (asyncTaskSubscription != null && !asyncTaskSubscription.isCancelled())
asyncTaskSubscription.cancel(true);
if (asyncTaskCard != null && !asyncTaskCard.isCancelled())
asyncTaskCard.cancel(true);
if (asyncTaskMasterPassphrase != null && !asyncTaskMasterPassphrase.isCancelled())
asyncTaskMasterPassphrase.cancel(true);
if (intervalTimer != null)
intervalTimer.cancel();
}
private void handleUpdateTimeLastSync() {
AddressbookSyncScheduler addressbookSync = new AddressbookSyncScheduler(getContext());
CalendarsSyncScheduler calendarSync = new CalendarsSyncScheduler(getContext());
Optional<Long> lastContactSync = addressbookSync.getTimeLastSync();
Optional<Long> lastCalendarSync = calendarSync.getTimeLastSync();
if (lastCalendarSync.isPresent() && !lastContactSync.isPresent())
timeLastSync = lastCalendarSync.get();
else if (!lastCalendarSync.isPresent() && lastContactSync.isPresent())
timeLastSync = lastContactSync.get();
else if (!lastCalendarSync.isPresent() && !lastContactSync.isPresent())
timeLastSync = -1;
else
timeLastSync = Math.min(lastContactSync.get(), lastCalendarSync.get());
if (!account.isPresent())
return;
syncInProgress = addressbookSync.syncInProgress(account.get().getOsAccount()) ||
calendarSync.syncInProgress(account.get().getOsAccount());
}
private void handleUpdateLayout() {
final String timeLastSyncText;
final String syncStatusText;
final int syncStatusDrawable;
if (!ContentResolver.getMasterSyncAutomatically()) {
syncStatusView.setText(getContext().getString(R.string.status_header_status_sync_disabled));
timeLastSyncView.setVisibility(GONE);
syncStatusView.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.sad_cloud, 0, 0);
invalidate();
return;
}
if (timeLastSync == -1) {
syncStatusView.setText(getContext().getString(R.string.status_header_sync_in_progress));
syncStatusView.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.sync_in_progress, 0, 0);
timeLastSyncView.setVisibility(GONE);
invalidate();
return;
}
else {
DateFormat formatter = DateFormat.getDateTimeInstance();
timeLastSyncText = formatter.format(new Date(timeLastSync));
timeLastSyncView.setText(getContext().getString(R.string.status_header_sync_time, timeLastSyncText));
timeLastSyncView.setVisibility(VISIBLE);
}
if (syncServerHasError) {
if (account.isPresent() && DavAccountHelper.isUsingOurServers(account.get()))
syncStatusText = getContext().getString(R.string.status_header_status_our_sync_service_error);
else
syncStatusText = getContext().getString(R.string.status_header_status_their_sync_service_error);
syncStatusDrawable = R.drawable.sad_cloud;
}
else if (registrationServerHasError) {
syncStatusText = getContext().getString(R.string.status_header_status_registration_service_error);
syncStatusDrawable = R.drawable.sad_cloud;
}
else if (!DavAccountHelper.getAccountPassword(getContext()).isPresent()) {
syncStatusText = getContext().getString(R.string.status_header_status_account_login_failed);
syncStatusDrawable = R.drawable.sad_cloud;
if (!authNotificationShown) {
NotificationDrawer.handleInvalidatePasswordAndShowAuthNotification(getContext());
authNotificationShown = true;
}
}
else if (!subscriptionIsValid) {
syncStatusText = getContext().getString(R.string.notification_flock_subscription_expired);
syncStatusDrawable = R.drawable.sad_cloud;
if (!subscriptionNotificationShown) {
subscriptionNotificationShown = true;
}
}
else if (!cardIsValid) {
syncStatusText = getContext().getString(R.string.status_header_status_auto_renew_error);
syncStatusDrawable = R.drawable.sad_cloud;
}
else if (!cipherPassphraseIsValid) {
syncStatusText = getContext().getString(R.string.status_header_status_encryption_password_incorrect);
syncStatusDrawable = R.drawable.sad_cloud;
}
else if (syncInProgress) {
syncStatusText = getContext().getString(R.string.status_header_sync_in_progress);
syncStatusDrawable = R.drawable.sync_in_progress;
}
else {
syncStatusText = getContext().getString(R.string.status_header_status_good);
syncStatusDrawable = R.drawable.happy_cloud;
}
syncStatusView.setText(syncStatusText);
syncStatusView.setCompoundDrawablesWithIntrinsicBounds(0, syncStatusDrawable, 0, 0);
invalidate();
}
private void handleUpdateSubscriptionIsValid() {
if (!account.isPresent() || (asyncTaskSubscription != null && !asyncTaskSubscription.isCancelled()))
return;
asyncTaskSubscription = new AsyncTask<String, Void, Bundle>() {
boolean subscriptionExpired = false;
@Override
protected Bundle doInBackground(String... params) {
Bundle result = new Bundle();
try {
subscriptionExpired = DavAccountHelper.isExpired(getContext(), account.get());
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SUCCESS);
} catch (IOException e) {
ErrorToaster.handleBundleError(e, result);
} catch (PropertyParseException e) {
ErrorToaster.handleBundleError(e, result);
} catch (DavException e) {
ErrorToaster.handleBundleError(e, result);
}
return result;
}
@Override
protected void onPostExecute(Bundle result) {
asyncTaskSubscription = null;
subscriptionIsValid = !subscriptionExpired;
if (result.getInt(ErrorToaster.KEY_STATUS_CODE) == ErrorToaster.CODE_DAV_SERVER_ERROR) {
if (!syncServerErrorNotificationShown || !syncServerHasError) {
ErrorToaster.handleDisplayToastBundledError(getContext(), result);
syncServerHasError = true;
syncServerErrorNotificationShown = true;
}
}
else if (result.getInt(ErrorToaster.KEY_STATUS_CODE) != ErrorToaster.CODE_SUCCESS)
ErrorToaster.handleDisplayToastBundledError(getContext(), result);
}
}.execute();
}
private void handleUpdateCardIsValid() {
if (!account.isPresent() || (asyncTaskCard != null && !asyncTaskCard.isCancelled()))
return;
asyncTaskCard = new AsyncTask<String, Void, Bundle>() {
boolean lastChargeFailed = false;
@Override
protected Bundle doInBackground(String... params) {
Bundle result = new Bundle();
try {
RegistrationApi registrationApi = new RegistrationApi(getContext());
if (registrationApi.getAccount(account.get()).getLastStripeChargeFailed())
lastChargeFailed = registrationApi.getCard(account.get()).isPresent();
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SUCCESS);
} catch (IOException e) {
ErrorToaster.handleBundleError(e, result);
} catch (RegistrationApiException e) {
ErrorToaster.handleBundleError(e, result);
}
return result;
}
@Override
protected void onPostExecute(Bundle result) {
asyncTaskCard = null;
cardIsValid = !lastChargeFailed;
if (result.getInt(ErrorToaster.KEY_STATUS_CODE) == ErrorToaster.CODE_REGISTRATION_API_SERVER_ERROR) {
if (!registrationServerErrorNotificationShown || !registrationServerHasError) {
ErrorToaster.handleDisplayToastBundledError(getContext(), result);
registrationServerHasError = true;
registrationServerErrorNotificationShown = true;
}
}
else if (result.getInt(ErrorToaster.KEY_STATUS_CODE) != ErrorToaster.CODE_SUCCESS)
ErrorToaster.handleDisplayToastBundledError(getContext(), result);
}
}.execute();
}
private void handleUpdateCipherPassphraseIsValid() {
if (asyncTaskMasterPassphrase != null && !asyncTaskMasterPassphrase.isCancelled())
return;
asyncTaskMasterPassphrase = new AsyncTask<String, Void, Bundle>() {
boolean passphraseIsValid = false;
@Override
protected Bundle doInBackground(String... params) {
Bundle result = new Bundle();
try {
passphraseIsValid = KeyHelper.masterPassphraseIsValid(getContext());
result.putInt(ErrorToaster.KEY_STATUS_CODE, ErrorToaster.CODE_SUCCESS);
} catch (IOException e) {
ErrorToaster.handleBundleError(e, result);
} catch (InvalidCipherVersionException e) {
Log.d(TAG, "caught invalid cipher version exception, likely due to migration.", e);
} catch (GeneralSecurityException e) {
ErrorToaster.handleBundleError(e, result);
}
return result;
}
@Override
protected void onPostExecute(Bundle result) {
asyncTaskMasterPassphrase = null;
cipherPassphraseIsValid = passphraseIsValid;
if (result.getInt(ErrorToaster.KEY_STATUS_CODE) != ErrorToaster.CODE_SUCCESS)
ErrorToaster.handleDisplayToastBundledError(getContext(), result);
}
}.execute();
}
private final Runnable refreshUiRunnable = new Runnable() {
@Override
public void run() {
handleUpdateTimeLastSync();
handleUpdateLayout();
}
};
private final Runnable refreshSubscriptionRunnable = new Runnable() {
@Override
public void run() {
handleUpdateSubscriptionIsValid();
}
};
private final Runnable refreshCardRunnable = new Runnable() {
@Override
public void run() {
handleUpdateCardIsValid();
}
};
private final Runnable refreshCipherPassphraseRunnable = new Runnable() {
@Override
public void run() {
handleUpdateCipherPassphraseIsValid();
}
};
public void handleStartPerpetualRefresh() {
account = DavAccountHelper.getAccount(getContext());
intervalTimer = new Timer();
TimerTask uiTask = new TimerTask() {
@Override
public void run() {
uiHandler.post(refreshUiRunnable);
}
};
TimerTask subscriptionTask = new TimerTask() {
@Override
public void run() {
uiHandler.post(refreshSubscriptionRunnable);
}
};
TimerTask cardTask = new TimerTask() {
@Override
public void run() {
uiHandler.post(refreshCardRunnable);
}
};
TimerTask passphraseTask = new TimerTask() {
@Override
public void run() {
uiHandler.post(refreshCipherPassphraseRunnable);
}
};
intervalTimer.schedule(uiTask, 0, 2000);
if (account.isPresent()) {
if (DavAccountHelper.isUsingOurServers(account.get())) {
intervalTimer.schedule(subscriptionTask, 0, 20000);
intervalTimer.schedule(cardTask, 0, 20000);
}
else
intervalTimer.schedule(passphraseTask, 0, 10000);
}
}
}