/* * Copyright (C) 2010 Nullbyte <http://nullbyte.eu> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.liato.bankdroid.appwidget; import com.liato.bankdroid.Helpers; import com.liato.bankdroid.MainActivity; import com.liato.bankdroid.R; import com.liato.bankdroid.banking.Account; import com.liato.bankdroid.banking.Bank; import com.liato.bankdroid.banking.BankFactory; import com.liato.bankdroid.banking.exceptions.BankChoiceException; import com.liato.bankdroid.banking.exceptions.BankException; import com.liato.bankdroid.banking.exceptions.LoginException; import com.liato.bankdroid.db.DBAdapter; import com.liato.bankdroid.liveview.LiveViewService; import com.liato.bankdroid.utils.LoggingUtils; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.PendingIntent.CanceledException; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.IBinder; import android.os.SystemClock; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.v4.app.NotificationCompat; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import timber.log.Timber; public class AutoRefreshService extends Service { public final static String BROADCAST_WIDGET_REFRESH = "com.liato.bankdroid.WIDGET_REFRESH"; public final static String BROADCAST_MAIN_REFRESH = "com.liato.bankdroid.MAIN_REFRESH"; public final static String BROADCAST_REMOTE_NOTIFIER = "org.damazio.notifier.service.UserReceiver.USER_MESSAGE"; public final static String BROADCAST_OPENWATCH_TEXT = "com.smartmadsoft.openwatch.action.TEXT"; public final static String BROADCAST_OPENWATCH_VIBRATE = "com.smartmadsoft.openwatch.action.VIBRATE"; public final static String ACTION_MAIN_SHOW_TRANSACTIONS = "com.liato.bankdroid.action.MAIN_SHOW_TRANSACTIONS"; public final static String BROADCAST_TRANSACTIONS_UPDATED = "com.liato.bankdroid.action.TRANSACTIONS"; public static void showNotification(final Bank bank, final Account account, final BigDecimal diff, Context context) { final SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); if (!prefs.getBoolean("notify_on_change", true)) { return; } String text = String.format("%s: %s%s", account.getName(), ((diff.compareTo(new BigDecimal(0)) == 1) ? "+" : ""), Helpers.formatBalance(diff, account.getCurrency())); if (!prefs.getBoolean("notify_delta_only", false)) { text = String.format("%s (%s)", text, Helpers.formatBalance(account.getBalance(), account.getCurrency())); } final NotificationManager notificationManager = (NotificationManager) context .getSystemService(NOTIFICATION_SERVICE); final NotificationCompat.Builder notification = new NotificationCompat.Builder(context) .setSmallIcon(bank.getImageResource()) .setContentTitle(bank.getDisplayName()) .setContentText(text); // Remove notification from statusbar when clicked notification.setAutoCancel(true); // http://www.freesound.org/samplesViewSingle.php?id=75235 // http://www.freesound.org/samplesViewSingle.php?id=91924 if (prefs.getString("notification_sound", null) != null) { notification.setSound(Uri.parse(prefs.getString( "notification_sound", null))); } if (prefs.getBoolean("notify_with_vibration", true)) { final long[] vib = {0, 90, 130, 80, 350, 190, 20, 380}; notification.setVibrate(vib); } if (prefs.getBoolean("notify_with_led", true)) { notification.setLights(prefs.getInt("notify_with_led_color", context.getResources().getColor(R.color.default_led_color)), 700, 200); } final PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0); notification.setContentIntent(contentIntent); String numNotifications = prefs.getString("num_notifications", "total"); int notificationId = (int) (numNotifications.equals("total") ? 0 : numNotifications.equals("bank") ? bank.getDbId() : numNotifications.equals("account") ? account.getId().hashCode() : SystemClock.elapsedRealtime()); notificationManager.notify(notificationId, notification.build()); // Broadcast to Remote Notifier if enabled // http://code.google.com/p/android-notifier/ if (prefs.getBoolean("notify_remotenotifier", false)) { final Intent i = new Intent(BROADCAST_REMOTE_NOTIFIER); i.putExtra("title", String.format("%s (%s)", bank.getName(), bank.getDisplayName())); i.putExtra("description", text); context.sendBroadcast(i); } // Broadcast to OpenWatch if enabled // http://forum.xda-developers.com/showthread.php?t=554551 if (prefs.getBoolean("notify_openwatch", false)) { Intent i; if (prefs.getBoolean("notify_openwatch_vibrate", false)) { i = new Intent(BROADCAST_OPENWATCH_VIBRATE); } else { i = new Intent(BROADCAST_OPENWATCH_TEXT); } i.putExtra("line1", String.format("%s (%s)", bank.getName(), bank.getDisplayName())); i.putExtra("line2", text); context.sendBroadcast(i); } // Broadcast to LiveView if enabled // http://www.sonyericsson.com/cws/products/accessories/overview/liveviewmicrodisplay if (prefs.getBoolean("notify_liveview", false)) { final Intent i = new Intent(context, LiveViewService.class); i.putExtra(LiveViewService.INTENT_EXTRA_ANNOUNCE, true); i.putExtra(LiveViewService.INTENT_EXTRA_TITLE, String.format("%s (%s)", bank.getName(), bank.getDisplayName())); i.putExtra(LiveViewService.INTENT_EXTRA_TEXT, text); context.startService(i); } } public static void broadcastTransactionUpdate(final Context context, final long bankId, final String accountId) { final Intent i = new Intent(BROADCAST_TRANSACTIONS_UPDATED); i.putExtra("accountId", Long.toString(bankId) + "_" + accountId); context.sendBroadcast(i); } public static void sendWidgetRefresh(final Context context) { // Send intent to BankdroidWidgetProvider final Intent updateIntent = new Intent(BROADCAST_WIDGET_REFRESH); final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, updateIntent, PendingIntent.FLAG_UPDATE_CURRENT); try { pendingIntent.send(); } catch (final CanceledException e) { Timber.w(e, "Problem occurred while updating widget"); } } @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); handleStart(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { handleStart(); return START_NOT_STICKY; } private void handleStart() { ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); NetworkInfo ni = cm.getActiveNetworkInfo(); if (ni != null && ni.isConnected() && shouldUpdateOnRoaming(ni)) { if (InsideUpdatePeriod()) { new DataRetrieverTask(this).execute(); } else { Timber.v("Skipping update due to not in update period."); stopSelf(); } } } private boolean shouldUpdateOnRoaming(NetworkInfo ni) { final SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(this); if (prefs.getBoolean("disable_during_roaming", false) && ni.isRoaming()) { return false; } return true; } private boolean InsideUpdatePeriod() { final SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(this); int start = prefs.getInt("refresh_start_minutes", 0); int stop = prefs.getInt("refresh_stop_minutes", 0); // If start is bigger than stop we always update. It should perhaps // be possible to set start to 17:00 and stop to 07:00 and have to // updates working from 17 to 07 around midnight if (start >= stop) { return true; } Date now = new Date(); int minutesSinceMidnight = now.getHours() * 60 + now.getMinutes(); return minutesSinceMidnight > start && minutesSinceMidnight < stop; } @Override public void onDestroy() { } @Override public IBinder onBind(final Intent intent) { return null; } static class DataRetrieverTask extends AsyncTask<String, String, Void> { private final SharedPreferences prefs; private ArrayList<String> errors; protected final AutoRefreshService autoRefreshService; private Resources res; // This constructor is for unit testing only protected DataRetrieverTask(AutoRefreshService autoRefreshService, SharedPreferences prefs) { this.autoRefreshService = autoRefreshService; this.prefs = prefs; } public DataRetrieverTask(AutoRefreshService autoRefreshService) { this(autoRefreshService, PreferenceManager.getDefaultSharedPreferences(autoRefreshService)); } @Override protected void onPreExecute() { } protected List<Bank> getBanks() { return BankFactory.banksFromDb(autoRefreshService, true); } @NonNull protected DBAdapter getDBAdapter() { return new DBAdapter(autoRefreshService); } protected void sendWidgetRefresh() { final Intent updateIntent = new Intent(BROADCAST_MAIN_REFRESH); autoRefreshService.sendBroadcast(updateIntent); AutoRefreshService.sendWidgetRefresh(autoRefreshService); } @Override protected Void doInBackground(final String... args) { errors = new ArrayList<>(); Boolean refreshWidgets = false; final List<Bank> banks = getBanks(); if (banks.isEmpty()) { return null; } final DBAdapter db = getDBAdapter(); BigDecimal currentBalance; BigDecimal diff; BigDecimal minDelta = new BigDecimal(prefs.getString("notify_min_delta", "0")); final HashMap<String, Account> accounts = new HashMap<>(); for (final Bank bank : banks) { if (prefs.getBoolean("debug_mode", false) && prefs.getBoolean("debug_only_testbank", false)) { Timber.d( "Only_Testbank is ON. Skipping update for %s", bank.getName()); continue; } if (bank.isDisabled()) { LoggingUtils.logDisabledBank(bank); continue; } try { currentBalance = bank.getBalance(); accounts.clear(); for (final Account account : bank.getAccounts()) { accounts.put(account.getId(), account); } bank.update(); diff = currentBalance.subtract(bank.getBalance()); if (diff.compareTo(BigDecimal.ZERO) != 0) { refreshWidgets = true; } if (diff.compareTo(new BigDecimal(0)) != 0 && diff.abs().compareTo(minDelta) != -1) { Account oldAccount; for (final Account account : bank.getAccounts()) { oldAccount = accounts.get(account.getId()); if (oldAccount != null) { if (account.getBalance().compareTo( oldAccount.getBalance()) != 0) { boolean notify = false; switch (account.getType()) { case Account.REGULAR: notify = prefs.getBoolean( "notify_for_deposit", true); break; case Account.FUNDS: notify = prefs.getBoolean( "notify_for_funds", false); break; case Account.LOANS: notify = prefs.getBoolean( "notify_for_loans", false); break; case Account.CCARD: notify = prefs.getBoolean( "notify_for_ccards", true); break; case Account.OTHER: notify = prefs.getBoolean( "notify_for_other", false); break; } if (account.isHidden() || !account.isNotify()) { notify = false; } if (notify) { diff = account.getBalance().subtract( oldAccount.getBalance()); showNotification(bank, account, diff, autoRefreshService); } } } } if (prefs.getBoolean( "autoupdates_transactions_enabled", true)) { bank.updateAllTransactions(); LoggingUtils.logBankUpdate(bank, true); } else { LoggingUtils.logBankUpdate(bank, false); } } bank.closeConnection(); db.updateBank(bank); // Send update for all accounts since we're overwriting the // database transaction history if (prefs.getBoolean("content_provider_enabled", false)) { for (final Account account : bank.getAccounts()) { broadcastTransactionUpdate(autoRefreshService.getBaseContext(), bank.getDbId(), account.getId()); } } } catch (final BankException e) { // Refresh widgets if an update fails Timber.e(e, "Could not update bank %s", bank.getName()); } catch (final LoginException e) { Timber.d(e, "Invalid credentials for bank %s", bank.getName()); refreshWidgets = true; db.disableBank(bank.getDbId()); } catch (BankChoiceException e) { Timber.w(e, "BankChoiceException"); } catch (Exception e) { Timber.e(e, "An unexpected error occurred while updating bank %s", bank.getName()); } } if (refreshWidgets) { sendWidgetRefresh(); } return null; } @Override protected void onProgressUpdate(final String... args) { } @Override protected void onPostExecute(final Void unused) { if ((this.errors != null) && !this.errors.isEmpty()) { final StringBuilder errormsg = new StringBuilder(); errormsg.append(res.getText(R.string.accounts_were_not_updated) + ":\n"); for (final String err : errors) { errormsg.append(err); errormsg.append("\n"); } } Editor edit = prefs.edit(); edit.putLong("autoupdates_last_update", System.currentTimeMillis()); edit.apply(); autoRefreshService.stopSelf(); } } }