package com.piusvelte.sonet.loader; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import com.piusvelte.sonet.About; import com.piusvelte.sonet.BuildConfig; import com.piusvelte.sonet.R; import com.piusvelte.sonet.Sonet; import com.piusvelte.sonet.SonetCrypto; import com.piusvelte.sonet.SonetService; import com.piusvelte.sonet.provider.Entity; import com.piusvelte.sonet.provider.Notifications; import com.piusvelte.sonet.provider.Statuses; import com.piusvelte.sonet.provider.StatusesStyles; import com.piusvelte.sonet.provider.WidgetAccountsView; import com.piusvelte.sonet.provider.Widgets; import com.piusvelte.sonet.provider.WidgetsSettings; import com.piusvelte.sonet.social.Client; import static com.piusvelte.sonet.Sonet.ACTION_REFRESH; import static com.piusvelte.sonet.Sonet.NOTIFY_ID; /** * Created by bemmanuel on 5/18/15. */ public class StatusesLoader extends AsyncTask<Integer, String, Integer> { private static final String TAG = "StatusesLoader"; private SonetService mSonetService; public StatusesLoader(@NonNull SonetService sonetService) { mSonetService = sonetService; } @Override protected Integer doInBackground(Integer... params) { // first handle deletes, then scroll updates, finally regular updates final int appWidgetId = params[0]; final String widget = Integer.toString(appWidgetId); final boolean reload = params[1] != 0; if (BuildConfig.DEBUG) { Log.d(TAG, "loading widget:" + widget + ",reload:" + reload); } WidgetsSettings.Settings settings = WidgetsSettings.getSettings(mSonetService, appWidgetId); int refreshInterval = settings.interval; boolean backgroundUpdate = settings.isBackgroundUpdate; // the widget will start out as the default widget.xml, which simply says "loading..." // if there's a cache, that should be quickly reloaded while new updates come down // otherwise, replace the widget with "loading..." // clear the messages mSonetService.getContentResolver().delete(Statuses.getContentUri(mSonetService), Statuses.WIDGET + "=? and " + Statuses.ACCOUNT + "=?", new String[] { Integer.toString(AppWidgetManager.INVALID_APPWIDGET_ID),// all statuses Long.toString(Sonet.INVALID_ACCOUNT_ID) }); Cursor statuses = mSonetService.getContentResolver().query(Statuses.getContentUri(mSonetService), new String[] { Statuses._ID }, Statuses.WIDGET + "=?", new String[] { Integer.toString(AppWidgetManager.INVALID_APPWIDGET_ID) },// all statuses null); boolean hasCache = statuses.moveToFirst(); statuses.close(); // the alarm should always be set, rather than depend on the tasks to complete if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID && !hasCache || reload && refreshInterval > 0) { mSonetService.mAlarmManager.cancel(PendingIntent.getService(mSonetService, 0, new Intent(mSonetService, SonetService.class).setAction(widget), 0)); mSonetService.mAlarmManager .set(backgroundUpdate ? AlarmManager.RTC_WAKEUP : AlarmManager.RTC, System.currentTimeMillis() + refreshInterval, PendingIntent.getService(mSonetService, 0, new Intent(mSonetService, SonetService.class) .setData(Uri.withAppendedPath(Widgets.getContentUri(mSonetService), widget)) .setAction(ACTION_REFRESH), 0)); } // get the accounts Cursor accounts = mSonetService.getContentResolver().query(WidgetAccountsView.getContentUri(mSonetService), new String[] { WidgetAccountsView.ACCOUNT, WidgetAccountsView.TOKEN, WidgetAccountsView.SECRET, WidgetAccountsView.SERVICE, WidgetAccountsView.SID }, WidgetAccountsView.WIDGET + "=?", new String[] { Integer.toString(AppWidgetManager.INVALID_APPWIDGET_ID) },// use invalid appwidget to get all accounts null); // TODO remove this, and indicate loading using the empty view if (!hasCache || !accounts.moveToFirst()) { // if no cache inform the user that the widget is loading addStatusItem(widget, mSonetService.getString(R.string.updating), appWidgetId); } // TODO remove all of this "loading" // loading takes time, so don't leave an empty widget sitting there if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { // build the widget mSonetService.buildWidgetButtons(appWidgetId); } else { // TODO this isn't necessary // update the About.java for in-app viewing mSonetService.getContentResolver().notifyChange(StatusesStyles.getContentUri(mSonetService), null); } if (BuildConfig.DEBUG) { Log.d(TAG, "loading widget=" + widget + "; accounts count=" + accounts.getCount()); } if (accounts.moveToFirst()) { final int accountIndex = accounts.getColumnIndexOrThrow(WidgetAccountsView.ACCOUNT); final int tokenIndex = accounts.getColumnIndexOrThrow(WidgetAccountsView.TOKEN); final int secretIndex = accounts.getColumnIndexOrThrow(WidgetAccountsView.SECRET); final int serviceIndex = accounts.getColumnIndexOrThrow(WidgetAccountsView.SERVICE); final int sidIndex = accounts.getColumnIndexOrThrow(WidgetAccountsView.SID); // only reload if the token's can be decrypted and if there's no cache or a reload is requested if (!hasCache || reload) { mSonetService.mNotify = null; int notifications = 0; // load the updates while (!accounts.isAfterLast()) { long account = accounts.getLong(accountIndex); int service = accounts.getInt(serviceIndex); if (BuildConfig.DEBUG) { Log.d(TAG, "loading account=" + account + "; service=" + service); } WidgetsSettings.Settings accountSettings = WidgetsSettings.getSettings(mSonetService, appWidgetId, account); boolean time24hr = accountSettings.isTime24hr; int status_count = Sonet.default_statuses_per_account; notifications = accountSettings.notificationsMask(); // if no connection, only update the status_bg and icons if (mSonetService.mConnectivityManager.getActiveNetworkInfo() != null && mSonetService.mConnectivityManager .getActiveNetworkInfo().isConnected()) { SonetCrypto sonetCrypto = SonetCrypto.getInstance(mSonetService); String token = sonetCrypto.Decrypt(accounts.getString(tokenIndex)); String secret = sonetCrypto.Decrypt(accounts.getString(secretIndex)); String accountEsid = sonetCrypto.Decrypt(accounts.getString(sidIndex)); Client client = new Client.Builder(mSonetService) .setNetwork(service) .setAccount(accountEsid) .setCredentials(token, secret) .build(); String notificationMessage = client.getFeed(appWidgetId, Integer.toString(AppWidgetManager.INVALID_APPWIDGET_ID),// all statuses here account, status_count, time24hr, true, notifications); if (TextUtils.isEmpty(mSonetService.mNotify)) { mSonetService.mNotify = notificationMessage; } else if (!TextUtils.isEmpty(notificationMessage)) { mSonetService.mNotify = mSonetService.getString(R.string.notify_multiple_updates); } // remove old notifications mSonetService.getContentResolver().delete(Notifications.getContentUri(mSonetService), Notifications.CLEARED + "=1 and " + Notifications.ACCOUNT + "=? and " + Notifications.CREATED + "<?", new String[] { Long.toString(account), Long.toString(System.currentTimeMillis() - 86400000) }); } else { // no network connection if (hasCache) { // update created text updateCreatedText(widget, Long.toString(account), time24hr); } else { // TODO remove // clear the "loading" message and display "no connection" mSonetService.getContentResolver() .delete(Statuses.getContentUri(mSonetService), Statuses.WIDGET + "=?", new String[] { Integer.toString(AppWidgetManager.INVALID_APPWIDGET_ID) }); addStatusItem(widget, mSonetService.getString(R.string.no_connection), appWidgetId); } } accounts.moveToNext(); } if (notifications != 0 && mSonetService.mNotify != null) { publishProgress(Integer.toString(notifications)); } } // TODO remove // delete the existing loading and informational messages mSonetService.getContentResolver() .delete(Statuses.getContentUri(mSonetService), Statuses.WIDGET + "=? and " + Statuses.ACCOUNT + "=?", new String[] { Integer.toString(AppWidgetManager.INVALID_APPWIDGET_ID), Long.toString(Sonet.INVALID_ACCOUNT_ID) }); // check statuses again Cursor statusesCheck = mSonetService.getContentResolver() .query(Statuses.getContentUri(mSonetService), new String[] { Statuses._ID }, Statuses.WIDGET + "=?", new String[] { Integer.toString(AppWidgetManager.INVALID_APPWIDGET_ID) },// all statuses here null); hasCache = statusesCheck.moveToFirst(); statusesCheck.close(); // TODO remove if (!hasCache) { // there should be a loading message displaying // if no updates have been loaded, display "no updates" addStatusItem(widget, mSonetService.getString(R.string.no_updates), appWidgetId); } } else { // no accounts, clear cache mSonetService.getContentResolver().delete(Statuses.getContentUri(mSonetService), Statuses.WIDGET + "=?", new String[] { Integer.toString(AppWidgetManager.INVALID_APPWIDGET_ID) }); // insert no accounts message addStatusItem(widget, mSonetService.getString(R.string.no_accounts), appWidgetId); } accounts.close(); // always update buttons, if !scrollable update widget both times, otherwise build scrollable first, requery second // see if the tasks are finished // non-scrollable widgets will be completely rebuilt, while scrollable widgets while be notified to requery if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { mSonetService.buildWidgetButtons(appWidgetId); } else { // notify change to About.java mSonetService.getContentResolver().notifyChange(StatusesStyles.getContentUri(mSonetService), null); } return appWidgetId; } @Override protected void onCancelled(Integer appWidgetId) { // Log.d(TAG,"loader cancelled"); } @Override protected void onProgressUpdate(String... updates) { int notifications = Integer.parseInt(updates[0]); if (notifications != 0) { Notification notification = new Notification(R.drawable.notification, mSonetService.mNotify, System.currentTimeMillis()); notification.setLatestEventInfo(mSonetService.getBaseContext(), "New messages", mSonetService.mNotify, PendingIntent.getActivity(mSonetService, 0, About.createIntent(mSonetService, About.DRAWER_FEED/**About.DRAWER_NOTIFICATIONS*/) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), 0)); notification.defaults |= notifications; ((NotificationManager) mSonetService.getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFY_ID, notification); } } @Override protected void onPostExecute(Integer appWidgetId) { // remove self from thread list if (!SonetService.mStatusesLoaders.isEmpty() && SonetService.mStatusesLoaders.containsKey(appWidgetId)) { SonetService.mStatusesLoaders.remove(appWidgetId); } // Log.d(TAG,"finished update, check queue"); if (SonetService.mStatusesLoaders.isEmpty()) { // Log.d(TAG,"stop service"); Sonet.release(); mSonetService.stopSelfResult(mSonetService.mStartId); } } // TODO remove this generic item used for status messages private void addStatusItem(String widget, String message, int appWidgetId) { long id; long created = System.currentTimeMillis(); int service = 0; boolean time24hr = false; long accountId = Sonet.INVALID_ACCOUNT_ID; String sid = "-1"; String esid = ""; String friend = mSonetService.getString(R.string.app_name); Cursor entity = mSonetService.getContentResolver().query(Entity.getContentUri(mSonetService), new String[] { Entity._ID }, Entity.ACCOUNT + "=? and " + Entity.ESID + "=?", new String[] { Long.toString(accountId), esid }, null); if (entity.moveToFirst()) { id = entity.getLong(entity.getColumnIndexOrThrow(Entity._ID)); } else { ContentValues entityValues = new ContentValues(); entityValues.put(Entity.ESID, esid); entityValues.put(Entity.FRIEND, friend); entityValues.put(Entity.ACCOUNT, accountId); id = Long.parseLong( mSonetService.getContentResolver().insert(Entity.getContentUri(mSonetService), entityValues).getLastPathSegment()); } entity.close(); ContentValues values = new ContentValues(); values.put(Statuses.CREATED, created); values.put(Statuses.ENTITY, id); values.put(Statuses.MESSAGE, message); values.put(Statuses.SERVICE, service); values.put(Statuses.CREATEDTEXT, Sonet.getCreatedText(created, time24hr)); values.put(Statuses.WIDGET, appWidgetId); values.put(Statuses.ACCOUNT, accountId); values.put(Statuses.SID, sid); values.put(Statuses.FRIEND_OVERRIDE, friend); long statusId = Long.parseLong(mSonetService.getContentResolver().insert(Statuses.getContentUri(mSonetService), values).getLastPathSegment()); } private boolean updateCreatedText(String widget, String account, boolean time24hr) { boolean statuses_updated = false; Cursor statuses = mSonetService.getContentResolver() .query(Statuses.getContentUri(mSonetService), new String[] { Statuses._ID, Statuses.CREATED }, Statuses.WIDGET + "=? and " + Statuses.ACCOUNT + "=?", new String[] { Integer.toString(AppWidgetManager.INVALID_APPWIDGET_ID), account }, null); if (statuses.moveToFirst()) { while (!statuses.isAfterLast()) { ContentValues values = new ContentValues(); values.put(Statuses.CREATEDTEXT, Sonet.getCreatedText(statuses.getLong(1), time24hr)); mSonetService.getContentResolver().update(Statuses.getContentUri(mSonetService), values, Statuses._ID + "=?", new String[] { Long.toString(statuses.getLong(0)) }); statuses.moveToNext(); } statuses_updated = true; } statuses.close(); return statuses_updated; } }