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 d9c12c8..09f4c23 100644 --- a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java @@ -43,7 +43,6 @@ import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.graphics.Palette; import android.support.v7.widget.Toolbar; -import android.text.Selection; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; @@ -113,9 +112,12 @@ import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.fragment.BookmarksFragment; import acr.browser.lightning.fragment.TabsFragment; +import acr.browser.lightning.search.Suggestions; import acr.browser.lightning.search.SuggestionsAdapter; -import acr.browser.lightning.react.Observable; -import acr.browser.lightning.react.Schedulers; + +import com.anthonycr.bonsai.Observable; +import com.anthonycr.bonsai.Schedulers; + import acr.browser.lightning.receiver.NetworkReceiver; import acr.browser.lightning.utils.DrawableUtils; import acr.browser.lightning.utils.KeyboardHelper; @@ -166,7 +168,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements private View mCustomView; // Adapter - private SuggestionsAdapter mSuggestionsAdapter; + private Suggestions mSuggestionsAdapter; // Callback private CustomViewCallback mCustomViewCallback; @@ -217,9 +219,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements private static final int API = android.os.Build.VERSION.SDK_INT; private static final String NETWORK_BROADCAST_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; private static final LayoutParams MATCH_PARENT = new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT); + LayoutParams.MATCH_PARENT); private static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); protected abstract boolean isIncognito(); @@ -234,16 +236,16 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() - .detectDiskReads() - .detectDiskWrites() - .detectNetwork() - .penaltyLog() - .build()); + .detectDiskReads() + .detectDiskWrites() + .detectNetwork() + .penaltyLog() + .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() - .detectLeakedClosableObjects() - .detectLeakedSqlLiteObjects() - .penaltyLog() - .build()); + .detectLeakedClosableObjects() + .detectLeakedSqlLiteObjects() + .penaltyLog() + .build()); } super.onCreate(savedInstanceState); @@ -279,7 +281,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mDarkTheme = mPreferences.getUseTheme() != 0 || isIncognito(); mIconColor = mDarkTheme ? ThemeUtils.getIconDarkThemeColor(this) : ThemeUtils.getIconLightThemeColor(this); mDisabledIconColor = mDarkTheme ? ContextCompat.getColor(this, R.color.icon_dark_theme_disabled) : - ContextCompat.getColor(this, R.color.icon_light_theme_disabled); + ContextCompat.getColor(this, R.color.icon_light_theme_disabled); mShowTabsInDrawer = mPreferences.getShowTabsInDrawer(!isTablet()); // initialize background ColorDrawable @@ -336,10 +338,10 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements final FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager - .beginTransaction() - .replace(containerId, tabsFragment, TAG_TABS_FRAGMENT) - .replace(R.id.right_drawer, bookmarksFragment, TAG_BOOKMARK_FRAGMENT) - .commit(); + .beginTransaction() + .replace(containerId, tabsFragment, TAG_TABS_FRAGMENT) + .replace(R.id.right_drawer, bookmarksFragment, TAG_BOOKMARK_FRAGMENT) + .commit(); if (mShowTabsInDrawer) { mToolbarLayout.removeView(findViewById(R.id.tabs_toolbar_container)); } @@ -441,7 +443,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } private class SearchListenerClass implements OnKeyListener, OnEditorActionListener, - OnFocusChangeListener, OnTouchListener, SearchView.PreFocusListener { + OnFocusChangeListener, OnTouchListener, SearchView.PreFocusListener { @Override public boolean onKey(View searchView, int keyCode, KeyEvent keyEvent) { @@ -467,10 +469,10 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements // hide the keyboard and search the web when the enter key // button is pressed if (actionId == EditorInfo.IME_ACTION_GO || actionId == EditorInfo.IME_ACTION_DONE - || actionId == EditorInfo.IME_ACTION_NEXT - || actionId == EditorInfo.IME_ACTION_SEND - || actionId == EditorInfo.IME_ACTION_SEARCH - || (arg2.getAction() == KeyEvent.KEYCODE_ENTER)) { + || actionId == EditorInfo.IME_ACTION_NEXT + || actionId == EditorInfo.IME_ACTION_SEND + || actionId == EditorInfo.IME_ACTION_SEARCH + || (arg2.getAction() == KeyEvent.KEYCODE_ENTER)) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mSearch.getWindowToken(), 0); searchTheWeb(mSearch.getText().toString()); @@ -507,7 +509,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements public boolean onTouch(View v, MotionEvent event) { if (mSearch.getCompoundDrawables()[2] != null) { boolean tappedX = event.getX() > (mSearch.getWidth() - - mSearch.getPaddingRight() - mIcon.getIntrinsicWidth()); + - mSearch.getPaddingRight() - mIcon.getIntrinsicWidth()); if (tappedX) { if (event.getAction() == MotionEvent.ACTION_UP) { if (mSearch.hasFocus()) { @@ -575,23 +577,23 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } if (width > maxWidth) { DrawerLayout.LayoutParams params = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerLeft - .getLayoutParams(); + .getLayoutParams(); params.width = maxWidth; mDrawerLeft.setLayoutParams(params); mDrawerLeft.requestLayout(); DrawerLayout.LayoutParams paramsRight = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerRight - .getLayoutParams(); + .getLayoutParams(); paramsRight.width = maxWidth; mDrawerRight.setLayoutParams(paramsRight); mDrawerRight.requestLayout(); } else { DrawerLayout.LayoutParams params = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerLeft - .getLayoutParams(); + .getLayoutParams(); params.width = width; mDrawerLeft.setLayoutParams(params); mDrawerLeft.requestLayout(); DrawerLayout.LayoutParams paramsRight = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerRight - .getLayoutParams(); + .getLayoutParams(); paramsRight.width = width; mDrawerRight.setLayoutParams(paramsRight); mDrawerRight.requestLayout(); @@ -646,7 +648,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements case 0: mSearchText = mPreferences.getSearchUrl(); if (!mSearchText.startsWith(Constants.HTTP) - && !mSearchText.startsWith(Constants.HTTPS)) { + && !mSearchText.startsWith(Constants.HTTPS)) { mSearchText = Constants.GOOGLE_SEARCH; } break; @@ -700,8 +702,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements searchTheWeb(mSearch.getText().toString()); } } else if ((keyCode == KeyEvent.KEYCODE_MENU) - && (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) - && (Build.MANUFACTURER.compareTo("LGE") == 0)) { + && (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) + && (Build.MANUFACTURER.compareTo("LGE") == 0)) { // Workaround for stupid LG devices that crash return true; } @@ -711,8 +713,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements @Override public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) { if ((keyCode == KeyEvent.KEYCODE_MENU) - && (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) - && (Build.MANUFACTURER.compareTo("LGE") == 0)) { + && (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) + && (Build.MANUFACTURER.compareTo("LGE") == 0)) { // Workaround for stupid LG devices that crash openOptionsMenu(); return true; @@ -804,8 +806,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements // By using a manager, adds a bookmark and notifies third parties about that private void addBookmark(final String title, final String url) { final HistoryItem item = !mBookmarkManager.isBookmark(url) - ? new HistoryItem(url, title) - : null; + ? new HistoryItem(url, title) + : null; if (item != null && mBookmarkManager.addBookmark(item)) { mSuggestionsAdapter.refreshBookmarks(); mEventBus.post(new BrowserEvents.BookmarkAdded(title, url)); @@ -814,8 +816,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements private void deleteBookmark(final String title, final String url) { final HistoryItem item = mBookmarkManager.isBookmark(url) - ? new HistoryItem(url, title) - : null; + ? new HistoryItem(url, title) + : null; if (item != null && mBookmarkManager.deleteBookmark(item)) { mSuggestionsAdapter.refreshBookmarks(); mEventBus.post(new BrowserEvents.CurrentPageUrl(url)); @@ -833,15 +835,15 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements getHome.setHint(getResources().getString(R.string.search_hint)); finder.setView(getHome); finder.setPositiveButton(getResources().getString(R.string.search_hint), - new DialogInterface.OnClickListener() { + new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String query = getHome.getText().toString(); - if (!query.isEmpty()) - showSearchInterfaceBar(query); - } - }); + @Override + public void onClick(DialogInterface dialog, int which) { + String query = getHome.getText().toString(); + if (!query.isEmpty()) + showSearchInterfaceBar(query); + } + }); finder.show(); } @@ -877,7 +879,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } AlertDialog.Builder builder = new AlertDialog.Builder(this); ArrayAdapter adapter = new ArrayAdapter<>(this, - android.R.layout.simple_list_item_1); + android.R.layout.simple_list_item_1); adapter.add(this.getString(R.string.close_all_tabs)); adapter.add(this.getString(R.string.close_other_tabs)); adapter.add(this.getString(R.string.close_tab)); @@ -1000,11 +1002,11 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements public void showBlockedLocalFileDialog(DialogInterface.OnClickListener listener) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setCancelable(true) - .setTitle(R.string.title_warning) - .setMessage(R.string.message_blocked_local) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.action_open, listener) - .show(); + .setTitle(R.string.title_warning) + .setMessage(R.string.message_blocked_local) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.action_open, listener) + .show(); } @Override @@ -1218,6 +1220,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements protected void onDestroy() { Log.d(TAG, "onDestroy"); + mDrawerHandler.removeCallbacksAndMessages(null); + mPresenter.shutdown(); if (mHistoryDatabase != null) { @@ -1328,7 +1332,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mCurrentUiColor = color; mToolbarLayout.setBackgroundColor(color); mSearchBackground.getBackground().setColorFilter(DrawableUtils.mixColor(interpolatedTime, - startSearchColor, finalSearchColor), PorterDuff.Mode.SRC_IN); + startSearchColor, finalSearchColor), PorterDuff.Mode.SRC_IN); } }; animation.setDuration(300); @@ -1393,7 +1397,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements public void updateTabNumber(int number) { if (mArrowImage != null && mShowTabsInDrawer) { mArrowImage.setImageBitmap(DrawableUtils.getRoundedNumberImage(number, Utils.dpToPx(24), - Utils.dpToPx(24), ThemeUtils.getIconThemeColor(this, mDarkTheme), Utils.dpToPx(2.5f))); + Utils.dpToPx(24), ThemeUtils.getIconThemeColor(this, mDarkTheme), Utils.dpToPx(2.5f))); } } @@ -1429,7 +1433,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements */ private void initializeSearchSuggestions(final AutoCompleteTextView getUrl) { - mSuggestionsAdapter = new SuggestionsAdapter(this, mDarkTheme, isIncognito()); + mSuggestionsAdapter = new Suggestions(this, mDarkTheme, isIncognito()); getUrl.setThreshold(1); getUrl.setDropDownWidth(-1); @@ -1772,7 +1776,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } private class VideoCompletionListener implements MediaPlayer.OnCompletionListener, - MediaPlayer.OnErrorListener { + MediaPlayer.OnErrorListener { @Override public boolean onError(MediaPlayer mp, int what, int extra) { @@ -1814,16 +1818,16 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements if (enabled) { if (immersive) { decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); } else { decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); } window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); + WindowManager.LayoutParams.FLAG_FULLSCREEN); } else { window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); @@ -1967,6 +1971,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { view.getViewTreeObserver().removeOnGlobalLayoutListener(this); } else { + //noinspection deprecation view.getViewTreeObserver().removeGlobalOnLayoutListener(this); } runnable.run(); @@ -2228,7 +2233,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements public void bookmarkChanged(final BookmarkEvents.BookmarkChanged event) { final LightningView currentTab = mTabsManager.getCurrentTab(); if (currentTab != null && currentTab.getUrl().startsWith(Constants.FILE) - && currentTab.getUrl().endsWith(BookmarkPage.FILENAME)) { + && currentTab.getUrl().endsWith(BookmarkPage.FILENAME)) { currentTab.loadBookmarkpage(); } if (currentTab != null) { @@ -2245,7 +2250,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements public void bookmarkDeleted(final BookmarkEvents.Deleted event) { final LightningView currentTab = mTabsManager.getCurrentTab(); if (currentTab != null && currentTab.getUrl().startsWith(Constants.FILE) - && currentTab.getUrl().endsWith(BookmarkPage.FILENAME)) { + && currentTab.getUrl().endsWith(BookmarkPage.FILENAME)) { currentTab.loadBookmarkpage(); } if (currentTab != null) { diff --git a/app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java b/app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java index fa2460b..e939e5a 100644 --- a/app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java @@ -9,9 +9,9 @@ import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import acr.browser.lightning.R; -import acr.browser.lightning.react.Action; -import acr.browser.lightning.react.Observable; -import acr.browser.lightning.react.Subscriber; +import com.anthonycr.bonsai.Action; +import com.anthonycr.bonsai.Observable; +import com.anthonycr.bonsai.Subscriber; @SuppressWarnings("deprecation") public class IncognitoActivity extends BrowserActivity { diff --git a/app/src/main/java/acr/browser/lightning/activity/MainActivity.java b/app/src/main/java/acr/browser/lightning/activity/MainActivity.java index 32ca6eb..982bec7 100644 --- a/app/src/main/java/acr/browser/lightning/activity/MainActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/MainActivity.java @@ -9,9 +9,9 @@ import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import acr.browser.lightning.R; -import acr.browser.lightning.react.Action; -import acr.browser.lightning.react.Observable; -import acr.browser.lightning.react.Subscriber; +import com.anthonycr.bonsai.Action; +import com.anthonycr.bonsai.Observable; +import com.anthonycr.bonsai.Subscriber; @SuppressWarnings("deprecation") public class MainActivity extends BrowserActivity { diff --git a/app/src/main/java/acr/browser/lightning/activity/ReadingActivity.java b/app/src/main/java/acr/browser/lightning/activity/ReadingActivity.java index 088ae2f..d8b918a 100644 --- a/app/src/main/java/acr/browser/lightning/activity/ReadingActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/ReadingActivity.java @@ -28,12 +28,12 @@ import acr.browser.lightning.R; import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.preference.PreferenceManager; -import acr.browser.lightning.react.Action; -import acr.browser.lightning.react.Observable; -import acr.browser.lightning.react.OnSubscribe; -import acr.browser.lightning.react.Subscriber; -import acr.browser.lightning.react.Schedulers; -import acr.browser.lightning.react.Subscription; +import com.anthonycr.bonsai.Action; +import com.anthonycr.bonsai.Observable; +import com.anthonycr.bonsai.OnSubscribe; +import com.anthonycr.bonsai.Subscriber; +import com.anthonycr.bonsai.Schedulers; +import com.anthonycr.bonsai.Subscription; import acr.browser.lightning.reading.HtmlFetcher; import acr.browser.lightning.reading.JResult; import acr.browser.lightning.utils.ThemeUtils; diff --git a/app/src/main/java/acr/browser/lightning/activity/TabsManager.java b/app/src/main/java/acr/browser/lightning/activity/TabsManager.java index 6903117..494c897 100644 --- a/app/src/main/java/acr/browser/lightning/activity/TabsManager.java +++ b/app/src/main/java/acr/browser/lightning/activity/TabsManager.java @@ -28,11 +28,11 @@ import acr.browser.lightning.constant.StartPage; import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.HistoryDatabase; import acr.browser.lightning.preference.PreferenceManager; -import acr.browser.lightning.react.Action; -import acr.browser.lightning.react.Observable; -import acr.browser.lightning.react.OnSubscribe; -import acr.browser.lightning.react.Schedulers; -import acr.browser.lightning.react.Subscriber; +import com.anthonycr.bonsai.Action; +import com.anthonycr.bonsai.Observable; +import com.anthonycr.bonsai.OnSubscribe; +import com.anthonycr.bonsai.Schedulers; +import com.anthonycr.bonsai.Subscriber; import acr.browser.lightning.utils.FileUtils; import acr.browser.lightning.utils.UrlUtils; import acr.browser.lightning.view.LightningView; diff --git a/app/src/main/java/acr/browser/lightning/activity/ThemableBrowserActivity.java b/app/src/main/java/acr/browser/lightning/activity/ThemableBrowserActivity.java index de5de7a..c8a6fea 100644 --- a/app/src/main/java/acr/browser/lightning/activity/ThemableBrowserActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/ThemableBrowserActivity.java @@ -3,12 +3,8 @@ package acr.browser.lightning.activity; import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; -import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; -import java.util.ArrayDeque; -import java.util.Queue; - import javax.inject.Inject; import acr.browser.lightning.R; diff --git a/app/src/main/java/acr/browser/lightning/app/AppComponent.java b/app/src/main/java/acr/browser/lightning/app/AppComponent.java index f46d31f..2c4b87b 100644 --- a/app/src/main/java/acr/browser/lightning/app/AppComponent.java +++ b/app/src/main/java/acr/browser/lightning/app/AppComponent.java @@ -17,6 +17,7 @@ import acr.browser.lightning.fragment.DebugSettingsFragment; import acr.browser.lightning.fragment.LightningPreferenceFragment; import acr.browser.lightning.fragment.PrivacySettingsFragment; import acr.browser.lightning.fragment.TabsFragment; +import acr.browser.lightning.search.Suggestions; import acr.browser.lightning.search.SuggestionsAdapter; import acr.browser.lightning.utils.AdBlock; import acr.browser.lightning.utils.ProxyUtils; @@ -70,4 +71,6 @@ public interface AppComponent { void inject(DebugSettingsFragment fragment); + void inject(Suggestions suggestions); + } diff --git a/app/src/main/java/acr/browser/lightning/browser/BrowserPresenter.java b/app/src/main/java/acr/browser/lightning/browser/BrowserPresenter.java index 7b79208..1921e9c 100644 --- a/app/src/main/java/acr/browser/lightning/browser/BrowserPresenter.java +++ b/app/src/main/java/acr/browser/lightning/browser/BrowserPresenter.java @@ -17,7 +17,7 @@ import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.controller.UIController; import acr.browser.lightning.preference.PreferenceManager; -import acr.browser.lightning.react.OnSubscribe; +import com.anthonycr.bonsai.OnSubscribe; import acr.browser.lightning.utils.UrlUtils; import acr.browser.lightning.view.LightningView; diff --git a/app/src/main/java/acr/browser/lightning/database/BookmarkLocalSync.java b/app/src/main/java/acr/browser/lightning/database/BookmarkLocalSync.java index 827c099..9d0476c 100644 --- a/app/src/main/java/acr/browser/lightning/database/BookmarkLocalSync.java +++ b/app/src/main/java/acr/browser/lightning/database/BookmarkLocalSync.java @@ -11,9 +11,9 @@ import android.util.Log; import java.util.ArrayList; import java.util.List; -import acr.browser.lightning.react.Action; -import acr.browser.lightning.react.Observable; -import acr.browser.lightning.react.Subscriber; +import com.anthonycr.bonsai.Action; +import com.anthonycr.bonsai.Observable; +import com.anthonycr.bonsai.Subscriber; import acr.browser.lightning.utils.Utils; public class BookmarkLocalSync { diff --git a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java index 25279f0..7a29040 100644 --- a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java +++ b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java @@ -217,9 +217,14 @@ public class LightningDialogBuilder { mEventBus.post(new BrowserEvents.OpenUrlInNewTab(url)); break; case DialogInterface.BUTTON_NEGATIVE: - mHistoryDatabase.deleteHistoryItem(url); - // openHistory(); - mEventBus.post(new BrowserEvents.OpenHistoryInCurrentTab()); + BrowserApp.getIOThread().execute(new Runnable() { + @Override + public void run() { + mHistoryDatabase.deleteHistoryItem(url); + // openHistory(); + mEventBus.post(new BrowserEvents.OpenHistoryInCurrentTab()); + } + }); break; case DialogInterface.BUTTON_NEUTRAL: mEventBus.post(new BrowserEvents.OpenUrlInCurrentTab(url)); diff --git a/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java b/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java index 3246821..999db35 100644 --- a/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java +++ b/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java @@ -39,8 +39,8 @@ import acr.browser.lightning.database.BookmarkLocalSync; import acr.browser.lightning.database.BookmarkLocalSync.Source; import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.HistoryItem; -import acr.browser.lightning.react.OnSubscribe; -import acr.browser.lightning.react.Schedulers; +import com.anthonycr.bonsai.OnSubscribe; +import com.anthonycr.bonsai.Schedulers; import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Utils; diff --git a/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java b/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java index 4560d52..8600698 100644 --- a/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java +++ b/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java @@ -49,11 +49,11 @@ import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.async.ImageDownloadTask; -import acr.browser.lightning.react.Action; -import acr.browser.lightning.react.Observable; -import acr.browser.lightning.react.OnSubscribe; -import acr.browser.lightning.react.Schedulers; -import acr.browser.lightning.react.Subscriber; +import com.anthonycr.bonsai.Action; +import com.anthonycr.bonsai.Observable; +import com.anthonycr.bonsai.OnSubscribe; +import com.anthonycr.bonsai.Schedulers; +import com.anthonycr.bonsai.Subscriber; import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.view.LightningView; diff --git a/app/src/main/java/acr/browser/lightning/fragment/TabsFragment.java b/app/src/main/java/acr/browser/lightning/fragment/TabsFragment.java index 7d9c5a3..fbf3f7d 100644 --- a/app/src/main/java/acr/browser/lightning/fragment/TabsFragment.java +++ b/app/src/main/java/acr/browser/lightning/fragment/TabsFragment.java @@ -330,6 +330,7 @@ public class TabsFragment extends Fragment implements View.OnClickListener, View if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { holder.layout.setBackground(foregroundDrawable); } else { + //noinspection deprecation holder.layout.setBackgroundDrawable(foregroundDrawable); } if (!mIsIncognito && mColorMode) { @@ -341,6 +342,7 @@ public class TabsFragment extends Fragment implements View.OnClickListener, View if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { holder.layout.setBackground(mBackgroundTabDrawable); } else { + //noinspection deprecation holder.layout.setBackgroundDrawable(mBackgroundTabDrawable); } holder.favicon.setImageBitmap(getDesaturatedBitmap(favicon)); diff --git a/app/src/main/java/acr/browser/lightning/react/Action.java b/app/src/main/java/acr/browser/lightning/react/Action.java deleted file mode 100644 index 0c1a22e..0000000 --- a/app/src/main/java/acr/browser/lightning/react/Action.java +++ /dev/null @@ -1,16 +0,0 @@ -package acr.browser.lightning.react; - -import android.support.annotation.NonNull; - -public interface Action { - /** - * Should be overridden to send the subscriber - * events such as {@link Subscriber#onNext(Object)} - * or {@link Subscriber#onComplete()}. - * - * @param subscriber the subscriber that is sent in - * when the user of the Observable - * subscribes. - */ - void onSubscribe(@NonNull Subscriber subscriber); -} diff --git a/app/src/main/java/acr/browser/lightning/react/Observable.java b/app/src/main/java/acr/browser/lightning/react/Observable.java deleted file mode 100644 index 700a39e..0000000 --- a/app/src/main/java/acr/browser/lightning/react/Observable.java +++ /dev/null @@ -1,254 +0,0 @@ -package acr.browser.lightning.react; - -import android.os.Looper; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Log; - -import java.util.concurrent.Executor; - -import acr.browser.lightning.utils.Preconditions; - -/** - * An RxJava implementation. This class allows work - * to be done on a certain thread and then allows - * items to be emitted on a different thread. It is - * a replacement for {@link android.os.AsyncTask}. - * - * @param the type that the Observable will emit. - */ -public class Observable { - - private static final String TAG = Observable.class.getSimpleName(); - - @NonNull private final Action mAction; - @Nullable private Executor mSubscriberThread; - @Nullable private Executor mObserverThread; - @NonNull private final Executor mDefault; - - private Observable(@NonNull Action action) { - mAction = action; - Looper looper = Looper.myLooper(); - Preconditions.checkNonNull(looper); - mDefault = new ThreadExecutor(looper); - } - - /** - * Static creator method that creates an Observable from the - * {@link Action} that is passed in as the parameter. Action - * must not be null. - * - * @param action the Action to perform - * @param the type that will be emitted to the onSubscribe - * @return a valid non-null Observable. - */ - @NonNull - public static Observable create(@NonNull Action action) { - Preconditions.checkNonNull(action); - return new Observable<>(action); - } - - /** - * Tells the Observable what Executor that the onSubscribe - * work should run on. - * - * @param subscribeExecutor the Executor to run the work on. - * @return returns this so that calls can be conveniently chained. - */ - public Observable subscribeOn(@NonNull Executor subscribeExecutor) { - mSubscriberThread = subscribeExecutor; - return this; - } - - /** - * Tells the Observable what Executor the onSubscribe should observe - * the work on. - * - * @param observerExecutor the Executor to run to callback on. - * @return returns this so that calls can be conveniently chained. - */ - public Observable observeOn(@NonNull Executor observerExecutor) { - mObserverThread = observerExecutor; - return this; - } - - /** - * Subscribes immediately to the Observable and ignores - * all onComplete and onNext calls. - */ - public void subscribe() { - executeOnSubscriberThread(new Runnable() { - @Override - public void run() { - mAction.onSubscribe(new Subscriber() { - @Override - public void unsubscribe() {} - - @Override - public void onComplete() {} - - @Override - public void onStart() {} - - @Override - public void onError(@NonNull Throwable throwable) {} - - @Override - public void onNext(T item) {} - }); - } - }); - } - - /** - * Immediately subscribes to the Observable and starts - * sending events from the Observable to the {@link OnSubscribe}. - * - * @param onSubscribe the class that wishes to receive onNext and - * onComplete callbacks from the Observable. - */ - public Subscription subscribe(@NonNull OnSubscribe onSubscribe) { - - Preconditions.checkNonNull(onSubscribe); - - final Subscriber subscriber = new SubscriberImpl<>(onSubscribe, this); - - subscriber.onStart(); - - executeOnSubscriberThread(new Runnable() { - @Override - public void run() { - mAction.onSubscribe(subscriber); - } - }); - - return subscriber; - } - - private void executeOnObserverThread(@NonNull Runnable runnable) { - if (mObserverThread != null) { - mObserverThread.execute(runnable); - } else { - mDefault.execute(runnable); - } - } - - private void executeOnSubscriberThread(@NonNull Runnable runnable) { - if (mSubscriberThread != null) { - mSubscriberThread.execute(runnable); - } else { - mDefault.execute(runnable); - } - } - - private static class SubscriberImpl implements Subscriber { - - @Nullable private volatile OnSubscribe mOnSubscribe; - @NonNull private final Observable mObservable; - private boolean mOnCompleteExecuted = false; - private boolean mOnError = false; - - public SubscriberImpl(@NonNull OnSubscribe onSubscribe, @NonNull Observable observable) { - mOnSubscribe = onSubscribe; - mObservable = observable; - } - - @Override - public void unsubscribe() { - mOnSubscribe = null; - } - - @Override - public void onComplete() { - OnSubscribe onSubscribe = mOnSubscribe; - if (!mOnCompleteExecuted && onSubscribe != null && !mOnError) { - mOnCompleteExecuted = true; - mObservable.executeOnObserverThread(new OnCompleteRunnable<>(onSubscribe)); - } else if (!mOnError) { - Log.e(TAG, "onComplete called more than once"); - throw new RuntimeException("onComplete called more than once"); - } - } - - @Override - public void onStart() { - OnSubscribe onSubscribe = mOnSubscribe; - if (onSubscribe != null) { - mObservable.executeOnObserverThread(new OnStartRunnable<>(onSubscribe)); - } - } - - @Override - public void onError(@NonNull final Throwable throwable) { - OnSubscribe onSubscribe = mOnSubscribe; - if (onSubscribe != null) { - mOnError = true; - mObservable.executeOnObserverThread(new OnErrorRunnable<>(onSubscribe, throwable)); - } - } - - @Override - public void onNext(final T item) { - OnSubscribe onSubscribe = mOnSubscribe; - if (!mOnCompleteExecuted && onSubscribe != null) { - mObservable.executeOnObserverThread(new OnNextRunnable<>(onSubscribe, item)); - } else { - Log.e(TAG, "onComplete has been already called, onNext should not be called"); - throw new RuntimeException("onNext should not be called after onComplete has been called"); - } - } - } - - private static class OnCompleteRunnable implements Runnable { - private final OnSubscribe onSubscribe; - - public OnCompleteRunnable(@NonNull OnSubscribe onSubscribe) {this.onSubscribe = onSubscribe;} - - @Override - public void run() { - onSubscribe.onComplete(); - } - } - - private static class OnNextRunnable implements Runnable { - private final OnSubscribe onSubscribe; - private final T item; - - public OnNextRunnable(@NonNull OnSubscribe onSubscribe, T item) { - this.onSubscribe = onSubscribe; - this.item = item; - } - - @Override - public void run() { - onSubscribe.onNext(item); - } - } - - private static class OnErrorRunnable implements Runnable { - private final OnSubscribe onSubscribe; - private final Throwable throwable; - - public OnErrorRunnable(@NonNull OnSubscribe onSubscribe, @NonNull Throwable throwable) { - this.onSubscribe = onSubscribe; - this.throwable = throwable; - } - - @Override - public void run() { - onSubscribe.onError(throwable); - } - } - - private static class OnStartRunnable implements Runnable { - private final OnSubscribe onSubscribe; - - public OnStartRunnable(@NonNull OnSubscribe onSubscribe) {this.onSubscribe = onSubscribe;} - - @Override - public void run() { - onSubscribe.onStart(); - } - } -} - diff --git a/app/src/main/java/acr/browser/lightning/react/OnSubscribe.java b/app/src/main/java/acr/browser/lightning/react/OnSubscribe.java deleted file mode 100644 index fb65d7f..0000000 --- a/app/src/main/java/acr/browser/lightning/react/OnSubscribe.java +++ /dev/null @@ -1,47 +0,0 @@ -package acr.browser.lightning.react; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -public abstract class OnSubscribe { - - /** - * Called when the observable - * runs into an error that will - * cause it to abort and not finish. - * Receiving this callback means that - * the observable is dead and no - * {@link #onComplete()} or {@link #onNext(Object)} - * callbacks will be called. - * - * @param throwable an optional throwable that could - * be sent. - */ - public void onError(@NonNull Throwable throwable) {} - - /** - * Called before the observer begins - * to process and emit items or complete. - */ - public void onStart() {} - - /** - * Called when the Observer emits an - * item. It can be called multiple times. - * It cannot be called after onComplete - * has been called. - * - * @param item the item that has been emitted, - * can be null. - */ - public void onNext(@Nullable T item) {} - - /** - * This method is called when the observer is - * finished sending the subscriber events. It - * is guaranteed that no other methods will be - * called on the OnSubscribe after this method - * has been called. - */ - public void onComplete() {} -} diff --git a/app/src/main/java/acr/browser/lightning/react/Schedulers.java b/app/src/main/java/acr/browser/lightning/react/Schedulers.java deleted file mode 100644 index 4368c96..0000000 --- a/app/src/main/java/acr/browser/lightning/react/Schedulers.java +++ /dev/null @@ -1,46 +0,0 @@ -package acr.browser.lightning.react; - -import android.os.Looper; -import android.support.annotation.NonNull; - -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -public class Schedulers { - private static final Executor sWorker = Executors.newFixedThreadPool(4); - private static final Executor sIOWorker = Executors.newSingleThreadExecutor(); - private static final Executor sMain = new ThreadExecutor(Looper.getMainLooper()); - - /** - * The worker thread executor, will - * execute work on any one of multiple - * threads. - * - * @return a non-null executor. - */ - @NonNull - public static Executor worker() { - return sWorker; - } - - /** - * The main thread. - * - * @return a non-null executor that does work on the main thread. - */ - @NonNull - public static Executor main() { - return sMain; - } - - /** - * The io thread. - * - * @return a non-null executor that does - * work on a single thread off the main thread. - */ - @NonNull - public static Executor io() { - return sIOWorker; - } -} diff --git a/app/src/main/java/acr/browser/lightning/react/Subscriber.java b/app/src/main/java/acr/browser/lightning/react/Subscriber.java deleted file mode 100644 index 72920f1..0000000 --- a/app/src/main/java/acr/browser/lightning/react/Subscriber.java +++ /dev/null @@ -1,51 +0,0 @@ -package acr.browser.lightning.react; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -public interface Subscriber extends Subscription { - - /** - * Called immediately upon subscribing - * and before the Observable begins - * emitting items. This should not be - * called by the creator of the Observable - * and is rather called internally by the - * Observable class itself. - */ - void onStart(); - - /** - * Called when the observable - * runs into an error that will - * cause it to abort and not finish. - * Receiving this callback means that - * the observable is dead and no - * {@link #onComplete()} or {@link #onNext(Object)} - * callbacks will be called. - * - * @param throwable an optional throwable that could - * be sent. - */ - void onError(@NonNull Throwable throwable); - - /** - * Called when the Observer emits an - * item. It can be called multiple times. - * It cannot be called after onComplete - * has been called. - * - * @param item the item that has been emitted, - * can be null. - */ - void onNext(@Nullable T item); - - /** - * This method is called when the observer is - * finished sending the subscriber events. It - * is guaranteed that no other methods will be - * called on the OnSubscribe after this method - * has been called. - */ - void onComplete(); -} diff --git a/app/src/main/java/acr/browser/lightning/react/Subscription.java b/app/src/main/java/acr/browser/lightning/react/Subscription.java deleted file mode 100644 index 627b60b..0000000 --- a/app/src/main/java/acr/browser/lightning/react/Subscription.java +++ /dev/null @@ -1,7 +0,0 @@ -package acr.browser.lightning.react; - -public interface Subscription { - - void unsubscribe(); - -} diff --git a/app/src/main/java/acr/browser/lightning/react/ThreadExecutor.java b/app/src/main/java/acr/browser/lightning/react/ThreadExecutor.java deleted file mode 100644 index e352726..0000000 --- a/app/src/main/java/acr/browser/lightning/react/ThreadExecutor.java +++ /dev/null @@ -1,21 +0,0 @@ -package acr.browser.lightning.react; - -import android.os.Handler; -import android.os.Looper; -import android.support.annotation.NonNull; - -import java.util.concurrent.Executor; - -class ThreadExecutor implements Executor { - - private final Handler mHandler; - - public ThreadExecutor(@NonNull Looper looper) { - mHandler = new Handler(looper); - } - - @Override - public void execute(@NonNull Runnable command) { - mHandler.post(command); - } -} diff --git a/app/src/main/java/acr/browser/lightning/search/Suggestions.java b/app/src/main/java/acr/browser/lightning/search/Suggestions.java new file mode 100644 index 0000000..340b7ee --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/search/Suggestions.java @@ -0,0 +1,418 @@ +package acr.browser.lightning.search; + +import android.app.Application; +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.ImageView; +import android.widget.TextView; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +import javax.inject.Inject; + +import acr.browser.lightning.R; +import acr.browser.lightning.app.BrowserApp; +import acr.browser.lightning.database.BookmarkManager; +import acr.browser.lightning.database.HistoryDatabase; +import acr.browser.lightning.database.HistoryItem; +import acr.browser.lightning.preference.PreferenceManager; +import com.anthonycr.bonsai.Action; +import com.anthonycr.bonsai.Observable; +import com.anthonycr.bonsai.OnSubscribe; +import com.anthonycr.bonsai.Scheduler; +import com.anthonycr.bonsai.Schedulers; +import com.anthonycr.bonsai.Subscriber; +import acr.browser.lightning.utils.ThemeUtils; + +public class Suggestions extends BaseAdapter implements Filterable { + + private static final Scheduler FILTER_SCHEDULER = Schedulers.newSingleThreadedScheduler(); + + public static final String CACHE_FILE_TYPE = ".sgg"; + + private final List mFilteredList = new ArrayList<>(5); + + private final List mHistory = new ArrayList<>(5); + private final List mBookmarks = new ArrayList<>(5); + private final List mSuggestions = new ArrayList<>(5); + + private static final int MAX_SUGGESTIONS = 5; + + @NonNull private final Drawable mSearchDrawable; + @NonNull private final Drawable mHistoryDrawable; + @NonNull private final Drawable mBookmarkDrawable; + + private final Comparator mFilterComparator = new SuggestionsComparator(); + + @Inject HistoryDatabase mDatabaseHandler; + @Inject BookmarkManager mBookmarkManager; + @Inject PreferenceManager mPreferenceManager; + + private final List mAllBookmarks = new ArrayList<>(5); + + private boolean mDarkTheme; + private boolean mUseGoogle = true; + private boolean mIsIncognito = true; + @NonNull private Context mContext; + + public Suggestions(@NonNull Context context, boolean dark, boolean incognito) { + super(); + BrowserApp.getAppComponent().inject(this); + mContext = context; + mDarkTheme = dark || incognito; + mIsIncognito = incognito; + + mUseGoogle = mPreferenceManager.getGoogleSearchSuggestionsEnabled(); + + refreshBookmarks(); + + mSearchDrawable = ThemeUtils.getThemedDrawable(context, R.drawable.ic_search, mDarkTheme); + mBookmarkDrawable = ThemeUtils.getThemedDrawable(context, R.drawable.ic_bookmark, mDarkTheme); + mHistoryDrawable = ThemeUtils.getThemedDrawable(context, R.drawable.ic_history, mDarkTheme); + + clearCache(); + } + + public void refreshPreferences() { + mUseGoogle = mPreferenceManager.getGoogleSearchSuggestionsEnabled(); + } + + private void clearCache() { + BrowserApp.getTaskThread().execute(new ClearCacheRunnable(BrowserApp.get(mContext))); + } + + public void refreshBookmarks() { + mAllBookmarks.clear(); + mAllBookmarks.addAll(mBookmarkManager.getAllBookmarks(true)); + } + + @Override + public int getCount() { + return mFilteredList.size(); + } + + @Override + public Object getItem(int position) { + if (position > mFilteredList.size() || position < 0) { + return null; + } + return mFilteredList.get(position); + } + + @Override + public long getItemId(int position) { + return 0; + } + + private static class SuggestionHolder { + + public SuggestionHolder(View view) { + mTitle = (TextView) view.findViewById(R.id.title); + mUrl = (TextView) view.findViewById(R.id.url); + mImage = (ImageView) view.findViewById(R.id.suggestionIcon); + } + + ImageView mImage; + TextView mTitle; + TextView mUrl; + + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + SuggestionHolder holder; + + if (convertView == null) { + LayoutInflater inflater = LayoutInflater.from(mContext); + convertView = inflater.inflate(R.layout.two_line_autocomplete, parent, false); + + holder = new SuggestionHolder(convertView); + convertView.setTag(holder); + } else { + holder = (SuggestionHolder) convertView.getTag(); + } + HistoryItem web; + web = mFilteredList.get(position); + holder.mTitle.setText(web.getTitle()); + holder.mUrl.setText(web.getUrl()); + + Drawable image; + switch (web.getImageId()) { + case R.drawable.ic_bookmark: { + if (mDarkTheme) + holder.mTitle.setTextColor(Color.WHITE); + image = mBookmarkDrawable; + break; + } + case R.drawable.ic_search: { + if (mDarkTheme) + holder.mTitle.setTextColor(Color.WHITE); + image = mSearchDrawable; + break; + } + case R.drawable.ic_history: { + if (mDarkTheme) + holder.mTitle.setTextColor(Color.WHITE); + image = mHistoryDrawable; + break; + } + default: + if (mDarkTheme) + holder.mTitle.setTextColor(Color.WHITE); + image = mSearchDrawable; + break; + } + + holder.mImage.setImageDrawable(image); + + return convertView; + } + + @Override + public Filter getFilter() { + return new SearchFilter(this); + } + + private synchronized void publishResults(List list) { + mFilteredList.clear(); + mFilteredList.addAll(list); + notifyDataSetChanged(); + } + + private void clearSuggestions() { + Observable.create(new Action() { + @Override + public void onSubscribe(@NonNull Subscriber subscriber) { + mBookmarks.clear(); + mHistory.clear(); + mSuggestions.clear(); + subscriber.onComplete(); + } + }).subscribeOn(FILTER_SCHEDULER) + .observeOn(Schedulers.main()) + .subscribe(); + } + + private void combineResults(final @Nullable List bookmarkList, + final @Nullable List historyList, + final @Nullable List suggestionList) { + Observable.create(new Action>() { + @Override + public void onSubscribe(@NonNull Subscriber> subscriber) { + List list = new ArrayList<>(5); + if (bookmarkList != null) { + mBookmarks.clear(); + mBookmarks.addAll(bookmarkList); + } + if (historyList != null) { + mHistory.clear(); + mHistory.addAll(historyList); + } + if (suggestionList != null) { + mSuggestions.clear(); + mSuggestions.addAll(suggestionList); + } + Iterator bookmark = mBookmarks.iterator(); + Iterator history = mHistory.iterator(); + Iterator suggestion = mSuggestions.listIterator(); + while (list.size() < MAX_SUGGESTIONS) { + if (!bookmark.hasNext() && !suggestion.hasNext() && !history.hasNext()) { + break; + } + if (bookmark.hasNext()) { + list.add(bookmark.next()); + } + if (suggestion.hasNext() && list.size() < MAX_SUGGESTIONS) { + list.add(suggestion.next()); + } + if (history.hasNext() && list.size() < MAX_SUGGESTIONS) { + list.add(history.next()); + } + } + + Collections.sort(list, mFilterComparator); + subscriber.onNext(list); + subscriber.onComplete(); + } + }).subscribeOn(FILTER_SCHEDULER) + .observeOn(Schedulers.main()) + .subscribe(new OnSubscribe>() { + @Override + public void onNext(@Nullable List item) { + publishResults(item); + } + }); + + } + + @NonNull + private Observable> getBookmarksForQuery(@NonNull final String query) { + return Observable.create(new Action>() { + @Override + public void onSubscribe(@NonNull Subscriber> subscriber) { + List bookmarks = new ArrayList<>(5); + int counter = 0; + for (int n = 0; n < mAllBookmarks.size(); n++) { + if (counter >= 5) { + break; + } + if (mAllBookmarks.get(n).getTitle().toLowerCase(Locale.getDefault()) + .startsWith(query)) { + bookmarks.add(mAllBookmarks.get(n)); + counter++; + } else if (mAllBookmarks.get(n).getUrl().contains(query)) { + bookmarks.add(mAllBookmarks.get(n)); + counter++; + } + } + subscriber.onNext(bookmarks); + subscriber.onComplete(); + } + }); + } + + @NonNull + private Observable> getSuggestionsForQuery(@NonNull final String query) { + return SuggestionsTask.getObservable(query, mContext); + } + + @NonNull + private Observable> getHistoryForQuery(@NonNull final String query) { + return Observable.create(new Action>() { + @Override + public void onSubscribe(@NonNull Subscriber> subscriber) { + List historyList = mDatabaseHandler.findItemsContaining(query); + subscriber.onNext(historyList); + subscriber.onComplete(); + } + }); + } + + private boolean shouldRequestNetwork() { + return mUseGoogle && !mIsIncognito; + } + + private static class SearchFilter extends Filter { + + @NonNull private Suggestions mSuggestions; + + public SearchFilter(@NonNull Suggestions suggestions) { + mSuggestions = suggestions; + } + + @Override + protected FilterResults performFiltering(CharSequence constraint) { + FilterResults results = new FilterResults(); + if (constraint == null || constraint.length() == 0) { + mSuggestions.clearSuggestions(); + return results; + } + String query = constraint.toString().toLowerCase(Locale.getDefault()); + + if (mSuggestions.shouldRequestNetwork() && !SuggestionsTask.isRequestInProgress()) { + mSuggestions.getSuggestionsForQuery(query) + .subscribeOn(Schedulers.worker()) + .observeOn(Schedulers.main()) + .subscribe(new OnSubscribe>() { + @Override + public void onNext(@Nullable List item) { + mSuggestions.combineResults(null, null, item); + } + }); + } + + mSuggestions.getBookmarksForQuery(query) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new OnSubscribe>() { + @Override + public void onNext(@Nullable List item) { + mSuggestions.combineResults(item, null, null); + } + }); + + mSuggestions.getHistoryForQuery(query) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new OnSubscribe>() { + @Override + public void onNext(@Nullable List item) { + mSuggestions.combineResults(null, item, null); + } + }); + results.count = 1; + return results; + } + + @Override + public CharSequence convertResultToString(Object resultValue) { + return ((HistoryItem) resultValue).getUrl(); + } + + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + mSuggestions.combineResults(null, null, null); + } + } + + private static class ClearCacheRunnable implements Runnable { + + @NonNull + private final Application app; + + public ClearCacheRunnable(@NonNull Application app) { + this.app = app; + } + + @Override + public void run() { + File dir = new File(app.getCacheDir().toString()); + String[] fileList = dir.list(new NameFilter()); + long earliestTimeAllowed = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); + for (String fileName : fileList) { + File file = new File(dir.getPath() + fileName); + if (earliestTimeAllowed > file.lastModified()) { + file.delete(); + } + } + } + + private static class NameFilter implements FilenameFilter { + + @Override + public boolean accept(File dir, @NonNull String filename) { + return filename.endsWith(CACHE_FILE_TYPE); + } + } + } + + private static class SuggestionsComparator implements Comparator { + + @Override + public int compare(@NonNull HistoryItem lhs, @NonNull HistoryItem rhs) { + if (lhs.getImageId() == rhs.getImageId()) return 0; + if (lhs.getImageId() == R.drawable.ic_bookmark) return -1; + if (rhs.getImageId() == R.drawable.ic_bookmark) return 1; + if (lhs.getImageId() == R.drawable.ic_history) return -1; + return 1; + } + } +} diff --git a/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.java b/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.java index 4dd8378..d12b967 100644 --- a/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.java +++ b/app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.java @@ -227,6 +227,8 @@ public class SuggestionsAdapter extends BaseAdapter implements Filterable, Sugge int counter = 0; synchronized (mBookmarks) { mBookmarks.clear(); + // TODO Synchronizing on this... freezes up during getFilteredList method execution since + // both are synchronized on this synchronized (SuggestionsAdapter.this) { for (int n = 0; n < mAllBookmarks.size(); n++) { if (counter >= 5) { diff --git a/app/src/main/java/acr/browser/lightning/search/SuggestionsTask.java b/app/src/main/java/acr/browser/lightning/search/SuggestionsTask.java new file mode 100644 index 0000000..293f558 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/search/SuggestionsTask.java @@ -0,0 +1,223 @@ +package acr.browser.lightning.search; + +import android.app.Application; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.lang.ref.WeakReference; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import acr.browser.lightning.R; +import acr.browser.lightning.app.BrowserApp; +import acr.browser.lightning.database.HistoryItem; +import com.anthonycr.bonsai.Action; +import com.anthonycr.bonsai.Observable; +import com.anthonycr.bonsai.Subscriber; +import acr.browser.lightning.utils.Utils; + +public class SuggestionsTask { + + private static final String TAG = RetrieveSuggestionsTask.class.getSimpleName(); + + private static final Pattern SPACE_PATTERN = Pattern.compile(" ", Pattern.LITERAL); + private static final String ENCODING = "ISO-8859-1"; + private static final long INTERVAL_DAY = TimeUnit.DAYS.toMillis(1); + private static final String DEFAULT_LANGUAGE = "en"; + @Nullable private static XmlPullParser sXpp; + @Nullable private static String sLanguage; + @NonNull private final SuggestionsResult mResultCallback; + @NonNull private final Application mApplication; + @NonNull private final String mSearchSubtitle; + @NonNull private String mQuery; + + private static volatile boolean sIsTaskExecuting = false; + + public static boolean isRequestInProgress() { + return sIsTaskExecuting; + } + + public static Observable> getObservable(@NonNull final String query, @NonNull final Context context) { + return Observable.create(new Action>() { + @Override + public void onSubscribe(@NonNull final Subscriber> subscriber) { + sIsTaskExecuting = true; + new SuggestionsTask(query, BrowserApp.get(context), new SuggestionsResult() { + @Override + public void resultReceived(@NonNull List searchResults) { + subscriber.onNext(searchResults); + subscriber.onComplete(); + } + }).run(); + sIsTaskExecuting = false; + } + }); + } + + private SuggestionsTask(@NonNull String query, + @NonNull Application application, + @NonNull SuggestionsResult callback) { + mQuery = query; + mResultCallback = callback; + mApplication = application; + mSearchSubtitle = mApplication.getString(R.string.suggestion); + } + + @NonNull + private static synchronized String getLanguage() { + if (sLanguage == null) { + sLanguage = Locale.getDefault().getLanguage(); + } + if (TextUtils.isEmpty(sLanguage)) { + sLanguage = DEFAULT_LANGUAGE; + } + return sLanguage; + } + + @NonNull + private static synchronized XmlPullParser getParser() throws XmlPullParserException { + if (sXpp == null) { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + sXpp = factory.newPullParser(); + } + return sXpp; + } + + private void run() { + List filter = new ArrayList<>(5); + try { + mQuery = SPACE_PATTERN.matcher(mQuery).replaceAll("+"); + URLEncoder.encode(mQuery, ENCODING); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + File cache = downloadSuggestionsForQuery(mQuery, getLanguage(), mApplication); + if (!cache.exists()) { + post(filter); + return; + } + InputStream fileInput = null; + try { + fileInput = new BufferedInputStream(new FileInputStream(cache)); + XmlPullParser parser = getParser(); + parser.setInput(fileInput, ENCODING); + int eventType = parser.getEventType(); + int counter = 0; + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG && "suggestion".equals(parser.getName())) { + String suggestion = parser.getAttributeValue(null, "data"); + filter.add(new HistoryItem(mSearchSubtitle + " \"" + suggestion + '"', + suggestion, R.drawable.ic_search)); + counter++; + if (counter >= 5) { + break; + } + } + eventType = parser.next(); + } + } catch (Exception e) { + post(filter); + return; + } finally { + Utils.close(fileInput); + } + post(filter); + } + + private void post(@NonNull List result) { + mResultCallback.resultReceived(result); + } + + /** + * This method downloads the search suggestions for the specific query. + * NOTE: This is a blocking operation, do not run on the UI thread. + * + * @param query the query to get suggestions for + * @return the cache file containing the suggestions + */ + @NonNull + private static File downloadSuggestionsForQuery(@NonNull String query, String language, @NonNull Application app) { + File cacheFile = new File(app.getCacheDir(), query.hashCode() + Suggestions.CACHE_FILE_TYPE); + if (System.currentTimeMillis() - INTERVAL_DAY < cacheFile.lastModified()) { + return cacheFile; + } + if (!isNetworkConnected(app)) { + return cacheFile; + } + InputStream in = null; + FileOutputStream fos = null; + try { + // Old API that doesn't support HTTPS + // http://google.com/complete/search?q= + query + &output=toolbar&hl= + language + URL url = new URL("https://suggestqueries.google.com/complete/search?output=toolbar&hl=" + + language + "&q=" + query); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setDoInput(true); + connection.connect(); + if (connection.getResponseCode() >= HttpURLConnection.HTTP_MULT_CHOICE || + connection.getResponseCode() < HttpURLConnection.HTTP_OK) { + Log.e(TAG, "Search API Responded with code: " + connection.getResponseCode()); + connection.disconnect(); + return cacheFile; + } + in = connection.getInputStream(); + + if (in != null) { + //noinspection IOResourceOpenedButNotSafelyClosed + fos = new FileOutputStream(cacheFile); + int buffer; + while ((buffer = in.read()) != -1) { + fos.write(buffer); + } + fos.flush(); + } + connection.disconnect(); + cacheFile.setLastModified(System.currentTimeMillis()); + } catch (Exception e) { + Log.w(TAG, "Problem getting search suggestions", e); + } finally { + Utils.close(in); + Utils.close(fos); + } + return cacheFile; + } + + private static boolean isNetworkConnected(@NonNull Context context) { + NetworkInfo networkInfo = getActiveNetworkInfo(context); + return networkInfo != null && networkInfo.isConnected(); + } + + @Nullable + private static NetworkInfo getActiveNetworkInfo(@NonNull Context context) { + ConnectivityManager connectivity = (ConnectivityManager) context + .getApplicationContext() + .getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity == null) { + return null; + } + return connectivity.getActiveNetworkInfo(); + } + +} diff --git a/app/src/main/java/acr/browser/lightning/utils/Utils.java b/app/src/main/java/acr/browser/lightning/utils/Utils.java index 705ed2c..a437a5f 100644 --- a/app/src/main/java/acr/browser/lightning/utils/Utils.java +++ b/app/src/main/java/acr/browser/lightning/utils/Utils.java @@ -32,6 +32,8 @@ import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.view.View; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; import android.webkit.URLUtil; import com.anthonycr.grant.PermissionsManager; @@ -54,6 +56,8 @@ import acr.browser.lightning.preference.PreferenceManager; public final class Utils { + private static final String TAG = Utils.class.getSimpleName(); + public static boolean doesSupportHeaders() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; } @@ -139,7 +143,11 @@ public final class Utils { * @param resource the string resource to show to the user. */ public static void showSnackbar(@NonNull Activity activity, @StringRes int resource) { - View view = activity.findViewById(android.R.id.content); + View view = activity.findViewById(R.id.coordinator_layout); + if (view == null) { + Log.d(TAG, "Unable to find coordinator layout, using content view"); + view = activity.findViewById(android.R.id.content); + } if (view == null) return; Snackbar.make(view, resource, Snackbar.LENGTH_SHORT).show(); } @@ -151,7 +159,11 @@ public final class Utils { * @param message the string message to show to the user. */ public static void showSnackbar(@NonNull Activity activity, @NonNull String message) { - View view = activity.findViewById(android.R.id.content); + View view = activity.findViewById(R.id.coordinator_layout); + if (view == null) { + Log.d(TAG, "Unable to find coordinator layout, using content view"); + view = activity.findViewById(android.R.id.content); + } if (view == null) return; Snackbar.make(view, message, Snackbar.LENGTH_SHORT).show(); } diff --git a/app/src/main/java/acr/browser/lightning/view/LightningView.java b/app/src/main/java/acr/browser/lightning/view/LightningView.java index e982555..777c570 100644 --- a/app/src/main/java/acr/browser/lightning/view/LightningView.java +++ b/app/src/main/java/acr/browser/lightning/view/LightningView.java @@ -50,11 +50,11 @@ import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.download.LightningDownloadListener; import acr.browser.lightning.preference.PreferenceManager; -import acr.browser.lightning.react.Action; -import acr.browser.lightning.react.Observable; -import acr.browser.lightning.react.Schedulers; -import acr.browser.lightning.react.Subscriber; -import acr.browser.lightning.react.OnSubscribe; +import com.anthonycr.bonsai.Action; +import com.anthonycr.bonsai.Observable; +import com.anthonycr.bonsai.Schedulers; +import com.anthonycr.bonsai.Subscriber; +import com.anthonycr.bonsai.OnSubscribe; import acr.browser.lightning.utils.ProxyUtils; import acr.browser.lightning.utils.UrlUtils; import acr.browser.lightning.utils.Utils; diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 313800b..acae3ed 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,49 +1,57 @@ - + tools:context=".activity.BrowserActivity"> - + android:background="@null" + android:orientation="vertical"> - - - - - - - - - - - - - - - - \ No newline at end of file + android:fitsSystemWindows="true"> + + + + + + + + + + + + + + + + + + \ No newline at end of file