Move from internal observable classes to bonsai, fix threading problems with suggestions
This commit is contained in:
parent
ca385422dc
commit
e19d08513b
@ -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<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
|
||||
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) {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package acr.browser.lightning.react;
|
||||
|
||||
public interface Subscription {
|
||||
|
||||
void unsubscribe();
|
||||
|
||||
}
|
@ -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
Normal file
418
app/src/main/java/acr/browser/lightning/search/Suggestions.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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">
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<include layout="@layout/toolbar"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/ui_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/browser_content"/>
|
||||
<include layout="@layout/toolbar"/>
|
||||
|
||||
<include layout="@layout/search_interface"/>
|
||||
</LinearLayout>
|
||||
<include layout="@layout/browser_content"/>
|
||||
|
||||
<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"/>
|
||||
<include layout="@layout/search_interface"/>
|
||||
</LinearLayout>
|
||||
|
||||
<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"/>
|
||||
<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"/>
|
||||
|
||||
</android.support.v4.widget.DrawerLayout>
|
||||
<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"/>
|
||||
|
||||
</LinearLayout>
|
||||
</android.support.v4.widget.DrawerLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</android.support.design.widget.CoordinatorLayout>
|
Loading…
x
Reference in New Issue
Block a user