Browse Source

Move from internal observable classes to bonsai, fix threading problems with suggestions

master
Anthony Restaino 8 years ago
parent
commit
e19d08513b
  1. 135
      app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java
  2. 6
      app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java
  3. 6
      app/src/main/java/acr/browser/lightning/activity/MainActivity.java
  4. 12
      app/src/main/java/acr/browser/lightning/activity/ReadingActivity.java
  5. 10
      app/src/main/java/acr/browser/lightning/activity/TabsManager.java
  6. 4
      app/src/main/java/acr/browser/lightning/activity/ThemableBrowserActivity.java
  7. 3
      app/src/main/java/acr/browser/lightning/app/AppComponent.java
  8. 2
      app/src/main/java/acr/browser/lightning/browser/BrowserPresenter.java
  9. 6
      app/src/main/java/acr/browser/lightning/database/BookmarkLocalSync.java
  10. 11
      app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java
  11. 4
      app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java
  12. 10
      app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java
  13. 2
      app/src/main/java/acr/browser/lightning/fragment/TabsFragment.java
  14. 16
      app/src/main/java/acr/browser/lightning/react/Action.java
  15. 254
      app/src/main/java/acr/browser/lightning/react/Observable.java
  16. 47
      app/src/main/java/acr/browser/lightning/react/OnSubscribe.java
  17. 46
      app/src/main/java/acr/browser/lightning/react/Schedulers.java
  18. 51
      app/src/main/java/acr/browser/lightning/react/Subscriber.java
  19. 7
      app/src/main/java/acr/browser/lightning/react/Subscription.java
  20. 21
      app/src/main/java/acr/browser/lightning/react/ThreadExecutor.java
  21. 418
      app/src/main/java/acr/browser/lightning/search/Suggestions.java
  22. 2
      app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.java
  23. 223
      app/src/main/java/acr/browser/lightning/search/SuggestionsTask.java
  24. 16
      app/src/main/java/acr/browser/lightning/utils/Utils.java
  25. 10
      app/src/main/java/acr/browser/lightning/view/LightningView.java
  26. 86
      app/src/main/res/layout/activity_main.xml

135
app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java

@ -43,7 +43,6 @@ import android.support.v7.app.ActionBar; @@ -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; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -877,7 +879,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
ArrayAdapter<String> 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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) {

6
app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java

@ -9,9 +9,9 @@ import android.webkit.CookieManager; @@ -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 {

6
app/src/main/java/acr/browser/lightning/activity/MainActivity.java

@ -9,9 +9,9 @@ import android.webkit.CookieManager; @@ -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 {

12
app/src/main/java/acr/browser/lightning/activity/ReadingActivity.java

@ -28,12 +28,12 @@ import acr.browser.lightning.R; @@ -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;

10
app/src/main/java/acr/browser/lightning/activity/TabsManager.java

@ -28,11 +28,11 @@ import acr.browser.lightning.constant.StartPage; @@ -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;

4
app/src/main/java/acr/browser/lightning/activity/ThemableBrowserActivity.java

@ -3,12 +3,8 @@ package acr.browser.lightning.activity; @@ -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;

3
app/src/main/java/acr/browser/lightning/app/AppComponent.java

@ -17,6 +17,7 @@ import acr.browser.lightning.fragment.DebugSettingsFragment; @@ -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 { @@ -70,4 +71,6 @@ public interface AppComponent {
void inject(DebugSettingsFragment fragment);
void inject(Suggestions suggestions);
}

2
app/src/main/java/acr/browser/lightning/browser/BrowserPresenter.java

@ -17,7 +17,7 @@ import acr.browser.lightning.app.BrowserApp; @@ -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;

6
app/src/main/java/acr/browser/lightning/database/BookmarkLocalSync.java

@ -11,9 +11,9 @@ import android.util.Log; @@ -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 {

11
app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java

@ -217,9 +217,14 @@ public class LightningDialogBuilder { @@ -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));

4
app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java

@ -39,8 +39,8 @@ import acr.browser.lightning.database.BookmarkLocalSync; @@ -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;

10
app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java

@ -49,11 +49,11 @@ import acr.browser.lightning.database.HistoryItem; @@ -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;

2
app/src/main/java/acr/browser/lightning/fragment/TabsFragment.java

@ -330,6 +330,7 @@ public class TabsFragment extends Fragment implements View.OnClickListener, View @@ -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 @@ -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));

16
app/src/main/java/acr/browser/lightning/react/Action.java

@ -1,16 +0,0 @@ @@ -1,16 +0,0 @@
package acr.browser.lightning.react;
import android.support.annotation.NonNull;
public interface Action<T> {
/**
* 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<T> subscriber);
}

254
app/src/main/java/acr/browser/lightning/react/Observable.java

@ -1,254 +0,0 @@ @@ -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 <T> the type that the Observable will emit.
*/
public class Observable<T> {
private static final String TAG = Observable.class.getSimpleName();
@NonNull private final Action<T> mAction;
@Nullable private Executor mSubscriberThread;
@Nullable private Executor mObserverThread;
@NonNull private final Executor mDefault;
private Observable(@NonNull Action<T> 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 <T> the type that will be emitted to the onSubscribe
* @return a valid non-null Observable.
*/
@NonNull
public static <T> Observable<T> create(@NonNull Action<T> 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<T> 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<T> 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<T>() {
@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<T> onSubscribe) {
Preconditions.checkNonNull(onSubscribe);
final Subscriber<T> 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<T> implements Subscriber<T> {
@Nullable private volatile OnSubscribe<T> mOnSubscribe;
@NonNull private final Observable<T> mObservable;
private boolean mOnCompleteExecuted = false;
private boolean mOnError = false;
public SubscriberImpl(@NonNull OnSubscribe<T> onSubscribe, @NonNull Observable<T> observable) {
mOnSubscribe = onSubscribe;
mObservable = observable;
}
@Override
public void unsubscribe() {
mOnSubscribe = null;
}
@Override
public void onComplete() {
OnSubscribe<T> 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<T> onSubscribe = mOnSubscribe;
if (onSubscribe != null) {
mObservable.executeOnObserverThread(new OnStartRunnable<>(onSubscribe));
}
}
@Override
public void onError(@NonNull final Throwable throwable) {
OnSubscribe<T> onSubscribe = mOnSubscribe;
if (onSubscribe != null) {
mOnError = true;
mObservable.executeOnObserverThread(new OnErrorRunnable<>(onSubscribe, throwable));
}
}
@Override
public void onNext(final T item) {
OnSubscribe<T> 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<T> implements Runnable {
private final OnSubscribe<T> onSubscribe;
public OnCompleteRunnable(@NonNull OnSubscribe<T> onSubscribe) {this.onSubscribe = onSubscribe;}
@Override
public void run() {
onSubscribe.onComplete();
}
}
private static class OnNextRunnable<T> implements Runnable {
private final OnSubscribe<T> onSubscribe;
private final T item;
public OnNextRunnable(@NonNull OnSubscribe<T> onSubscribe, T item) {
this.onSubscribe = onSubscribe;
this.item = item;
}
@Override
public void run() {
onSubscribe.onNext(item);
}
}
private static class OnErrorRunnable<T> implements Runnable {
private final OnSubscribe<T> onSubscribe;
private final Throwable throwable;
public OnErrorRunnable(@NonNull OnSubscribe<T> onSubscribe, @NonNull Throwable throwable) {
this.onSubscribe = onSubscribe;
this.throwable = throwable;
}
@Override
public void run() {
onSubscribe.onError(throwable);
}
}
private static class OnStartRunnable<T> implements Runnable {
private final OnSubscribe<T> onSubscribe;
public OnStartRunnable(@NonNull OnSubscribe<T> onSubscribe) {this.onSubscribe = onSubscribe;}
@Override
public void run() {
onSubscribe.onStart();
}
}
}

47
app/src/main/java/acr/browser/lightning/react/OnSubscribe.java

@ -1,47 +0,0 @@ @@ -1,47 +0,0 @@
package acr.browser.lightning.react;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
public abstract class OnSubscribe<T> {
/**
* 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() {}
}

46
app/src/main/java/acr/browser/lightning/react/Schedulers.java

@ -1,46 +0,0 @@ @@ -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;
}
}

51
app/src/main/java/acr/browser/lightning/react/Subscriber.java

@ -1,51 +0,0 @@ @@ -1,51 +0,0 @@
package acr.browser.lightning.react;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
public interface Subscriber<T> 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();
}

7
app/src/main/java/acr/browser/lightning/react/Subscription.java

@ -1,7 +0,0 @@ @@ -1,7 +0,0 @@
package acr.browser.lightning.react;
public interface Subscription {
void unsubscribe();
}

21
app/src/main/java/acr/browser/lightning/react/ThreadExecutor.java

@ -1,21 +0,0 @@ @@ -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);
}
}

418
app/src/main/java/acr/browser/lightning/search/Suggestions.java

@ -0,0 +1,418 @@ @@ -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<HistoryItem> mFilteredList = new ArrayList<>(5);
private final List<HistoryItem> mHistory = new ArrayList<>(5);
private final List<HistoryItem> mBookmarks = new ArrayList<>(5);
private final List<HistoryItem> 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<HistoryItem> mFilterComparator = new SuggestionsComparator();
@Inject HistoryDatabase mDatabaseHandler;
@Inject BookmarkManager mBookmarkManager;
@Inject PreferenceManager mPreferenceManager;
private final List<HistoryItem> 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<HistoryItem> list) {
mFilteredList.clear();
mFilteredList.addAll(list);
notifyDataSetChanged();
}
private void clearSuggestions() {
Observable.create(new Action<Void>() {
@Override
public void onSubscribe(@NonNull Subscriber<Void> subscriber) {
mBookmarks.clear();
mHistory.clear();
mSuggestions.clear();
subscriber.onComplete();
}
}).subscribeOn(FILTER_SCHEDULER)
.observeOn(Schedulers.main())
.subscribe();
}
private void combineResults(final @Nullable List<HistoryItem> bookmarkList,
final @Nullable List<HistoryItem> historyList,
final @Nullable List<HistoryItem> suggestionList) {
Observable.create(new Action<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull Subscriber<List<HistoryItem>> subscriber) {
List<HistoryItem> 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<HistoryItem> bookmark = mBookmarks.iterator();
Iterator<HistoryItem> history = mHistory.iterator();
Iterator<HistoryItem> 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<List<HistoryItem>>() {
@Override
public void onNext(@Nullable List<HistoryItem> item) {
publishResults(item);
}
});
}
@NonNull
private Observable<List<HistoryItem>> getBookmarksForQuery(@NonNull final String query) {
return Observable.create(new Action<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull Subscriber<List<HistoryItem>> subscriber) {
List<HistoryItem> 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<List<HistoryItem>> getSuggestionsForQuery(@NonNull final String query) {
return SuggestionsTask.getObservable(query, mContext);
}
@NonNull
private Observable<List<HistoryItem>> getHistoryForQuery(@NonNull final String query) {
return Observable.create(new Action<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull Subscriber<List<HistoryItem>> subscriber) {
List<HistoryItem> 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<List<HistoryItem>>() {
@Override
public void onNext(@Nullable List<HistoryItem> item) {
mSuggestions.combineResults(null, null, item);
}
});
}
mSuggestions.getBookmarksForQuery(query)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new OnSubscribe<List<HistoryItem>>() {
@Override
public void onNext(@Nullable List<HistoryItem> item) {
mSuggestions.combineResults(item, null, null);
}
});
mSuggestions.getHistoryForQuery(query)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.main())
.subscribe(new OnSubscribe<List<HistoryItem>>() {
@Override
public void onNext(@Nullable List<HistoryItem> 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<HistoryItem> {
@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;
}
}
}

2
app/src/main/java/acr/browser/lightning/search/SuggestionsAdapter.java

@ -227,6 +227,8 @@ public class SuggestionsAdapter extends BaseAdapter implements Filterable, Sugge @@ -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) {

223
app/src/main/java/acr/browser/lightning/search/SuggestionsTask.java

@ -0,0 +1,223 @@ @@ -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<List<HistoryItem>> getObservable(@NonNull final String query, @NonNull final Context context) {
return Observable.create(new Action<List<HistoryItem>>() {
@Override
public void onSubscribe(@NonNull final Subscriber<List<HistoryItem>> subscriber) {
sIsTaskExecuting = true;
new SuggestionsTask(query, BrowserApp.get(context), new SuggestionsResult() {
@Override
public void resultReceived(@NonNull List<HistoryItem> 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<HistoryItem> 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<HistoryItem> 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();
}
}

16
app/src/main/java/acr/browser/lightning/utils/Utils.java

@ -32,6 +32,8 @@ import android.text.TextUtils; @@ -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; @@ -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 { @@ -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 { @@ -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();
}

10
app/src/main/java/acr/browser/lightning/view/LightningView.java

@ -50,11 +50,11 @@ import acr.browser.lightning.database.BookmarkManager; @@ -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;

86
app/src/main/res/layout/activity_main.xml

@ -1,49 +1,57 @@ @@ -1,49 +1,57 @@
<!-- Copyright 2014 ACR Development -->
<LinearLayout
android:id="@+id/main_layout"
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinator_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null"
android:orientation="vertical">
tools:context=".activity.BrowserActivity">
<android.support.v4.widget.DrawerLayout
android:id="@+id/drawer_layout"
<LinearLayout
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
android:background="@null"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ui_layout"
<android.support.v4.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar"/>
<include layout="@layout/browser_content"/>
<include layout="@layout/search_interface"/>
</LinearLayout>
<FrameLayout
android:id="@+id/left_drawer"
android:layout_width="@dimen/navigation_width"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="?attr/drawerBackground"
android:fitsSystemWindows="true"
android:weightSum="1"/>
<FrameLayout
android:id="@+id/right_drawer"
android:layout_width="@dimen/navigation_width"
android:layout_height="match_parent"
android:layout_gravity="end"
android:background="?attr/drawerBackground"
android:fitsSystemWindows="true"
android:weightSum="1"/>
</android.support.v4.widget.DrawerLayout>
</LinearLayout>
android:fitsSystemWindows="true">
<LinearLayout
android:id="@+id/ui_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar"/>
<include layout="@layout/browser_content"/>
<include layout="@layout/search_interface"/>
</LinearLayout>
<FrameLayout
android:id="@+id/left_drawer"
android:layout_width="@dimen/navigation_width"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="?attr/drawerBackground"
android:fitsSystemWindows="true"
android:weightSum="1"/>
<FrameLayout
android:id="@+id/right_drawer"
android:layout_width="@dimen/navigation_width"
android:layout_height="match_parent"
android:layout_gravity="end"
android:background="?attr/drawerBackground"
android:fitsSystemWindows="true"
android:weightSum="1"/>
</android.support.v4.widget.DrawerLayout>
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
Loading…
Cancel
Save