diff --git a/clients/android/NewsBlur/AndroidManifest.xml b/clients/android/NewsBlur/AndroidManifest.xml index 2cf5b8fab..2d7e8643f 100644 --- a/clients/android/NewsBlur/AndroidManifest.xml +++ b/clients/android/NewsBlur/AndroidManifest.xml @@ -122,8 +122,6 @@ - - diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/Main.java b/clients/android/NewsBlur/src/com/newsblur/activity/Main.java index 4efc10ec2..5856810a9 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/Main.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/Main.java @@ -15,20 +15,17 @@ import android.view.Window; import com.newsblur.R; import com.newsblur.fragment.FolderListFragment; import com.newsblur.fragment.LogoutDialogFragment; -import com.newsblur.fragment.SyncUpdateFragment; import com.newsblur.service.BootReceiver; -import com.newsblur.service.SyncService; import com.newsblur.service.NBSyncService; import com.newsblur.util.FeedUtils; import com.newsblur.util.PrefsUtils; import com.newsblur.view.StateToggleButton.StateChangedListener; -public class Main extends NbActivity implements StateChangedListener, SyncUpdateFragment.SyncUpdateFragmentInterface { +public class Main extends NbActivity implements StateChangedListener { private ActionBar actionBar; private FolderListFragment folderFeedList; private FragmentManager fragmentManager; - private SyncUpdateFragment syncFragment; private static final String TAG = "MainActivity"; private Menu menu; @@ -48,18 +45,9 @@ public class Main extends NbActivity implements StateChangedListener, SyncUpdate fragmentManager = getFragmentManager(); folderFeedList = (FolderListFragment) fragmentManager.findFragmentByTag("folderFeedListFragment"); folderFeedList.setRetainInstance(true); - - syncFragment = (SyncUpdateFragment) fragmentManager.findFragmentByTag(SyncUpdateFragment.TAG); - if (syncFragment == null) { - syncFragment = new SyncUpdateFragment(); - fragmentManager.beginTransaction().add(syncFragment, SyncUpdateFragment.TAG).commit(); - // for our first sync, don't just trigger a heavyweight refresh, do it in two steps - // so the UI appears more quickly (per the docs at newsblur.com/api) - if (PrefsUtils.isTimeToAutoSync(this)) { - triggerFirstSync(); - } - } + //also make sure the interval sync is scheduled, in case it was just now enabled + BootReceiver.scheduleSyncService(this); } @Override @@ -71,40 +59,11 @@ public class Main extends NbActivity implements StateChangedListener, SyncUpdate // clear all stories from the DB, the story activities will load them. FeedUtils.clearStories(this); - //trigger an autosync right now, just in case - Intent i = new Intent(this, NBSyncService.class); - startService(i); - //also make sure the interval sync is scheduled, in case it was just now enabled - BootReceiver.scheduleSyncService(this); } - /** - * Triggers an initial two-phase sync, so the UI can display quickly using /reader/feeds and - * then call /reader/refresh_feeds to get updated counts. - */ - private void triggerFirstSync() { - PrefsUtils.updateLastSyncTime(this); - setProgressBarIndeterminateVisibility(true); - setRefreshEnabled(false); - - final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class); - intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver); - intent.putExtra(SyncService.EXTRA_TASK_TYPE, SyncService.TaskType.FOLDER_UPDATE_TWO_STEP); - startService(intent); - } - - /** - * Triggers a full, manually requested refresh of feed/folder data and counts. - */ private void triggerRefresh() { - PrefsUtils.updateLastSyncTime(this); - setProgressBarIndeterminateVisibility(true); - setRefreshEnabled(false); - - final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, SyncService.class); - intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, syncFragment.receiver); - intent.putExtra(SyncService.EXTRA_TASK_TYPE, SyncService.TaskType.FOLDER_UPDATE_WITH_COUNT); - startService(intent); + Intent i = new Intent(this, NBSyncService.class); + startService(i); } private void setupActionBar() { @@ -156,39 +115,18 @@ public class Main extends NbActivity implements StateChangedListener, SyncUpdate } } - /** - * Called after the sync service completely finishes a task. - */ @Override - public void updateAfterSync() { + public void handleUpdate() { folderFeedList.hasUpdated(); - setProgressBarIndeterminateVisibility(false); - setRefreshEnabled(true); - } - - /** - * Called when the sync service has made enough progress to update the UI but not - * enough to stop the progress indicator. - */ - @Override - public void updatePartialSync() { - // TODO: move 2-step sync to new async lib and remove this method entirely - // folderFeedList.hasUpdated(); - } - - @Override - public void updateSyncStatus(boolean syncRunning) { - // TODO: the progress bar is activated manually elsewhere in this activity. this - // interface method may be redundant. - if (syncRunning) { - setProgressBarIndeterminateVisibility(true); + if (NBSyncService.isSyncRunning()) { + setProgressBarIndeterminateVisibility(true); setRefreshEnabled(false); - } + } else { + setProgressBarIndeterminateVisibility(false); + setRefreshEnabled(true); + } } - @Override - public void setNothingMoreToUpdate() { } - private void setRefreshEnabled(boolean enabled) { if (menu != null) { MenuItem item = menu.findItem(R.id.menu_refresh); diff --git a/clients/android/NewsBlur/src/com/newsblur/activity/NbActivity.java b/clients/android/NewsBlur/src/com/newsblur/activity/NbActivity.java index 27cdbb006..8db0927da 100644 --- a/clients/android/NewsBlur/src/com/newsblur/activity/NbActivity.java +++ b/clients/android/NewsBlur/src/com/newsblur/activity/NbActivity.java @@ -7,10 +7,19 @@ import android.util.Log; import com.newsblur.util.AppConstants; import com.newsblur.util.PrefsUtils; +import java.util.ArrayList; + public class NbActivity extends Activity { private final static String UNIQUE_LOGIN_KEY = "uniqueLoginKey"; private String uniqueLoginKey; + + /** + * 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 AllActivities = new ArrayList(); @Override protected void onCreate(Bundle bundle) { @@ -31,6 +40,20 @@ public class NbActivity extends Activity { if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "onResume"); super.onResume(); finishIfNotLoggedIn(); + + synchronized (AllActivities) { + AllActivities.add(this); + } + } + + @Override + protected void onPause() { + if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "onSuspend"); + super.onPause(); + + synchronized (AllActivities) { + AllActivities.remove(this); + } } protected void finishIfNotLoggedIn() { @@ -48,4 +71,31 @@ public class NbActivity extends Activity { super.onSaveInstanceState(savedInstanceState); } + /** + * Called on each NB activity after the DB has been updated by the sync service. This method + * should return as quickly as possible. + */ + protected void handleUpdate() {;} + + private void _handleUpdate() { + runOnUiThread(new Runnable() { + public void run() { + handleUpdate(); + } + }); + } + + /** + * Notify all activities in the app that the DB has been updated. + */ + public static void updateAllActivities() { + Log.d(NbActivity.class.getName(), "updating all activities . . ."); + synchronized (AllActivities) { + for (NbActivity activity : AllActivities) { + activity._handleUpdate(); + } + } + Log.d(NbActivity.class.getName(), " . . . done updating all activities"); + } + } diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/DeleteFeedFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/DeleteFeedFragment.java index 9724f73d1..e1e145325 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/DeleteFeedFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/DeleteFeedFragment.java @@ -11,7 +11,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.app.DialogFragment; -import android.app.FragmentManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -35,20 +34,10 @@ public class DeleteFeedFragment extends DialogFragment { return frag; } - private FragmentManager fragmentManager; - private SyncUpdateFragment syncFragment; - @Override public void onCreate(Bundle savedInstanceState) { setStyle(DialogFragment.STYLE_NO_TITLE, R.style.dialog); super.onCreate(savedInstanceState); - - fragmentManager = super.getFragmentManager(); - - syncFragment = (SyncUpdateFragment) fragmentManager.findFragmentByTag(SyncUpdateFragment.TAG); - if (syncFragment == null) { - syncFragment = new SyncUpdateFragment(); - } } @Override @@ -66,7 +55,7 @@ public class DeleteFeedFragment extends DialogFragment { // called from the feed view so finish Activity activity = DeleteFeedFragment.this.getActivity(); if (activity instanceof Main) { - ((Main)activity).updateAfterSync(); + ((Main)activity).handleUpdate(); } else { activity.finish(); } diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/LoginRegisterFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/LoginRegisterFragment.java index 79675883b..d22cf3722 100644 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/LoginRegisterFragment.java +++ b/clients/android/NewsBlur/src/com/newsblur/fragment/LoginRegisterFragment.java @@ -20,7 +20,6 @@ import com.newsblur.R; import com.newsblur.activity.LoginProgress; import com.newsblur.activity.RegisterProgress; import com.newsblur.network.APIManager; -import com.newsblur.service.DetachableResultReceiver; public class LoginRegisterFragment extends Fragment implements OnClickListener { @@ -28,10 +27,8 @@ public class LoginRegisterFragment extends Fragment implements OnClickListener { private EditText username, password; private ViewSwitcher viewSwitcher; - DetachableResultReceiver receiver; private EditText register_username, register_password, register_email; - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View v = inflater.inflate(R.layout.fragment_loginregister, container, false); diff --git a/clients/android/NewsBlur/src/com/newsblur/fragment/SyncUpdateFragment.java b/clients/android/NewsBlur/src/com/newsblur/fragment/SyncUpdateFragment.java deleted file mode 100644 index 85da8132f..000000000 --- a/clients/android/NewsBlur/src/com/newsblur/fragment/SyncUpdateFragment.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.newsblur.fragment; - -import android.app.Activity; -import android.os.Bundle; -import android.os.Handler; -import android.app.Fragment; -import android.util.Log; - -import com.newsblur.service.DetachableResultReceiver; -import com.newsblur.service.DetachableResultReceiver.Receiver; -import com.newsblur.service.SyncService; - -public class SyncUpdateFragment extends Fragment implements Receiver { - - public static final String TAG = SyncUpdateFragment.class.getName(); - public DetachableResultReceiver receiver; - public boolean syncRunning = false; - - public SyncUpdateFragment() { - receiver = new DetachableResultReceiver(new Handler()); - receiver.setReceiver(this); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setRetainInstance(true); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - } - - @Override - public void onReceiverResult(int resultCode, Bundle resultData) { - final SyncService.SyncStatus resultStatus = SyncService.SyncStatus.values()[resultCode]; - switch (resultStatus) { - case STATUS_FINISHED: - syncRunning = false; - if (getActivity() != null) { - ((SyncUpdateFragmentInterface) getActivity()).updateAfterSync(); - } - break; - case STATUS_PARTIAL_PROGRESS: - syncRunning = true; - if (getActivity() != null) { - ((SyncUpdateFragmentInterface) getActivity()).updatePartialSync(); - } - break; - case STATUS_RUNNING: - syncRunning = true; - break; - case STATUS_NO_MORE_UPDATES: - syncRunning = false; - if (getActivity() != null) { - ((SyncUpdateFragmentInterface) getActivity()).setNothingMoreToUpdate(); - } - break; - default: - syncRunning = false; - Log.e(this.getClass().getName(), "Sync completed with an unknown result."); - break; - } - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - ((SyncUpdateFragmentInterface) getActivity()).updateSyncStatus(syncRunning); - } - - - public interface SyncUpdateFragmentInterface { - public void updateAfterSync(); - public void updatePartialSync(); - public void setNothingMoreToUpdate(); - public void updateSyncStatus(boolean syncRunning); - } -} - diff --git a/clients/android/NewsBlur/src/com/newsblur/service/DetachableResultReceiver.java b/clients/android/NewsBlur/src/com/newsblur/service/DetachableResultReceiver.java deleted file mode 100644 index d13950bc2..000000000 --- a/clients/android/NewsBlur/src/com/newsblur/service/DetachableResultReceiver.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.newsblur.service; - -import android.os.Bundle; -import android.os.Handler; -import android.os.ResultReceiver; -import android.util.Log; - -/** - * This class is based on the Google I/O 2011 app's class of the same name. It allows for - * a resultreceiver to attach/detach to an activity and thus elegantly handle configuration changes. - */ -public class DetachableResultReceiver extends ResultReceiver { - - private final static String TAG = "DetachableResultReceiver"; - private Receiver receiver; - - public DetachableResultReceiver(Handler handler) { - super(handler); - } - - public void clearReceiver() { - receiver = null; - } - - public void setReceiver(final Receiver resultReceiver) { - receiver = resultReceiver; - } - - public interface Receiver { - public void onReceiverResult(int resultCode, Bundle resultData); - } - - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - if (receiver != null) { - receiver.onReceiverResult(resultCode, resultData); - } else { - Log.w(TAG, "No receiver to handle result: " + resultCode + " " + resultData.toString()); - } - } - -} diff --git a/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java b/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java index 86453c59b..6d950b848 100644 --- a/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java +++ b/clients/android/NewsBlur/src/com/newsblur/service/NBSyncService.java @@ -7,41 +7,73 @@ import android.os.PowerManager; import android.util.Log; import com.newsblur.R; +import com.newsblur.activity.NbActivity; +import com.newsblur.network.APIManager; public class NBSyncService extends Service { + private static boolean SyncRunning = false; + + private APIManager apiManager; + @Override public void onCreate() { super.onCreate(); Log.d(this.getClass().getName(), "onCreate"); + apiManager = new APIManager(this); } - @Override - public IBinder onBind(Intent intent) { - return null; - } - @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(this.getClass().getName(), "onStartCommand"); // TODO: check for sync flag or realtime flag - PowerManager pm = (PowerManager) getApplicationContext().getSystemService(POWER_SERVICE); - PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getSimpleName()); - wl.acquire(); + new Thread(new Runnable() { + public void run() { + doSync(); + } + }).start(); - // TODO: stuff! - - wl.release(); Log.d(this.getClass().getName(), "onStartCommand complete"); return Service.START_NOT_STICKY; } + private synchronized void doSync() { + Log.d(this.getClass().getName(), "starting sync . . ."); + + PowerManager pm = (PowerManager) getApplicationContext().getSystemService(POWER_SERVICE); + PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getSimpleName()); + try { + wl.acquire(); + + SyncRunning = true; + NbActivity.updateAllActivities(); + + apiManager.getFolderFeedMapping(true); + + SyncRunning = false; + NbActivity.updateAllActivities(); + + } finally { + wl.release(); + Log.d(this.getClass().getName(), " . . . sync done"); + } + } + + public static boolean isSyncRunning() { + return SyncRunning; + } + @Override public void onDestroy() { Log.d(this.getClass().getName(), "onDestroy"); } + @Override + public IBinder onBind(Intent intent) { + return null; + } + } diff --git a/clients/android/NewsBlur/src/com/newsblur/service/SyncService.java b/clients/android/NewsBlur/src/com/newsblur/service/SyncService.java deleted file mode 100644 index 0f2215a29..000000000 --- a/clients/android/NewsBlur/src/com/newsblur/service/SyncService.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.newsblur.service; - -import java.util.List; - -import android.app.IntentService; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.ResultReceiver; -import android.text.TextUtils; -import android.util.Log; -import android.widget.Toast; - -import com.newsblur.R; -import com.newsblur.database.FeedProvider; -import com.newsblur.domain.ValueMultimap; -import com.newsblur.network.APIConstants; -import com.newsblur.network.APIManager; -import com.newsblur.network.domain.SocialFeedResponse; -import com.newsblur.network.domain.StoriesResponse; -import com.newsblur.util.ReadFilter; -import com.newsblur.util.StoryOrder; - -/** - * A background-sync intent that, by virtue of extending IntentService, has several notable - * features: - * * invocations are FIFO and executed serially - * * the OS picks an appropriate thread for execution that won't block the UI, but recycles - * * supports callbacks where necessary - */ -public class SyncService extends IntentService { - - public static final String EXTRA_TASK_TYPE = "syncServiceTaskType"; - public static final String EXTRA_STATUS_RECEIVER = "resultReceiverExtra"; - - public enum SyncStatus { - STATUS_RUNNING, - STATUS_FINISHED, - STATUS_NO_MORE_UPDATES, - NOT_RUNNING, - STATUS_PARTIAL_PROGRESS, - }; - - public enum TaskType { - FOLDER_UPDATE_TWO_STEP, - FOLDER_UPDATE_WITH_COUNT - }; - - private APIManager apiManager; - private ContentResolver contentResolver; - - public SyncService() { - super(SyncService.class.getName()); - } - - @Override - public void onCreate() { - super.onCreate(); - apiManager = new APIManager(this); - contentResolver = getContentResolver(); - } - - @Override - protected void onHandleIntent(Intent intent) { - final ResultReceiver receiver = intent.getParcelableExtra(EXTRA_STATUS_RECEIVER); - try { - TaskType taskType = (TaskType) intent.getSerializableExtra(EXTRA_TASK_TYPE); - Log.d( this.getClass().getName(), "Sync Intent: " + taskType ); - - if (receiver != null) { - receiver.send(SyncStatus.STATUS_RUNNING.ordinal(), Bundle.EMPTY); - } - - // an extra result code to callback before the final STATUS_FINISHED that is always sent - SyncStatus resultStatus = null; - - switch (taskType) { - - case FOLDER_UPDATE_TWO_STEP: - // do a quick fetch of folders/feeds - apiManager.getFolderFeedMapping(false); - // notify UI of progress - if (receiver != null) { - receiver.send(SyncStatus.STATUS_PARTIAL_PROGRESS.ordinal(), Bundle.EMPTY); - } - // update feed counts - apiManager.refreshFeedCounts(); - // UI will be notified again by default - break; - - case FOLDER_UPDATE_WITH_COUNT: - apiManager.getFolderFeedMapping(true); - break; - - default: - Log.e(this.getClass().getName(), "SyncService called without relevant task assignment"); - break; - } - - // send the first result code if it was set. The STATUS_FINISHED is sent below - if ((receiver != null) && (resultStatus != null)) { - receiver.send(resultStatus.ordinal(), Bundle.EMPTY); - } - - Log.d( this.getClass().getName(), "Sync Intent complete"); - - } catch (Exception e) { - Log.e(this.getClass().getName(), "Couldn't synchronise with NewsBlur servers: " + e.getMessage(), e.getCause()); - e.printStackTrace(); - } finally { - if (receiver != null) { - receiver.send(SyncStatus.STATUS_FINISHED.ordinal(), Bundle.EMPTY); - } - } - - } - -} diff --git a/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java b/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java index 1309779ec..acf0bab25 100644 --- a/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java +++ b/clients/android/NewsBlur/src/com/newsblur/util/FeedUtils.java @@ -32,7 +32,6 @@ import com.newsblur.network.APIManager; import com.newsblur.network.domain.NewsBlurResponse; import com.newsblur.network.domain.SocialFeedResponse; import com.newsblur.network.domain.StoriesResponse; -import com.newsblur.service.SyncService; import com.newsblur.util.AppConstants; public class FeedUtils {