Cleanup NbActivity and remove static reference to all activities. Add broadcast receiver based sync mechanism between foreground activities and db updates by the background service.

This commit is contained in:
Andrei 2021-10-30 17:26:20 -07:00
parent 5f8e04eb66
commit ac23dbd630
20 changed files with 341 additions and 270 deletions

View file

@ -12,7 +12,8 @@
android:label="@string/newsblur"
android:theme="@style/NewsBlurTheme"
android:hardwareAccelerated="true"
android:fullBackupContent="@xml/backupscheme" >
android:fullBackupContent="@xml/backupscheme"
android:name=".NbApplication">
<activity
android:name=".activity.InitActivity"

View file

@ -39,7 +39,8 @@ dependencies {
implementation "com.google.android.material:material:1.3.0"
implementation "androidx.preference:preference-ktx:1.1.1"
implementation "androidx.browser:browser:1.3.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
implementation 'androidx.lifecycle:lifecycle-process:2.4.0'
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
}

View file

@ -0,0 +1,30 @@
package com.newsblur
import android.app.Application
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
class NbApplication : Application(), DefaultLifecycleObserver {
override fun onCreate() {
super<Application>.onCreate()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
isAppForeground = true
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
isAppForeground = false
}
companion object {
@JvmStatic
var isAppForeground = false
}
}

View file

@ -1,5 +1,9 @@
package com.newsblur.activity;
import static com.newsblur.service.NBSyncReceiver.UPDATE_REBUILD;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
import android.os.Bundle;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

View file

@ -1,5 +1,10 @@
package com.newsblur.activity;
import static com.newsblur.service.NBSyncReceiver.UPDATE_DB_READY;
import static com.newsblur.service.NBSyncReceiver.UPDATE_METADATA;
import static com.newsblur.service.NBSyncReceiver.UPDATE_REBUILD;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;

View file

@ -1,5 +1,7 @@
package com.newsblur.activity;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
import android.content.Intent;
import android.database.Cursor;
import android.text.TextUtils;

View file

@ -1,174 +0,0 @@
package com.newsblur.activity;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.widget.Toast;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.PrefConstants.ThemeValue;
import com.newsblur.util.UIUtils;
import java.util.ArrayList;
/**
* The base class for all Activities in the NewsBlur app. Handles enforcement of
* login state and tracking of sync/update broadcasts.
*/
public class NbActivity extends AppCompatActivity {
public static final int UPDATE_DB_READY = (1<<0);
public static final int UPDATE_METADATA = (1<<1);
public static final int UPDATE_STORY = (1<<2);
public static final int UPDATE_SOCIAL = (1<<3);
public static final int UPDATE_INTEL = (1<<4);
public static final int UPDATE_STATUS = (1<<5);
public static final int UPDATE_TEXT = (1<<6);
public static final int UPDATE_REBUILD = (1<<7);
private final static String UNIQUE_LOGIN_KEY = "uniqueLoginKey";
private String uniqueLoginKey;
private ThemeValue lastTheme = null;
/**
* Keep track of all activie activities so they can be notified when the sync service
* has updated the DB. This is essentially an ultra-lightweight implementation of a
* local, unfiltered broadcast manager.
*/
private static ArrayList<NbActivity> AllActivities = new ArrayList<NbActivity>();
@Override
protected void onCreate(Bundle bundle) {
com.newsblur.util.Log.offerContext(this);
com.newsblur.util.Log.d(this, "onCreate");
// this is not redundant to the applyThemePreference() call in onResume. the theme needs to be set
// before onCreate() in order to work
PrefsUtils.applyThemePreference(this);
lastTheme = PrefsUtils.getSelectedTheme(this);
// in rare cases of process interruption or DB corruption, an activity can launch without valid
// login creds. redirect the user back to the loging workflow.
if (PrefsUtils.getUserId(this) == null) {
com.newsblur.util.Log.e(this, "post-login activity launched without valid login.");
PrefsUtils.logout(this);
finish();
}
super.onCreate(bundle);
FeedUtils.offerInitContext(this);
if (bundle != null) {
uniqueLoginKey = bundle.getString(UNIQUE_LOGIN_KEY);
}
if (uniqueLoginKey == null) {
uniqueLoginKey = PrefsUtils.getUniqueLoginKey(this);
}
finishIfNotLoggedIn();
}
@Override
protected void onResume() {
com.newsblur.util.Log.d(this, "onResume" + UIUtils.getMemoryUsageDebug(this));
super.onResume();
finishIfNotLoggedIn();
// is is possible that another activity changed the theme while we were on the backstack
if (lastTheme != PrefsUtils.getSelectedTheme(this)) {
lastTheme = PrefsUtils.getSelectedTheme(this);
PrefsUtils.applyThemePreference(this);
UIUtils.restartActivity(this);
}
synchronized (AllActivities) {
AllActivities.add(this);
}
}
@Override
protected void onPause() {
com.newsblur.util.Log.d(this.getClass().getName(), "onPause");
super.onPause();
synchronized (AllActivities) {
AllActivities.remove(this);
}
}
protected void finishIfNotLoggedIn() {
String currentLoginKey = PrefsUtils.getUniqueLoginKey(this);
if(currentLoginKey == null || !currentLoginKey.equals(uniqueLoginKey)) {
com.newsblur.util.Log.d(this.getClass().getName(), "This activity was for a different login. finishing it.");
finish();
}
}
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
com.newsblur.util.Log.d(this, "onSave");
savedInstanceState.putString(UNIQUE_LOGIN_KEY, uniqueLoginKey);
super.onSaveInstanceState(savedInstanceState);
}
/**
* Pokes the sync service to perform any pending sync actions.
*/
protected void triggerSync() {
FeedUtils.triggerSync(this);
}
/**
* Called on each NB activity after the DB has been updated by the sync service.
*
* @param updateType one or more of the UPDATE_* flags in this class to indicate the
* type of update being broadcast.
*/
protected void handleUpdate(int updateType) {
com.newsblur.util.Log.w(this, "activity doesn't implement handleUpdate");
}
private void _handleUpdate(final int updateType) {
runOnUiThread(new Runnable() {
public void run() {
handleUpdate(updateType);
}
});
}
/**
* Notify all activities in the app that the DB has been updated. Should only be called
* by the sync service, which owns updating the DB.
*/
public static void updateAllActivities(int updateType) {
synchronized (AllActivities) {
for (NbActivity activity : AllActivities) {
activity._handleUpdate(updateType);
}
}
}
public static void toastError(final String message) {
synchronized (AllActivities) {
for (final NbActivity activity : AllActivities) {
activity.runOnUiThread(new Runnable() {
public void run() {
UIUtils.safeToast(activity, message, Toast.LENGTH_SHORT);
}
});
}
}
}
/**
* Gets the number of active/foreground NB activities. Used by the sync service to
* determine if the app is active so we can honour user requests not to run in
* the background.
*/
public static int getActiveActivityCount() {
return AllActivities.size();
}
}

View file

@ -0,0 +1,116 @@
package com.newsblur.activity
import android.content.IntentFilter
import com.newsblur.util.FeedUtils.offerInitContext
import com.newsblur.util.FeedUtils.triggerSync
import androidx.appcompat.app.AppCompatActivity
import com.newsblur.util.PrefConstants.ThemeValue
import android.os.Bundle
import com.newsblur.util.PrefsUtils
import com.newsblur.util.UIUtils
import com.newsblur.service.NBSyncReceiver
import com.newsblur.util.Log
/**
* The base class for all Activities in the NewsBlur app. Handles enforcement of
* login state and tracking of sync/update broadcasts.
*/
open class NbActivity : AppCompatActivity() {
private var uniqueLoginKey: String? = null
private var lastTheme: ThemeValue? = null
// Facilitates the db updates by the sync service on the UI
private val serviceSyncReceiver = object : NBSyncReceiver() {
override fun handleUpdateType(updateType: Int) {
runOnUiThread { handleUpdate(updateType) }
}
}
override fun onCreate(bundle: Bundle?) {
Log.offerContext(this)
Log.d(this, "onCreate")
// this is not redundant to the applyThemePreference() call in onResume. the theme needs to be set
// before onCreate() in order to work
PrefsUtils.applyThemePreference(this)
lastTheme = PrefsUtils.getSelectedTheme(this)
// in rare cases of process interruption or DB corruption, an activity can launch without valid
// login creds. redirect the user back to the loging workflow.
if (PrefsUtils.getUserId(this) == null) {
Log.e(this, "post-login activity launched without valid login.")
PrefsUtils.logout(this)
finish()
}
super.onCreate(bundle)
offerInitContext(this)
bundle?.let {
uniqueLoginKey = it.getString(UNIQUE_LOGIN_KEY)
}
if (uniqueLoginKey == null) {
uniqueLoginKey = PrefsUtils.getUniqueLoginKey(this)
}
finishIfNotLoggedIn()
}
override fun onResume() {
Log.d(this, "onResume" + UIUtils.getMemoryUsageDebug(this))
super.onResume()
finishIfNotLoggedIn()
// is is possible that another activity changed the theme while we were on the backstack
val currentSelectedTheme = PrefsUtils.getSelectedTheme(this)
if (lastTheme != currentSelectedTheme) {
lastTheme = currentSelectedTheme
PrefsUtils.applyThemePreference(this)
UIUtils.restartActivity(this)
}
registerReceiver(serviceSyncReceiver, IntentFilter().apply {
addAction(NBSyncReceiver.NB_SYNC_ACTION)
})
}
override fun onPause() {
Log.d(this.javaClass.name, "onPause")
super.onPause()
unregisterReceiver(serviceSyncReceiver)
}
private fun finishIfNotLoggedIn() {
val currentLoginKey = PrefsUtils.getUniqueLoginKey(this)
if (currentLoginKey == null || currentLoginKey != uniqueLoginKey) {
Log.d(this.javaClass.name, "This activity was for a different login. finishing it.")
finish()
}
}
override fun onSaveInstanceState(savedInstanceState: Bundle) {
Log.d(this, "onSave")
savedInstanceState.putString(UNIQUE_LOGIN_KEY, uniqueLoginKey)
super.onSaveInstanceState(savedInstanceState)
}
/**
* Pokes the sync service to perform any pending sync actions.
*/
protected fun triggerSync() {
triggerSync(this)
}
/**
* Called on each NB activity after the DB has been updated by the sync service.
*
* @param updateType one or more of the UPDATE_* flags in this class to indicate the
* type of update being broadcast.
*/
protected open fun handleUpdate(updateType: Int) {
Log.w(this, "activity doesn't implement handleUpdate")
}
}
private const val UNIQUE_LOGIN_KEY = "uniqueLoginKey"

View file

@ -24,6 +24,9 @@ import com.newsblur.databinding.ActivityReadingBinding
import com.newsblur.domain.Story
import com.newsblur.fragment.ReadingItemFragment
import com.newsblur.fragment.ReadingPagerFragment
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_REBUILD
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_STATUS
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_STORY
import com.newsblur.service.NBSyncService
import com.newsblur.util.*
import com.newsblur.util.PrefConstants.ThemeValue

View file

@ -24,6 +24,7 @@ import com.newsblur.network.domain.CommentResponse;
import com.newsblur.network.domain.StoriesResponse;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedSet;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.ReadingAction;
import com.newsblur.util.ReadFilter;
@ -1641,6 +1642,10 @@ public class BlurDatabaseHelper {
try {c.close();} catch (Exception e) {;}
}
public void sendSyncUpdate(int updateType) {
FeedUtils.syncUpdateStatus(context, updateType);
}
private static String conjoinSelections(CharSequence... args) {
StringBuilder s = null;
for (CharSequence c : args) {
@ -1674,5 +1679,4 @@ public class BlurDatabaseHelper {
private Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, CancellationSignal cancellationSignal) {
return dbRO.query(distinct, table, columns, selection, selectionArgs, groupBy, having, orderBy, limit, cancellationSignal);
}
}

View file

@ -1,5 +1,7 @@
package com.newsblur.database;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
@ -10,7 +12,6 @@ import androidx.viewpager.widget.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;
import com.newsblur.activity.NbActivity;
import com.newsblur.activity.Reading;
import com.newsblur.domain.Classifier;
import com.newsblur.domain.Story;
@ -283,7 +284,7 @@ public class ReadingAdapter extends PagerAdapter {
ReadingItemFragment rif = fragments.get(s.storyHash);
if (rif != null ) {
rif.offerStoryUpdate(s);
rif.handleUpdate(NbActivity.UPDATE_STORY);
rif.handleUpdate(UPDATE_STORY);
}
}
}

View file

@ -2,11 +2,9 @@ package com.newsblur.fragment;
import com.newsblur.R;
import com.newsblur.activity.ItemsList;
import com.newsblur.activity.NbActivity;
import com.newsblur.domain.Feed;
import com.newsblur.domain.SavedSearch;
import com.newsblur.domain.SocialFeed;
import com.newsblur.network.APIManager;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.UIUtils;
@ -75,18 +73,17 @@ public class DeleteFeedFragment extends DialogFragment {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
if (getArguments().getString(FEED_TYPE).equals(NORMAL_FEED)) {
FeedUtils.deleteFeed(getArguments().getString(FEED_ID), getArguments().getString(FOLDER_NAME), new APIManager(getActivity()));
FeedUtils.deleteFeed(getArguments().getString(FEED_ID), getArguments().getString(FOLDER_NAME), getActivity());
} else if (getArguments().getString(FEED_TYPE).equals(SAVED_SEARCH_FEED)) {
FeedUtils.deleteSavedSearch(getArguments().getString(FEED_ID), getArguments().getString(QUERY), new APIManager(getActivity()));
FeedUtils.deleteSavedSearch(getArguments().getString(FEED_ID), getArguments().getString(QUERY), getActivity());
} else {
FeedUtils.deleteSocialFeed(getArguments().getString(FEED_ID), new APIManager(getActivity()));
FeedUtils.deleteSocialFeed(getArguments().getString(FEED_ID), getActivity());
}
// if called from a feed view, end it
Activity activity = DeleteFeedFragment.this.getActivity();
if (activity instanceof ItemsList) {
activity.finish();
}
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA);
DeleteFeedFragment.this.dismiss();
}
});

View file

@ -19,7 +19,6 @@ import androidx.lifecycle.lifecycleScope
import com.google.android.material.chip.Chip
import com.newsblur.R
import com.newsblur.activity.FeedItemsList
import com.newsblur.activity.NbActivity
import com.newsblur.activity.Reading
import com.newsblur.databinding.FragmentReadingitemBinding
import com.newsblur.databinding.IncludeReadingItemCommentBinding
@ -28,6 +27,10 @@ import com.newsblur.domain.Story
import com.newsblur.domain.UserDetails
import com.newsblur.fragment.StoryUserTagsFragment.Companion.newInstance
import com.newsblur.network.APIManager
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_INTEL
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_SOCIAL
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_STORY
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_TEXT
import com.newsblur.service.OriginalTextService
import com.newsblur.util.*
import com.newsblur.util.PrefConstants.ThemeValue
@ -598,19 +601,19 @@ class ReadingItemFragment : NbFragment(), PopupMenu.OnMenuItemClickListener {
}
fun handleUpdate(updateType: Int) {
if (updateType and NbActivity.UPDATE_STORY != 0) {
if (updateType and UPDATE_STORY != 0) {
updateSaveButton()
updateShareButton()
setupItemCommentsAndShares()
}
if (updateType and NbActivity.UPDATE_TEXT != 0) {
if (updateType and UPDATE_TEXT != 0) {
reloadStoryContent()
}
if (updateType and NbActivity.UPDATE_SOCIAL != 0) {
if (updateType and UPDATE_SOCIAL != 0) {
updateShareButton()
setupItemCommentsAndShares()
}
if (updateType and NbActivity.UPDATE_INTEL != 0) {
if (updateType and UPDATE_INTEL != 0) {
classifier = FeedUtils.dbHelper!!.getClassifierForFeed(story!!.feedId)
setupTagsAndIntel()
}

View file

@ -14,6 +14,7 @@ public class NewsBlurResponse {
public String message;
public String[] errors;
public long readTime;
public int impactCode;
public boolean isError() {
if (isProtocolError) return true;

View file

@ -0,0 +1,31 @@
package com.newsblur.service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
abstract class NBSyncReceiver : BroadcastReceiver() {
abstract fun handleUpdateType(updateType: Int)
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == NB_SYNC_ACTION) {
handleUpdateType(intent.getIntExtra(NB_SYNC_UPDATE_TYPE, 0))
}
}
companion object {
const val NB_SYNC_ACTION = "nb.sync.action"
const val NB_SYNC_ERROR_MESSAGE = "nb_sync_error_msg"
const val NB_SYNC_UPDATE_TYPE = "nb_sync_update_type"
const val UPDATE_DB_READY = 1 shl 0
const val UPDATE_METADATA = 1 shl 1
const val UPDATE_STORY = 1 shl 2
const val UPDATE_SOCIAL = 1 shl 3
const val UPDATE_INTEL = 1 shl 4
const val UPDATE_STATUS = 1 shl 5
const val UPDATE_TEXT = 1 shl 6
const val UPDATE_REBUILD = 1 shl 7
}
}

View file

@ -9,10 +9,18 @@ import android.content.Intent;
import android.database.Cursor;
import android.os.Process;
import com.newsblur.NbApplication;
import com.newsblur.R;
import com.newsblur.activity.NbActivity;
import com.newsblur.database.BlurDatabaseHelper;
import static com.newsblur.database.BlurDatabaseHelper.closeQuietly;
import static com.newsblur.service.NBSyncReceiver.UPDATE_DB_READY;
import static com.newsblur.service.NBSyncReceiver.UPDATE_METADATA;
import static com.newsblur.service.NBSyncReceiver.UPDATE_REBUILD;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
import androidx.annotation.NonNull;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Folder;
@ -182,7 +190,7 @@ public class NBSyncService extends JobService {
public int onStartCommand(Intent intent, int flags, final int startId) {
com.newsblur.util.Log.d(this, "onStartCommand");
// only perform a sync if the app is actually running or background syncs are enabled
if ((NbActivity.getActiveActivityCount() > 0) || PrefsUtils.isBackgroundNeeded(this)) {
if (NbApplication.isAppForeground() || PrefsUtils.isBackgroundNeeded(this)) {
HaltNow = false;
// Services actually get invoked on the main system thread, and are not
// allowed to do tangible work. We spawn a thread to do so.
@ -215,7 +223,7 @@ public class NBSyncService extends JobService {
public boolean onStartJob(final JobParameters params) {
com.newsblur.util.Log.d(this, "onStartJob");
// only perform a sync if the app is actually running or background syncs are enabled
if ((NbActivity.getActiveActivityCount() > 0) || PrefsUtils.isBackgroundNeeded(this)) {
if (NbApplication.isAppForeground() || PrefsUtils.isBackgroundNeeded(this)) {
HaltNow = false;
// Services actually get invoked on the main system thread, and are not
// allowed to do tangible work. We spawn a thread to do so.
@ -260,7 +268,7 @@ public class NBSyncService extends JobService {
Log.d(this, "starting primary sync");
if (NbActivity.getActiveActivityCount() < 1) {
if (!NbApplication.isAppForeground()) {
// if the UI isn't running, politely run at background priority
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
} else {
@ -273,7 +281,7 @@ public class NBSyncService extends JobService {
if (OfflineNow) {
if (NetworkUtils.isOnline(this)) {
OfflineNow = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STATUS);
} else {
com.newsblur.util.Log.d(this, "Abandoning sync: network still offline");
return;
@ -285,7 +293,7 @@ public class NBSyncService extends JobService {
housekeeping();
// check to see if we are on an allowable network only after ensuring we have CPU
if (!( (NbActivity.getActiveActivityCount() > 0) ||
if (!( NbApplication.isAppForeground() ||
PrefsUtils.isEnableNotifications(this) ||
PrefsUtils.isBackgroundNetworkAllowed(this) ||
WidgetUtils.hasActiveAppWidgets(this)) ) {
@ -294,7 +302,7 @@ public class NBSyncService extends JobService {
}
// ping activities to indicate that housekeeping is done, and the DB is safe to use
NbActivity.updateAllActivities(NbActivity.UPDATE_DB_READY);
sendSyncUpdate(UPDATE_DB_READY);
// async text requests might have been queued up and are being waiting on by the live UI. give them priority
originalTextService.start();
@ -337,10 +345,10 @@ public class NBSyncService extends JobService {
boolean upgraded = PrefsUtils.checkForUpgrade(this);
if (upgraded) {
HousekeepingRunning = true;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS | NbActivity.UPDATE_REBUILD);
sendSyncUpdate(UPDATE_STATUS | UPDATE_REBUILD);
// wipe the local DB if this is a first background run. if this is a first foreground
// run, InitActivity will have wiped for us
if (NbActivity.getActiveActivityCount() < 1) {
if (!NbApplication.isAppForeground()) {
dbHelper.dropAndRecreateTables();
}
// in case this is the first time we have run since moving the cache to the new location,
@ -353,11 +361,11 @@ public class NBSyncService extends JobService {
boolean autoVac = PrefsUtils.isTimeToVacuum(this);
// this will lock up the DB for a few seconds, only do it if the UI is hidden
if (NbActivity.getActiveActivityCount() > 0) autoVac = false;
if (NbApplication.isAppForeground()) autoVac = false;
if (upgraded || autoVac) {
HousekeepingRunning = true;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STATUS);
com.newsblur.util.Log.i(this.getClass().getName(), "rebuilding DB . . .");
dbHelper.vacuum();
com.newsblur.util.Log.i(this.getClass().getName(), ". . . . done rebuilding DB");
@ -366,7 +374,7 @@ public class NBSyncService extends JobService {
} finally {
if (HousekeepingRunning) {
HousekeepingRunning = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA);
sendSyncUpdate(UPDATE_METADATA);
}
}
}
@ -387,7 +395,7 @@ public class NBSyncService extends JobService {
ActionsRunning = true;
actionsloop : while (c.moveToNext()) {
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STATUS);
String id = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_ID));
ReadingAction ra;
try {
@ -419,18 +427,19 @@ public class NBSyncService extends JobService {
com.newsblur.util.Log.i(this.getClass().getName(), "Discarding reading action with fatal message.");
dbHelper.clearAction(id);
String message = response.getErrorMessage(null);
if (message != null) NbActivity.toastError(message);
if (message != null) sendToastError(message);
} else {
// success!
dbHelper.clearAction(id);
FollowupActions.add(ra);
sendSyncUpdate(response.impactCode);
}
lastActionCount--;
}
} finally {
closeQuietly(c);
ActionsRunning = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STATUS);
}
}
@ -448,7 +457,7 @@ public class NBSyncService extends JobService {
int impact = ra.doLocal(dbHelper, true);
impactFlags |= impact;
}
NbActivity.updateAllActivities(impactFlags);
sendSyncUpdate(impactFlags);
// if there is a feed fetch loop running, don't clear, there will likely be races for
// stories that were just tapped as they were being re-fetched
@ -484,7 +493,7 @@ public class NBSyncService extends JobService {
com.newsblur.util.Log.i(this.getClass().getName(), "ready to sync feed list");
FFSyncRunning = true;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STATUS);
// there is an issue with feeds that have no folder or folders that list feeds that do not exist. capture them for workarounds.
Set<String> debugFeedIdsFromFolders = new HashSet<String>();
@ -618,7 +627,7 @@ public class NBSyncService extends JobService {
} finally {
FFSyncRunning = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA | NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_METADATA | UPDATE_STATUS);
}
}
@ -633,7 +642,7 @@ public class NBSyncService extends JobService {
if (RecountCandidates.size() < 1) return;
RecountsRunning = true;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STATUS);
// of all candidate feeds that were touched, now check to see if any
// actually need their counts fetched
@ -699,7 +708,7 @@ public class NBSyncService extends JobService {
} finally {
if (RecountsRunning) {
RecountsRunning = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA | NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_METADATA | UPDATE_STATUS);
}
FlushRecounts = false;
}
@ -760,7 +769,7 @@ public class NBSyncService extends JobService {
ReadFilter filter = PrefsUtils.getReadFilter(this, fs);
StorySyncRunning = true;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STATUS);
while (totalStoriesSeen < PendingFeedTarget) {
if (stopSync()) return;
@ -784,7 +793,7 @@ public class NBSyncService extends JobService {
insertStories(apiResponse, fs);
// re-do any very recent actions that were incorrectly overwritten by this page
finishActions();
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY | NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STORY | UPDATE_STATUS);
prefetchOriginalText(apiResponse);
@ -804,7 +813,7 @@ public class NBSyncService extends JobService {
} finally {
StorySyncRunning = false;
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
sendSyncUpdate(UPDATE_STATUS);
synchronized (PENDING_FEED_MUTEX) {
if (finished && fs.equals(PendingFeed)) PendingFeed = null;
}
@ -996,7 +1005,7 @@ public class NBSyncService extends JobService {
}
private boolean backoffBackgroundCalls() {
if (NbActivity.getActiveActivityCount() > 0) return false;
if (NbApplication.isAppForeground()) return false;
if (System.currentTimeMillis() > (lastAPIFailure + AppConstants.API_BACKGROUND_BACKOFF_MILLIS)) return false;
com.newsblur.util.Log.i(this.getClass().getName(), "abandoning background sync due to recent API failures.");
return true;
@ -1128,7 +1137,7 @@ public class NBSyncService extends JobService {
dbHelper.prepareReadingSession(fs);
// note which feedset we are loading so we can trigger another reset when it changes
dbHelper.setSessionFeedSet(fs);
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY | NbActivity.UPDATE_STATUS);
dbHelper.sendSyncUpdate(UPDATE_STORY | UPDATE_STATUS);
}
}
}
@ -1241,4 +1250,21 @@ public class NBSyncService extends JobService {
return s.toString();
}
protected void sendSyncUpdate(int update) {
Intent i = new Intent(NBSyncReceiver.NB_SYNC_ACTION);
i.putExtra(NBSyncReceiver.NB_SYNC_UPDATE_TYPE, update);
broadcastSync(i);
}
protected void sendToastError(@NonNull String message) {
Intent i = new Intent(NBSyncReceiver.NB_SYNC_ACTION);
i.putExtra(NBSyncReceiver.NB_SYNC_ERROR_MESSAGE, message);
broadcastSync(i);
}
private void broadcastSync(@NonNull Intent intent) {
if (NbApplication.isAppForeground()) {
sendBroadcast(intent);
}
}
}

View file

@ -1,6 +1,7 @@
package com.newsblur.service;
import com.newsblur.activity.NbActivity;
import static com.newsblur.service.NBSyncReceiver.UPDATE_TEXT;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.network.domain.StoryTextResponse;
import com.newsblur.util.AppConstants;
@ -84,7 +85,7 @@ public class OriginalTextService extends SubService {
}
}
} finally {
gotData(NbActivity.UPDATE_TEXT);
parent.sendSyncUpdate(UPDATE_TEXT);
hashes.removeAll(fetchedHashes);
}
}

View file

@ -1,8 +1,10 @@
package com.newsblur.service;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
import android.os.Process;
import com.newsblur.activity.NbActivity;
import com.newsblur.NbApplication;
import com.newsblur.util.AppConstants;
import com.newsblur.util.Log;
@ -36,7 +38,7 @@ public abstract class SubService {
Runnable r = new Runnable() {
public void run() {
if (parent.stopSync()) return;
if (NbActivity.getActiveActivityCount() < 1) {
if (!NbApplication.isAppForeground()) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_LESS_FAVORABLE );
} else {
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE + Process.THREAD_PRIORITY_LESS_FAVORABLE );
@ -52,7 +54,7 @@ public abstract class SubService {
executor.execute(new Runnable() {
public void run() {
parent.checkCompletion();
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
parent.sendSyncUpdate(UPDATE_STATUS);
}
});
} catch (RejectedExecutionException ree) {
@ -85,10 +87,6 @@ public abstract class SubService {
}
}
protected void gotData(int updateType) {
NbActivity.updateAllActivities(updateType);
}
public boolean isRunning() {
// don't advise completion until there are no tasks, or just one check task left
return (executor.getQueue().size() > 0);
@ -115,7 +113,7 @@ public abstract class SubService {
long cooloffTimeMs = Math.round(cooloffTime / 1000000.0);
if (cooloffTimeMs > AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS) cooloffTimeMs = AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS;
if (NbActivity.getActiveActivityCount() > 0 ) {
if (NbApplication.isAppForeground()) {
com.newsblur.util.Log.d(this.getClass().getName(), "Sleeping for : " + cooloffTimeMs + "ms to enforce max duty cycle.");
try {
Thread.sleep(cooloffTimeMs);

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.text.TextUtils
import com.newsblur.NbApplication
import com.newsblur.R
import com.newsblur.activity.NbActivity
import com.newsblur.database.BlurDatabaseHelper
@ -12,6 +13,10 @@ import com.newsblur.fragment.ReadingActionConfirmationFragment
import com.newsblur.network.APIConstants
import com.newsblur.network.APIManager
import com.newsblur.service.NBSyncService
import com.newsblur.service.NBSyncReceiver
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_METADATA
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_SOCIAL
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_STORY
import kotlinx.coroutines.GlobalScope
import java.util.*
@ -100,7 +105,7 @@ object FeedUtils {
doInBackground = {
val ra = if (saved) ReadingAction.saveStory(storyHash, userTags) else ReadingAction.unsaveStory(storyHash)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY)
syncUpdateStatus(context, UPDATE_STORY)
dbHelper!!.enqueueAction(ra)
triggerSync(context)
}
@ -108,15 +113,15 @@ object FeedUtils {
}
@JvmStatic
fun deleteSavedSearch(feedId: String?, query: String?, apiManager: APIManager) {
fun deleteSavedSearch(feedId: String?, query: String?, context: Context) {
GlobalScope.executeAsyncTask(
doInBackground = {
apiManager.deleteSearch(feedId, query)
APIManager(context).deleteSearch(feedId, query)
},
onPostExecute = { newsBlurResponse ->
if (!newsBlurResponse.isError) {
dbHelper!!.deleteSavedSearch(feedId, query)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA)
syncUpdateStatus(context, UPDATE_METADATA)
}
}
)
@ -138,29 +143,29 @@ object FeedUtils {
}
@JvmStatic
fun deleteFeed(feedId: String?, folderName: String?, apiManager: APIManager) {
fun deleteFeed(feedId: String?, folderName: String?, context: Context) {
GlobalScope.executeAsyncTask(
doInBackground = {
apiManager.deleteFeed(feedId, folderName)
APIManager(context).deleteFeed(feedId, folderName)
},
onPostExecute = {
// TODO: we can't check result.isError() because the delete call sets the .message property on all calls. find a better error check
dbHelper!!.deleteFeed(feedId)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA)
syncUpdateStatus(context, UPDATE_METADATA)
}
)
}
@JvmStatic
fun deleteSocialFeed(userId: String?, apiManager: APIManager) {
fun deleteSocialFeed(userId: String?, context: Context) {
GlobalScope.executeAsyncTask(
doInBackground = {
apiManager.unfollowUser(userId)
APIManager(context).unfollowUser(userId)
},
onPostExecute = {
// TODO: we can't check result.isError() because the delete call sets the .message property on all calls. find a better error check
dbHelper!!.deleteSocialFeed(userId)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA)
syncUpdateStatus(context, UPDATE_METADATA)
}
)
}
@ -238,7 +243,7 @@ object FeedUtils {
// update unread state and unread counts in the local DB
val impactedFeeds = dbHelper!!.setStoryReadState(story, read)
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY)
syncUpdateStatus(context, UPDATE_STORY)
NBSyncService.addRecountCandidates(impactedFeeds)
triggerSync(context)
@ -373,7 +378,7 @@ object FeedUtils {
doInBackground = {
dbHelper!!.enqueueAction(ra)
val impact = ra.doLocal(dbHelper)
NbActivity.updateAllActivities(impact)
syncUpdateStatus(context, impact)
triggerSync(context)
}
)
@ -418,7 +423,7 @@ object FeedUtils {
val ra = ReadingAction.shareStory(story.storyHash, story.id, story.feedId, sourceUserId, comment)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL or NbActivity.UPDATE_STORY)
syncUpdateStatus(context, UPDATE_SOCIAL or UPDATE_STORY)
triggerSync(context)
}
@ -427,7 +432,7 @@ object FeedUtils {
val ra = ReadingAction.renameFeed(feedId, newFeedName)
dbHelper!!.enqueueAction(ra)
val impact = ra.doLocal(dbHelper)
NbActivity.updateAllActivities(impact)
syncUpdateStatus(context, impact)
triggerSync(context)
}
@ -436,7 +441,7 @@ object FeedUtils {
val ra = ReadingAction.unshareStory(story.storyHash, story.id, story.feedId)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL or NbActivity.UPDATE_STORY)
syncUpdateStatus(context, UPDATE_SOCIAL or UPDATE_STORY)
triggerSync(context)
}
@ -444,7 +449,7 @@ object FeedUtils {
val ra = ReadingAction.likeComment(story.id, commentUserId, story.feedId)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
}
@ -452,7 +457,7 @@ object FeedUtils {
val ra = ReadingAction.unlikeComment(story.id, commentUserId, story.feedId)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
}
@ -461,7 +466,7 @@ object FeedUtils {
val ra = ReadingAction.replyToComment(storyId, feedId, commentUserId, replyText)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
}
@ -470,7 +475,7 @@ object FeedUtils {
val ra = ReadingAction.updateReply(story.id, story.feedId, commentUserId, replyId, replyText)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
}
@ -479,7 +484,7 @@ object FeedUtils {
val ra = ReadingAction.deleteReply(story.id, story.feedId, commentUserId, replyId)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL)
syncUpdateStatus(context, UPDATE_SOCIAL)
triggerSync(context)
}
@ -529,7 +534,7 @@ object FeedUtils {
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA)
syncUpdateStatus(context, UPDATE_METADATA)
triggerSync(context)
}
)
@ -540,7 +545,7 @@ object FeedUtils {
val ra = ReadingAction.instaFetch(feedId)
dbHelper!!.enqueueAction(ra)
ra.doLocal(dbHelper)
NbActivity.updateAllActivities(NbActivity.UPDATE_METADATA)
syncUpdateStatus(context, UPDATE_METADATA)
triggerSync(context)
}
@ -593,4 +598,15 @@ object FeedUtils {
val url = APIConstants.buildUrl(APIConstants.PATH_FEED_STATISTICS + feedId)
UIUtils.handleUri(context, Uri.parse(url))
}
@JvmStatic
fun syncUpdateStatus(context: Context, updateType: Int) {
if (NbApplication.isAppForeground) {
Intent(NBSyncReceiver.NB_SYNC_ACTION).apply {
putExtra(NBSyncReceiver.NB_SYNC_UPDATE_TYPE, updateType)
}.also {
context.sendBroadcast(it)
}
}
}
}

View file

@ -1,11 +1,15 @@
package com.newsblur.util;
import static com.newsblur.service.NBSyncReceiver.UPDATE_INTEL;
import static com.newsblur.service.NBSyncReceiver.UPDATE_METADATA;
import static com.newsblur.service.NBSyncReceiver.UPDATE_SOCIAL;
import static com.newsblur.service.NBSyncReceiver.UPDATE_STORY;
import android.content.ContentValues;
import android.database.Cursor;
import java.io.Serializable;
import com.newsblur.activity.NbActivity;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.domain.Classifier;
@ -13,13 +17,13 @@ import com.newsblur.network.domain.CommentResponse;
import com.newsblur.network.domain.NewsBlurResponse;
import com.newsblur.network.domain.StoriesResponse;
import com.newsblur.network.APIManager;
import com.newsblur.service.NBSyncReceiver;
import com.newsblur.service.NBSyncService;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@SuppressWarnings("serial")
public class ReadingAction implements Serializable {
private static final long serialVersionUID = 0L;
@ -370,7 +374,7 @@ public class ReadingAction implements Serializable {
} else {
com.newsblur.util.Log.w(this, "failed to refresh story data after action");
}
impact |= NbActivity.UPDATE_SOCIAL;
impact |= NBSyncReceiver.UPDATE_SOCIAL;
}
if (commentResponse != null) {
result = commentResponse;
@ -379,10 +383,11 @@ public class ReadingAction implements Serializable {
} else {
com.newsblur.util.Log.w(this, "failed to refresh comment data after action");
}
impact |= NbActivity.UPDATE_SOCIAL;
impact |= NBSyncReceiver.UPDATE_SOCIAL;
}
if (result != null && impact != 0) {
result.impactCode = impact;
}
NbActivity.updateAllActivities(impact);
return result;
}
@ -408,48 +413,48 @@ public class ReadingAction implements Serializable {
dbHelper.markStoriesRead(feedSet, olderThan, newerThan);
dbHelper.updateLocalFeedCounts(feedSet);
}
impact |= NbActivity.UPDATE_METADATA;
impact |= NbActivity.UPDATE_STORY;
impact |= UPDATE_METADATA;
impact |= UPDATE_STORY;
break;
case MARK_UNREAD:
dbHelper.setStoryReadState(storyHash, false);
impact |= NbActivity.UPDATE_METADATA;
impact |= UPDATE_METADATA;
break;
case SAVE:
dbHelper.setStoryStarred(storyHash, userTags, true);
impact |= NbActivity.UPDATE_METADATA;
impact |= UPDATE_METADATA;
break;
case UNSAVE:
dbHelper.setStoryStarred(storyHash, null, false);
impact |= NbActivity.UPDATE_METADATA;
impact |= UPDATE_METADATA;
break;
case SHARE:
if (isFollowup) break; // shares are only placeholders
dbHelper.setStoryShared(storyHash, true);
dbHelper.insertCommentPlaceholder(storyId, feedId, commentReplyText);
impact |= NbActivity.UPDATE_SOCIAL;
impact |= NbActivity.UPDATE_STORY;
impact |= UPDATE_SOCIAL;
impact |= UPDATE_STORY;
break;
case UNSHARE:
dbHelper.setStoryShared(storyHash, false);
dbHelper.clearSelfComments(storyId);
impact |= NbActivity.UPDATE_SOCIAL;
impact |= NbActivity.UPDATE_STORY;
impact |= UPDATE_SOCIAL;
impact |= UPDATE_STORY;
break;
case LIKE_COMMENT:
dbHelper.setCommentLiked(storyId, commentUserId, feedId, true);
impact |= NbActivity.UPDATE_SOCIAL;
impact |= UPDATE_SOCIAL;
break;
case UNLIKE_COMMENT:
dbHelper.setCommentLiked(storyId, commentUserId, feedId, false);
impact |= NbActivity.UPDATE_SOCIAL;
impact |= UPDATE_SOCIAL;
break;
case REPLY:
@ -459,22 +464,22 @@ public class ReadingAction implements Serializable {
case EDIT_REPLY:
dbHelper.editReply(replyId, commentReplyText);
impact |= NbActivity.UPDATE_SOCIAL;
impact |= UPDATE_SOCIAL;
break;
case DELETE_REPLY:
dbHelper.deleteReply(replyId);
impact |= NbActivity.UPDATE_SOCIAL;
impact |= UPDATE_SOCIAL;
break;
case MUTE_FEEDS:
case UNMUTE_FEEDS:
dbHelper.setFeedsActive(modifiedFeedIds, type == ActionType.UNMUTE_FEEDS);
impact |= NbActivity.UPDATE_METADATA;
impact |= UPDATE_METADATA;
break;
case SET_NOTIFY:
impact |= NbActivity.UPDATE_METADATA;
impact |= UPDATE_METADATA;
break;
case INSTA_FETCH:
@ -489,12 +494,12 @@ public class ReadingAction implements Serializable {
dbHelper.clearClassifiersForFeed(feedId);
classifier.feedId = feedId;
dbHelper.insertClassifier(classifier);
impact |= NbActivity.UPDATE_INTEL;
impact |= UPDATE_INTEL;
break;
case RENAME_FEED:
dbHelper.renameFeed(feedId, newFeedName);
impact |= NbActivity.UPDATE_METADATA;
impact |= UPDATE_METADATA;
break;
default: