diff --git a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java index acf0003..969afc0 100644 --- a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java @@ -137,7 +137,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements private RelativeLayout mSearchBar; // List - private final List mWebViewList = new ArrayList<>(); private LightningView mCurrentView; private WebView mWebView; @@ -189,6 +188,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements @Inject BookmarksDialogBuilder bookmarksDialogBuilder; + @Inject + TabsManager tabsManager; + // Image private Bitmap mWebpageBitmap; private final ColorDrawable mBackground = new ColorDrawable(); @@ -237,7 +239,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mShowTabsInDrawer = mPreferences.getShowTabsInDrawer(!isTablet()); mActivity = this; - mWebViewList.clear(); + // TODO Stefano, check this + // mWebViewList.clear(); mClickHandler = new ClickHandler(this); mBrowserFrame = (FrameLayout) findViewById(R.id.content_frame); @@ -272,7 +275,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements if (mShowTabsInDrawer) { - mTabAdapter = new LightningViewAdapter(this, R.layout.tab_list_item, mWebViewList); + mTabAdapter = new LightningViewAdapter(this, R.layout.tab_list_item, tabsManager.getTabsList()); mDrawerListLeft = (RecyclerView) findViewById(R.id.left_drawer_list); mDrawerListLeft.setOverScrollMode(View.OVER_SCROLL_IF_CONTENT_SCROLLS); RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); @@ -280,7 +283,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mDrawerListLeft.setHasFixedSize(true); mToolbarLayout.removeView(horizontalListView); } else { - mTabAdapter = new LightningViewAdapter(this, R.layout.tab_list_item_horizontal, mWebViewList); + mTabAdapter = new LightningViewAdapter(this, R.layout.tab_list_item_horizontal, tabsManager.getTabsList()); mDrawerListLeft = horizontalListView; mDrawerListLeft.setOverScrollMode(View.OVER_SCROLL_NEVER); mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, mDrawerLeft); @@ -873,10 +876,11 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements @Override public void onClick(View v) { - int position = mDrawerListLeft.getChildAdapterPosition(v); - if (mCurrentView != mWebViewList.get(position)) { + final int position = mDrawerListLeft.getChildAdapterPosition(v); + final LightningView tab = tabsManager.getTabAtPosition(position); + if (tab != null && mCurrentView != tab) { mIsNewIntent = false; - showTab(mWebViewList.get(position)); + showTab(tab); } } } @@ -1018,25 +1022,22 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements public void onTrimMemory(int level) { if (level > TRIM_MEMORY_MODERATE && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { Log.d(Constants.TAG, "Low Memory, Free Memory"); - for (int n = 0, size = mWebViewList.size(); n < size; n++) { - mWebViewList.get(n).freeMemory(); - } + tabsManager.freeMemory(); } } synchronized boolean newTab(String url, boolean show) { // Limit number of tabs for limited version of app - if (!Constants.FULL_VERSION && mWebViewList.size() >= 10) { + if (!Constants.FULL_VERSION && tabsManager.size() >= 10) { Utils.showSnackbar(this, R.string.max_tabs); return false; } mIsNewIntent = false; - LightningView startingTab = new LightningView(mActivity, url, mDarkTheme, isIncognito(), this); + LightningView startingTab = tabsManager.newTab(mActivity, url, mDarkTheme, isIncognito(), this); if (mIdGenerator == 0) { startingTab.resumeTimers(); } mIdGenerator++; - mWebViewList.add(startingTab); if (show) { showTab(startingTab); @@ -1045,7 +1046,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements new Handler().postDelayed(new Runnable() { @Override public void run() { - mDrawerListLeft.smoothScrollToPosition(mWebViewList.size() - 1); + mDrawerListLeft.smoothScrollToPosition(tabsManager.size() - 1); } }, 300); @@ -1053,49 +1054,47 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } private synchronized void deleteTab(int position) { - if (position >= mWebViewList.size()) { + final LightningView reference = tabsManager.getTabAtPosition(position); + if (reference == null) { return; } - int current = mWebViewList.indexOf(mCurrentView); +// What? + int current = tabsManager.getPositionForTab(mCurrentView); if (current < 0) { return; } - LightningView reference = mWebViewList.get(position); - if (reference == null) { - return; - } if (!reference.getUrl().startsWith(Constants.FILE) && !isIncognito()) { mPreferences.setSavedUrl(reference.getUrl()); } - boolean isShown = reference.isShown(); + final boolean isShown = reference.isShown(); if (isShown) { mBrowserFrame.setBackgroundColor(mBackgroundColor); } if (current > position) { - mWebViewList.remove(position); - showTab(mWebViewList.get(current - 1)); + tabsManager.deleteTab(position); + showTab(tabsManager.getTabAtPosition(current - 1)); updateTabs(); reference.onDestroy(); - } else if (mWebViewList.size() > position + 1) { + } else if (tabsManager.size() > position + 1) { if (current == position) { - showTab(mWebViewList.get(position + 1)); - mWebViewList.remove(position); - showTab(mWebViewList.get(position)); + showTab(tabsManager.getTabAtPosition(position + 1)); + tabsManager.deleteTab(position); + showTab(tabsManager.getTabAtPosition(position)); updateTabs(); } else { - mWebViewList.remove(position); + tabsManager.deleteTab(position); } reference.onDestroy(); - } else if (mWebViewList.size() > 1) { + } else if (tabsManager.size() > 1) { if (current == position) { - showTab(mWebViewList.get(position - 1)); - mWebViewList.remove(position); - showTab(mWebViewList.get(position - 1)); + showTab(tabsManager.getTabAtPosition(position - 1)); + tabsManager.deleteTab(position); + showTab(tabsManager.getTabAtPosition(position - 1)); updateTabs(); } else { - mWebViewList.remove(position); + tabsManager.deleteTab(position); } reference.onDestroy(); @@ -1103,7 +1102,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements if (mCurrentView.getUrl().startsWith(Constants.FILE) || mCurrentView.getUrl().equals(mHomepage)) { closeActivity(); } else { - mWebViewList.remove(position); + tabsManager.deleteTab(position); performExitCleanUp(); reference.pauseTimers(); reference.onDestroy(); @@ -1150,7 +1149,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements @Override public boolean onKeyLongPress(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { - showCloseDialog(mWebViewList.indexOf(mCurrentView)); + showCloseDialog(tabsManager.positionOf(mCurrentView)); } return true; } @@ -1160,12 +1159,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements performExitCleanUp(); mCurrentView = null; mWebView = null; - for (int n = 0, size = mWebViewList.size(); n < size; n++) { - if (mWebViewList.get(n) != null) { - mWebViewList.get(n).onDestroy(); - } - } - mWebViewList.clear(); + tabsManager.shutdown(); mTabAdapter.notifyDataSetChanged(); finish(); } @@ -1189,7 +1183,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mCurrentView.goBack(); } } else { - deleteTab(mWebViewList.indexOf(mCurrentView)); + deleteTab(tabsManager.positionOf(mCurrentView)); } } else { Log.e(Constants.TAG, "This shouldn't happen ever"); @@ -1220,12 +1214,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements void saveOpenTabs() { if (mPreferences.getRestoreLostTabsEnabled()) { - String s = ""; - for (int n = 0, size = mWebViewList.size(); n < size; n++) { - if (!mWebViewList.get(n).getUrl().isEmpty()) { - s = s + mWebViewList.get(n).getUrl() + "|$|SEPARATOR|$|"; - } - } + final String s = tabsManager.tabsString(); mPreferences.setMemoryUrl(s); } } @@ -1266,13 +1255,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } mHistoryDatabase = HistoryDatabase.getInstance(); initializePreferences(); - for (int n = 0, size = mWebViewList.size(); n < size; n++) { - if (mWebViewList.get(n) != null) { - mWebViewList.get(n).initializePreferences(null, this); - } else { - mWebViewList.remove(n); - } - } + tabsManager.resume(this); supportInvalidateOptionsMenu(); @@ -1984,7 +1967,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements */ @Override public void onCloseWindow(LightningView view) { - deleteTab(mWebViewList.indexOf(view)); + deleteTab(tabsManager.positionOf(view)); } /** @@ -2278,7 +2261,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements if (mCurrentView.canGoBack()) { mCurrentView.goBack(); } else { - deleteTab(mWebViewList.indexOf(mCurrentView)); + deleteTab(tabsManager.positionOf(mCurrentView)); } } break; @@ -2353,11 +2336,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements super.onReceive(context, intent); boolean isConnected = isConnected(context); Log.d(Constants.TAG, "Network Connected: " + String.valueOf(isConnected)); - for (int n = 0, size = mWebViewList.size(); n < size; n++) { - WebView view = mWebViewList.get(n).getWebView(); - if (view != null) - view.setNetworkAvailable(isConnected); - } + tabsManager.notifyConnectioneStatus(isConnected); } }; diff --git a/app/src/main/java/acr/browser/lightning/activity/TabsManager.java b/app/src/main/java/acr/browser/lightning/activity/TabsManager.java new file mode 100644 index 0000000..aaf4e26 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/activity/TabsManager.java @@ -0,0 +1,172 @@ +package acr.browser.lightning.activity; + +import android.app.Activity; +import android.content.Context; +import android.support.annotation.Nullable; +import android.webkit.WebView; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import acr.browser.lightning.controller.BrowserController; +import acr.browser.lightning.view.LightningView; + +/** + * @author Stefano Pacifici + * @date 2015/09/14 + */ +public class TabsManager { + + private final List mWebViewList = new ArrayList<>(); + + @Inject + public TabsManager() { + } + + /** + * Return a clone of the current tabs list. The list will not be updated, the user has to fetch + * a new copy when notified. + * + * @return a copy of the current tabs list + */ + public List getTabsList() { + return new ArrayList<>(mWebViewList); + } + + /** + * Return the tab at the given position in tabs list, or null if position is not in tabs list + * range. + * + * @param position the index in tabs list + * @return the corespondent {@link LightningView}, or null if the index is invalid + */ + @Nullable + public LightningView getTabAtPosition(final int position) { + if (position < 0 || position >= mWebViewList.size()) { + return null; + } + + return mWebViewList.get(position); + } + + /** + * Try to low memory pressure + */ + public void freeMemory() { + for (LightningView tab: mWebViewList) { + tab.freeMemory(); + } + } + + /** + * Shutdown the manager + */ + public synchronized void shutdown() { + for (LightningView tab: mWebViewList) { + tab.onDestroy(); + } + mWebViewList.clear(); + } + + /** + * Resume the tabs + * + * @param context + */ + public synchronized void resume(final Context context) { + for (LightningView tab: mWebViewList) { + tab.initializePreferences(null, context); + } + } + + /** + * Forward network connection status to the webviews. + * + * @param isConnected + */ + public synchronized void notifyConnectioneStatus(final boolean isConnected) { + for (LightningView tab: mWebViewList) { + final WebView webView = tab.getWebView(); + if (webView != null) { + webView.setNetworkAvailable(isConnected); + } + } + } + /** + * @return The number of currently opened tabs + */ + public int size() { + return mWebViewList.size(); + } + + /** + * Create and return a new tab. The tab is automatically added to the tabs list. + * + * @param activity + * @param url + * @param darkTheme + * @param isIncognito + * @param controller + * @return + */ + public synchronized LightningView newTab(final Activity activity, + final String url, final boolean darkTheme, + final boolean isIncognito, + final BrowserController controller) { + final LightningView tab = new LightningView(activity, url, darkTheme, isIncognito, controller); + mWebViewList.add(tab); + return tab; + } + + /** + * Remove a tab and return its reference or null if the position is not in tabs range + * + * @param position The position of the tab to remove + * @return The removed tab reference or null + */ + @Nullable + public synchronized LightningView deleteTab(final int position) { + if (position >= mWebViewList.size()) { + return null; + } + final LightningView tab = mWebViewList.remove(position); + // TODO This should not be done outside this call + // tab.onDestroy(); + return tab; + } + + /** + * TODO I think it should be removed + * Return the position of the given tab + * @param tab + * @return + */ + public int positionOf(final LightningView tab) { + return mWebViewList.indexOf(tab); + } + + /** + * @return A string representation of the currently opened tabs + */ + public String tabsString() { + final StringBuilder builder = new StringBuilder(); + for (LightningView tab: mWebViewList) { + final String url = tab.getUrl(); + if (url != null && !url.isEmpty()) { + builder.append(url).append("|$|SEPARATOR|$|"); + } + } + return builder.toString(); + } + + /** + * TODO Remove this, temporary workaround + * @param tab + * @return + */ + public int getPositionForTab(final LightningView tab) { + return mWebViewList.indexOf(tab); + } +}