/* * Copyright (c) 2015, Nils Braden * * This file is part of ttrss-reader-fork. 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.ttrssreader.gui; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; import org.ttrssreader.R; import org.ttrssreader.controllers.Controller; import org.ttrssreader.controllers.DBHelper; import org.ttrssreader.controllers.Data; import org.ttrssreader.gui.dialogs.ChangelogDialog; import org.ttrssreader.gui.dialogs.WelcomeDialog; import org.ttrssreader.gui.fragments.CategoryListFragment; import org.ttrssreader.gui.fragments.FeedHeadlineListFragment; import org.ttrssreader.gui.fragments.FeedListFragment; import org.ttrssreader.gui.fragments.MainListFragment; import org.ttrssreader.gui.interfaces.IItemSelectedListener; import org.ttrssreader.model.pojos.Feed; import org.ttrssreader.utils.AsyncTask; import org.ttrssreader.utils.Utils; import java.util.LinkedHashSet; import java.util.Set; public class CategoryActivity extends MenuActivity implements IItemSelectedListener { private static final String TAG = CategoryActivity.class.getSimpleName(); private static final String DIALOG_WELCOME = "welcome"; private static final String DIALOG_UPDATE = "update"; private static final int SELECTED_VIRTUAL_CATEGORY = 1; private static final int SELECTED_CATEGORY = 2; private static final int SELECTED_LABEL = 3; private boolean cacherStarted = false; private CategoryUpdater categoryUpdater = null; private static final String SELECTED = "SELECTED"; private int selectedCategoryId = Integer.MIN_VALUE; @Override protected void onCreate(Bundle instance) { // Only needed to debug ANRs: // StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectCustomSlowCalls().detectDiskReads() // .detectDiskWrites().detectNetwork().penaltyLog().penaltyLog().build()); // StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects() // .detectLeakedClosableObjects().penaltyLog().build()); super.onCreate(instance); Bundle extras = getIntent().getExtras(); if (extras != null) { selectedCategoryId = extras.getInt(SELECTED, Integer.MIN_VALUE); } else if (instance != null) { selectedCategoryId = instance.getInt(SELECTED, Integer.MIN_VALUE); } FragmentManager fm = getFragmentManager(); CategoryListFragment categoryFragment = (CategoryListFragment) fm .findFragmentByTag(CategoryListFragment.FRAGMENT); if (categoryFragment == null) { fm.beginTransaction() .add(R.id.frame_main, CategoryListFragment.newInstance(), CategoryListFragment.FRAGMENT) .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) .commit(); } if (Utils.checkIsFirstRun()) { WelcomeDialog.getInstance().show(fm, DIALOG_WELCOME); } else if (Utils.checkIsNewVersion(this)) { ChangelogDialog.getInstance().show(fm, DIALOG_UPDATE); } else if (Utils.checkIsConfigInvalid()) { // Check if we have a server specified openConnectionErrorDialog((String) getText(R.string.CategoryActivity_NoServer)); } // Start caching if requested if (Controller.getInstance().cacheImagesOnStartup()) { boolean startCache = true; if (Controller.getInstance().cacheImagesOnlyWifi()) { // Check if Wifi is connected, if not don't start the ImageCache ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); if (!Utils.checkConnected(cm, true, true)) { Log.i(TAG, "Preference Start ImageCache only on WIFI set, doing nothing..."); startCache = false; } } // Indicate that the cacher started anyway so the refresh is supressed if the ImageCache is configured but // only for Wifi. cacherStarted = true; if (startCache) { Log.i(TAG, "Starting ImageCache..."); doStartImageCache(); } } } @Override protected int getLayoutResource() { return R.layout.main; } @Override public void onSaveInstanceState(Bundle outState) { outState.putInt(SELECTED, selectedCategoryId); super.onSaveInstanceState(outState); } @Override protected void onRestoreInstanceState(Bundle instance) { selectedCategoryId = instance.getInt(SELECTED, Integer.MIN_VALUE); super.onRestoreInstanceState(instance); } @Override public void dataLoadingFinished() { setTitleAndUnread(); } @Override protected void doRefresh() { super.doRefresh(); CategoryListFragment categoryFragment = getCategoryListFragment(); if (categoryFragment != null) categoryFragment.doRefresh(); FeedListFragment feedFragment = getFeedListFragment(); if (feedFragment != null) feedFragment.doRefresh(); setTitleAndUnread(); } public void setTitleAndUnread() { // Title and unread information: FeedListFragment feedFragment = getFeedListFragment(); if (feedFragment != null && feedFragment.isVisible() && !feedFragment.isEmptyPlaceholder()) { setTitle(feedFragment.getTitle()); setUnread(feedFragment.getUnread()); showBackArrow(); } else { CategoryListFragment categoryFragment = getCategoryListFragment(); if (categoryFragment != null && categoryFragment.isVisible()) { setTitle(categoryFragment.getTitle()); setUnread(categoryFragment.getUnread()); hideBackArrow(); } else { // we could be in onResume - just leave what's there right now } } } @Override protected void doUpdate(boolean forceUpdate) { // Only update if no categoryUpdater already running if (categoryUpdater != null) { if (categoryUpdater.getStatus().equals(AsyncTask.Status.FINISHED)) { categoryUpdater = null; } else { return; } } if (Data.getInstance().isConnected()) { if ((!isCacherRunning() && !cacherStarted) || forceUpdate) { categoryUpdater = new CategoryUpdater(forceUpdate); categoryUpdater.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } } } @Override public boolean onPrepareOptionsMenu(Menu menu) { boolean ret = super.onPrepareOptionsMenu(menu); menu.removeItem(R.id.Menu_MarkFeedRead); return ret; } @Override public final boolean onOptionsItemSelected(final MenuItem item) { if (super.onOptionsItemSelected(item)) return true; switch (item.getItemId()) { case R.id.Menu_Refresh: { doUpdate(true); return true; } default: return false; } } /** * This does a full update including all labels, feeds, categories and all articles. */ private class CategoryUpdater extends ActivityUpdater { private static final int DEFAULT_TASK_COUNT = 6; private CategoryUpdater(boolean forceUpdate) { super(forceUpdate); } @Override protected Void doInBackground(Void... params) { boolean onlyUnreadArticles = Controller.getInstance().onlyUnread(); Set<Feed> labels = new LinkedHashSet<>(); for (Feed f : DBHelper.getInstance().getFeeds(-2)) { if (f.unread == 0 && onlyUnreadArticles) continue; labels.add(f); } taskCount = DEFAULT_TASK_COUNT + labels.size(); int progress = 0; publishProgress(progress); // Try to synchronize any ids left in TABLE_MARK: Data.getInstance().synchronizeStatus(); publishProgress(++progress); // Cache articles for all categories Data.getInstance().cacheArticles(false, forceUpdate); publishProgress(++progress); // Refresh articles for all labels for (Feed f : labels) { Data.getInstance().updateArticles(f.id, false, false, false, forceUpdate); publishProgress(++progress); } // This stuff will be done in background without UI-notification, but the progress-calls will be done // anyway to ensure the UI is refreshed properly. Data.getInstance().updateVirtualCategories(getApplicationContext()); publishProgress(++progress); Data.getInstance().updateCategories(false); publishProgress(++progress); Data.getInstance().updateFeeds(Data.VCAT_ALL, false); publishProgress(++progress); Data.getInstance().calculateCounters(); Data.getInstance().notifyListeners(); publishProgress(++progress); // Silently remove articles which belong to feeds which do not exist on the server anymore: Data.getInstance().purgeOrphanedArticles(); publishProgress(Integer.MAX_VALUE); // Move progress forward to 100% return null; } } @Override public void itemSelected(MainListFragment source, int selectedId) { switch (source.getType()) { case CATEGORY: switch (decideCategorySelection(selectedId)) { case SELECTED_VIRTUAL_CATEGORY: displayHeadlines(selectedId, 0, false); break; case SELECTED_LABEL: displayHeadlines(selectedId, -2, false); break; case SELECTED_CATEGORY: if (Controller.getInstance().invertBrowsing()) { displayHeadlines(FeedHeadlineActivity.FEED_NO_ID, selectedId, true); } else { displayFeed(selectedId); } break; } break; case FEED: FeedListFragment feeds = (FeedListFragment) source; displayHeadlines(selectedId, feeds.getCategoryId(), false); break; default: Toast.makeText(this, "Invalid request!", Toast.LENGTH_SHORT).show(); break; } } public void displayHeadlines(int feedId, int categoryId, boolean selectArticles) { Intent i = new Intent(this, FeedHeadlineActivity.class); i.putExtra(FeedHeadlineListFragment.FEED_CAT_ID, categoryId); i.putExtra(FeedHeadlineListFragment.FEED_ID, feedId); i.putExtra(FeedHeadlineListFragment.FEED_SELECT_ARTICLES, selectArticles); i.putExtra(FeedHeadlineListFragment.ARTICLE_ID, Integer.MIN_VALUE); startActivity(i); } public void displayFeed(int categoryId) { if (mOnSaveInstanceStateCalled) { Log.w(TAG, "displayFeed() has been called after onSaveInstanceState(), this call has been supressed!"); Toast.makeText(this, "displayFeed() has been called after onSaveInstanceState(), this call has been supressed!", Toast.LENGTH_SHORT).show(); return; } selectedCategoryId = categoryId; FeedListFragment feedFragment = FeedListFragment.newInstance(categoryId); FragmentManager fm = getFragmentManager(); // Clear back stack fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); FragmentTransaction ft = fm.beginTransaction(); if (!Controller.isTablet) ft.addToBackStack(null); ft.replace(R.id.frame_sub, feedFragment, FeedListFragment.FRAGMENT); // Animation if (Controller.isTablet) ft.setCustomAnimations(R.animator.slide_in_left, android.R.animator.fade_out, android.R.animator.fade_in, R.animator.slide_out_left); else ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.commit(); } private static int decideCategorySelection(int selectedId) { if (selectedId < 0 && selectedId >= -4) { return SELECTED_VIRTUAL_CATEGORY; } else if (selectedId < -10) { return SELECTED_LABEL; } else { return SELECTED_CATEGORY; } } @Override public void onBackPressed() { selectedCategoryId = Integer.MIN_VALUE; // Back button automatically finishes the activity since Lollipop // so we have to work around by checking the backstack before FragmentManager fm = getFragmentManager(); if (fm.getBackStackEntryCount() > 0) { fm.popBackStack(); CategoryListFragment fragment = getCategoryListFragment(); if (fragment != null) { fragment.doRefresh(); } } else { super.onBackPressed(); } } private FeedListFragment getFeedListFragment() { return (FeedListFragment) getFragmentManager() .findFragmentByTag(FeedListFragment.FRAGMENT); } private CategoryListFragment getCategoryListFragment() { return (CategoryListFragment) getFragmentManager() .findFragmentByTag(CategoryListFragment.FRAGMENT); } }