From 9424a26e654ae59ac885a9208af6c05c73629699 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Sun, 27 Jan 2019 11:22:38 +0300 Subject: [PATCH] remove reading mode, search suggestions, and used by them additional modules --- app/build.gradle | 24 +- app/proguard-project.txt | 6 - app/src/main/AndroidManifest.xml | 11 - .../browser/activity/BrowserActivity.java | 4342 ++++++++--------- .../browser/fragment/BookmarksFragment.java | 10 - .../lightning/constant/Constants.java | 2 - .../purplei2p/lightning/di/AppComponent.java | 3 - .../dialog/LightningDialogBuilder.java | 4 - .../preference/PreferenceManager.java | 38 - .../reading/ArticleTextExtractor.java | 1215 ----- .../lightning/reading/Converter.java | 246 - .../lightning/reading/HtmlFetcher.java | 483 -- .../lightning/reading/ImageResult.java | 31 - .../purplei2p/lightning/reading/JResult.java | 274 -- .../lightning/reading/OutputFormatter.java | 216 - .../purplei2p/lightning/reading/SCache.java | 29 - .../purplei2p/lightning/reading/SHelper.java | 451 -- .../reading/activity/ReadingActivity.java | 342 -- .../lightning/search/SuggestionsAdapter.java | 737 ++- .../lightning/search/SuggestionsManager.kt | 35 - .../search/engine/BaseSearchEngine.java | 51 + .../search/engine/BaseSearchEngine.kt | 15 - .../lightning/search/engine/CustomSearch.java | 16 + .../lightning/search/engine/CustomSearch.kt | 12 - .../search/engine/DuckLiteSearch.java | 17 + .../lightning/search/engine/DuckLiteSearch.kt | 15 - .../lightning/search/engine/DuckSearch.java | 17 + .../lightning/search/engine/DuckSearch.kt | 15 - .../search/engine/LegworkSearch.java | 17 + .../lightning/search/engine/LegworkSearch.kt | 15 - .../suggestions/BaseSuggestionsModel.java | 165 - .../suggestions/DuckSuggestionsModel.java | 52 - .../suggestions/LegworkSuggestionsModel.java | 51 - .../fragment/GeneralSettingsFragment.java | 65 +- app/src/main/res/layout/bookmark_drawer.xml | 21 - app/src/main/res/menu-large/incognito.xml | 3 - app/src/main/res/menu-large/main.xml | 3 - app/src/main/res/menu-xlarge/incognito.xml | 3 - app/src/main/res/menu-xlarge/main.xml | 3 - app/src/main/res/menu/incognito.xml | 4 - app/src/main/res/menu/main.xml | 3 - app/src/main/res/values-ru/strings.xml | 5 - app/src/main/res/values/arrays.xml | 6 - app/src/main/res/values/strings.xml | 5 - app/src/main/res/xml/preference_general.xml | 3 - build.gradle | 6 +- 46 files changed, 2660 insertions(+), 6427 deletions(-) delete mode 100644 app/src/main/java/org/purplei2p/lightning/reading/ArticleTextExtractor.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/reading/Converter.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/reading/HtmlFetcher.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/reading/ImageResult.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/reading/JResult.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/reading/OutputFormatter.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/reading/SCache.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/reading/SHelper.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/reading/activity/ReadingActivity.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/search/SuggestionsManager.kt create mode 100644 app/src/main/java/org/purplei2p/lightning/search/engine/BaseSearchEngine.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/search/engine/BaseSearchEngine.kt create mode 100644 app/src/main/java/org/purplei2p/lightning/search/engine/CustomSearch.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/search/engine/CustomSearch.kt create mode 100644 app/src/main/java/org/purplei2p/lightning/search/engine/DuckLiteSearch.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/search/engine/DuckLiteSearch.kt create mode 100644 app/src/main/java/org/purplei2p/lightning/search/engine/DuckSearch.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/search/engine/DuckSearch.kt create mode 100644 app/src/main/java/org/purplei2p/lightning/search/engine/LegworkSearch.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/search/engine/LegworkSearch.kt delete mode 100644 app/src/main/java/org/purplei2p/lightning/search/suggestions/BaseSuggestionsModel.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/search/suggestions/DuckSuggestionsModel.java delete mode 100644 app/src/main/java/org/purplei2p/lightning/search/suggestions/LegworkSuggestionsModel.java diff --git a/app/build.gradle b/app/build.gradle index 55022b5..7f60481 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' apply plugin: 'com.getkeepsafe.dexcount' android { @@ -29,6 +28,9 @@ android { minifyEnabled false shrinkResources false proguardFiles 'proguard-project.txt' + applicationVariants.all { variant -> + renameAPK(variant, defaultConfig, 'debug') + } } release { @@ -36,6 +38,9 @@ android { shrinkResources true signingConfig signingConfigs.r4sas proguardFiles 'proguard-project.txt' + applicationVariants.all { variant -> + renameAPK(variant, defaultConfig, 'release') + } } } @@ -67,9 +72,6 @@ dependencies { compile "com.android.support:recyclerview-v7:$supportLibVersion" compile "com.android.support:support-v4:$supportLibVersion" - // html parsing for reading mode - compile 'org.jsoup:jsoup:1.10.2' - // dependency injection def daggerVersion = '2.11' compile "com.google.dagger:dagger:$daggerVersion" @@ -84,9 +86,6 @@ dependencies { // permissions compile 'com.anthonycr.grant:permissions:1.1.2' - // proxy support - compile 'com.squareup.okhttp3:okhttp:3.8.0' - // tor proxy def netcipherVersion = '2.0.0-alpha1' compile "info.guardianproject.netcipher:netcipher:$netcipherVersion" @@ -102,5 +101,14 @@ dependencies { releaseCompile "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion" // Kotlin - compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" } + +def renameAPK(variant, defaultConfig, buildType) { + variant.outputs.each { output -> + def formattedDate = new Date().format('yyMMdd') + + def file = output.packageApplication.outputFile + def fileName = defaultConfig.applicationId + "_" + defaultConfig.versionCode + "_" + formattedDate + "_" + buildType + ".apk" + output.packageApplication.outputFile = new File(file.parent, fileName) + } +} \ No newline at end of file diff --git a/app/proguard-project.txt b/app/proguard-project.txt index 51e19bc..84cb210 100644 --- a/app/proguard-project.txt +++ b/app/proguard-project.txt @@ -12,7 +12,6 @@ -keep public class * extends android.content.ContentProvider -keep public class * extends android.app.backup.BackupAgentHelper -keep public class * extends android.preference.Preference --keep public class org.purplei2p.lightning.reading.* -keepattributes *Annotation* @@ -35,11 +34,6 @@ @butterknife.* ; } -# this will fix a force close in ReadingActivity --keep public class org.jsoup.** { - public *; -} - # Without this rule, openFileChooser does not get called on KitKat -keep class org.purplei2p.lightning.view.LightningChromeClient { void openFileChooser(android.webkit.ValueCallback); diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 767539c..1005a96 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -139,17 +139,6 @@ - - - - - - - mUploadMessage; - private ValueCallback mFilePathCallback; - - // Primitives - private boolean mFullScreen; - private boolean mDarkTheme; - private boolean mIsFullScreen = false; - private boolean mIsImmersive = false; - private boolean mShowTabsInDrawer; - private boolean mSwapBookmarksAndTabs; - private int mOriginalOrientation; - private int mBackgroundColor; - private int mIconColor; - private int mDisabledIconColor; - private int mCurrentUiColor = Color.BLACK; - private long mKeyDownStartTime; - private String mSearchText; - private String mUntitledTitle; - private String mCameraPhotoPath; - - // The singleton BookmarkManager - @Inject BookmarkModel mBookmarkManager; - - @Inject HistoryModel mHistoryModel; - - @Inject LightningDialogBuilder mBookmarksDialogBuilder; - - @Inject SearchBoxModel mSearchBoxModel; - - @Inject SearchEngineProvider mSearchEngineProvider; - - private TabsManager mTabsManager; - - // Image - private Bitmap mWebpageBitmap; - private final ColorDrawable mBackground = new ColorDrawable(); - private Drawable mDeleteIcon, mRefreshIcon, mClearIcon, mIcon; - - private BrowserPresenter mPresenter; - private TabsView mTabsView; - private BookmarksView mBookmarksView; - - // Proxy - @Inject ProxyUtils mProxyUtils; - - // Constant - 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); - private static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams( - LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - - protected abstract boolean isIncognito(); - - public abstract void closeActivity(); - - public abstract void updateHistory(@Nullable final String title, @NonNull final String url); - - @NonNull - protected abstract Completable updateCookiePreference(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - BrowserApp.getAppComponent().inject(this); - setContentView(R.layout.activity_main); - ButterKnife.bind(this); - - mTabsManager = new TabsManager(); - mPresenter = new BrowserPresenter(this, isIncognito()); - - initialize(savedInstanceState); - } - - private synchronized void initialize(Bundle savedInstanceState) { - initializeToolbarHeight(getResources().getConfiguration()); - setSupportActionBar(mToolbar); - ActionBar actionBar = getSupportActionBar(); - - //TODO make sure dark theme flag gets set correctly - 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); - mShowTabsInDrawer = mPreferences.getShowTabsInDrawer(!isTablet()); - mSwapBookmarksAndTabs = mPreferences.getBookmarksAndTabsSwapped(); - - // initialize background ColorDrawable - int primaryColor = ThemeUtils.getPrimaryColor(this); - mBackground.setColor(primaryColor); - - // Drawer stutters otherwise - mDrawerLeft.setLayerType(View.LAYER_TYPE_NONE, null); - mDrawerRight.setLayerType(View.LAYER_TYPE_NONE, null); - - mDrawerLayout.addDrawerListener(new DrawerListener() { - @Override - public void onDrawerSlide(View drawerView, float slideOffset) {} - - @Override - public void onDrawerOpened(View drawerView) {} - - @Override - public void onDrawerClosed(View drawerView) {} - - @Override - public void onDrawerStateChanged(int newState) { - if (newState == DrawerLayout.STATE_DRAGGING) { - mDrawerLeft.setLayerType(View.LAYER_TYPE_HARDWARE, null); - mDrawerRight.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } else if (newState == DrawerLayout.STATE_IDLE) { - mDrawerLeft.setLayerType(View.LAYER_TYPE_NONE, null); - mDrawerRight.setLayerType(View.LAYER_TYPE_NONE, null); - } - } - }); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !mShowTabsInDrawer) { - getWindow().setStatusBarColor(Color.BLACK); - } - - setNavigationDrawerWidth(); - mDrawerLayout.addDrawerListener(new DrawerLocker()); - - mWebpageBitmap = ThemeUtils.getThemedBitmap(this, R.drawable.ic_webpage, mDarkTheme); - - final FragmentManager fragmentManager = getSupportFragmentManager(); - - TabsFragment tabsFragment = (TabsFragment) fragmentManager.findFragmentByTag(TAG_TABS_FRAGMENT); - BookmarksFragment bookmarksFragment = (BookmarksFragment) fragmentManager.findFragmentByTag(TAG_BOOKMARK_FRAGMENT); - - if (tabsFragment != null) { - fragmentManager.beginTransaction().remove(tabsFragment).commit(); - } - tabsFragment = TabsFragment.createTabsFragment(isIncognito(), mShowTabsInDrawer); - - mTabsView = tabsFragment; - - if (bookmarksFragment != null) { - fragmentManager.beginTransaction().remove(bookmarksFragment).commit(); - } - bookmarksFragment = BookmarksFragment.createFragment(isIncognito()); - - mBookmarksView = bookmarksFragment; - - fragmentManager.executePendingTransactions(); - - fragmentManager - .beginTransaction() - .replace(getTabsFragmentViewId(), tabsFragment, TAG_TABS_FRAGMENT) - .replace(getBookmarksFragmentViewId(), bookmarksFragment, TAG_BOOKMARK_FRAGMENT) - .commit(); - if (mShowTabsInDrawer) { - mToolbarLayout.removeView(findViewById(R.id.tabs_toolbar_container)); - } - - Preconditions.checkNonNull(actionBar); - - // set display options of the ActionBar - actionBar.setDisplayShowTitleEnabled(false); - actionBar.setDisplayShowHomeEnabled(false); - actionBar.setDisplayShowCustomEnabled(true); - actionBar.setCustomView(R.layout.toolbar_content); - - View customView = actionBar.getCustomView(); - LayoutParams lp = customView.getLayoutParams(); - lp.width = LayoutParams.MATCH_PARENT; - lp.height = LayoutParams.MATCH_PARENT; - customView.setLayoutParams(lp); - - mArrowImage = customView.findViewById(R.id.arrow); - FrameLayout arrowButton = customView.findViewById(R.id.arrow_button); - if (mShowTabsInDrawer) { - if (mArrowImage.getWidth() <= 0) { - mArrowImage.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - } - updateTabNumber(0); - - // Post drawer locking in case the activity is being recreated - Handlers.MAIN.post(new Runnable() { - @Override - public void run() { - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, getTabDrawer()); - } - }); - } else { - - // Post drawer locking in case the activity is being recreated - Handlers.MAIN.post(new Runnable() { - @Override - public void run() { - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, getTabDrawer()); - } - }); - mArrowImage.setImageResource(R.drawable.ic_action_home); - mArrowImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); - } - - // Post drawer locking in case the activity is being recreated - Handlers.MAIN.post(new Runnable() { - @Override - public void run() { - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, getBookmarkDrawer()); - } - }); - - arrowButton.setOnClickListener(this); - - // create the search EditText in the ToolBar - mSearch = customView.findViewById(R.id.search); - mSearchBackground = customView.findViewById(R.id.search_container); - - // initialize search background color - mSearchBackground.getBackground().setColorFilter(getSearchBarColor(primaryColor, primaryColor), PorterDuff.Mode.SRC_IN); - mSearch.setHintTextColor(ThemeUtils.getThemedTextHintColor(mDarkTheme)); - mSearch.setTextColor(mDarkTheme ? Color.WHITE : Color.BLACK); - - mUntitledTitle = getString(R.string.untitled); - mBackgroundColor = ThemeUtils.getPrimaryColor(this); - mDeleteIcon = ThemeUtils.getThemedDrawable(this, R.drawable.ic_action_delete, mDarkTheme); - mRefreshIcon = ThemeUtils.getThemedDrawable(this, R.drawable.ic_action_refresh, mDarkTheme); - mClearIcon = ThemeUtils.getThemedDrawable(this, R.drawable.ic_action_delete, mDarkTheme); - - int iconBounds = Utils.dpToPx(24); - mDeleteIcon.setBounds(0, 0, iconBounds, iconBounds); - mRefreshIcon.setBounds(0, 0, iconBounds, iconBounds); - mClearIcon.setBounds(0, 0, iconBounds, iconBounds); - mIcon = mRefreshIcon; - SearchListenerClass search = new SearchListenerClass(); - mSearch.setCompoundDrawablePadding(Utils.dpToPx(3)); - mSearch.setCompoundDrawables(null, null, mRefreshIcon, null); - mSearch.setOnKeyListener(search); - mSearch.setOnFocusChangeListener(search); - mSearch.setOnEditorActionListener(search); - mSearch.setOnTouchListener(search); - mSearch.setOnPreFocusListener(search); - - initializeSearchSuggestions(mSearch); - - mDrawerLayout.setDrawerShadow(R.drawable.drawer_right_shadow, GravityCompat.END); - mDrawerLayout.setDrawerShadow(R.drawable.drawer_left_shadow, GravityCompat.START); - - if (API <= Build.VERSION_CODES.JELLY_BEAN_MR2) { - //noinspection deprecation - WebIconDatabase.getInstance().open(getDir("icons", MODE_PRIVATE).getPath()); - } - - @SuppressWarnings("VariableNotUsedInsideIf") - Intent intent = savedInstanceState == null ? getIntent() : null; - - boolean launchedFromHistory = intent != null && (intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0; - - if (isPanicTrigger(intent)) { - setIntent(null); - panicClean(); - } else { - if (launchedFromHistory) { - intent = null; - } - mPresenter.setupTabs(intent); - setIntent(null); - } - } - - @IdRes - private int getBookmarksFragmentViewId() { - return mSwapBookmarksAndTabs ? R.id.left_drawer : R.id.right_drawer; - } - - private int getTabsFragmentViewId() { - if (mShowTabsInDrawer) { - return mSwapBookmarksAndTabs ? R.id.right_drawer : R.id.left_drawer; - } else { - return R.id.tabs_toolbar_container; - } - } - - /** - * Determines if an intent is originating - * from a panic trigger. - * - * @param intent the intent to check. - * @return true if the panic trigger sent - * the intent, false otherwise. - */ - protected static boolean isPanicTrigger(@Nullable Intent intent) { - return intent != null && INTENT_PANIC_TRIGGER.equals(intent.getAction()); - } - - protected void panicClean() { - Log.d(TAG, "Closing browser"); - mTabsManager.newTab(this, "", false); - mTabsManager.switchToTab(0); - mTabsManager.clearSavedState(); - HistoryPage.deleteHistoryPage(getApplication()).subscribe(); - closeBrowser(); - // System exit needed in the case of receiving - // the panic intent since finish() isn't completely - // closing the browser - System.exit(1); - } - - private class SearchListenerClass implements OnKeyListener, OnEditorActionListener, - OnFocusChangeListener, OnTouchListener, SearchView.PreFocusListener { - - @Override - public boolean onKey(View searchView, int keyCode, KeyEvent keyEvent) { - - switch (keyCode) { - case KeyEvent.KEYCODE_ENTER: - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mSearch.getWindowToken(), 0); - searchTheWeb(mSearch.getText().toString()); - final LightningView currentView = mTabsManager.getCurrentTab(); - if (currentView != null) { - currentView.requestFocus(); - } - return true; - default: - break; - } - return false; - } - - @Override - public boolean onEditorAction(TextView arg0, int actionId, KeyEvent arg2) { - // 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)) { - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mSearch.getWindowToken(), 0); - searchTheWeb(mSearch.getText().toString()); - final LightningView currentView = mTabsManager.getCurrentTab(); - if (currentView != null) { - currentView.requestFocus(); - } - return true; - } - return false; - } - - @Override - public void onFocusChange(final View v, final boolean hasFocus) { - final LightningView currentView = mTabsManager.getCurrentTab(); - if (!hasFocus && currentView != null) { - setIsLoading(currentView.getProgress() < 100); - updateUrl(currentView.getUrl(), false); - } else if (hasFocus && currentView != null) { - - // Hack to make sure the text gets selected - ((SearchView) v).selectAll(); - mIcon = mClearIcon; - mSearch.setCompoundDrawables(null, null, mClearIcon, null); - } - - if (!hasFocus) { - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mSearch.getWindowToken(), 0); - } - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (mSearch.getCompoundDrawables()[2] != null) { - boolean tappedX = event.getX() > (mSearch.getWidth() - - mSearch.getPaddingRight() - mIcon.getIntrinsicWidth()); - if (tappedX) { - if (event.getAction() == MotionEvent.ACTION_UP) { - if (mSearch.hasFocus()) { - mSearch.setText(""); - } else { - refreshOrStop(); - } - } - return true; - } - } - return false; - } - - @Override - public void onPreFocus() { - final LightningView currentView = mTabsManager.getCurrentTab(); - if (currentView == null) { - return; - } - String url = currentView.getUrl(); - if (!UrlUtils.isSpecialUrl(url)) { - if (!mSearch.hasFocus()) { - mSearch.setText(url); - } - } - } - } - - private class DrawerLocker implements DrawerListener { - - @Override - public void onDrawerClosed(View v) { - View tabsDrawer = getTabDrawer(); - View bookmarksDrawer = getBookmarkDrawer(); - - if (v == tabsDrawer) { - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, bookmarksDrawer); - } else if (mShowTabsInDrawer) { - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, tabsDrawer); - } - } - - @Override - public void onDrawerOpened(View v) { - View tabsDrawer = getTabDrawer(); - View bookmarksDrawer = getBookmarkDrawer(); - - if (v == tabsDrawer) { - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, bookmarksDrawer); - } else { - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, tabsDrawer); - } - } - - @Override - public void onDrawerSlide(View v, float arg) {} - - @Override - public void onDrawerStateChanged(int arg) {} - - } - - private void setNavigationDrawerWidth() { - int width = getResources().getDisplayMetrics().widthPixels - Utils.dpToPx(56); - int maxWidth; - if (isTablet()) { - maxWidth = Utils.dpToPx(320); - } else { - maxWidth = Utils.dpToPx(300); - } - if (width > maxWidth) { - DrawerLayout.LayoutParams params = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerLeft - .getLayoutParams(); - params.width = maxWidth; - mDrawerLeft.setLayoutParams(params); - mDrawerLeft.requestLayout(); - DrawerLayout.LayoutParams paramsRight = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerRight - .getLayoutParams(); - paramsRight.width = maxWidth; - mDrawerRight.setLayoutParams(paramsRight); - mDrawerRight.requestLayout(); - } else { - DrawerLayout.LayoutParams params = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerLeft - .getLayoutParams(); - params.width = width; - mDrawerLeft.setLayoutParams(params); - mDrawerLeft.requestLayout(); - DrawerLayout.LayoutParams paramsRight = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerRight - .getLayoutParams(); - paramsRight.width = width; - mDrawerRight.setLayoutParams(paramsRight); - mDrawerRight.requestLayout(); - } - } - - private void initializePreferences() { - final LightningView currentView = mTabsManager.getCurrentTab(); - mFullScreen = mPreferences.getFullScreenEnabled(); - boolean colorMode = mPreferences.getColorModeEnabled(); - colorMode &= !mDarkTheme; - if (!isIncognito() && !colorMode && !mDarkTheme && mWebpageBitmap != null) { - changeToolbarBackground(mWebpageBitmap, null); - } else if (!isIncognito() && currentView != null && !mDarkTheme) { - changeToolbarBackground(currentView.getFavicon(), null); - } else if (!isIncognito() && !mDarkTheme && mWebpageBitmap != null) { - changeToolbarBackground(mWebpageBitmap, null); - } - - FragmentManager manager = getSupportFragmentManager(); - Fragment tabsFragment = manager.findFragmentByTag(TAG_TABS_FRAGMENT); - if (tabsFragment instanceof TabsFragment) { - ((TabsFragment) tabsFragment).reinitializePreferences(); - } - Fragment bookmarksFragment = manager.findFragmentByTag(TAG_BOOKMARK_FRAGMENT); - if (bookmarksFragment instanceof BookmarksFragment) { - ((BookmarksFragment) bookmarksFragment).reinitializePreferences(); - } - - // TODO layout transition causing memory leak -// mBrowserFrame.setLayoutTransition(new LayoutTransition()); - - setFullscreen(mPreferences.getHideStatusBarEnabled(), false); - - BaseSearchEngine currentSearchEngine = mSearchEngineProvider.getCurrentSearchEngine(); - mSearchText = currentSearchEngine.getQueryUrl(); - - updateCookiePreference().subscribeOn(Schedulers.worker()).subscribe(); - mProxyUtils.updateProxySettings(this); - } - - @Override - public void onWindowVisibleToUserAfterResume() { - super.onWindowVisibleToUserAfterResume(); - mToolbarLayout.setTranslationY(0); - setWebViewTranslation(mToolbarLayout.getHeight()); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_ENTER) { - if (mSearch.hasFocus()) { - searchTheWeb(mSearch.getText().toString()); - } - } else if ((keyCode == KeyEvent.KEYCODE_MENU) - && (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) - && (Build.MANUFACTURER.compareTo("LGE") == 0)) { - // Workaround for stupid LG devices that crash - return true; - } else if (keyCode == KeyEvent.KEYCODE_BACK) { - mKeyDownStartTime = System.currentTimeMillis(); - Handlers.MAIN.postDelayed(mLongPressBackRunnable, ViewConfiguration.getLongPressTimeout()); - } - return super.onKeyDown(keyCode, event); - } - - private final Runnable mLongPressBackRunnable = new Runnable() { - @Override - public void run() { - final LightningView currentTab = mTabsManager.getCurrentTab(); - showCloseDialog(mTabsManager.positionOf(currentTab)); - } - }; - - @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)) { - // Workaround for stupid LG devices that crash - openOptionsMenu(); - return true; - } else if (keyCode == KeyEvent.KEYCODE_BACK) { - Handlers.MAIN.removeCallbacks(mLongPressBackRunnable); - if ((System.currentTimeMillis() - mKeyDownStartTime) > ViewConfiguration.getLongPressTimeout()) { - return true; - } - } - return super.onKeyUp(keyCode, event); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - // Keyboard shortcuts - if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (event.isCtrlPressed()) { - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_F: - // Search in page - findInPage(); - return true; - case KeyEvent.KEYCODE_T: - // Open new tab - newTab(null, true); - return true; - case KeyEvent.KEYCODE_W: - // Close current tab - mPresenter.deleteTab(mTabsManager.indexOfCurrentTab()); - return true; - case KeyEvent.KEYCODE_Q: - // Close browser - closeBrowser(); - return true; - case KeyEvent.KEYCODE_R: - // Refresh current tab - LightningView currentTab = mTabsManager.getCurrentTab(); - if (currentTab != null) { - currentTab.reload(); - } - return true; - case KeyEvent.KEYCODE_TAB: - int nextIndex; - if (event.isShiftPressed()) { - // Go back one tab - if (mTabsManager.indexOfCurrentTab() > 0) { - nextIndex = mTabsManager.indexOfCurrentTab() - 1; - } else { - nextIndex = mTabsManager.last(); - } - } else { - // Go forward one tab - if (mTabsManager.indexOfCurrentTab() < mTabsManager.last()) { - nextIndex = mTabsManager.indexOfCurrentTab() + 1; - } else { - nextIndex = 0; - } - } - mPresenter.tabChanged(nextIndex); - return true; - } - } else if (event.getKeyCode() == KeyEvent.KEYCODE_SEARCH) { - // Highlight search field - mSearch.requestFocus(); - mSearch.selectAll(); - return true; - } else if (event.isAltPressed()) { - // Alt + tab number - if (KeyEvent.KEYCODE_0 <= event.getKeyCode() && event.getKeyCode() <= KeyEvent.KEYCODE_9) { - int nextIndex; - if (event.getKeyCode() > mTabsManager.last() + KeyEvent.KEYCODE_1 || event.getKeyCode() == KeyEvent.KEYCODE_0) { - nextIndex = mTabsManager.last(); - } else { - nextIndex = event.getKeyCode() - KeyEvent.KEYCODE_1; - } - mPresenter.tabChanged(nextIndex); - return true; - } - } - } - return super.dispatchKeyEvent(event); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - final LightningView currentView = mTabsManager.getCurrentTab(); - final String currentUrl = currentView != null ? currentView.getUrl() : null; - // Handle action buttons - switch (item.getItemId()) { - case android.R.id.home: - if (mDrawerLayout.isDrawerOpen(getBookmarkDrawer())) { - mDrawerLayout.closeDrawer(getBookmarkDrawer()); - } - return true; - case R.id.action_back: - if (currentView != null && currentView.canGoBack()) { - currentView.goBack(); - } - return true; - case R.id.action_forward: - if (currentView != null && currentView.canGoForward()) { - currentView.goForward(); - } - return true; - case R.id.action_add_to_homescreen: - if (currentView != null) { - HistoryItem shortcut = new HistoryItem(currentView.getUrl(), currentView.getTitle()); - shortcut.setBitmap(currentView.getFavicon()); - Utils.createShortcut(this, shortcut); - } - return true; - case R.id.action_new_tab: - newTab(null, true); - return true; - case R.id.action_incognito: - startActivity(new Intent(this, IncognitoActivity.class)); - overridePendingTransition(R.anim.slide_up_in, R.anim.fade_out_scale); - return true; - case R.id.action_share: - new IntentUtils(this).shareUrl(currentUrl, currentView != null ? currentView.getTitle() : null); - return true; - case R.id.action_bookmarks: - openBookmarks(); - return true; - case R.id.action_copy: - if (currentUrl != null && !UrlUtils.isSpecialUrl(currentUrl)) { - ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("label", currentUrl); - clipboard.setPrimaryClip(clip); - Utils.showSnackbar(this, R.string.message_link_copied); - } - return true; - case R.id.action_settings: - startActivity(new Intent(this, SettingsActivity.class)); - return true; - case R.id.action_history: - openHistory(); - return true; - case R.id.action_downloads: - openDownloads(); - return true; - case R.id.action_add_bookmark: - if (currentUrl != null && !UrlUtils.isSpecialUrl(currentUrl)) { - addBookmark(currentView.getTitle(), currentUrl); - } - return true; - case R.id.action_find: - findInPage(); - return true; - case R.id.action_reading_mode: - if (currentUrl != null) { - Intent read = new Intent(this, ReadingActivity.class); - read.putExtra(Constants.LOAD_READING_URL, currentUrl); - startActivity(read); - } - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - // 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 = new HistoryItem(url, title); - mBookmarkManager.addBookmarkIfNotExists(item) - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.main()) - .subscribe(new SingleOnSubscribe() { - @Override - public void onItem(@Nullable Boolean item) { - if (Boolean.TRUE.equals(item)) { - mSuggestionsAdapter.refreshBookmarks(); - mBookmarksView.handleUpdatedUrl(url); - } - } - }); - } - - private void deleteBookmark(final String title, final String url) { - final HistoryItem item = new HistoryItem(url, title); - - mBookmarkManager.deleteBookmark(item) - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.main()) - .subscribe(new SingleOnSubscribe() { - @Override - public void onItem(@Nullable Boolean item) { - if (Boolean.TRUE.equals(item)) { - mSuggestionsAdapter.refreshBookmarks(); - mBookmarksView.handleUpdatedUrl(url); - } - } - }); - } - - private void putToolbarInRoot() { - if (mToolbarLayout.getParent() != mUiLayout) { - if (mToolbarLayout.getParent() != null) { - ((ViewGroup) mToolbarLayout.getParent()).removeView(mToolbarLayout); - } - - mUiLayout.addView(mToolbarLayout, 0); - mUiLayout.requestLayout(); - } - setWebViewTranslation(0); - } - - private void overlayToolbarOnWebView() { - if (mToolbarLayout.getParent() != mBrowserFrame) { - if (mToolbarLayout.getParent() != null) { - ((ViewGroup) mToolbarLayout.getParent()).removeView(mToolbarLayout); - } - - mBrowserFrame.addView(mToolbarLayout); - mBrowserFrame.requestLayout(); - } - setWebViewTranslation(mToolbarLayout.getHeight()); - } - - private void setWebViewTranslation(float translation) { - if (mFullScreen && mCurrentView != null) { - mCurrentView.setTranslationY(translation); - } else if (mCurrentView != null) { - mCurrentView.setTranslationY(0); - } - } - - /** - * method that shows a dialog asking what string the user wishes to search - * for. It highlights the text entered. - */ - private void findInPage() { - BrowserDialog.showEditText(this, - R.string.action_find, - R.string.search_hint, - R.string.search_hint, new BrowserDialog.EditorListener() { - @Override - public void onClick(String text) { - if (!TextUtils.isEmpty(text)) { - mPresenter.findInPage(text); - showFindInPageControls(text); - } - } - }); - } - - private void showFindInPageControls(@NonNull String text) { - mSearchBar.setVisibility(View.VISIBLE); - - TextView tw = (TextView) findViewById(R.id.search_query); - tw.setText('\'' + text + '\''); - - ImageButton up = (ImageButton) findViewById(R.id.button_next); - up.setOnClickListener(this); - - ImageButton down = (ImageButton) findViewById(R.id.button_back); - down.setOnClickListener(this); - - ImageButton quit = (ImageButton) findViewById(R.id.button_quit); - quit.setOnClickListener(this); - } - - @Override - public TabsManager getTabModel() { - return mTabsManager; - } - - @Override - public void showCloseDialog(final int position) { - if (position < 0) { - return; - } - BrowserDialog.show(this, R.string.dialog_title_close_browser, - new BrowserDialog.Item(R.string.close_tab) { - @Override - public void onClick() { - mPresenter.deleteTab(position); - } - }, - new BrowserDialog.Item(R.string.close_other_tabs) { - @Override - public void onClick() { - mPresenter.closeAllOtherTabs(); - } - }, - new BrowserDialog.Item(R.string.close_all_tabs) { - @Override - public void onClick() { - closeBrowser(); - } - }); - } - - @Override - public void notifyTabViewRemoved(int position) { - Log.d(TAG, "Notify Tab Removed: " + position); - mTabsView.tabRemoved(position); - } - - @Override - public void notifyTabViewAdded() { - Log.d(TAG, "Notify Tab Added"); - mTabsView.tabAdded(); - } - - @Override - public void notifyTabViewChanged(int position) { - Log.d(TAG, "Notify Tab Changed: " + position); - mTabsView.tabChanged(position); - } - - @Override - public void notifyTabViewInitialized() { - Log.d(TAG, "Notify Tabs Initialized"); - mTabsView.tabsInitialized(); - } - - @Override - public void tabChanged(LightningView tab) { - mPresenter.tabChangeOccurred(tab); - } - - @Override - public void removeTabView() { - - Log.d(TAG, "Remove the tab view"); - - // Set the background color so the color mode color doesn't show through - mBrowserFrame.setBackgroundColor(mBackgroundColor); - - removeViewFromParent(mCurrentView); - - mCurrentView = null; - - // Use a delayed handler to make the transition smooth - // otherwise it will get caught up with the showTab code - // and cause a janky motion - Handlers.MAIN.postDelayed(new Runnable() { - @Override - public void run() { - mDrawerLayout.closeDrawers(); - } - }, 200); - - } - - @Override - public void setTabView(@NonNull final View view) { - if (mCurrentView == view) { - return; - } - - Log.d(TAG, "Setting the tab view"); - - // Set the background color so the color mode color doesn't show through - mBrowserFrame.setBackgroundColor(mBackgroundColor); - - removeViewFromParent(view); - removeViewFromParent(mCurrentView); - - mBrowserFrame.addView(view, 0, MATCH_PARENT); - if (mFullScreen) { - view.setTranslationY(mToolbarLayout.getHeight() + mToolbarLayout.getTranslationY()); - } else { - view.setTranslationY(0); - } - - view.requestFocus(); - - mCurrentView = view; - - showActionBar(); - - // Use a delayed handler to make the transition smooth - // otherwise it will get caught up with the showTab code - // and cause a janky motion - Handlers.MAIN.postDelayed(new Runnable() { - @Override - public void run() { - mDrawerLayout.closeDrawers(); - } - }, 200); - - // Handlers.MAIN.postDelayed(new Runnable() { - // @Override - // public void run() { - // Remove browser frame background to reduce overdraw - //TODO evaluate performance - // mBrowserFrame.setBackgroundColor(Color.TRANSPARENT); - // } - // }, 300); - } - - @Override - public void showBlockedLocalFileDialog(@NonNull DialogInterface.OnClickListener listener) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - Dialog dialog = 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(); - - BrowserDialog.setDialogSize(this, dialog); - } - - @Override - public void showSnackbar(@StringRes int resource) { - Utils.showSnackbar(this, resource); - } - - @Override - public void tabCloseClicked(int position) { - mPresenter.deleteTab(position); - } - - @Override - public void tabClicked(int position) { - showTab(position); - } - - @Override - public void newTabButtonClicked() { - mPresenter.newTab(null, true); - } - - @Override - public void newTabButtonLongClicked() { - String url = mPreferences.getSavedUrl(); - if (url != null) { - newTab(url, true); - - Utils.showSnackbar(this, R.string.deleted_tab); - } - mPreferences.setSavedUrl(null); - } - - @Override - public void bookmarkButtonClicked() { - final LightningView currentTab = mTabsManager.getCurrentTab(); - final String url = currentTab != null ? currentTab.getUrl() : null; - final String title = currentTab != null ? currentTab.getTitle() : null; - if (url == null) { - return; - } - - if (!UrlUtils.isSpecialUrl(url)) { - mBookmarkManager.isBookmark(url) - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.main()) - .subscribe(new SingleOnSubscribe() { - @Override - public void onItem(@Nullable Boolean item) { - if (Boolean.TRUE.equals(item)) { - deleteBookmark(title, url); - } else { - addBookmark(title, url); - } - } - }); - } - } - - @Override - public void bookmarkItemClicked(@NonNull HistoryItem item) { - mPresenter.loadUrlInCurrentView(item.getUrl()); - // keep any jank from happening when the drawer is closed after the - // URL starts to load - Handlers.MAIN.postDelayed(new Runnable() { - @Override - public void run() { - closeDrawers(null); - } - }, 150); - } - - @Override - public void handleHistoryChange() { - openHistory(); - } - - /** - * displays the WebView contained in the LightningView Also handles the - * removal of previous views - * - * @param position the poition of the tab to display - */ - // TODO move to presenter - private synchronized void showTab(final int position) { - mPresenter.tabChanged(position); - } - - private static void removeViewFromParent(@Nullable View view) { - if (view == null) { - return; - } - ViewParent parent = view.getParent(); - if (parent instanceof ViewGroup) { - ((ViewGroup) parent).removeView(view); - } - } - - protected void handleNewIntent(Intent intent) { - mPresenter.onNewIntent(intent); - } - - @Override - public void closeEmptyTab() { - // Currently do nothing - // Possibly closing the current tab might close the browser - // and mess stuff up - } - - @Override - public void onTrimMemory(int level) { - if (level > TRIM_MEMORY_MODERATE && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - Log.d(TAG, "Low Memory, Free Memory"); - mPresenter.onAppLowMemory(); - } - } - - // TODO move to presenter - private synchronized boolean newTab(String url, boolean show) { - return mPresenter.newTab(url, show); - } - - protected void performExitCleanUp() { - final LightningView currentTab = mTabsManager.getCurrentTab(); - if (mPreferences.getClearCacheExit() && currentTab != null && !isIncognito()) { - WebUtils.clearCache(currentTab.getWebView()); - Log.d(TAG, "Cache Cleared"); - } - if (mPreferences.getClearHistoryExitEnabled() && !isIncognito()) { - WebUtils.clearHistory(this, mHistoryModel); - Log.d(TAG, "History Cleared"); - } - if (mPreferences.getClearCookiesExitEnabled() && !isIncognito()) { - WebUtils.clearCookies(this); - Log.d(TAG, "Cookies Cleared"); - } - if (mPreferences.getClearWebStorageExitEnabled() && !isIncognito()) { - WebUtils.clearWebStorage(); - Log.d(TAG, "WebStorage Cleared"); - } else if (isIncognito()) { - WebUtils.clearWebStorage(); // We want to make sure incognito mode is secure - } - mSuggestionsAdapter.clearCache(); - } - - @Override - public void onConfigurationChanged(final Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - Log.d(TAG, "onConfigurationChanged"); - - if (mFullScreen) { - showActionBar(); - mToolbarLayout.setTranslationY(0); - setWebViewTranslation(mToolbarLayout.getHeight()); - } - - supportInvalidateOptionsMenu(); - initializeToolbarHeight(newConfig); - } - - private void initializeToolbarHeight(@NonNull final Configuration configuration) { - // TODO externalize the dimensions - doOnLayout(mUiLayout, new Runnable() { - @Override - public void run() { - int toolbarSize; - if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { - // In portrait toolbar should be 56 dp tall - toolbarSize = Utils.dpToPx(56); - } else { - // In landscape toolbar should be 48 dp tall - toolbarSize = Utils.dpToPx(52); - } - mToolbar.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, toolbarSize)); - mToolbar.setMinimumHeight(toolbarSize); - doOnLayout(mToolbar, new Runnable() { - @Override - public void run() { - setWebViewTranslation(mToolbarLayout.getHeight()); - } - }); - mToolbar.requestLayout(); - - } - }); - } - - public void closeBrowser() { - mBrowserFrame.setBackgroundColor(mBackgroundColor); - removeViewFromParent(mCurrentView); - performExitCleanUp(); - int size = mTabsManager.size(); - mTabsManager.shutdown(); - mCurrentView = null; - for (int n = 0; n < size; n++) { - mTabsView.tabRemoved(0); - } - finish(); - } - - @Override - public synchronized void onBackPressed() { - final LightningView currentTab = mTabsManager.getCurrentTab(); - if (mDrawerLayout.isDrawerOpen(getTabDrawer())) { - mDrawerLayout.closeDrawer(getTabDrawer()); - } else if (mDrawerLayout.isDrawerOpen(getBookmarkDrawer())) { - mBookmarksView.navigateBack(); - } else { - if (currentTab != null) { - Log.d(TAG, "onBackPressed"); - if (mSearch.hasFocus()) { - currentTab.requestFocus(); - } else if (currentTab.canGoBack()) { - if (!currentTab.isShown()) { - onHideCustomView(); - } else { - currentTab.goBack(); - } - } else { - if (mCustomView != null || mCustomViewCallback != null) { - onHideCustomView(); - } else { - mPresenter.deleteTab(mTabsManager.positionOf(currentTab)); - } - } - } else { - Log.e(TAG, "This shouldn't happen ever"); - super.onBackPressed(); - } - } - } - - @Override - protected void onPause() { - super.onPause(); - Log.d(TAG, "onPause"); - mTabsManager.pauseAll(); - try { - getApplication().unregisterReceiver(mNetworkReceiver); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Receiver was not registered", e); - } - if (isIncognito() && isFinishing()) { - overridePendingTransition(R.anim.fade_in_scale, R.anim.slide_down_out); - } - } - - protected void saveOpenTabs() { - if (mPreferences.getRestoreLostTabsEnabled()) { - mTabsManager.saveState(); - } - } - - @Override - protected void onStop() { - super.onStop(); - } - - @Override - protected void onDestroy() { - Log.d(TAG, "onDestroy"); - - Handlers.MAIN.removeCallbacksAndMessages(null); - - mPresenter.shutdown(); - - super.onDestroy(); - } - - @Override - protected void onStart() { - super.onStart(); - } - - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - mTabsManager.shutdown(); - } - - @Override - protected void onResume() { - super.onResume(); - Log.d(TAG, "onResume"); - if (mSwapBookmarksAndTabs != mPreferences.getBookmarksAndTabsSwapped()) { - restart(); - } - - if (mSuggestionsAdapter != null) { - mSuggestionsAdapter.refreshPreferences(); - mSuggestionsAdapter.refreshBookmarks(); - } - mTabsManager.resumeAll(this); - initializePreferences(); - - supportInvalidateOptionsMenu(); - - IntentFilter filter = new IntentFilter(); - filter.addAction(NETWORK_BROADCAST_ACTION); - getApplication().registerReceiver(mNetworkReceiver, filter); - - if (mFullScreen) { - overlayToolbarOnWebView(); - } else { - putToolbarInRoot(); - } - } - - /** - * searches the web for the query fixing any and all problems with the input - * checks if it is a search, url, etc. - */ - private void searchTheWeb(@NonNull String query) { - final LightningView currentTab = mTabsManager.getCurrentTab(); - if (query.isEmpty()) { - return; - } - String searchUrl = mSearchText + UrlUtils.QUERY_PLACE_HOLDER; - query = query.trim(); - if (currentTab != null) { - currentTab.stopLoading(); - mPresenter.loadUrlInCurrentView(UrlUtils.smartUrlFilter(query, true, searchUrl)); - } - } - - /** - * Animates the color of the toolbar from one color to another. Optionally animates - * the color of the tab background, for use when the tabs are displayed on the top - * of the screen. - * - * @param favicon the Bitmap to extract the color from - * @param tabBackground the optional LinearLayout to color - */ - @Override - public void changeToolbarBackground(@NonNull Bitmap favicon, @Nullable final Drawable tabBackground) { - final int defaultColor = ContextCompat.getColor(this, R.color.primary_color); - if (mCurrentUiColor == Color.BLACK) { - mCurrentUiColor = defaultColor; - } - Palette.from(favicon).generate(new Palette.PaletteAsyncListener() { - @Override - public void onGenerated(Palette palette) { - - // OR with opaque black to remove transparency glitches - int color = 0xff000000 | palette.getVibrantColor(defaultColor); - - final int finalColor; // Lighten up the dark color if it is - // too dark - if (!mShowTabsInDrawer || Utils.isColorTooDark(color)) { - finalColor = Utils.mixTwoColors(defaultColor, color, 0.25f); - } else { - finalColor = color; - } - - final Window window = getWindow(); - if (!mShowTabsInDrawer) { - window.setBackgroundDrawable(new ColorDrawable(Color.BLACK)); - } - - final int startSearchColor = getSearchBarColor(mCurrentUiColor, defaultColor); - final int finalSearchColor = getSearchBarColor(finalColor, defaultColor); - - Animation animation = new Animation() { - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - final int color = DrawableUtils.mixColor(interpolatedTime, mCurrentUiColor, finalColor); - if (mShowTabsInDrawer) { - mBackground.setColor(color); - Handlers.MAIN.post(new Runnable() { - @Override - public void run() { - window.setBackgroundDrawable(mBackground); - } - }); - } else if (tabBackground != null) { - tabBackground.setColorFilter(color, PorterDuff.Mode.SRC_IN); - } - mCurrentUiColor = color; - mToolbarLayout.setBackgroundColor(color); - mSearchBackground.getBackground().setColorFilter(DrawableUtils.mixColor(interpolatedTime, - startSearchColor, finalSearchColor), PorterDuff.Mode.SRC_IN); - } - }; - animation.setDuration(300); - mToolbarLayout.startAnimation(animation); - } - }); - } - - private int getSearchBarColor(int requestedColor, int defaultColor) { - if (requestedColor == defaultColor) { - return mDarkTheme ? DrawableUtils.mixColor(0.25f, defaultColor, Color.WHITE) : Color.WHITE; - } else { - return DrawableUtils.mixColor(0.25f, requestedColor, Color.WHITE); - } - } - - @Override - public boolean getUseDarkTheme() { - return mDarkTheme; - } - - @ColorInt - @Override - public int getUiColor() { - return mCurrentUiColor; - } - - @Override - public void updateUrl(@Nullable String url, boolean isLoading) { - if (url == null || mSearch == null || mSearch.hasFocus()) { - return; - } - final LightningView currentTab = mTabsManager.getCurrentTab(); - mBookmarksView.handleUpdatedUrl(url); - - String currentTitle = currentTab != null ? currentTab.getTitle() : null; - - mSearch.setText(mSearchBoxModel.getDisplayContent(url, currentTitle, isLoading)); - } - - @Override - 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))); - } - } - - @Override - public void updateProgress(int n) { - setIsLoading(n < 100); - mProgressBar.setProgress(n); - } - - protected void addItemToHistory(@Nullable final String title, @NonNull final String url) { - if (UrlUtils.isSpecialUrl(url)) { - return; - } - - mHistoryModel.visitHistoryItem(url, title) - .subscribeOn(Schedulers.io()) - .subscribe(new CompletableOnSubscribe() { - @Override - public void onError(@NonNull Throwable throwable) { - Log.e(TAG, "Exception while updating history", throwable); - } - }); - } - - /** - * method to generate search suggestions for the AutoCompleteTextView from - * previously searched URLs - */ - private void initializeSearchSuggestions(final AutoCompleteTextView getUrl) { - - mSuggestionsAdapter = new SuggestionsAdapter(this, mDarkTheme, isIncognito()); - - getUrl.setThreshold(1); - getUrl.setDropDownWidth(-1); - getUrl.setDropDownAnchor(R.id.toolbar_layout); - getUrl.setOnItemClickListener(new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView adapterView, View view, int pos, long l) { - String url = null; - CharSequence urlString = ((TextView) view.findViewById(R.id.url)).getText(); - if (urlString != null) { - url = urlString.toString(); - } - if (url == null || url.startsWith(getString(R.string.suggestion))) { - CharSequence searchString = ((TextView) view.findViewById(R.id.title)).getText(); - if (searchString != null) { - url = searchString.toString(); - } - } - if (url == null) { - return; - } - getUrl.setText(url); - searchTheWeb(url); - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(getUrl.getWindowToken(), 0); - mPresenter.onAutoCompleteItemPressed(); - } - - }); - - getUrl.setSelectAllOnFocus(true); - getUrl.setAdapter(mSuggestionsAdapter); - } - - /** - * function that opens the HTML history page in the browser - */ - private void openHistory() { - new HistoryPage().getHistoryPage() - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.main()) - .subscribe(new SingleOnSubscribe() { - @Override - public void onItem(@Nullable String item) { - Preconditions.checkNonNull(item); - LightningView view = mTabsManager.getCurrentTab(); - if (view != null) { - view.loadUrl(item); - } - } - }); - } - - private void openDownloads() { - new DownloadsPage().getDownloadsPage() - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.main()) - .subscribe(new SingleOnSubscribe() { - @Override - public void onItem(@Nullable String item) { - Preconditions.checkNonNull(item); - LightningView view = mTabsManager.getCurrentTab(); - if (view != null) { - view.loadUrl(item); - } - } - }); - } - - private View getBookmarkDrawer() { - return mSwapBookmarksAndTabs ? mDrawerLeft : mDrawerRight; - } - - private View getTabDrawer() { - return mSwapBookmarksAndTabs ? mDrawerRight : mDrawerLeft; - } - - /** - * helper function that opens the bookmark drawer - */ - private void openBookmarks() { - if (mDrawerLayout.isDrawerOpen(getTabDrawer())) { - mDrawerLayout.closeDrawers(); - } - mDrawerLayout.openDrawer(getBookmarkDrawer()); - } - - /** - * This method closes any open drawer and executes - * the runnable after the drawers are completely closed. - * - * @param runnable an optional runnable to run after - * the drawers are closed. - */ - protected final void closeDrawers(@Nullable final Runnable runnable) { - if (!mDrawerLayout.isDrawerOpen(mDrawerLeft) && !mDrawerLayout.isDrawerOpen(mDrawerRight)) { - if (runnable != null) { - runnable.run(); - return; - } - } - mDrawerLayout.closeDrawers(); - - mDrawerLayout.addDrawerListener(new DrawerListener() { - @Override - public void onDrawerSlide(View drawerView, float slideOffset) {} - - @Override - public void onDrawerOpened(View drawerView) {} - - @Override - public void onDrawerClosed(View drawerView) { - if (runnable != null) { - runnable.run(); - } - mDrawerLayout.removeDrawerListener(this); - } - - @Override - public void onDrawerStateChanged(int newState) {} - }); - } - - @Override - public void setForwardButtonEnabled(boolean enabled) { - if (mForwardMenuItem != null && mForwardMenuItem.getIcon() != null) { - int colorFilter; - if (enabled) { - colorFilter = mIconColor; - } else { - colorFilter = mDisabledIconColor; - } - mForwardMenuItem.getIcon().setColorFilter(colorFilter, PorterDuff.Mode.SRC_IN); - mForwardMenuItem.setIcon(mForwardMenuItem.getIcon()); - } - } - - @Override - public void setBackButtonEnabled(boolean enabled) { - if (mBackMenuItem != null && mBackMenuItem.getIcon() != null) { - int colorFilter; - if (enabled) { - colorFilter = mIconColor; - } else { - colorFilter = mDisabledIconColor; - } - mBackMenuItem.getIcon().setColorFilter(colorFilter, PorterDuff.Mode.SRC_IN); - mBackMenuItem.setIcon(mBackMenuItem.getIcon()); - } - } - - private MenuItem mBackMenuItem; - private MenuItem mForwardMenuItem; - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - mBackMenuItem = menu.findItem(R.id.action_back); - mForwardMenuItem = menu.findItem(R.id.action_forward); - if (mBackMenuItem != null && mBackMenuItem.getIcon() != null) - mBackMenuItem.getIcon().setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); - if (mForwardMenuItem != null && mForwardMenuItem.getIcon() != null) - mForwardMenuItem.getIcon().setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); - return super.onCreateOptionsMenu(menu); - } - - /** - * opens a file chooser - * param ValueCallback is the message from the WebView indicating a file chooser - * should be opened - */ - @Override - public void openFileChooser(ValueCallback uploadMsg) { - mUploadMessage = uploadMsg; - Intent i = new Intent(Intent.ACTION_GET_CONTENT); - i.addCategory(Intent.CATEGORY_OPENABLE); - i.setType("*/*"); - startActivityForResult(Intent.createChooser(i, getString(R.string.title_file_chooser)), 1); - } - - /** - * used to allow uploading into the browser - */ - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent intent) { - if (API < Build.VERSION_CODES.LOLLIPOP) { - if (requestCode == 1) { - if (null == mUploadMessage) { - return; - } - Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); - mUploadMessage.onReceiveValue(result); - mUploadMessage = null; - - } - } - - if (requestCode != 1 || mFilePathCallback == null) { - super.onActivityResult(requestCode, resultCode, intent); - return; - } - - Uri[] results = null; - - // Check that the response is a good one - if (resultCode == Activity.RESULT_OK) { - if (intent == null) { - // If there is not data, then we may have taken a photo - if (mCameraPhotoPath != null) { - results = new Uri[]{Uri.parse(mCameraPhotoPath)}; - } - } else { - String dataString = intent.getDataString(); - if (dataString != null) { - results = new Uri[]{Uri.parse(dataString)}; - } - } - } - - mFilePathCallback.onReceiveValue(results); - mFilePathCallback = null; - } - - @Override - public void showFileChooser(ValueCallback filePathCallback) { - if (mFilePathCallback != null) { - mFilePathCallback.onReceiveValue(null); - } - mFilePathCallback = filePathCallback; - - Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - if (takePictureIntent.resolveActivity(this.getPackageManager()) != null) { - // Create the File where the photo should go - File photoFile = null; - try { - photoFile = Utils.createImageFile(); - takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath); - } catch (IOException ex) { - // Error occurred while creating the File - Log.e(TAG, "Unable to create Image File", ex); - } - - // Continue only if the File was successfully created - if (photoFile != null) { - mCameraPhotoPath = "file:" + photoFile.getAbsolutePath(); - takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); - } else { - takePictureIntent = null; - } - } - - Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT); - contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); - contentSelectionIntent.setType("*/*"); - - Intent[] intentArray; - if (takePictureIntent != null) { - intentArray = new Intent[]{takePictureIntent}; - } else { - intentArray = new Intent[0]; - } - - Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); - chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent); - chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser"); - chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray); - - startActivityForResult(chooserIntent, 1); - } - - @Override - public synchronized void onShowCustomView(View view, CustomViewCallback callback) { - int requestedOrientation = mOriginalOrientation = getRequestedOrientation(); - onShowCustomView(view, callback, requestedOrientation); - } - - @Override - public synchronized void onShowCustomView(final View view, CustomViewCallback callback, int requestedOrientation) { - final LightningView currentTab = mTabsManager.getCurrentTab(); - if (view == null || mCustomView != null) { - if (callback != null) { - try { - callback.onCustomViewHidden(); - } catch (Exception e) { - Log.e(TAG, "Error hiding custom view", e); - } - } - return; - } - try { - view.setKeepScreenOn(true); - } catch (SecurityException e) { - Log.e(TAG, "WebView is not allowed to keep the screen on"); - } - mOriginalOrientation = getRequestedOrientation(); - mCustomViewCallback = callback; - mCustomView = view; - - setRequestedOrientation(requestedOrientation); - final FrameLayout decorView = (FrameLayout) getWindow().getDecorView(); - - mFullscreenContainer = new FrameLayout(this); - mFullscreenContainer.setBackgroundColor(ContextCompat.getColor(this, android.R.color.black)); - if (view instanceof FrameLayout) { - if (((FrameLayout) view).getFocusedChild() instanceof VideoView) { - mVideoView = (VideoView) ((FrameLayout) view).getFocusedChild(); - mVideoView.setOnErrorListener(new VideoCompletionListener()); - mVideoView.setOnCompletionListener(new VideoCompletionListener()); - } - } else if (view instanceof VideoView) { - mVideoView = (VideoView) view; - mVideoView.setOnErrorListener(new VideoCompletionListener()); - mVideoView.setOnCompletionListener(new VideoCompletionListener()); - } - decorView.addView(mFullscreenContainer, COVER_SCREEN_PARAMS); - mFullscreenContainer.addView(mCustomView, COVER_SCREEN_PARAMS); - decorView.requestLayout(); - setFullscreen(true, true); - if (currentTab != null) { - currentTab.setVisibility(View.INVISIBLE); - } - } - - @Override - public void closeBookmarksDrawer() { - mDrawerLayout.closeDrawer(getBookmarkDrawer()); - } - - @Override - public void onHideCustomView() { - final LightningView currentTab = mTabsManager.getCurrentTab(); - if (mCustomView == null || mCustomViewCallback == null || currentTab == null) { - if (mCustomViewCallback != null) { - try { - mCustomViewCallback.onCustomViewHidden(); - } catch (Exception e) { - Log.e(TAG, "Error hiding custom view", e); - } - mCustomViewCallback = null; - } - return; - } - Log.d(TAG, "onHideCustomView"); - currentTab.setVisibility(View.VISIBLE); - try { - mCustomView.setKeepScreenOn(false); - } catch (SecurityException e) { - Log.e(TAG, "WebView is not allowed to keep the screen on"); - } - setFullscreen(mPreferences.getHideStatusBarEnabled(), false); - if (mFullscreenContainer != null) { - ViewGroup parent = (ViewGroup) mFullscreenContainer.getParent(); - if (parent != null) { - parent.removeView(mFullscreenContainer); - } - mFullscreenContainer.removeAllViews(); - } - - mFullscreenContainer = null; - mCustomView = null; - if (mVideoView != null) { - Log.d(TAG, "VideoView is being stopped"); - mVideoView.stopPlayback(); - mVideoView.setOnErrorListener(null); - mVideoView.setOnCompletionListener(null); - mVideoView = null; - } - if (mCustomViewCallback != null) { - try { - mCustomViewCallback.onCustomViewHidden(); - } catch (Exception e) { - Log.e(TAG, "Error hiding custom view", e); - } - } - mCustomViewCallback = null; - setRequestedOrientation(mOriginalOrientation); - } - - private class VideoCompletionListener implements MediaPlayer.OnCompletionListener, - MediaPlayer.OnErrorListener { - - @Override - public boolean onError(MediaPlayer mp, int what, int extra) { - return false; - } - - @Override - public void onCompletion(MediaPlayer mp) { - onHideCustomView(); - } - - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - Log.d(TAG, "onWindowFocusChanged"); - if (hasFocus) { - setFullscreen(mIsFullScreen, mIsImmersive); - } - } - - @Override - public void onBackButtonPressed() { - final LightningView currentTab = mTabsManager.getCurrentTab(); - if (currentTab != null) { - if (currentTab.canGoBack()) { - currentTab.goBack(); - closeDrawers(null); - } else { - mPresenter.deleteTab(mTabsManager.positionOf(currentTab)); - } - } - } - - @Override - public void onForwardButtonPressed() { - final LightningView currentTab = mTabsManager.getCurrentTab(); - if (currentTab != null) { - if (currentTab.canGoForward()) { - currentTab.goForward(); - closeDrawers(null); - } - } - } - - @Override - public void onHomeButtonPressed() { - final LightningView currentTab = mTabsManager.getCurrentTab(); - if (currentTab != null) { - currentTab.loadHomepage(); - closeDrawers(null); - } - } - - /** - * This method sets whether or not the activity will display - * in full-screen mode (i.e. the ActionBar will be hidden) and - * whether or not immersive mode should be set. This is used to - * set both parameters correctly as during a full-screen video, - * both need to be set, but other-wise we leave it up to user - * preference. - * - * @param enabled true to enable full-screen, false otherwise - * @param immersive true to enable immersive mode, false otherwise - */ - private void setFullscreen(boolean enabled, boolean immersive) { - mIsFullScreen = enabled; - mIsImmersive = immersive; - Window window = getWindow(); - View decor = window.getDecorView(); - 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); - } else { - decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); - } - window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); - } - } - - /** - * This method handles the JavaScript callback to create a new tab. - * Basically this handles the event that JavaScript needs to create - * a popup. - * - * @param resultMsg the transport message used to send the URL to - * the newly created WebView. - */ - @Override - public synchronized void onCreateWindow(Message resultMsg) { - if (resultMsg == null) { - return; - } - if (newTab("", true)) { - LightningView newTab = mTabsManager.getTabAtPosition(mTabsManager.size() - 1); - if (newTab != null) { - final WebView webView = newTab.getWebView(); - if (webView != null) { - WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; - transport.setWebView(webView); - resultMsg.sendToTarget(); - } - } - } - } - - /** - * Closes the specified {@link LightningView}. This implements - * the JavaScript callback that asks the tab to close itself and - * is especially helpful when a page creates a redirect and does - * not need the tab to stay open any longer. - * - * @param view the LightningView to close, delete it. - */ - @Override - public void onCloseWindow(LightningView view) { - mPresenter.deleteTab(mTabsManager.positionOf(view)); - } - - /** - * Hide the ActionBar using an animation if we are in full-screen - * mode. This method also re-parents the ActionBar if its parent is - * incorrect so that the animation can happen correctly. - */ - @Override - public void hideActionBar() { - if (mFullScreen) { - if (mToolbarLayout == null || mBrowserFrame == null) - return; - - final int height = mToolbarLayout.getHeight(); - if (mToolbarLayout.getTranslationY() > -0.01f) { - Animation show = new Animation() { - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - float trans = interpolatedTime * height; - mToolbarLayout.setTranslationY(-trans); - setWebViewTranslation(height - trans); - } - }; - show.setDuration(250); - show.setInterpolator(new BezierDecelerateInterpolator()); - mBrowserFrame.startAnimation(show); - } - } - } - - /** - * Display the ActionBar using an animation if we are in full-screen - * mode. This method also re-parents the ActionBar if its parent is - * incorrect so that the animation can happen correctly. - */ - @Override - public void showActionBar() { - if (mFullScreen) { - Log.d(TAG, "showActionBar"); - if (mToolbarLayout == null) - return; - - int height = mToolbarLayout.getHeight(); - if (height == 0) { - mToolbarLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - height = mToolbarLayout.getMeasuredHeight(); - } - - final int totalHeight = height; - if (mToolbarLayout.getTranslationY() < -(height - 0.01f)) { - Animation show = new Animation() { - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - float trans = interpolatedTime * totalHeight; - mToolbarLayout.setTranslationY(trans - totalHeight); - setWebViewTranslation(trans); - } - }; - show.setDuration(250); - show.setInterpolator(new BezierDecelerateInterpolator()); - mBrowserFrame.startAnimation(show); - } - } - } - - @Override - public void handleBookmarksChange() { - final LightningView currentTab = mTabsManager.getCurrentTab(); - if (currentTab != null && UrlUtils.isBookmarkUrl(currentTab.getUrl())) { - currentTab.loadBookmarkpage(); - } - if (currentTab != null) { - mBookmarksView.handleUpdatedUrl(currentTab.getUrl()); - } - } - - @Override - public void handleDownloadDeleted() { - final LightningView currentTab = mTabsManager.getCurrentTab(); - if (currentTab != null && UrlUtils.isDownloadsUrl(currentTab.getUrl())) { - currentTab.loadDownloadspage(); - } - if (currentTab != null) { - mBookmarksView.handleUpdatedUrl(currentTab.getUrl()); - } - } - - @Override - public void handleBookmarkDeleted(@NonNull HistoryItem item) { - mBookmarksView.handleBookmarkDeleted(item); - handleBookmarksChange(); - } - - @Override - public void handleNewTab(@NonNull LightningDialogBuilder.NewTab newTabType, @NonNull String url) { - mDrawerLayout.closeDrawers(); - switch (newTabType) { - case FOREGROUND: - newTab(url, true); - break; - case BACKGROUND: - newTab(url, false); - break; - case INCOGNITO: - Intent intent = new Intent(BrowserActivity.this, IncognitoActivity.class); - intent.setData(Uri.parse(url)); - startActivity(intent); - overridePendingTransition(R.anim.slide_up_in, R.anim.fade_out_scale); - break; - } - } - - /** - * Performs an action when the provided view is laid out. - * - * @param view the view to listen to for layouts. - * @param runnable the runnable to run when the view is - * laid out. - */ - private static void doOnLayout(@NonNull final View view, @NonNull final Runnable runnable) { - view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - view.getViewTreeObserver().removeOnGlobalLayoutListener(this); - } else { - //noinspection deprecation - view.getViewTreeObserver().removeGlobalOnLayoutListener(this); - } - runnable.run(); - } - }); - } - - /** - * This method lets the search bar know that the page is currently loading - * and that it should display the stop icon to indicate to the user that - * pressing it stops the page from loading - */ - private void setIsLoading(boolean isLoading) { - if (!mSearch.hasFocus()) { - mIcon = isLoading ? mDeleteIcon : mRefreshIcon; - mSearch.setCompoundDrawables(null, null, mIcon, null); - } - } - - /** - * handle presses on the refresh icon in the search bar, if the page is - * loading, stop the page, if it is done loading refresh the page. - * See setIsFinishedLoading and setIsLoading for displaying the correct icon - */ - private void refreshOrStop() { - final LightningView currentTab = mTabsManager.getCurrentTab(); - if (currentTab != null) { - if (currentTab.getProgress() < 100) { - currentTab.stopLoading(); - } else { - currentTab.reload(); - } - } - } - - /** - * Handle the click event for the views that are using - * this class as a click listener. This method should - * distinguish between the various views using their IDs. - * - * @param v the view that the user has clicked - */ - @Override - public void onClick(View v) { - final LightningView currentTab = mTabsManager.getCurrentTab(); - if (currentTab == null) { - return; - } - switch (v.getId()) { - case R.id.arrow_button: - if (mSearch != null && mSearch.hasFocus()) { - currentTab.requestFocus(); - } else if (mShowTabsInDrawer) { - mDrawerLayout.openDrawer(getTabDrawer()); - } else { - currentTab.loadHomepage(); - } - break; - case R.id.button_next: - currentTab.findNext(); - break; - case R.id.button_back: - currentTab.findPrevious(); - break; - case R.id.button_quit: - currentTab.clearFindMatches(); - mSearchBar.setVisibility(View.GONE); - break; - case R.id.action_reading: - Intent read = new Intent(this, ReadingActivity.class); - read.putExtra(Constants.LOAD_READING_URL, currentTab.getUrl()); - startActivity(read); - break; - case R.id.action_toggle_desktop: - currentTab.toggleDesktopUA(this); - currentTab.reload(); - closeDrawers(null); - break; - } - } - - /** - * This NetworkReceiver notifies each of the WebViews in the browser whether - * the network is currently connected or not. This is important because some - * JavaScript properties rely on the WebView knowing the current network state. - * It is used to help the browser be compliant with the HTML5 spec, sec. 5.7.7 - */ - private final NetworkReceiver mNetworkReceiver = new NetworkReceiver() { - @Override - public void onConnectivityChange(boolean isConnected) { - Log.d(TAG, "Network Connected: " + isConnected); - mTabsManager.notifyConnectionStatus(isConnected); - } - }; - - /** - * Handle the callback that permissions requested have been granted or not. - * This method should act upon the results of the permissions request. - * - * @param requestCode the request code sent when initially making the request - * @param permissions the array of the permissions that was requested - * @param grantResults the results of the permissions requests that provides - * information on whether the request was granted or not - */ - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - PermissionsManager.getInstance().notifyPermissionsChange(permissions, grantResults); - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - } + // Toolbar Views + @BindView(R.id.toolbar) Toolbar mToolbar; + private View mSearchBackground; + private SearchView mSearch; + private ImageView mArrowImage; + + // Current tab view being displayed + @Nullable private View mCurrentView; + + // Full Screen Video Views + private FrameLayout mFullscreenContainer; + private VideoView mVideoView; + private View mCustomView; + + // Adapter + private SuggestionsAdapter mSuggestionsAdapter; + + // Callback + private CustomViewCallback mCustomViewCallback; + private ValueCallback mUploadMessage; + private ValueCallback mFilePathCallback; + + // Primitives + private boolean mFullScreen; + private boolean mDarkTheme; + private boolean mIsFullScreen = false; + private boolean mIsImmersive = false; + private boolean mShowTabsInDrawer; + private boolean mSwapBookmarksAndTabs; + private int mOriginalOrientation; + private int mBackgroundColor; + private int mIconColor; + private int mDisabledIconColor; + private int mCurrentUiColor = Color.BLACK; + private long mKeyDownStartTime; + private String mSearchText; + private String mUntitledTitle; + private String mCameraPhotoPath; + + // The singleton BookmarkManager + @Inject BookmarkModel mBookmarkManager; + + @Inject HistoryModel mHistoryModel; + + @Inject LightningDialogBuilder mBookmarksDialogBuilder; + + @Inject SearchBoxModel mSearchBoxModel; + + @Inject SearchEngineProvider mSearchEngineProvider; + + private TabsManager mTabsManager; + + // Image + private Bitmap mWebpageBitmap; + private final ColorDrawable mBackground = new ColorDrawable(); + private Drawable mDeleteIcon, mRefreshIcon, mClearIcon, mIcon; + + private BrowserPresenter mPresenter; + private TabsView mTabsView; + private BookmarksView mBookmarksView; + + // Proxy + @Inject ProxyUtils mProxyUtils; + + // Constant + 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); + private static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + + protected abstract boolean isIncognito(); + + public abstract void closeActivity(); + + public abstract void updateHistory(@Nullable final String title, @NonNull final String url); + + @NonNull + protected abstract Completable updateCookiePreference(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + BrowserApp.getAppComponent().inject(this); + setContentView(R.layout.activity_main); + ButterKnife.bind(this); + + mTabsManager = new TabsManager(); + mPresenter = new BrowserPresenter(this, isIncognito()); + + initialize(savedInstanceState); + } + + private synchronized void initialize(Bundle savedInstanceState) { + initializeToolbarHeight(getResources().getConfiguration()); + setSupportActionBar(mToolbar); + ActionBar actionBar = getSupportActionBar(); + + //TODO make sure dark theme flag gets set correctly + 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); + mShowTabsInDrawer = mPreferences.getShowTabsInDrawer(!isTablet()); + mSwapBookmarksAndTabs = mPreferences.getBookmarksAndTabsSwapped(); + + // initialize background ColorDrawable + int primaryColor = ThemeUtils.getPrimaryColor(this); + mBackground.setColor(primaryColor); + + // Drawer stutters otherwise + mDrawerLeft.setLayerType(View.LAYER_TYPE_NONE, null); + mDrawerRight.setLayerType(View.LAYER_TYPE_NONE, null); + + mDrawerLayout.addDrawerListener(new DrawerListener() { + @Override + public void onDrawerSlide(View drawerView, float slideOffset) {} + + @Override + public void onDrawerOpened(View drawerView) {} + + @Override + public void onDrawerClosed(View drawerView) {} + + @Override + public void onDrawerStateChanged(int newState) { + if (newState == DrawerLayout.STATE_DRAGGING) { + mDrawerLeft.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mDrawerRight.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } else if (newState == DrawerLayout.STATE_IDLE) { + mDrawerLeft.setLayerType(View.LAYER_TYPE_NONE, null); + mDrawerRight.setLayerType(View.LAYER_TYPE_NONE, null); + } + } + }); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !mShowTabsInDrawer) { + getWindow().setStatusBarColor(Color.BLACK); + } + + setNavigationDrawerWidth(); + mDrawerLayout.addDrawerListener(new DrawerLocker()); + + mWebpageBitmap = ThemeUtils.getThemedBitmap(this, R.drawable.ic_webpage, mDarkTheme); + + final FragmentManager fragmentManager = getSupportFragmentManager(); + + TabsFragment tabsFragment = (TabsFragment) fragmentManager.findFragmentByTag(TAG_TABS_FRAGMENT); + BookmarksFragment bookmarksFragment = (BookmarksFragment) fragmentManager.findFragmentByTag(TAG_BOOKMARK_FRAGMENT); + + if (tabsFragment != null) { + fragmentManager.beginTransaction().remove(tabsFragment).commit(); + } + tabsFragment = TabsFragment.createTabsFragment(isIncognito(), mShowTabsInDrawer); + + mTabsView = tabsFragment; + + if (bookmarksFragment != null) { + fragmentManager.beginTransaction().remove(bookmarksFragment).commit(); + } + bookmarksFragment = BookmarksFragment.createFragment(isIncognito()); + + mBookmarksView = bookmarksFragment; + + fragmentManager.executePendingTransactions(); + + fragmentManager + .beginTransaction() + .replace(getTabsFragmentViewId(), tabsFragment, TAG_TABS_FRAGMENT) + .replace(getBookmarksFragmentViewId(), bookmarksFragment, TAG_BOOKMARK_FRAGMENT) + .commit(); + if (mShowTabsInDrawer) { + mToolbarLayout.removeView(findViewById(R.id.tabs_toolbar_container)); + } + + Preconditions.checkNonNull(actionBar); + + // set display options of the ActionBar + actionBar.setDisplayShowTitleEnabled(false); + actionBar.setDisplayShowHomeEnabled(false); + actionBar.setDisplayShowCustomEnabled(true); + actionBar.setCustomView(R.layout.toolbar_content); + + View customView = actionBar.getCustomView(); + LayoutParams lp = customView.getLayoutParams(); + lp.width = LayoutParams.MATCH_PARENT; + lp.height = LayoutParams.MATCH_PARENT; + customView.setLayoutParams(lp); + + mArrowImage = customView.findViewById(R.id.arrow); + FrameLayout arrowButton = customView.findViewById(R.id.arrow_button); + if (mShowTabsInDrawer) { + if (mArrowImage.getWidth() <= 0) { + mArrowImage.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + } + updateTabNumber(0); + + // Post drawer locking in case the activity is being recreated + Handlers.MAIN.post(new Runnable() { + @Override + public void run() { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, getTabDrawer()); + } + }); + } else { + + // Post drawer locking in case the activity is being recreated + Handlers.MAIN.post(new Runnable() { + @Override + public void run() { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, getTabDrawer()); + } + }); + mArrowImage.setImageResource(R.drawable.ic_action_home); + mArrowImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); + } + + // Post drawer locking in case the activity is being recreated + Handlers.MAIN.post(new Runnable() { + @Override + public void run() { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, getBookmarkDrawer()); + } + }); + + arrowButton.setOnClickListener(this); + + // create the search EditText in the ToolBar + mSearch = customView.findViewById(R.id.search); + mSearchBackground = customView.findViewById(R.id.search_container); + + // initialize search background color + mSearchBackground.getBackground().setColorFilter(getSearchBarColor(primaryColor, primaryColor), PorterDuff.Mode.SRC_IN); + mSearch.setHintTextColor(ThemeUtils.getThemedTextHintColor(mDarkTheme)); + mSearch.setTextColor(mDarkTheme ? Color.WHITE : Color.BLACK); + + mUntitledTitle = getString(R.string.untitled); + mBackgroundColor = ThemeUtils.getPrimaryColor(this); + mDeleteIcon = ThemeUtils.getThemedDrawable(this, R.drawable.ic_action_delete, mDarkTheme); + mRefreshIcon = ThemeUtils.getThemedDrawable(this, R.drawable.ic_action_refresh, mDarkTheme); + mClearIcon = ThemeUtils.getThemedDrawable(this, R.drawable.ic_action_delete, mDarkTheme); + + int iconBounds = Utils.dpToPx(24); + mDeleteIcon.setBounds(0, 0, iconBounds, iconBounds); + mRefreshIcon.setBounds(0, 0, iconBounds, iconBounds); + mClearIcon.setBounds(0, 0, iconBounds, iconBounds); + mIcon = mRefreshIcon; + SearchListenerClass search = new SearchListenerClass(); + mSearch.setCompoundDrawablePadding(Utils.dpToPx(3)); + mSearch.setCompoundDrawables(null, null, mRefreshIcon, null); + mSearch.setOnKeyListener(search); + mSearch.setOnFocusChangeListener(search); + mSearch.setOnEditorActionListener(search); + mSearch.setOnTouchListener(search); + mSearch.setOnPreFocusListener(search); + + initializeSearchSuggestions(mSearch); + + mDrawerLayout.setDrawerShadow(R.drawable.drawer_right_shadow, GravityCompat.END); + mDrawerLayout.setDrawerShadow(R.drawable.drawer_left_shadow, GravityCompat.START); + + if (API <= Build.VERSION_CODES.JELLY_BEAN_MR2) { + //noinspection deprecation + WebIconDatabase.getInstance().open(getDir("icons", MODE_PRIVATE).getPath()); + } + + @SuppressWarnings("VariableNotUsedInsideIf") + Intent intent = savedInstanceState == null ? getIntent() : null; + + boolean launchedFromHistory = intent != null && (intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0; + + if (isPanicTrigger(intent)) { + setIntent(null); + panicClean(); + } else { + if (launchedFromHistory) { + intent = null; + } + mPresenter.setupTabs(intent); + setIntent(null); + } + } + + @IdRes + private int getBookmarksFragmentViewId() { + return mSwapBookmarksAndTabs ? R.id.left_drawer : R.id.right_drawer; + } + + private int getTabsFragmentViewId() { + if (mShowTabsInDrawer) { + return mSwapBookmarksAndTabs ? R.id.right_drawer : R.id.left_drawer; + } else { + return R.id.tabs_toolbar_container; + } + } + + /** + * Determines if an intent is originating + * from a panic trigger. + * + * @param intent the intent to check. + * @return true if the panic trigger sent + * the intent, false otherwise. + */ + protected static boolean isPanicTrigger(@Nullable Intent intent) { + return intent != null && INTENT_PANIC_TRIGGER.equals(intent.getAction()); + } + + protected void panicClean() { + Log.d(TAG, "Closing browser"); + mTabsManager.newTab(this, "", false); + mTabsManager.switchToTab(0); + mTabsManager.clearSavedState(); + HistoryPage.deleteHistoryPage(getApplication()).subscribe(); + closeBrowser(); + // System exit needed in the case of receiving + // the panic intent since finish() isn't completely + // closing the browser + System.exit(1); + } + + private class SearchListenerClass implements OnKeyListener, OnEditorActionListener, + OnFocusChangeListener, OnTouchListener, SearchView.PreFocusListener { + + @Override + public boolean onKey(View searchView, int keyCode, KeyEvent keyEvent) { + + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mSearch.getWindowToken(), 0); + searchTheWeb(mSearch.getText().toString()); + final LightningView currentView = mTabsManager.getCurrentTab(); + if (currentView != null) { + currentView.requestFocus(); + } + return true; + default: + break; + } + return false; + } + + @Override + public boolean onEditorAction(TextView arg0, int actionId, KeyEvent arg2) { + // 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)) { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mSearch.getWindowToken(), 0); + searchTheWeb(mSearch.getText().toString()); + final LightningView currentView = mTabsManager.getCurrentTab(); + if (currentView != null) { + currentView.requestFocus(); + } + return true; + } + return false; + } + + @Override + public void onFocusChange(final View v, final boolean hasFocus) { + final LightningView currentView = mTabsManager.getCurrentTab(); + if (!hasFocus && currentView != null) { + setIsLoading(currentView.getProgress() < 100); + updateUrl(currentView.getUrl(), false); + } else if (hasFocus && currentView != null) { + + // Hack to make sure the text gets selected + ((SearchView) v).selectAll(); + mIcon = mClearIcon; + mSearch.setCompoundDrawables(null, null, mClearIcon, null); + } + + if (!hasFocus) { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(mSearch.getWindowToken(), 0); + } + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (mSearch.getCompoundDrawables()[2] != null) { + boolean tappedX = event.getX() > (mSearch.getWidth() + - mSearch.getPaddingRight() - mIcon.getIntrinsicWidth()); + if (tappedX) { + if (event.getAction() == MotionEvent.ACTION_UP) { + if (mSearch.hasFocus()) { + mSearch.setText(""); + } else { + refreshOrStop(); + } + } + return true; + } + } + return false; + } + + @Override + public void onPreFocus() { + final LightningView currentView = mTabsManager.getCurrentTab(); + if (currentView == null) { + return; + } + String url = currentView.getUrl(); + if (!UrlUtils.isSpecialUrl(url)) { + if (!mSearch.hasFocus()) { + mSearch.setText(url); + } + } + } + } + + private class DrawerLocker implements DrawerListener { + + @Override + public void onDrawerClosed(View v) { + View tabsDrawer = getTabDrawer(); + View bookmarksDrawer = getBookmarkDrawer(); + + if (v == tabsDrawer) { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, bookmarksDrawer); + } else if (mShowTabsInDrawer) { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, tabsDrawer); + } + } + + @Override + public void onDrawerOpened(View v) { + View tabsDrawer = getTabDrawer(); + View bookmarksDrawer = getBookmarkDrawer(); + + if (v == tabsDrawer) { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, bookmarksDrawer); + } else { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, tabsDrawer); + } + } + + @Override + public void onDrawerSlide(View v, float arg) {} + + @Override + public void onDrawerStateChanged(int arg) {} + + } + + private void setNavigationDrawerWidth() { + int width = getResources().getDisplayMetrics().widthPixels - Utils.dpToPx(56); + int maxWidth; + if (isTablet()) { + maxWidth = Utils.dpToPx(320); + } else { + maxWidth = Utils.dpToPx(300); + } + if (width > maxWidth) { + DrawerLayout.LayoutParams params = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerLeft + .getLayoutParams(); + params.width = maxWidth; + mDrawerLeft.setLayoutParams(params); + mDrawerLeft.requestLayout(); + DrawerLayout.LayoutParams paramsRight = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerRight + .getLayoutParams(); + paramsRight.width = maxWidth; + mDrawerRight.setLayoutParams(paramsRight); + mDrawerRight.requestLayout(); + } else { + DrawerLayout.LayoutParams params = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerLeft + .getLayoutParams(); + params.width = width; + mDrawerLeft.setLayoutParams(params); + mDrawerLeft.requestLayout(); + DrawerLayout.LayoutParams paramsRight = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerRight + .getLayoutParams(); + paramsRight.width = width; + mDrawerRight.setLayoutParams(paramsRight); + mDrawerRight.requestLayout(); + } + } + + private void initializePreferences() { + final LightningView currentView = mTabsManager.getCurrentTab(); + mFullScreen = mPreferences.getFullScreenEnabled(); + boolean colorMode = mPreferences.getColorModeEnabled(); + colorMode &= !mDarkTheme; + if (!isIncognito() && !colorMode && !mDarkTheme && mWebpageBitmap != null) { + changeToolbarBackground(mWebpageBitmap, null); + } else if (!isIncognito() && currentView != null && !mDarkTheme) { + changeToolbarBackground(currentView.getFavicon(), null); + } else if (!isIncognito() && !mDarkTheme && mWebpageBitmap != null) { + changeToolbarBackground(mWebpageBitmap, null); + } + + FragmentManager manager = getSupportFragmentManager(); + Fragment tabsFragment = manager.findFragmentByTag(TAG_TABS_FRAGMENT); + if (tabsFragment instanceof TabsFragment) { + ((TabsFragment) tabsFragment).reinitializePreferences(); + } + Fragment bookmarksFragment = manager.findFragmentByTag(TAG_BOOKMARK_FRAGMENT); + if (bookmarksFragment instanceof BookmarksFragment) { + ((BookmarksFragment) bookmarksFragment).reinitializePreferences(); + } + + // TODO layout transition causing memory leak +// mBrowserFrame.setLayoutTransition(new LayoutTransition()); + + setFullscreen(mPreferences.getHideStatusBarEnabled(), false); + + BaseSearchEngine currentSearchEngine = mSearchEngineProvider.getCurrentSearchEngine(); + mSearchText = currentSearchEngine.getQueryUrl(); + + updateCookiePreference().subscribeOn(Schedulers.worker()).subscribe(); + mProxyUtils.updateProxySettings(this); + } + + @Override + public void onWindowVisibleToUserAfterResume() { + super.onWindowVisibleToUserAfterResume(); + mToolbarLayout.setTranslationY(0); + setWebViewTranslation(mToolbarLayout.getHeight()); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_ENTER) { + if (mSearch.hasFocus()) { + searchTheWeb(mSearch.getText().toString()); + } + } else if ((keyCode == KeyEvent.KEYCODE_MENU) + && (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) + && (Build.MANUFACTURER.compareTo("LGE") == 0)) { + // Workaround for stupid LG devices that crash + return true; + } else if (keyCode == KeyEvent.KEYCODE_BACK) { + mKeyDownStartTime = System.currentTimeMillis(); + Handlers.MAIN.postDelayed(mLongPressBackRunnable, ViewConfiguration.getLongPressTimeout()); + } + return super.onKeyDown(keyCode, event); + } + + private final Runnable mLongPressBackRunnable = new Runnable() { + @Override + public void run() { + final LightningView currentTab = mTabsManager.getCurrentTab(); + showCloseDialog(mTabsManager.positionOf(currentTab)); + } + }; + + @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)) { + // Workaround for stupid LG devices that crash + openOptionsMenu(); + return true; + } else if (keyCode == KeyEvent.KEYCODE_BACK) { + Handlers.MAIN.removeCallbacks(mLongPressBackRunnable); + if ((System.currentTimeMillis() - mKeyDownStartTime) > ViewConfiguration.getLongPressTimeout()) { + return true; + } + } + return super.onKeyUp(keyCode, event); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + // Keyboard shortcuts + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (event.isCtrlPressed()) { + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_F: + // Search in page + findInPage(); + return true; + case KeyEvent.KEYCODE_T: + // Open new tab + newTab(null, true); + return true; + case KeyEvent.KEYCODE_W: + // Close current tab + mPresenter.deleteTab(mTabsManager.indexOfCurrentTab()); + return true; + case KeyEvent.KEYCODE_Q: + // Close browser + closeBrowser(); + return true; + case KeyEvent.KEYCODE_R: + // Refresh current tab + LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null) { + currentTab.reload(); + } + return true; + case KeyEvent.KEYCODE_TAB: + int nextIndex; + if (event.isShiftPressed()) { + // Go back one tab + if (mTabsManager.indexOfCurrentTab() > 0) { + nextIndex = mTabsManager.indexOfCurrentTab() - 1; + } else { + nextIndex = mTabsManager.last(); + } + } else { + // Go forward one tab + if (mTabsManager.indexOfCurrentTab() < mTabsManager.last()) { + nextIndex = mTabsManager.indexOfCurrentTab() + 1; + } else { + nextIndex = 0; + } + } + mPresenter.tabChanged(nextIndex); + return true; + } + } else if (event.getKeyCode() == KeyEvent.KEYCODE_SEARCH) { + // Highlight search field + mSearch.requestFocus(); + mSearch.selectAll(); + return true; + } else if (event.isAltPressed()) { + // Alt + tab number + if (KeyEvent.KEYCODE_0 <= event.getKeyCode() && event.getKeyCode() <= KeyEvent.KEYCODE_9) { + int nextIndex; + if (event.getKeyCode() > mTabsManager.last() + KeyEvent.KEYCODE_1 || event.getKeyCode() == KeyEvent.KEYCODE_0) { + nextIndex = mTabsManager.last(); + } else { + nextIndex = event.getKeyCode() - KeyEvent.KEYCODE_1; + } + mPresenter.tabChanged(nextIndex); + return true; + } + } + } + return super.dispatchKeyEvent(event); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final LightningView currentView = mTabsManager.getCurrentTab(); + final String currentUrl = currentView != null ? currentView.getUrl() : null; + // Handle action buttons + switch (item.getItemId()) { + case android.R.id.home: + if (mDrawerLayout.isDrawerOpen(getBookmarkDrawer())) { + mDrawerLayout.closeDrawer(getBookmarkDrawer()); + } + return true; + case R.id.action_back: + if (currentView != null && currentView.canGoBack()) { + currentView.goBack(); + } + return true; + case R.id.action_forward: + if (currentView != null && currentView.canGoForward()) { + currentView.goForward(); + } + return true; + case R.id.action_add_to_homescreen: + if (currentView != null) { + HistoryItem shortcut = new HistoryItem(currentView.getUrl(), currentView.getTitle()); + shortcut.setBitmap(currentView.getFavicon()); + Utils.createShortcut(this, shortcut); + } + return true; + case R.id.action_new_tab: + newTab(null, true); + return true; + case R.id.action_incognito: + startActivity(new Intent(this, IncognitoActivity.class)); + overridePendingTransition(R.anim.slide_up_in, R.anim.fade_out_scale); + return true; + case R.id.action_share: + new IntentUtils(this).shareUrl(currentUrl, currentView != null ? currentView.getTitle() : null); + return true; + case R.id.action_bookmarks: + openBookmarks(); + return true; + case R.id.action_copy: + if (currentUrl != null && !UrlUtils.isSpecialUrl(currentUrl)) { + ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("label", currentUrl); + clipboard.setPrimaryClip(clip); + Utils.showSnackbar(this, R.string.message_link_copied); + } + return true; + case R.id.action_settings: + startActivity(new Intent(this, SettingsActivity.class)); + return true; + case R.id.action_history: + openHistory(); + return true; + case R.id.action_downloads: + openDownloads(); + return true; + case R.id.action_add_bookmark: + if (currentUrl != null && !UrlUtils.isSpecialUrl(currentUrl)) { + addBookmark(currentView.getTitle(), currentUrl); + } + return true; + case R.id.action_find: + findInPage(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + // 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 = new HistoryItem(url, title); + mBookmarkManager.addBookmarkIfNotExists(item) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable Boolean item) { + if (Boolean.TRUE.equals(item)) { + mBookmarksView.handleUpdatedUrl(url); + } + } + }); + } + + private void deleteBookmark(final String title, final String url) { + final HistoryItem item = new HistoryItem(url, title); + + mBookmarkManager.deleteBookmark(item) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable Boolean item) { + if (Boolean.TRUE.equals(item)) { + mBookmarksView.handleUpdatedUrl(url); + } + } + }); + } + + private void putToolbarInRoot() { + if (mToolbarLayout.getParent() != mUiLayout) { + if (mToolbarLayout.getParent() != null) { + ((ViewGroup) mToolbarLayout.getParent()).removeView(mToolbarLayout); + } + + mUiLayout.addView(mToolbarLayout, 0); + mUiLayout.requestLayout(); + } + setWebViewTranslation(0); + } + + private void overlayToolbarOnWebView() { + if (mToolbarLayout.getParent() != mBrowserFrame) { + if (mToolbarLayout.getParent() != null) { + ((ViewGroup) mToolbarLayout.getParent()).removeView(mToolbarLayout); + } + + mBrowserFrame.addView(mToolbarLayout); + mBrowserFrame.requestLayout(); + } + setWebViewTranslation(mToolbarLayout.getHeight()); + } + + private void setWebViewTranslation(float translation) { + if (mFullScreen && mCurrentView != null) { + mCurrentView.setTranslationY(translation); + } else if (mCurrentView != null) { + mCurrentView.setTranslationY(0); + } + } + + /** + * method that shows a dialog asking what string the user wishes to search + * for. It highlights the text entered. + */ + private void findInPage() { + BrowserDialog.showEditText(this, + R.string.action_find, + R.string.search_hint, + R.string.search_hint, new BrowserDialog.EditorListener() { + @Override + public void onClick(String text) { + if (!TextUtils.isEmpty(text)) { + mPresenter.findInPage(text); + showFindInPageControls(text); + } + } + }); + } + + private void showFindInPageControls(@NonNull String text) { + mSearchBar.setVisibility(View.VISIBLE); + + TextView tw = (TextView) findViewById(R.id.search_query); + tw.setText('\'' + text + '\''); + + ImageButton up = (ImageButton) findViewById(R.id.button_next); + up.setOnClickListener(this); + + ImageButton down = (ImageButton) findViewById(R.id.button_back); + down.setOnClickListener(this); + + ImageButton quit = (ImageButton) findViewById(R.id.button_quit); + quit.setOnClickListener(this); + } + + @Override + public TabsManager getTabModel() { + return mTabsManager; + } + + @Override + public void showCloseDialog(final int position) { + if (position < 0) { + return; + } + BrowserDialog.show(this, R.string.dialog_title_close_browser, + new BrowserDialog.Item(R.string.close_tab) { + @Override + public void onClick() { + mPresenter.deleteTab(position); + } + }, + new BrowserDialog.Item(R.string.close_other_tabs) { + @Override + public void onClick() { + mPresenter.closeAllOtherTabs(); + } + }, + new BrowserDialog.Item(R.string.close_all_tabs) { + @Override + public void onClick() { + closeBrowser(); + } + }); + } + + @Override + public void notifyTabViewRemoved(int position) { + Log.d(TAG, "Notify Tab Removed: " + position); + mTabsView.tabRemoved(position); + } + + @Override + public void notifyTabViewAdded() { + Log.d(TAG, "Notify Tab Added"); + mTabsView.tabAdded(); + } + + @Override + public void notifyTabViewChanged(int position) { + Log.d(TAG, "Notify Tab Changed: " + position); + mTabsView.tabChanged(position); + } + + @Override + public void notifyTabViewInitialized() { + Log.d(TAG, "Notify Tabs Initialized"); + mTabsView.tabsInitialized(); + } + + @Override + public void tabChanged(LightningView tab) { + mPresenter.tabChangeOccurred(tab); + } + + @Override + public void removeTabView() { + + Log.d(TAG, "Remove the tab view"); + + // Set the background color so the color mode color doesn't show through + mBrowserFrame.setBackgroundColor(mBackgroundColor); + + removeViewFromParent(mCurrentView); + + mCurrentView = null; + + // Use a delayed handler to make the transition smooth + // otherwise it will get caught up with the showTab code + // and cause a janky motion + Handlers.MAIN.postDelayed(new Runnable() { + @Override + public void run() { + mDrawerLayout.closeDrawers(); + } + }, 200); + + } + + @Override + public void setTabView(@NonNull final View view) { + if (mCurrentView == view) { + return; + } + + Log.d(TAG, "Setting the tab view"); + + // Set the background color so the color mode color doesn't show through + mBrowserFrame.setBackgroundColor(mBackgroundColor); + + removeViewFromParent(view); + removeViewFromParent(mCurrentView); + + mBrowserFrame.addView(view, 0, MATCH_PARENT); + if (mFullScreen) { + view.setTranslationY(mToolbarLayout.getHeight() + mToolbarLayout.getTranslationY()); + } else { + view.setTranslationY(0); + } + + view.requestFocus(); + + mCurrentView = view; + + showActionBar(); + + // Use a delayed handler to make the transition smooth + // otherwise it will get caught up with the showTab code + // and cause a janky motion + Handlers.MAIN.postDelayed(new Runnable() { + @Override + public void run() { + mDrawerLayout.closeDrawers(); + } + }, 200); + + // Handlers.MAIN.postDelayed(new Runnable() { + // @Override + // public void run() { + // Remove browser frame background to reduce overdraw + //TODO evaluate performance + // mBrowserFrame.setBackgroundColor(Color.TRANSPARENT); + // } + // }, 300); + } + + @Override + public void showBlockedLocalFileDialog(@NonNull DialogInterface.OnClickListener listener) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + Dialog dialog = 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(); + + BrowserDialog.setDialogSize(this, dialog); + } + + @Override + public void showSnackbar(@StringRes int resource) { + Utils.showSnackbar(this, resource); + } + + @Override + public void tabCloseClicked(int position) { + mPresenter.deleteTab(position); + } + + @Override + public void tabClicked(int position) { + showTab(position); + } + + @Override + public void newTabButtonClicked() { + mPresenter.newTab(null, true); + } + + @Override + public void newTabButtonLongClicked() { + String url = mPreferences.getSavedUrl(); + if (url != null) { + newTab(url, true); + + Utils.showSnackbar(this, R.string.deleted_tab); + } + mPreferences.setSavedUrl(null); + } + + @Override + public void bookmarkButtonClicked() { + final LightningView currentTab = mTabsManager.getCurrentTab(); + final String url = currentTab != null ? currentTab.getUrl() : null; + final String title = currentTab != null ? currentTab.getTitle() : null; + if (url == null) { + return; + } + + if (!UrlUtils.isSpecialUrl(url)) { + mBookmarkManager.isBookmark(url) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable Boolean item) { + if (Boolean.TRUE.equals(item)) { + deleteBookmark(title, url); + } else { + addBookmark(title, url); + } + } + }); + } + } + + @Override + public void bookmarkItemClicked(@NonNull HistoryItem item) { + mPresenter.loadUrlInCurrentView(item.getUrl()); + // keep any jank from happening when the drawer is closed after the + // URL starts to load + Handlers.MAIN.postDelayed(new Runnable() { + @Override + public void run() { + closeDrawers(null); + } + }, 150); + } + + @Override + public void handleHistoryChange() { + openHistory(); + } + + /** + * displays the WebView contained in the LightningView Also handles the + * removal of previous views + * + * @param position the poition of the tab to display + */ + // TODO move to presenter + private synchronized void showTab(final int position) { + mPresenter.tabChanged(position); + } + + private static void removeViewFromParent(@Nullable View view) { + if (view == null) { + return; + } + ViewParent parent = view.getParent(); + if (parent instanceof ViewGroup) { + ((ViewGroup) parent).removeView(view); + } + } + + protected void handleNewIntent(Intent intent) { + mPresenter.onNewIntent(intent); + } + + @Override + public void closeEmptyTab() { + // Currently do nothing + // Possibly closing the current tab might close the browser + // and mess stuff up + } + + @Override + public void onTrimMemory(int level) { + if (level > TRIM_MEMORY_MODERATE && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + Log.d(TAG, "Low Memory, Free Memory"); + mPresenter.onAppLowMemory(); + } + } + + // TODO move to presenter + private synchronized boolean newTab(String url, boolean show) { + return mPresenter.newTab(url, show); + } + + protected void performExitCleanUp() { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (mPreferences.getClearCacheExit() && currentTab != null && !isIncognito()) { + WebUtils.clearCache(currentTab.getWebView()); + Log.d(TAG, "Cache Cleared"); + } + if (mPreferences.getClearHistoryExitEnabled() && !isIncognito()) { + WebUtils.clearHistory(this, mHistoryModel); + Log.d(TAG, "History Cleared"); + } + if (mPreferences.getClearCookiesExitEnabled() && !isIncognito()) { + WebUtils.clearCookies(this); + Log.d(TAG, "Cookies Cleared"); + } + if (mPreferences.getClearWebStorageExitEnabled() && !isIncognito()) { + WebUtils.clearWebStorage(); + Log.d(TAG, "WebStorage Cleared"); + } else if (isIncognito()) { + WebUtils.clearWebStorage(); // We want to make sure incognito mode is secure + } + } + + @Override + public void onConfigurationChanged(final Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + Log.d(TAG, "onConfigurationChanged"); + + if (mFullScreen) { + showActionBar(); + mToolbarLayout.setTranslationY(0); + setWebViewTranslation(mToolbarLayout.getHeight()); + } + + supportInvalidateOptionsMenu(); + initializeToolbarHeight(newConfig); + } + + private void initializeToolbarHeight(@NonNull final Configuration configuration) { + // TODO externalize the dimensions + doOnLayout(mUiLayout, new Runnable() { + @Override + public void run() { + int toolbarSize; + if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { + // In portrait toolbar should be 56 dp tall + toolbarSize = Utils.dpToPx(56); + } else { + // In landscape toolbar should be 48 dp tall + toolbarSize = Utils.dpToPx(52); + } + mToolbar.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, toolbarSize)); + mToolbar.setMinimumHeight(toolbarSize); + doOnLayout(mToolbar, new Runnable() { + @Override + public void run() { + setWebViewTranslation(mToolbarLayout.getHeight()); + } + }); + mToolbar.requestLayout(); + + } + }); + } + + public void closeBrowser() { + mBrowserFrame.setBackgroundColor(mBackgroundColor); + removeViewFromParent(mCurrentView); + performExitCleanUp(); + int size = mTabsManager.size(); + mTabsManager.shutdown(); + mCurrentView = null; + for (int n = 0; n < size; n++) { + mTabsView.tabRemoved(0); + } + finish(); + } + + @Override + public synchronized void onBackPressed() { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (mDrawerLayout.isDrawerOpen(getTabDrawer())) { + mDrawerLayout.closeDrawer(getTabDrawer()); + } else if (mDrawerLayout.isDrawerOpen(getBookmarkDrawer())) { + mBookmarksView.navigateBack(); + } else { + if (currentTab != null) { + Log.d(TAG, "onBackPressed"); + if (mSearch.hasFocus()) { + currentTab.requestFocus(); + } else if (currentTab.canGoBack()) { + if (!currentTab.isShown()) { + onHideCustomView(); + } else { + currentTab.goBack(); + } + } else { + if (mCustomView != null || mCustomViewCallback != null) { + onHideCustomView(); + } else { + mPresenter.deleteTab(mTabsManager.positionOf(currentTab)); + } + } + } else { + Log.e(TAG, "This shouldn't happen ever"); + super.onBackPressed(); + } + } + } + + @Override + protected void onPause() { + super.onPause(); + Log.d(TAG, "onPause"); + mTabsManager.pauseAll(); + try { + getApplication().unregisterReceiver(mNetworkReceiver); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Receiver was not registered", e); + } + if (isIncognito() && isFinishing()) { + overridePendingTransition(R.anim.fade_in_scale, R.anim.slide_down_out); + } + } + + protected void saveOpenTabs() { + if (mPreferences.getRestoreLostTabsEnabled()) { + mTabsManager.saveState(); + } + } + + @Override + protected void onStop() { + super.onStop(); + } + + @Override + protected void onDestroy() { + Log.d(TAG, "onDestroy"); + + Handlers.MAIN.removeCallbacksAndMessages(null); + + mPresenter.shutdown(); + + super.onDestroy(); + } + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + mTabsManager.shutdown(); + } + + @Override + protected void onResume() { + super.onResume(); + Log.d(TAG, "onResume"); + if (mSwapBookmarksAndTabs != mPreferences.getBookmarksAndTabsSwapped()) { + restart(); + } + + mTabsManager.resumeAll(this); + initializePreferences(); + + supportInvalidateOptionsMenu(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(NETWORK_BROADCAST_ACTION); + getApplication().registerReceiver(mNetworkReceiver, filter); + + if (mFullScreen) { + overlayToolbarOnWebView(); + } else { + putToolbarInRoot(); + } + } + + /** + * searches the web for the query fixing any and all problems with the input + * checks if it is a search, url, etc. + */ + private void searchTheWeb(@NonNull String query) { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (query.isEmpty()) { + return; + } + String searchUrl = mSearchText + UrlUtils.QUERY_PLACE_HOLDER; + query = query.trim(); + if (currentTab != null) { + currentTab.stopLoading(); + mPresenter.loadUrlInCurrentView(UrlUtils.smartUrlFilter(query, true, searchUrl)); + } + } + + /** + * Animates the color of the toolbar from one color to another. Optionally animates + * the color of the tab background, for use when the tabs are displayed on the top + * of the screen. + * + * @param favicon the Bitmap to extract the color from + * @param tabBackground the optional LinearLayout to color + */ + @Override + public void changeToolbarBackground(@NonNull Bitmap favicon, @Nullable final Drawable tabBackground) { + final int defaultColor = ContextCompat.getColor(this, R.color.primary_color); + if (mCurrentUiColor == Color.BLACK) { + mCurrentUiColor = defaultColor; + } + Palette.from(favicon).generate(new Palette.PaletteAsyncListener() { + @Override + public void onGenerated(Palette palette) { + + // OR with opaque black to remove transparency glitches + int color = 0xff000000 | palette.getVibrantColor(defaultColor); + + final int finalColor; // Lighten up the dark color if it is + // too dark + if (!mShowTabsInDrawer || Utils.isColorTooDark(color)) { + finalColor = Utils.mixTwoColors(defaultColor, color, 0.25f); + } else { + finalColor = color; + } + + final Window window = getWindow(); + if (!mShowTabsInDrawer) { + window.setBackgroundDrawable(new ColorDrawable(Color.BLACK)); + } + + final int startSearchColor = getSearchBarColor(mCurrentUiColor, defaultColor); + final int finalSearchColor = getSearchBarColor(finalColor, defaultColor); + + Animation animation = new Animation() { + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + final int color = DrawableUtils.mixColor(interpolatedTime, mCurrentUiColor, finalColor); + if (mShowTabsInDrawer) { + mBackground.setColor(color); + Handlers.MAIN.post(new Runnable() { + @Override + public void run() { + window.setBackgroundDrawable(mBackground); + } + }); + } else if (tabBackground != null) { + tabBackground.setColorFilter(color, PorterDuff.Mode.SRC_IN); + } + mCurrentUiColor = color; + mToolbarLayout.setBackgroundColor(color); + mSearchBackground.getBackground().setColorFilter(DrawableUtils.mixColor(interpolatedTime, + startSearchColor, finalSearchColor), PorterDuff.Mode.SRC_IN); + } + }; + animation.setDuration(300); + mToolbarLayout.startAnimation(animation); + } + }); + } + + private int getSearchBarColor(int requestedColor, int defaultColor) { + if (requestedColor == defaultColor) { + return mDarkTheme ? DrawableUtils.mixColor(0.25f, defaultColor, Color.WHITE) : Color.WHITE; + } else { + return DrawableUtils.mixColor(0.25f, requestedColor, Color.WHITE); + } + } + + @Override + public boolean getUseDarkTheme() { + return mDarkTheme; + } + + @ColorInt + @Override + public int getUiColor() { + return mCurrentUiColor; + } + + @Override + public void updateUrl(@Nullable String url, boolean isLoading) { + if (url == null || mSearch == null || mSearch.hasFocus()) { + return; + } + final LightningView currentTab = mTabsManager.getCurrentTab(); + mBookmarksView.handleUpdatedUrl(url); + + String currentTitle = currentTab != null ? currentTab.getTitle() : null; + + mSearch.setText(mSearchBoxModel.getDisplayContent(url, currentTitle, isLoading)); + } + + @Override + 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))); + } + } + + @Override + public void updateProgress(int n) { + setIsLoading(n < 100); + mProgressBar.setProgress(n); + } + + protected void addItemToHistory(@Nullable final String title, @NonNull final String url) { + if (UrlUtils.isSpecialUrl(url)) { + return; + } + + mHistoryModel.visitHistoryItem(url, title) + .subscribeOn(Schedulers.io()) + .subscribe(new CompletableOnSubscribe() { + @Override + public void onError(@NonNull Throwable throwable) { + Log.e(TAG, "Exception while updating history", throwable); + } + }); + } + + /** + * method to generate search suggestions for the AutoCompleteTextView from + * previously searched URLs + */ + private void initializeSearchSuggestions(final AutoCompleteTextView getUrl) { + + mSuggestionsAdapter = new SuggestionsAdapter(this, mDarkTheme, isIncognito()); + + getUrl.setThreshold(1); + getUrl.setDropDownWidth(-1); + getUrl.setDropDownAnchor(R.id.toolbar_layout); + getUrl.setOnItemClickListener(new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView adapterView, View view, int pos, long l) { + String url = null; + CharSequence urlString = ((TextView) view.findViewById(R.id.url)).getText(); + if (urlString != null) { + url = urlString.toString(); + } + if (url == null || url.startsWith(getString(R.string.suggestion))) { + CharSequence searchString = ((TextView) view.findViewById(R.id.title)).getText(); + if (searchString != null) { + url = searchString.toString(); + } + } + if (url == null) { + return; + } + getUrl.setText(url); + searchTheWeb(url); + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(getUrl.getWindowToken(), 0); + mPresenter.onAutoCompleteItemPressed(); + } + + }); + + getUrl.setSelectAllOnFocus(true); + getUrl.setAdapter(mSuggestionsAdapter); + } + + /** + * function that opens the HTML history page in the browser + */ + private void openHistory() { + new HistoryPage().getHistoryPage() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable String item) { + Preconditions.checkNonNull(item); + LightningView view = mTabsManager.getCurrentTab(); + if (view != null) { + view.loadUrl(item); + } + } + }); + } + + private void openDownloads() { + new DownloadsPage().getDownloadsPage() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable String item) { + Preconditions.checkNonNull(item); + LightningView view = mTabsManager.getCurrentTab(); + if (view != null) { + view.loadUrl(item); + } + } + }); + } + + private View getBookmarkDrawer() { + return mSwapBookmarksAndTabs ? mDrawerLeft : mDrawerRight; + } + + private View getTabDrawer() { + return mSwapBookmarksAndTabs ? mDrawerRight : mDrawerLeft; + } + + /** + * helper function that opens the bookmark drawer + */ + private void openBookmarks() { + if (mDrawerLayout.isDrawerOpen(getTabDrawer())) { + mDrawerLayout.closeDrawers(); + } + mDrawerLayout.openDrawer(getBookmarkDrawer()); + } + + /** + * This method closes any open drawer and executes + * the runnable after the drawers are completely closed. + * + * @param runnable an optional runnable to run after + * the drawers are closed. + */ + protected final void closeDrawers(@Nullable final Runnable runnable) { + if (!mDrawerLayout.isDrawerOpen(mDrawerLeft) && !mDrawerLayout.isDrawerOpen(mDrawerRight)) { + if (runnable != null) { + runnable.run(); + return; + } + } + mDrawerLayout.closeDrawers(); + + mDrawerLayout.addDrawerListener(new DrawerListener() { + @Override + public void onDrawerSlide(View drawerView, float slideOffset) {} + + @Override + public void onDrawerOpened(View drawerView) {} + + @Override + public void onDrawerClosed(View drawerView) { + if (runnable != null) { + runnable.run(); + } + mDrawerLayout.removeDrawerListener(this); + } + + @Override + public void onDrawerStateChanged(int newState) {} + }); + } + + @Override + public void setForwardButtonEnabled(boolean enabled) { + if (mForwardMenuItem != null && mForwardMenuItem.getIcon() != null) { + int colorFilter; + if (enabled) { + colorFilter = mIconColor; + } else { + colorFilter = mDisabledIconColor; + } + mForwardMenuItem.getIcon().setColorFilter(colorFilter, PorterDuff.Mode.SRC_IN); + mForwardMenuItem.setIcon(mForwardMenuItem.getIcon()); + } + } + + @Override + public void setBackButtonEnabled(boolean enabled) { + if (mBackMenuItem != null && mBackMenuItem.getIcon() != null) { + int colorFilter; + if (enabled) { + colorFilter = mIconColor; + } else { + colorFilter = mDisabledIconColor; + } + mBackMenuItem.getIcon().setColorFilter(colorFilter, PorterDuff.Mode.SRC_IN); + mBackMenuItem.setIcon(mBackMenuItem.getIcon()); + } + } + + private MenuItem mBackMenuItem; + private MenuItem mForwardMenuItem; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + mBackMenuItem = menu.findItem(R.id.action_back); + mForwardMenuItem = menu.findItem(R.id.action_forward); + if (mBackMenuItem != null && mBackMenuItem.getIcon() != null) + mBackMenuItem.getIcon().setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); + if (mForwardMenuItem != null && mForwardMenuItem.getIcon() != null) + mForwardMenuItem.getIcon().setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); + return super.onCreateOptionsMenu(menu); + } + + /** + * opens a file chooser + * param ValueCallback is the message from the WebView indicating a file chooser + * should be opened + */ + @Override + public void openFileChooser(ValueCallback uploadMsg) { + mUploadMessage = uploadMsg; + Intent i = new Intent(Intent.ACTION_GET_CONTENT); + i.addCategory(Intent.CATEGORY_OPENABLE); + i.setType("*/*"); + startActivityForResult(Intent.createChooser(i, getString(R.string.title_file_chooser)), 1); + } + + /** + * used to allow uploading into the browser + */ + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + if (API < Build.VERSION_CODES.LOLLIPOP) { + if (requestCode == 1) { + if (null == mUploadMessage) { + return; + } + Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); + mUploadMessage.onReceiveValue(result); + mUploadMessage = null; + + } + } + + if (requestCode != 1 || mFilePathCallback == null) { + super.onActivityResult(requestCode, resultCode, intent); + return; + } + + Uri[] results = null; + + // Check that the response is a good one + if (resultCode == Activity.RESULT_OK) { + if (intent == null) { + // If there is not data, then we may have taken a photo + if (mCameraPhotoPath != null) { + results = new Uri[]{Uri.parse(mCameraPhotoPath)}; + } + } else { + String dataString = intent.getDataString(); + if (dataString != null) { + results = new Uri[]{Uri.parse(dataString)}; + } + } + } + + mFilePathCallback.onReceiveValue(results); + mFilePathCallback = null; + } + + @Override + public void showFileChooser(ValueCallback filePathCallback) { + if (mFilePathCallback != null) { + mFilePathCallback.onReceiveValue(null); + } + mFilePathCallback = filePathCallback; + + Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + if (takePictureIntent.resolveActivity(this.getPackageManager()) != null) { + // Create the File where the photo should go + File photoFile = null; + try { + photoFile = Utils.createImageFile(); + takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath); + } catch (IOException ex) { + // Error occurred while creating the File + Log.e(TAG, "Unable to create Image File", ex); + } + + // Continue only if the File was successfully created + if (photoFile != null) { + mCameraPhotoPath = "file:" + photoFile.getAbsolutePath(); + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); + } else { + takePictureIntent = null; + } + } + + Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT); + contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); + contentSelectionIntent.setType("*/*"); + + Intent[] intentArray; + if (takePictureIntent != null) { + intentArray = new Intent[]{takePictureIntent}; + } else { + intentArray = new Intent[0]; + } + + Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); + chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent); + chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser"); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray); + + startActivityForResult(chooserIntent, 1); + } + + @Override + public synchronized void onShowCustomView(View view, CustomViewCallback callback) { + int requestedOrientation = mOriginalOrientation = getRequestedOrientation(); + onShowCustomView(view, callback, requestedOrientation); + } + + @Override + public synchronized void onShowCustomView(final View view, CustomViewCallback callback, int requestedOrientation) { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (view == null || mCustomView != null) { + if (callback != null) { + try { + callback.onCustomViewHidden(); + } catch (Exception e) { + Log.e(TAG, "Error hiding custom view", e); + } + } + return; + } + try { + view.setKeepScreenOn(true); + } catch (SecurityException e) { + Log.e(TAG, "WebView is not allowed to keep the screen on"); + } + mOriginalOrientation = getRequestedOrientation(); + mCustomViewCallback = callback; + mCustomView = view; + + setRequestedOrientation(requestedOrientation); + final FrameLayout decorView = (FrameLayout) getWindow().getDecorView(); + + mFullscreenContainer = new FrameLayout(this); + mFullscreenContainer.setBackgroundColor(ContextCompat.getColor(this, android.R.color.black)); + if (view instanceof FrameLayout) { + if (((FrameLayout) view).getFocusedChild() instanceof VideoView) { + mVideoView = (VideoView) ((FrameLayout) view).getFocusedChild(); + mVideoView.setOnErrorListener(new VideoCompletionListener()); + mVideoView.setOnCompletionListener(new VideoCompletionListener()); + } + } else if (view instanceof VideoView) { + mVideoView = (VideoView) view; + mVideoView.setOnErrorListener(new VideoCompletionListener()); + mVideoView.setOnCompletionListener(new VideoCompletionListener()); + } + decorView.addView(mFullscreenContainer, COVER_SCREEN_PARAMS); + mFullscreenContainer.addView(mCustomView, COVER_SCREEN_PARAMS); + decorView.requestLayout(); + setFullscreen(true, true); + if (currentTab != null) { + currentTab.setVisibility(View.INVISIBLE); + } + } + + @Override + public void closeBookmarksDrawer() { + mDrawerLayout.closeDrawer(getBookmarkDrawer()); + } + + @Override + public void onHideCustomView() { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (mCustomView == null || mCustomViewCallback == null || currentTab == null) { + if (mCustomViewCallback != null) { + try { + mCustomViewCallback.onCustomViewHidden(); + } catch (Exception e) { + Log.e(TAG, "Error hiding custom view", e); + } + mCustomViewCallback = null; + } + return; + } + Log.d(TAG, "onHideCustomView"); + currentTab.setVisibility(View.VISIBLE); + try { + mCustomView.setKeepScreenOn(false); + } catch (SecurityException e) { + Log.e(TAG, "WebView is not allowed to keep the screen on"); + } + setFullscreen(mPreferences.getHideStatusBarEnabled(), false); + if (mFullscreenContainer != null) { + ViewGroup parent = (ViewGroup) mFullscreenContainer.getParent(); + if (parent != null) { + parent.removeView(mFullscreenContainer); + } + mFullscreenContainer.removeAllViews(); + } + + mFullscreenContainer = null; + mCustomView = null; + if (mVideoView != null) { + Log.d(TAG, "VideoView is being stopped"); + mVideoView.stopPlayback(); + mVideoView.setOnErrorListener(null); + mVideoView.setOnCompletionListener(null); + mVideoView = null; + } + if (mCustomViewCallback != null) { + try { + mCustomViewCallback.onCustomViewHidden(); + } catch (Exception e) { + Log.e(TAG, "Error hiding custom view", e); + } + } + mCustomViewCallback = null; + setRequestedOrientation(mOriginalOrientation); + } + + private class VideoCompletionListener implements MediaPlayer.OnCompletionListener, + MediaPlayer.OnErrorListener { + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + return false; + } + + @Override + public void onCompletion(MediaPlayer mp) { + onHideCustomView(); + } + + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + Log.d(TAG, "onWindowFocusChanged"); + if (hasFocus) { + setFullscreen(mIsFullScreen, mIsImmersive); + } + } + + @Override + public void onBackButtonPressed() { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null) { + if (currentTab.canGoBack()) { + currentTab.goBack(); + closeDrawers(null); + } else { + mPresenter.deleteTab(mTabsManager.positionOf(currentTab)); + } + } + } + + @Override + public void onForwardButtonPressed() { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null) { + if (currentTab.canGoForward()) { + currentTab.goForward(); + closeDrawers(null); + } + } + } + + @Override + public void onHomeButtonPressed() { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null) { + currentTab.loadHomepage(); + closeDrawers(null); + } + } + + /** + * This method sets whether or not the activity will display + * in full-screen mode (i.e. the ActionBar will be hidden) and + * whether or not immersive mode should be set. This is used to + * set both parameters correctly as during a full-screen video, + * both need to be set, but other-wise we leave it up to user + * preference. + * + * @param enabled true to enable full-screen, false otherwise + * @param immersive true to enable immersive mode, false otherwise + */ + private void setFullscreen(boolean enabled, boolean immersive) { + mIsFullScreen = enabled; + mIsImmersive = immersive; + Window window = getWindow(); + View decor = window.getDecorView(); + 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); + } else { + decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + } + window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + } + } + + /** + * This method handles the JavaScript callback to create a new tab. + * Basically this handles the event that JavaScript needs to create + * a popup. + * + * @param resultMsg the transport message used to send the URL to + * the newly created WebView. + */ + @Override + public synchronized void onCreateWindow(Message resultMsg) { + if (resultMsg == null) { + return; + } + if (newTab("", true)) { + LightningView newTab = mTabsManager.getTabAtPosition(mTabsManager.size() - 1); + if (newTab != null) { + final WebView webView = newTab.getWebView(); + if (webView != null) { + WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; + transport.setWebView(webView); + resultMsg.sendToTarget(); + } + } + } + } + + /** + * Closes the specified {@link LightningView}. This implements + * the JavaScript callback that asks the tab to close itself and + * is especially helpful when a page creates a redirect and does + * not need the tab to stay open any longer. + * + * @param view the LightningView to close, delete it. + */ + @Override + public void onCloseWindow(LightningView view) { + mPresenter.deleteTab(mTabsManager.positionOf(view)); + } + + /** + * Hide the ActionBar using an animation if we are in full-screen + * mode. This method also re-parents the ActionBar if its parent is + * incorrect so that the animation can happen correctly. + */ + @Override + public void hideActionBar() { + if (mFullScreen) { + if (mToolbarLayout == null || mBrowserFrame == null) + return; + + final int height = mToolbarLayout.getHeight(); + if (mToolbarLayout.getTranslationY() > -0.01f) { + Animation show = new Animation() { + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + float trans = interpolatedTime * height; + mToolbarLayout.setTranslationY(-trans); + setWebViewTranslation(height - trans); + } + }; + show.setDuration(250); + show.setInterpolator(new BezierDecelerateInterpolator()); + mBrowserFrame.startAnimation(show); + } + } + } + + /** + * Display the ActionBar using an animation if we are in full-screen + * mode. This method also re-parents the ActionBar if its parent is + * incorrect so that the animation can happen correctly. + */ + @Override + public void showActionBar() { + if (mFullScreen) { + Log.d(TAG, "showActionBar"); + if (mToolbarLayout == null) + return; + + int height = mToolbarLayout.getHeight(); + if (height == 0) { + mToolbarLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + height = mToolbarLayout.getMeasuredHeight(); + } + + final int totalHeight = height; + if (mToolbarLayout.getTranslationY() < -(height - 0.01f)) { + Animation show = new Animation() { + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + float trans = interpolatedTime * totalHeight; + mToolbarLayout.setTranslationY(trans - totalHeight); + setWebViewTranslation(trans); + } + }; + show.setDuration(250); + show.setInterpolator(new BezierDecelerateInterpolator()); + mBrowserFrame.startAnimation(show); + } + } + } + + @Override + public void handleBookmarksChange() { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null && UrlUtils.isBookmarkUrl(currentTab.getUrl())) { + currentTab.loadBookmarkpage(); + } + if (currentTab != null) { + mBookmarksView.handleUpdatedUrl(currentTab.getUrl()); + } + } + + @Override + public void handleDownloadDeleted() { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null && UrlUtils.isDownloadsUrl(currentTab.getUrl())) { + currentTab.loadDownloadspage(); + } + if (currentTab != null) { + mBookmarksView.handleUpdatedUrl(currentTab.getUrl()); + } + } + + @Override + public void handleBookmarkDeleted(@NonNull HistoryItem item) { + mBookmarksView.handleBookmarkDeleted(item); + handleBookmarksChange(); + } + + @Override + public void handleNewTab(@NonNull LightningDialogBuilder.NewTab newTabType, @NonNull String url) { + mDrawerLayout.closeDrawers(); + switch (newTabType) { + case FOREGROUND: + newTab(url, true); + break; + case BACKGROUND: + newTab(url, false); + break; + case INCOGNITO: + Intent intent = new Intent(BrowserActivity.this, IncognitoActivity.class); + intent.setData(Uri.parse(url)); + startActivity(intent); + overridePendingTransition(R.anim.slide_up_in, R.anim.fade_out_scale); + break; + } + } + + /** + * Performs an action when the provided view is laid out. + * + * @param view the view to listen to for layouts. + * @param runnable the runnable to run when the view is + * laid out. + */ + private static void doOnLayout(@NonNull final View view, @NonNull final Runnable runnable) { + view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } else { + //noinspection deprecation + view.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } + runnable.run(); + } + }); + } + + /** + * This method lets the search bar know that the page is currently loading + * and that it should display the stop icon to indicate to the user that + * pressing it stops the page from loading + */ + private void setIsLoading(boolean isLoading) { + if (!mSearch.hasFocus()) { + mIcon = isLoading ? mDeleteIcon : mRefreshIcon; + mSearch.setCompoundDrawables(null, null, mIcon, null); + } + } + + /** + * handle presses on the refresh icon in the search bar, if the page is + * loading, stop the page, if it is done loading refresh the page. + * See setIsFinishedLoading and setIsLoading for displaying the correct icon + */ + private void refreshOrStop() { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null) { + if (currentTab.getProgress() < 100) { + currentTab.stopLoading(); + } else { + currentTab.reload(); + } + } + } + + /** + * Handle the click event for the views that are using + * this class as a click listener. This method should + * distinguish between the various views using their IDs. + * + * @param v the view that the user has clicked + */ + @Override + public void onClick(View v) { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab == null) { + return; + } + switch (v.getId()) { + case R.id.arrow_button: + if (mSearch != null && mSearch.hasFocus()) { + currentTab.requestFocus(); + } else if (mShowTabsInDrawer) { + mDrawerLayout.openDrawer(getTabDrawer()); + } else { + currentTab.loadHomepage(); + } + break; + case R.id.button_next: + currentTab.findNext(); + break; + case R.id.button_back: + currentTab.findPrevious(); + break; + case R.id.button_quit: + currentTab.clearFindMatches(); + mSearchBar.setVisibility(View.GONE); + break; + case R.id.action_toggle_desktop: + currentTab.toggleDesktopUA(this); + currentTab.reload(); + closeDrawers(null); + break; + } + } + + /** + * This NetworkReceiver notifies each of the WebViews in the browser whether + * the network is currently connected or not. This is important because some + * JavaScript properties rely on the WebView knowing the current network state. + * It is used to help the browser be compliant with the HTML5 spec, sec. 5.7.7 + */ + private final NetworkReceiver mNetworkReceiver = new NetworkReceiver() { + @Override + public void onConnectivityChange(boolean isConnected) { + Log.d(TAG, "Network Connected: " + isConnected); + mTabsManager.notifyConnectionStatus(isConnected); + } + }; + + /** + * Handle the callback that permissions requested have been granted or not. + * This method should act upon the results of the permissions request. + * + * @param requestCode the request code sent when initially making the request + * @param permissions the array of the permissions that was requested + * @param grantResults the results of the permissions requests that provides + * information on whether the request was granted or not + */ + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + PermissionsManager.getInstance().notifyPermissionsChange(permissions, grantResults); + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } } diff --git a/app/src/main/java/org/purplei2p/lightning/browser/fragment/BookmarksFragment.java b/app/src/main/java/org/purplei2p/lightning/browser/fragment/BookmarksFragment.java index fb3b194..c317beb 100644 --- a/app/src/main/java/org/purplei2p/lightning/browser/fragment/BookmarksFragment.java +++ b/app/src/main/java/org/purplei2p/lightning/browser/fragment/BookmarksFragment.java @@ -35,7 +35,6 @@ import javax.inject.Inject; import org.purplei2p.lightning.R; import org.purplei2p.lightning.browser.bookmark.BookmarkUiModel; -import org.purplei2p.lightning.reading.activity.ReadingActivity; import org.purplei2p.lightning.browser.TabsManager; import org.purplei2p.lightning.animation.AnimationUtils; import org.purplei2p.lightning.BrowserApp; @@ -178,7 +177,6 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, } }); setupNavigationButton(view, R.id.action_add_bookmark, R.id.icon_star); - setupNavigationButton(view, R.id.action_reading, R.id.icon_reading); setupNavigationButton(view, R.id.action_toggle_desktop, R.id.icon_desktop); mBookmarkAdapter = new BookmarkListAdapter(mFaviconModel, mFolderBitmap, mWebpageBitmap); @@ -341,14 +339,6 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, case R.id.action_add_bookmark: mUiController.bookmarkButtonClicked(); break; - case R.id.action_reading: - LightningView currentTab = getTabsManager().getCurrentTab(); - if (currentTab != null) { - Intent read = new Intent(getActivity(), ReadingActivity.class); - read.putExtra(Constants.LOAD_READING_URL, currentTab.getUrl()); - startActivity(read); - } - break; case R.id.action_toggle_desktop: LightningView current = getTabsManager().getCurrentTab(); if (current != null) { diff --git a/app/src/main/java/org/purplei2p/lightning/constant/Constants.java b/app/src/main/java/org/purplei2p/lightning/constant/Constants.java index 4241cec..c3f7148 100644 --- a/app/src/main/java/org/purplei2p/lightning/constant/Constants.java +++ b/app/src/main/java/org/purplei2p/lightning/constant/Constants.java @@ -47,8 +47,6 @@ public final class Constants { " return '';\n" + "}());"; - public static final String LOAD_READING_URL = "ReadingUrl"; - // URL Schemes public static final String HTTP = "http://"; public static final String HTTPS = "https://"; diff --git a/app/src/main/java/org/purplei2p/lightning/di/AppComponent.java b/app/src/main/java/org/purplei2p/lightning/di/AppComponent.java index 165aa64..bc13d4a 100644 --- a/app/src/main/java/org/purplei2p/lightning/di/AppComponent.java +++ b/app/src/main/java/org/purplei2p/lightning/di/AppComponent.java @@ -3,7 +3,6 @@ package org.purplei2p.lightning.di; import javax.inject.Singleton; import org.purplei2p.lightning.browser.activity.BrowserActivity; -import org.purplei2p.lightning.reading.activity.ReadingActivity; import org.purplei2p.lightning.browser.TabsManager; import org.purplei2p.lightning.browser.activity.ThemableBrowserActivity; import org.purplei2p.lightning.settings.activity.ThemableSettingsActivity; @@ -56,8 +55,6 @@ public interface AppComponent { void inject(ProxyUtils proxyUtils); - void inject(ReadingActivity activity); - void inject(LightningWebClient webClient); void inject(ThemableSettingsActivity activity); diff --git a/app/src/main/java/org/purplei2p/lightning/dialog/LightningDialogBuilder.java b/app/src/main/java/org/purplei2p/lightning/dialog/LightningDialogBuilder.java index 0944fc3..46e9db4 100644 --- a/app/src/main/java/org/purplei2p/lightning/dialog/LightningDialogBuilder.java +++ b/app/src/main/java/org/purplei2p/lightning/dialog/LightningDialogBuilder.java @@ -208,10 +208,6 @@ public class LightningDialogBuilder { @Override public void onItem(@Nullable List folders) { Preconditions.checkNonNull(folders); - final ArrayAdapter suggestionsAdapter = new ArrayAdapter<>(activity, - android.R.layout.simple_dropdown_item_1line, folders); - getFolder.setThreshold(1); - getFolder.setAdapter(suggestionsAdapter); editBookmarkDialog.setView(dialogLayout); editBookmarkDialog.setPositiveButton(activity.getString(R.string.action_ok), new DialogInterface.OnClickListener() { diff --git a/app/src/main/java/org/purplei2p/lightning/preference/PreferenceManager.java b/app/src/main/java/org/purplei2p/lightning/preference/PreferenceManager.java index 786f076..31d0a3d 100644 --- a/app/src/main/java/org/purplei2p/lightning/preference/PreferenceManager.java +++ b/app/src/main/java/org/purplei2p/lightning/preference/PreferenceManager.java @@ -42,8 +42,6 @@ public class PreferenceManager { static final String BLOCK_THIRD_PARTY = "thirdParty"; static final String ENABLE_COLOR_MODE = "colorMode"; static final String URL_BOX_CONTENTS = "urlContent"; - static final String INVERT_COLORS = "invertColors"; - static final String READING_TEXT_SIZE = "readingTextSize"; static final String THEME = "Theme"; static final String TEXT_ENCODING = "textEncoding"; static final String CLEAR_WEBSTORAGE_EXIT = "clearWebStorageExit"; @@ -51,7 +49,6 @@ public class PreferenceManager { static final String DO_NOT_TRACK = "doNotTrack"; static final String IDENTIFYING_HEADERS = "removeIdentifyingHeaders"; static final String SWAP_BOOKMARKS_AND_TABS = "swapBookmarksAndTabs"; - static final String SEARCH_SUGGESTIONS = "searchSuggestions"; static final String BLACK_STATUS_BAR = "blackStatusBar"; static final String USE_PROXY = "useProxy"; @@ -62,12 +59,6 @@ public class PreferenceManager { static final String LEAK_CANARY = "leakCanary"; } - public enum Suggestion { - SUGGESTION_LEGWORK, - SUGGESTION_DUCK, - SUGGESTION_NONE - } - @NonNull private final SharedPreferences mPrefs; private static final String PREFERENCES = "settings"; @@ -77,19 +68,6 @@ public class PreferenceManager { mPrefs = context.getSharedPreferences(PREFERENCES, 0); } - @NonNull - public Suggestion getSearchSuggestionChoice() { - try { - return Suggestion.valueOf(mPrefs.getString(Name.SEARCH_SUGGESTIONS, Suggestion.SUGGESTION_LEGWORK.name())); - } catch (IllegalArgumentException ignored) { - return Suggestion.SUGGESTION_NONE; - } - } - - public void setSearchSuggestionChoice(@NonNull Suggestion suggestion) { - putString(Name.SEARCH_SUGGESTIONS, suggestion.name()); - } - public boolean getBookmarksAndTabsSwapped() { return mPrefs.getBoolean(Name.SWAP_BOOKMARKS_AND_TABS, false); } @@ -152,10 +130,6 @@ public class PreferenceManager { return mPrefs.getBoolean(Name.INCOGNITO_COOKIES, false); } - public boolean getInvertColors() { - return mPrefs.getBoolean(Name.INVERT_COLORS, false); - } - public boolean getJavaScriptEnabled() { return mPrefs.getBoolean(Name.JAVASCRIPT, true); } @@ -177,10 +151,6 @@ public class PreferenceManager { return mPrefs.getInt(Name.USE_PROXY_PORT, 4444); } - public int getReadingTextSize() { - return mPrefs.getInt(Name.READING_TEXT_SIZE, 2); - } - public int getRenderingMode() { return mPrefs.getInt(Name.RENDERING_MODE, 0); } @@ -358,10 +328,6 @@ public class PreferenceManager { putBoolean(Name.INCOGNITO_COOKIES, enable); } - public void setInvertColors(boolean enable) { - putBoolean(Name.INVERT_COLORS, enable); - } - public void setJavaScriptEnabled(boolean enable) { putBoolean(Name.JAVASCRIPT, enable); } @@ -374,10 +340,6 @@ public class PreferenceManager { putBoolean(Name.POPUPS, enable); } - public void setReadingTextSize(int size) { - putInt(Name.READING_TEXT_SIZE, size); - } - public void setRenderingMode(int mode) { putInt(Name.RENDERING_MODE, mode); } diff --git a/app/src/main/java/org/purplei2p/lightning/reading/ArticleTextExtractor.java b/app/src/main/java/org/purplei2p/lightning/reading/ArticleTextExtractor.java deleted file mode 100644 index 2f58daa..0000000 --- a/app/src/main/java/org/purplei2p/lightning/reading/ArticleTextExtractor.java +++ /dev/null @@ -1,1215 +0,0 @@ -package org.purplei2p.lightning.reading; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Pattern; -import java.util.regex.Matcher; -import java.util.Date; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; -import org.jsoup.select.Selector.SelectorParseException; - -/** - * This class is thread safe. - * Class for content extraction from string form of webpage - * 'extractContent' is main call from external programs/classes - * - * @author Alex P (ifesdjeen from jreadability) - * @author Peter Karich - */ -public class ArticleTextExtractor { - - // Interessting nodes - private static final Pattern NODES = Pattern.compile("p|div|td|h1|h2|article|section"); - // Unlikely candidates - private String unlikelyStr; - private Pattern UNLIKELY; - // Most likely positive candidates - private String positiveStr; - private Pattern POSITIVE; - // Most likely negative candidates - private String negativeStr; - private Pattern NEGATIVE; - private static final Pattern NEGATIVE_STYLE = - Pattern.compile("hidden|display: ?none|font-size: ?small"); - private static final Pattern IGNORE_AUTHOR_PARTS = - Pattern.compile("by|name|author|posted|twitter|handle|news", Pattern.CASE_INSENSITIVE); - private static final Set IGNORED_TITLE_PARTS = new LinkedHashSet() { - { - add("hacker news"); - add("facebook"); - add("home"); - add("articles"); - } - }; - private static final OutputFormatter DEFAULT_FORMATTER = new OutputFormatter(); - private OutputFormatter formatter = DEFAULT_FORMATTER; - - private static final int MAX_AUTHOR_NAME_LENGHT = 255; - private static final int MIN_AUTHOR_NAME_LENGTH = 4; - private static final List CLEAN_AUTHOR_PATTERNS = Collections.singletonList( - Pattern.compile("By\\S*(.*)[\\.,].*") - ); - private static final int MAX_AUTHOR_DESC_LENGHT = 1000; - private static final int MAX_IMAGE_LENGHT = 255; - - // For debugging - private static final boolean DEBUG_WEIGHTS = false; - private static final int MAX_LOG_LENGTH = 200; - - public ArticleTextExtractor() { - setUnlikely("com(bx|ment|munity)|dis(qus|cuss)|e(xtra|[-]?mail)|foot|" - + "header|menu|re(mark|ply)|rss|sh(are|outbox)|sponsor" - + "a(d|ll|gegate|rchive|ttachment)|(pag(er|ination))|popup|print|" - + "login|si(debar|gn|ngle)"); - setPositive("(^(body|content|h?entry|main|page|post|text|blog|story|haupt))" - + "|arti(cle|kel)|instapaper_body"); - setNegative("nav($|igation)|user|com(ment|bx)|(^com-)|contact|" - + "foot|masthead|(me(dia|ta))|outbrain|promo|related|scroll|(sho(utbox|pping))|" - + "sidebar|sponsor|tags|tool|widget|player|disclaimer|toc|infobox|vcard"); - } - - private ArticleTextExtractor setUnlikely(String unlikelyStr) { - this.unlikelyStr = unlikelyStr; - UNLIKELY = Pattern.compile(unlikelyStr); - return this; - } - - public ArticleTextExtractor addUnlikely(String unlikelyMatches) { - return setUnlikely(unlikelyStr + '|' + unlikelyMatches); - } - - private ArticleTextExtractor setPositive(String positiveStr) { - this.positiveStr = positiveStr; - POSITIVE = Pattern.compile(positiveStr); - return this; - } - - public ArticleTextExtractor addPositive(String pos) { - return setPositive(positiveStr + '|' + pos); - } - - private ArticleTextExtractor setNegative(String negativeStr) { - this.negativeStr = negativeStr; - NEGATIVE = Pattern.compile(negativeStr); - return this; - } - - public ArticleTextExtractor addNegative(String neg) { - setNegative(negativeStr + '|' + neg); - return this; - } - - public void setOutputFormatter(OutputFormatter formatter) { - this.formatter = formatter; - } - - /** - * @param html extracts article text from given html string. wasn't tested - * with improper HTML, although jSoup should be able to handle minor stuff. - * @returns extracted article, all HTML tags stripped - */ - public JResult extractContent(String html, int maxContentSize) throws Exception { - return extractContent(new JResult(), html, maxContentSize); - } - - public JResult extractContent(String html) throws Exception { - return extractContent(new JResult(), html, 0); - } - - public JResult extractContent(JResult res, String html, int maxContentSize) throws Exception { - return extractContent(res, html, formatter, true, maxContentSize); - } - - public JResult extractContent(JResult res, String html) throws Exception { - return extractContent(res, html, formatter, true, 0); - } - - private JResult extractContent(JResult res, String html, OutputFormatter formatter, - Boolean extractimages, int maxContentSize) throws Exception { - if (html.isEmpty()) - throw new IllegalArgumentException("html string is empty!?"); - - // http://jsoup.org/cookbook/extracting-data/selector-syntax - return extractContent(res, Jsoup.parse(html), formatter, extractimages, maxContentSize); - } - - // Returns the best node match based on the weights (see getWeight for strategy) - private Element getBestMatchElement(Collection nodes) { - int maxWeight = -200; // why -200 now instead of 0? - Element bestMatchElement = null; - - boolean ignoreMaxWeightLimit = false; - for (Element entry : nodes) { - - int currentWeight = getWeight(entry, false); - if (currentWeight > maxWeight) { - maxWeight = currentWeight; - bestMatchElement = entry; - - /* - // NOTE: This optimization fails with large pages that - contains chunks of text that can be mistaken by articles, since we - want the best accuracy possible, I am disabling it for now. AP. - - // The original code had a limit of 200, the intention was that - // if a node had a weight greater than it, then it most likely - // it was the main content. - // However this assumption fails when the amount of text in the - // children (or grandchildren) is too large. If we detect this - // case then the limit is ignored and we try all the nodes to select - // the one with the absolute maximum weight. - if (maxWeight > 500){ - ignoreMaxWeightLimit = true; - continue; - } - - // formerly 200, increased to 250 to account for the fact - // we are not adding the weights of the grand children to the - // tally. - - if (maxWeight > 250 && !ignoreMaxWeightLimit) - break; - */ - } - } - - return bestMatchElement; - } - - private JResult extractContent(JResult res, Document doc, OutputFormatter formatter, - Boolean extractimages, int maxContentSize) throws Exception { - Document origDoc = doc.clone(); - JResult result = extractContent(res, doc, formatter, extractimages, maxContentSize, true); - //System.out.println("result.getText().length()="+result.getText().length()); - if (result.getText().isEmpty()) { - result = extractContent(res, origDoc, formatter, extractimages, maxContentSize, false); - } - return result; - } - - - // main workhorse - private JResult extractContent(JResult res, Document doc, OutputFormatter formatter, - Boolean extractimages, int maxContentSize, boolean cleanScripts) { - if (doc == null) - throw new NullPointerException("missing document"); - - // get the easy stuff - res.setTitle(extractTitle(doc)); - res.setDescription(extractDescription(doc)); - res.setCanonicalUrl(extractCanonicalUrl(doc)); - res.setType(extractType(doc)); - res.setSitename(extractSitename(doc)); - res.setLanguage(extractLanguage(doc)); - - // get author information - res.setAuthorName(extractAuthorName(doc)); - res.setAuthorDescription(extractAuthorDescription(doc, res.getAuthorName())); - - // add extra selection gravity to any element containing author name - // wasn't useful in the case I implemented it for, but might be later - /* - Elements authelems = doc.select(":containsOwn(" + res.getAuthorName() + ")"); - for (Element elem : authelems) { - elem.attr("extragravityscore", Integer.toString(100)); - System.out.println("modified element " + elem.toString()); - } - */ - - // get date from document, if not present, extract from URL if possible - Date docdate = extractDate(doc); - if (docdate == null) { - String dateStr = SHelper.estimateDate(res.getUrl()); - docdate = parseDate(dateStr); - res.setDate(docdate); - } else { - res.setDate(docdate); - } - - // now remove the clutter - if (cleanScripts) { - prepareDocument(doc); - } - - // init elements and get the one with highest weight (see getWeight for strategy) - Collection nodes = getNodes(doc); - Element bestMatchElement = getBestMatchElement(nodes); - - // do extraction from the best element - if (bestMatchElement != null) { - if (extractimages) { - List images = new ArrayList<>(); - Element imgEl = determineImageSource(bestMatchElement, images); - if (imgEl != null) { - res.setImageUrl(SHelper.replaceSpaces(imgEl.attr("src"))); - // TODO remove parent container of image if it is contained in bestMatchElement - // to avoid image subtitles flooding in - - res.setImages(images); - } - } - - // clean before grabbing text - String text = formatter.getFormattedText(bestMatchElement); - text = removeTitleFromText(text, res.getTitle()); - // this fails for short facebook post and probably tweets: text.length() > res.getDescription().length() - if (text.length() > res.getTitle().length()) { - if (maxContentSize > 0) { - if (text.length() > maxContentSize) { - text = utf8truncate(text, maxContentSize); - } - } - res.setText(text); - } - - // extract links from the same best element - String fullhtml = bestMatchElement.toString(); - Elements children = bestMatchElement.select("a[href]"); // a with href = link - String linkstr; - Integer linkpos; - Integer lastlinkpos = 0; - for (Element child : children) { - linkstr = child.toString(); - linkpos = fullhtml.indexOf(linkstr, lastlinkpos); - res.addLink(child.attr("abs:href"), child.text(), linkpos); - lastlinkpos = linkpos; - } - } - - if (extractimages) { - if (res.getImageUrl().isEmpty()) { - res.setImageUrl(extractImageUrl(doc)); - } - } - - res.setRssUrl(extractRssUrl(doc)); - res.setVideoUrl(extractVideoUrl(doc)); - res.setFaviconUrl(extractFaviconUrl(doc)); - res.setKeywords(extractKeywords(doc)); - - // Sanity checks in author - if (res.getAuthorName().length() > MAX_AUTHOR_NAME_LENGHT) { - res.setAuthorName(utf8truncate(res.getAuthorName(), MAX_AUTHOR_NAME_LENGHT)); - } - - // Sanity checks in author description. - String authorDescSnippet = getSnippet(res.getAuthorDescription()); - if (getSnippet(res.getText()).equals(authorDescSnippet) || - getSnippet(res.getDescription()).equals(authorDescSnippet)) { - res.setAuthorDescription(""); - } else { - if (res.getAuthorDescription().length() > MAX_AUTHOR_DESC_LENGHT) { - res.setAuthorDescription(utf8truncate(res.getAuthorDescription(), MAX_AUTHOR_DESC_LENGHT)); - } - } - - // Sanity checks in image name - if (res.getImageUrl().length() > MAX_IMAGE_LENGHT) { - // doesn't make sense to truncate a URL - res.setImageUrl(""); - } - - return res; - } - - private static String getSnippet(String data) { - if (data.length() < 50) - return data; - else - return data.substring(0, 50); - } - - private static String extractTitle(Document doc) { - String title = cleanTitle(doc.title()); - if (title.isEmpty()) { - title = SHelper.innerTrim(doc.select("head title").text()); - if (title.isEmpty()) { - title = SHelper.innerTrim(doc.select("head meta[name=title]").attr("content")); - if (title.isEmpty()) { - title = SHelper.innerTrim(doc.select("head meta[property=og:title]").attr("content")); - if (title.isEmpty()) { - title = SHelper.innerTrim(doc.select("head meta[name=twitter:title]").attr("content")); - if (title.isEmpty()) { - title = SHelper.innerTrim(doc.select("h1:first-of-type").text()); - } - } - } - } - } - return title; - } - - private static String extractCanonicalUrl(Document doc) { - String url = SHelper.replaceSpaces(doc.select("head link[rel=canonical]").attr("href")); - if (url.isEmpty()) { - url = SHelper.replaceSpaces(doc.select("head meta[property=og:url]").attr("content")); - if (url.isEmpty()) { - url = SHelper.replaceSpaces(doc.select("head meta[name=twitter:url]").attr("content")); - } - } - return url; - } - - private static String extractDescription(Document doc) { - String description = SHelper.innerTrim(doc.select("head meta[name=description]").attr("content")); - if (description.isEmpty()) { - description = SHelper.innerTrim(doc.select("head meta[property=og:description]").attr("content")); - if (description.isEmpty()) { - description = SHelper.innerTrim(doc.select("head meta[name=twitter:description]").attr("content")); - } - } - return description; - } - - // Returns the publication Date or null - private static Date extractDate(Document doc) { - String dateStr = ""; - - // try some locations that nytimes uses - Element elem = doc.select("meta[name=ptime]").first(); - if (elem != null) { - dateStr = SHelper.innerTrim(elem.attr("content")); - // elem.attr("extragravityscore", Integer.toString(100)); - // System.out.println("date modified element " + elem.toString()); - } - - if (dateStr.isEmpty()) { - dateStr = SHelper.innerTrim(doc.select("meta[name=utime]").attr("content")); - } - if (dateStr.isEmpty()) { - dateStr = SHelper.innerTrim(doc.select("meta[name=pdate]").attr("content")); - } - if (dateStr.isEmpty()) { - dateStr = SHelper.innerTrim(doc.select("meta[property=article:published]").attr("content")); - } - if (dateStr.isEmpty()) { - return parseDate(dateStr); - } - - // taking this stuff directly from Juicer (and converted to Java) - // opengraph (?) - Elements elems = doc.select("meta[property=article:published_time]"); - if (!elems.isEmpty()) { - Element el = elems.get(0); - if (el.hasAttr("content")) { - dateStr = el.attr("content"); - try { - if (dateStr.endsWith("Z")) { - dateStr = dateStr.substring(0, dateStr.length() - 1) + "GMT-00:00"; - } else { - dateStr = String.format(dateStr.substring(0, dateStr.length() - 6), - dateStr.substring(dateStr.length() - 6, - dateStr.length())); - } - } catch (StringIndexOutOfBoundsException ex) { - // do nothing - } - return parseDate(dateStr); - } - } - - // rnews - elems = doc.select("meta[property=dateCreated], span[property=dateCreated]"); - if (!elems.isEmpty()) { - Element el = elems.get(0); - if (el.hasAttr("content")) { - dateStr = el.attr("content"); - - return parseDate(dateStr); - } else { - return parseDate(el.text()); - } - } - - // schema.org creativework - elems = doc.select("meta[itemprop=datePublished], span[itemprop=datePublished]"); - if (!elems.isEmpty()) { - Element el = elems.get(0); - if (el.hasAttr("content")) { - dateStr = el.attr("content"); - - return parseDate(dateStr); - } else if (el.hasAttr("value")) { - dateStr = el.attr("value"); - - return parseDate(dateStr); - } else { - return parseDate(el.text()); - } - } - - // parsely page (?) - /* skip conversion for now, seems highly specific and uses new lib - elems = doc.select("meta[name=parsely-page]"); - if (elems.size() > 0) { - implicit val formats = net.liftweb.json.DefaultFormats - - Element el = elems.get(0); - if(el.hasAttr("content")) { - val json = parse(el.attr("content")) - - return DateUtils.parseDateStrictly((json \ "pub_date").extract[String], Array("yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd'T'HH:mm:ss'Z'", "yyyy-MM-dd'T'HH:mm:ssZZ", "yyyy-MM-dd'T'HH:mm:ssz")) - } - } - */ - - // BBC - elems = doc.select("meta[name=OriginalPublicationDate]"); - if (!elems.isEmpty()) { - Element el = elems.get(0); - if (el.hasAttr("content")) { - dateStr = el.attr("content"); - return parseDate(dateStr); - } - } - - // wired - elems = doc.select("meta[name=DisplayDate]"); - if (!elems.isEmpty()) { - Element el = elems.get(0); - if (el.hasAttr("content")) { - dateStr = el.attr("content"); - return parseDate(dateStr); - } - } - - // wildcard - elems = doc.select("meta[name*=date]"); - if (!elems.isEmpty()) { - Element el = elems.get(0); - if (el.hasAttr("content")) { - dateStr = el.attr("content"); - return parseDate(dateStr); - } - } - - // blogger - elems = doc.select(".date-header"); - if (!elems.isEmpty()) { - Element el = elems.get(0); - dateStr = el.text(); - return parseDate(dateStr); - } - - return null; - } - - private static Date parseDate(String dateStr) { -// String[] parsePatterns = { -// "yyyy-MM-dd'T'HH:mm:ssz", -// "yyyy-MM-dd HH:mm:ss", -// "yyyy/MM/dd HH:mm:ss", -// "yyyy-MM-dd HH:mm", -// "yyyy/MM/dd HH:mm", -// "yyyy-MM-dd", -// "yyyy/MM/dd", -// "MM/dd/yyyy HH:mm:ss", -// "MM-dd-yyyy HH:mm:ss", -// "MM/dd/yyyy HH:mm", -// "MM-dd-yyyy HH:mm", -// "MM/dd/yyyy", -// "MM-dd-yyyy", -// "EEE, MMM dd, yyyy", -// "MM/dd/yyyy hh:mm:ss a", -// "MM-dd-yyyy hh:mm:ss a", -// "MM/dd/yyyy hh:mm a", -// "MM-dd-yyyy hh:mm a", -// "yyyy-MM-dd hh:mm:ss a", -// "yyyy/MM/dd hh:mm:ss a ", -// "yyyy-MM-dd hh:mm a", -// "yyyy/MM/dd hh:mm ", -// "dd MMM yyyy", -// "dd MMMM yyyy", -// "yyyyMMddHHmm", -// "yyyyMMdd HHmm", -// "dd-MM-yyyy HH:mm:ss", -// "dd/MM/yyyy HH:mm:ss", -// "dd MMM yyyy HH:mm:ss", -// "dd MMMM yyyy HH:mm:ss", -// "dd-MM-yyyy HH:mm", -// "dd/MM/yyyy HH:mm", -// "dd MMM yyyy HH:mm", -// "dd MMMM yyyy HH:mm", -// "yyyyMMddHHmmss", -// "yyyyMMdd HHmmss", -// "yyyyMMdd" -// }; -// - return new Date(0); - -// try { -// return DateUtils.parseDateStrictly(dateStr, parsePatterns); -// } catch (Exception ex) { -// return null; -// } - } - - // Returns the author name or null - private String extractAuthorName(Document doc) { - String authorName = ""; - - // first try the Google Author tag - Element result = doc.select("body [rel*=author]").first(); - if (result != null) - authorName = SHelper.innerTrim(result.ownText()); - - // if that doesn't work, try some other methods - if (authorName.isEmpty()) { - - // meta tag approaches, get content - result = doc.select("head meta[name=author]").first(); - if (result != null) { - authorName = SHelper.innerTrim(result.attr("content")); - } - - if (authorName.isEmpty()) { // for "opengraph" - authorName = SHelper.innerTrim(doc.select("head meta[property=article:author]").attr("content")); - } - if (authorName.isEmpty()) { // OpenGraph twitter:creator tag - authorName = SHelper.innerTrim(doc.select("head meta[property=twitter:creator]").attr("content")); - } - if (authorName.isEmpty()) { // for "schema.org creativework" - authorName = SHelper.innerTrim(doc.select("meta[itemprop=author], span[itemprop=author]").attr("content")); - } - - // other hacks - if (authorName.isEmpty()) { - try { - // build up a set of elements which have likely author-related terms - // .X searches for class X - Elements matches = doc.select("a[rel=author],.byline-name,.byLineTag,.byline,.author,.by,.writer,.address"); - - if (matches == null || matches.isEmpty()) { - matches = doc.select("body [class*=author]"); - } - - if (matches == null || matches.isEmpty()) { - matches = doc.select("body [title*=author]"); - } - - // a hack for huffington post - if (matches == null || matches.isEmpty()) { - matches = doc.select(".staff_info dl a[href]"); - } - - // a hack for http://sports.espn.go.com/ - if (matches == null || matches.isEmpty()) { - matches = doc.select("cite[class*=source]"); - } - - // select the best element from them - if (matches != null) { - Element bestMatch = getBestMatchElement(matches); - - if (!(bestMatch == null)) { - authorName = bestMatch.text(); - - if (authorName.length() < MIN_AUTHOR_NAME_LENGTH) { - authorName = bestMatch.text(); - } - - authorName = SHelper.innerTrim(IGNORE_AUTHOR_PARTS.matcher(authorName).replaceAll("")); - - if (authorName.contains(",")) { - authorName = authorName.split(",")[0]; - } - } - } - } catch (Exception e) { - System.out.println(e.toString()); - } - } - } - - for (Pattern pattern : CLEAN_AUTHOR_PATTERNS) { - Matcher matcher = pattern.matcher(authorName); - if (matcher.matches()) { - authorName = SHelper.innerTrim(matcher.group(1)); - break; - } - } - - return authorName; - } - - // Returns the author description or null - private String extractAuthorDescription(Document doc, String authorName) { - - String authorDesc = ""; - - if (authorName.isEmpty()) - return ""; - - // Special case for entrepreneur.com - Elements matches = doc.select(".byline > .bio"); - if (matches != null && !matches.isEmpty()) { - Element bestMatch = matches.first(); // assume it is the first. - authorDesc = bestMatch.text(); - return authorDesc; - } - - // Special case for huffingtonpost.com - matches = doc.select(".byline span[class*=teaser]"); - if (matches != null && !matches.isEmpty()) { - Element bestMatch = matches.first(); // assume it is the first. - authorDesc = bestMatch.text(); - return authorDesc; - } - - try { - Elements nodes = doc.select(":containsOwn(" + authorName + ')'); - Element bestMatch = getBestMatchElement(nodes); - if (bestMatch != null) - authorDesc = bestMatch.text(); - } catch (SelectorParseException se) { - // Avoid error when selector is invalid - } - - return authorDesc; - } - - private static Collection extractKeywords(Document doc) { - String content = SHelper.innerTrim(doc.select("head meta[name=keywords]").attr("content")); - - if (content.startsWith("[") && content.endsWith("]")) - content = content.substring(1, content.length() - 1); - - String[] split = content.split("\\s*,\\s*"); - if (split.length > 1 || (split.length > 0 && split[0] != null && !split[0].isEmpty())) - return Arrays.asList(split); - return Collections.emptyList(); - } - - /** - * Tries to extract an image url from metadata if determineImageSource - * failed - * - * @return image url or empty str - */ - private static String extractImageUrl(Document doc) { - // use open graph tag to get image - String imageUrl = SHelper.replaceSpaces(doc.select("head meta[property=og:image]").attr("content")); - if (imageUrl.isEmpty()) { - imageUrl = SHelper.replaceSpaces(doc.select("head meta[name=twitter:image]").attr("content")); - if (imageUrl.isEmpty()) { - // prefer link over thumbnail-meta if empty - imageUrl = SHelper.replaceSpaces(doc.select("link[rel=image_src]").attr("href")); - if (imageUrl.isEmpty()) { - imageUrl = SHelper.replaceSpaces(doc.select("head meta[name=thumbnail]").attr("content")); - } - } - } - return imageUrl; - } - - private static String extractRssUrl(Document doc) { - return SHelper.replaceSpaces(doc.select("link[rel=alternate]").select("link[type=application/rss+xml]").attr("href")); - } - - private static String extractVideoUrl(Document doc) { - return SHelper.replaceSpaces(doc.select("head meta[property=og:video]").attr("content")); - } - - private static String extractFaviconUrl(Document doc) { - String faviconUrl = SHelper.replaceSpaces(doc.select("head link[rel=icon]").attr("href")); - if (faviconUrl.isEmpty()) { - faviconUrl = SHelper.replaceSpaces(doc.select("head link[rel^=shortcut],link[rel$=icon]").attr("href")); - } - return faviconUrl; - } - - private static String extractType(Document doc) { - return SHelper.innerTrim(doc.select("head meta[property=og:type]").attr("content")); - } - - private static String extractSitename(Document doc) { - String sitename = SHelper.innerTrim(doc.select("head meta[property=og:site_name]").attr("content")); - if (sitename.isEmpty()) { - sitename = SHelper.innerTrim(doc.select("head meta[name=twitter:site]").attr("content")); - } - if (sitename.isEmpty()) { - sitename = SHelper.innerTrim(doc.select("head meta[property=og:site_name]").attr("content")); - } - return sitename; - } - - private static String extractLanguage(Document doc) { - String language = SHelper.innerTrim(doc.select("head meta[property=language]").attr("content")); - if (language.isEmpty()) { - language = SHelper.innerTrim(doc.select("html").attr("lang")); - if (language.isEmpty()) { - language = SHelper.innerTrim(doc.select("head meta[property=og:locale]").attr("content")); - } - } - if (!language.isEmpty()) { - if (language.length() > 2) { - language = language.substring(0, 2); - } - } - return language; - } - - /** - * Weights current element. By matching it with positive candidates and - * weighting child nodes. Since it's impossible to predict which exactly - * names, ids or class names will be used in HTML, major role is played by - * child nodes - * - * @param e Element to weight, along with child nodes - */ - private int getWeight(Element e, boolean checkextra) { - int weight = calcWeight(e); - int ownTextWeight = (int) Math.round(e.ownText().length() / 100.0 * 10); - weight += ownTextWeight; - int childrenWeight = weightChildNodes(e); - weight += childrenWeight; - - // add additional weight using possible 'extragravityscore' attribute - if (checkextra) { - Element xelem = e.select("[extragravityscore]").first(); - if (xelem != null) { - // System.out.println("HERE found one: " + xelem.toString()); - weight += Integer.parseInt(xelem.attr("extragravityscore")); - // System.out.println("WITH WEIGHT: " + xelem.attr("extragravityscore")); - } - } - - return weight; - } - - /** - * Weights a child nodes of given Element. During tests some difficulties - * were met. For instance, not every single document has nested paragraph - * tags inside of the major article tag. Sometimes people are adding one - * more nesting level. So, we're adding 4 points for every 100 symbols - * contained in tag nested inside of the current weighted element, but only - * 3 points for every element that's nested 2 levels deep. This way we give - * more chances to extract the element that has less nested levels, - * increasing probability of the correct extraction. - * - * @param rootEl Element, who's child nodes will be weighted - */ - private int weightChildNodes(Element rootEl) { - int weight = 0; - Element caption = null; - List pEls = new ArrayList<>(5); - - for (Element child : rootEl.children()) { - String ownText = child.ownText(); - int ownTextLength = ownText.length(); - if (ownTextLength < 20) - continue; - - if (ownTextLength > 200) { - int childOwnTextWeight = Math.max(50, ownTextLength / 10); - weight += childOwnTextWeight; - } - - if (child.tagName().equals("h1") || child.tagName().equals("h2")) { - int h2h1Weight = 30; - weight += h2h1Weight; - } else if (child.tagName().equals("div") || child.tagName().equals("p")) { - int calcChildWeight = calcWeightForChild(child, ownText); - weight += calcChildWeight; - if (child.tagName().equals("p") && ownTextLength > 50) - pEls.add(child); - - if (child.className().toLowerCase().equals("caption")) - caption = child; - } - } - - // - // Visit grandchildren, This section visits the grandchildren - // of the node and calculate their weights. Note that grandchildren - // weights are only worth 1/3 of children's - // - int grandChildrenWeight = 0; - for (Element child2 : rootEl.children()) { - - // If the node looks negative don't include it in the weights - // instead penalize the grandparent. This is done to try to - // avoid giving weigths to navigation nodes, etc. - if (NEGATIVE.matcher(child2.id()).find() || - NEGATIVE.matcher(child2.className()).find()) { - grandChildrenWeight -= 30; - continue; - } - - for (Element grandchild : child2.children()) { - int grandchildWeight = 0; - String ownText = grandchild.ownText(); - int ownTextLength = ownText.length(); - if (ownTextLength < 20) - continue; - - if (ownTextLength > 200) { - int childOwnTextWeight = Math.max(50, ownTextLength / 10); - grandchildWeight += childOwnTextWeight; - } - - if (grandchild.tagName().equals("h1") || grandchild.tagName().equals("h2")) { - int h2h1Weight = 30; - grandchildWeight += h2h1Weight; - } else if (grandchild.tagName().equals("div") || grandchild.tagName().equals("p")) { - int calcChildWeight = calcWeightForChild(grandchild, ownText); - grandchildWeight += calcChildWeight; - } - - grandChildrenWeight += grandchildWeight; - } - } - - grandChildrenWeight = grandChildrenWeight / 3; - weight += grandChildrenWeight; - - // use caption and image - if (caption != null) { - int captionWeight = 30; - weight += captionWeight; - } - - if (pEls.size() >= 2) { - for (Element subEl : rootEl.children()) { - if ("h1;h2;h3;h4;h5;h6".contains(subEl.tagName())) { - int h1h2h3Weight = 20; - weight += h1h2h3Weight; - // headerEls.add(subEl); - } else if ("table;li;td;th".contains(subEl.tagName())) { - addScore(subEl, -30); - } - - if ("p".contains(subEl.tagName())) - addScore(subEl, 30); - } - } - return weight; - } - - private static void addScore(Element el, int score) { - int old = getScore(el); - setScore(el, score + old); - } - - private static int getScore(Element el) { - int old = 0; - try { - old = Integer.parseInt(el.attr("gravityScore")); - } catch (Exception ignored) { - } - return old; - } - - private static void setScore(Element el, int score) { - el.attr("gravityScore", Integer.toString(score)); - } - - private static int calcWeightForChild(Element child, String ownText) { - int c = SHelper.count(ownText, """); - c += SHelper.count(ownText, "<"); - c += SHelper.count(ownText, ">"); - c += SHelper.count(ownText, "px"); - int val; - if (c > 5) - val = -30; - else - val = (int) Math.round(ownText.length() / 35.0); - - addScore(child, val); - return val; - } - - private int calcWeight(Element e) { - int weight = 0; - if (POSITIVE.matcher(e.className()).find()) - weight += 35; - - if (POSITIVE.matcher(e.id()).find()) - weight += 45; - - if (UNLIKELY.matcher(e.className()).find()) - weight -= 20; - - if (UNLIKELY.matcher(e.id()).find()) - weight -= 20; - - if (NEGATIVE.matcher(e.className()).find()) - weight -= 50; - - if (NEGATIVE.matcher(e.id()).find()) - weight -= 50; - - String style = e.attr("style"); - if (style != null && !style.isEmpty() && NEGATIVE_STYLE.matcher(style).find()) - weight -= 50; - - String itemprop = e.attr("itemprop"); - if (itemprop != null && !itemprop.isEmpty() && POSITIVE.matcher(itemprop).find()) { - weight += 100; - } - - return weight; - } - - private static Element determineImageSource(Element el, List images) { - int maxWeight = 0; - Element maxNode = null; - Elements els = el.select("img"); - if (els.isEmpty()) - els = el.parent().select("img"); - - double score = 1; - for (Element e : els) { - String sourceUrl = e.attr("src"); - if (sourceUrl.isEmpty() || isAdImage(sourceUrl)) - continue; - - int weight = 0; - int height = 0; - try { - height = Integer.parseInt(e.attr("height")); - if (height >= 50) - weight += 20; - else - weight -= 20; - } catch (Exception ignored) { - } - - int width = 0; - try { - width = Integer.parseInt(e.attr("width")); - if (width >= 50) - weight += 20; - else - weight -= 20; - } catch (Exception ignored) { - } - String alt = e.attr("alt"); - if (alt.length() > 35) - weight += 20; - - String title = e.attr("title"); - if (title.length() > 35) - weight += 20; - - String rel; - boolean noFollow = false; - if (e.parent() != null) { - rel = e.parent().attr("rel"); - if (rel != null && rel.contains("nofollow")) { - noFollow = rel.contains("nofollow"); - weight -= 40; - } - } - - weight = (int) (weight * score); - if (weight > maxWeight) { - maxWeight = weight; - maxNode = e; - score = score / 2; - } - - ImageResult image = new ImageResult(sourceUrl, weight, title, height, width, alt, noFollow); - images.add(image); - } - - Collections.sort(images, new ImageComparator()); - return maxNode; - } - - /** - * Prepares document. Currently only stipping unlikely candidates, since - * from time to time they're getting more score than good ones especially in - * cases when major text is short. - * - * @param doc document to prepare. Passed as reference, and changed inside - * of function - */ - private static void prepareDocument(Document doc) { -// stripUnlikelyCandidates(doc); - removeScriptsAndStyles(doc); - } - - /** - * Removes unlikely candidates from HTML. Currently takes id and class name - * and matches them against list of patterns - * - * @param doc document to strip unlikely candidates from - */ - protected void stripUnlikelyCandidates(Document doc) { - for (Element child : doc.select("body").select("*")) { - String className = child.className().toLowerCase(); - String id = child.id().toLowerCase(); - - if (NEGATIVE.matcher(className).find() - || NEGATIVE.matcher(id).find()) { - child.remove(); - } - } - } - - private static Document removeScriptsAndStyles(Document doc) { - Elements scripts = doc.getElementsByTag("script"); - for (Element item : scripts) { - item.remove(); - } - Elements noscripts = doc.getElementsByTag("noscript"); - for (Element item : noscripts) { - item.remove(); - } - - Elements styles = doc.getElementsByTag("style"); - for (Element style : styles) { - style.remove(); - } - - return doc; - } - - private static boolean isAdImage(String imageUrl) { - return SHelper.count(imageUrl, "ad") >= 2; - } - - /** - * Match only exact matching as longestSubstring can be too fuzzy - */ - private static String removeTitleFromText(String text, String title) { - // don't do this as its terrible to read -// int index1 = text.toLowerCase().indexOf(title.toLowerCase()); -// if (index1 >= 0) -// text = text.substring(index1 + title.length()); -// return text.trim(); - return text; - } - - /** - * based on a delimeter in the title take the longest piece or do some - * custom logic based on the site - * - * @param title - * @param delimeter - * @return - */ - private static String doTitleSplits(String title, String delimeter) { - String largeText = ""; - int largetTextLen = 0; - String[] titlePieces = title.split(delimeter); - - // take the largest split - for (String p : titlePieces) { - if (p.length() > largetTextLen) { - largeText = p; - largetTextLen = p.length(); - } - } - - largeText = largeText.replace("»", " "); - largeText = largeText.replace("»", " "); - return largeText.trim(); - } - - /** - * @return a set of all important nodes - */ - private static Collection getNodes(Document doc) { - Map nodes = new LinkedHashMap<>(64); - int score = 100; - for (Element el : doc.select("body").select("*")) { - if (NODES.matcher(el.tagName()).matches()) { - nodes.put(el, null); - setScore(el, score); - score = score / 2; - } - } - return nodes.keySet(); - } - - private static String cleanTitle(String title) { - -// int index = title.lastIndexOf("|"); -// if (index > 0 && title.length() / 2 < index) -// title = title.substring(0, index + 1); - - int counter = 0; - String[] strs = title.split("\\|"); - StringBuilder res = new StringBuilder(strs.length); - for (String part : strs) { - if (IGNORED_TITLE_PARTS.contains(part.toLowerCase().trim())) - continue; - - if (counter == strs.length - 1 && res.length() > part.length()) - continue; - - if (counter > 0) - res.append('|'); - - res.append(part); - counter++; - } - - return SHelper.innerTrim(res.toString()); - } - - /** - * Truncate a Java string so that its UTF-8 representation will not - * exceed the specified number of bytes. - *

- * For discussion of why you might want to do this, see - * http://lpar.ath0.com/2011/06/07/unicode-alchemy-with-db2/ - */ - private static String utf8truncate(String input, int length) { - StringBuilder result = new StringBuilder(length); - int resultlen = 0; - for (int i = 0; i < input.length(); i++) { - char c = input.charAt(i); - int charlen = 0; - if (c <= 0x7f) { - charlen = 1; - } else if (c <= 0x7ff) { - charlen = 2; - } else if (c <= 0xd7ff) { - charlen = 3; - } else if (c <= 0xdbff) { - charlen = 4; - } else if (c <= 0xdfff) { - charlen = 0; - } else { - charlen = 3; - } - if (resultlen + charlen > length) { - break; - } - result.append(c); - resultlen += charlen; - } - return result.toString(); - } - - - /** - * Comparator for Image by weight - * - * @author Chris Alexander, chris@chris-alexander.co.uk - */ - private static class ImageComparator implements Comparator { - - @Override - public int compare(ImageResult o1, ImageResult o2) { - // Returns the highest weight first - return o2.weight.compareTo(o1.weight); - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/org/purplei2p/lightning/reading/Converter.java b/app/src/main/java/org/purplei2p/lightning/reading/Converter.java deleted file mode 100644 index 32f32bf..0000000 --- a/app/src/main/java/org/purplei2p/lightning/reading/Converter.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2011 Peter Karich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.purplei2p.lightning.reading; - -import android.util.Log; - -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.util.Locale; - -/** - * This class is not thread safe. Use one new instance every time due to - * encoding variable. - * - * @author Peter Karich - */ -public class Converter { - - private static final String TAG = "Converter"; - - private final static String UTF8 = "UTF-8"; - private final static String ISO = "ISO-8859-1"; - private final static int K2 = 2048; - private int maxBytes = 1000000 / 2; - private String encoding; - private String url; - - public Converter(String urlOnlyHint) { - url = urlOnlyHint; - } - - public Converter() { - } - - public Converter setMaxBytes(int maxBytes) { - this.maxBytes = maxBytes; - return this; - } - - public static String extractEncoding(String contentType) { - String[] values; - if (contentType != null) - values = contentType.split(";"); - else - values = new String[0]; - - String charset = ""; - - for (String value : values) { - value = value.trim().toLowerCase(Locale.getDefault()); - - if (value.startsWith("charset=")) - charset = value.substring("charset=".length()); - } - - // http1.1 says ISO-8859-1 is the default charset - if (charset.isEmpty()) - charset = ISO; - - return charset; - } - - public String getEncoding() { - if (encoding == null) - return ""; - return encoding.toLowerCase(Locale.getDefault()); - } - - public String streamToString(InputStream is) { - return streamToString(is, maxBytes, encoding); - } - - public String streamToString(InputStream is, String enc) { - return streamToString(is, maxBytes, enc); - } - - /** - * reads bytes off the string and returns a string - * - * @param is input stream to read - * @param maxBytes - * The max bytes that we want to read from the input stream - * @return String - */ - private String streamToString(InputStream is, int maxBytes, String enc) { - encoding = enc; - // Http 1.1. standard is iso-8859-1 not utf8 :( - // but we force utf-8 as youtube assumes it ;) - if (encoding == null || encoding.isEmpty()) - encoding = UTF8; - - BufferedInputStream in = null; - try { - in = new BufferedInputStream(is, K2); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - - // detect encoding with the help of meta tag - try { - in.mark(K2 * 2); - String tmpEnc = detectCharset("charset=", output, in, encoding); - if (tmpEnc != null) - encoding = tmpEnc; - else { - Log.d(TAG, "no charset found in first stage"); - // detect with the help of xml beginning ala - // encoding="charset" - tmpEnc = detectCharset("encoding=", output, in, encoding); - if (tmpEnc != null) - encoding = tmpEnc; - else - Log.d(TAG, "no charset found in second stage"); - } - - if (!Charset.isSupported(encoding)) - throw new UnsupportedEncodingException(encoding); - } catch (UnsupportedEncodingException e) { - Log.d(TAG, - "Using default encoding:" + UTF8 + " problem:" + e.getMessage() - + " encoding:" + encoding + ' ' + url); - encoding = UTF8; - } - - // SocketException: Connection reset - // IOException: missing CR => problem on server (probably some xml - // character thing?) - // IOException: Premature EOF => socket unexpectly closed from - // server - int bytesRead = output.size(); - byte[] arr = new byte[K2]; - while (true) { - if (bytesRead >= maxBytes) { - Log.d(TAG, "Maxbyte of " + maxBytes - + " exceeded! Maybe html is now broken but try it nevertheless. Url: " - + url); - break; - } - - int n = in.read(arr); - if (n < 0) - break; - bytesRead += n; - output.write(arr, 0, n); - } - - return output.toString(encoding); - } catch (IOException e) { - Log.e(TAG, e.toString() + " url:" + url); - } finally { - if (in != null) { - try { - in.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - return ""; - } - - /** - * This method detects the charset even if the first call only returns some - * bytes. It will read until 4K bytes are reached and then try to determine - * the encoding - * - * @throws IOException - */ - private static String detectCharset(String key, ByteArrayOutputStream bos, BufferedInputStream in, - String enc) throws IOException { - - // Grab better encoding from stream - byte[] arr = new byte[K2]; - int nSum = 0; - while (nSum < K2) { - int n = in.read(arr); - if (n < 0) - break; - - nSum += n; - bos.write(arr, 0, n); - } - - String str = bos.toString(enc); - int encIndex = str.indexOf(key); - int clength = key.length(); - if (encIndex > 0) { - char startChar = str.charAt(encIndex + clength); - int lastEncIndex; - if (startChar == '\'') - // if we have charset='something' - lastEncIndex = str.indexOf('\'', ++encIndex + clength); - else if (startChar == '\"') - // if we have charset="something" - lastEncIndex = str.indexOf('\"', ++encIndex + clength); - else { - // if we have "text/html; charset=utf-8" - int first = str.indexOf('\"', encIndex + clength); - if (first < 0) - first = Integer.MAX_VALUE; - - // or "text/html; charset=utf-8 " - int sec = str.indexOf(' ', encIndex + clength); - if (sec < 0) - sec = Integer.MAX_VALUE; - lastEncIndex = Math.min(first, sec); - - // or "text/html; charset=utf-8 ' - int third = str.indexOf('\'', encIndex + clength); - if (third > 0) - lastEncIndex = Math.min(lastEncIndex, third); - } - - // re-read byte array with different encoding - // assume that the encoding string cannot be greater than 40 chars - if (lastEncIndex > encIndex + clength && lastEncIndex < encIndex + clength + 40) { - String tmpEnc = SHelper.encodingCleanup(str.substring(encIndex + clength, - lastEncIndex)); - try { - in.reset(); - bos.reset(); - return tmpEnc; - } catch (IOException ex) { - Log.e(TAG, "Couldn't reset stream to re-read with new encoding " - + tmpEnc + ' ' + ex.toString()); - } - } - } - return null; - } -} diff --git a/app/src/main/java/org/purplei2p/lightning/reading/HtmlFetcher.java b/app/src/main/java/org/purplei2p/lightning/reading/HtmlFetcher.java deleted file mode 100644 index a017340..0000000 --- a/app/src/main/java/org/purplei2p/lightning/reading/HtmlFetcher.java +++ /dev/null @@ -1,483 +0,0 @@ -/* - * Copyright 2011 Peter Karich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.purplei2p.lightning.reading; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.Proxy; -import java.net.URL; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.regex.Pattern; -import java.util.zip.GZIPInputStream; -import java.util.zip.Inflater; -import java.util.zip.InflaterInputStream; - -import org.purplei2p.lightning.utils.Utils; - -/** - * Class to fetch articles. This class is thread safe. - * - * @author Peter Karich - */ -public class HtmlFetcher { - - private static final Pattern SPACE = Pattern.compile(" "); - - static { - SHelper.enableCookieMgmt(); - SHelper.enableUserAgentOverwrite(); - SHelper.enableAnySSL(); - } - - public static void main(String[] args) throws Exception { - BufferedReader reader = null; - BufferedWriter writer = null; - try { - - //noinspection IOResourceOpenedButNotSafelyClosed - reader = new BufferedReader(new FileReader("urls.txt")); - String line; - Set existing = new LinkedHashSet<>(); - while ((line = reader.readLine()) != null) { - int index1 = line.indexOf('\"'); - int index2 = line.indexOf('\"', index1 + 1); - String url = line.substring(index1 + 1, index2); - String domainStr = SHelper.extractDomain(url, true); - String counterStr = ""; - // TODO more similarities - if (existing.contains(domainStr)) - counterStr = "2"; - else - existing.add(domainStr); - - String html = new HtmlFetcher().fetchAsString(url, 2000); - String outFile = domainStr + counterStr + ".html"; - //noinspection IOResourceOpenedButNotSafelyClosed - writer = new BufferedWriter(new FileWriter(outFile)); - writer.write(html); - } - } finally { - Utils.close(reader); - Utils.close(writer); - } - } - - private String referrer = "http://jetsli.de/crawler"; - private String userAgent = "Mozilla/5.0 (compatible; Jetslide; +" + referrer + ')'; - private String cacheControl = "max-age=0"; - private String language = "en-us"; - private String accept = "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"; - private String charset = "UTF-8"; - private SCache cache; - private final AtomicInteger cacheCounter = new AtomicInteger(0); - private int maxTextLength = -1; - private ArticleTextExtractor extractor = new ArticleTextExtractor(); - private Set furtherResolveNecessary = new LinkedHashSet() { - { - add("bit.ly"); - add("cli.gs"); - add("deck.ly"); - add("fb.me"); - add("feedproxy.google.com"); - add("flic.kr"); - add("fur.ly"); - add("goo.gl"); - add("is.gd"); - add("ink.co"); - add("j.mp"); - add("lnkd.in"); - add("on.fb.me"); - add("ow.ly"); - add("plurl.us"); - add("sns.mx"); - add("snurl.com"); - add("su.pr"); - add("t.co"); - add("tcrn.ch"); - add("tl.gd"); - add("tiny.cc"); - add("tinyurl.com"); - add("tmi.me"); - add("tr.im"); - add("twurl.nl"); - } - }; - - public HtmlFetcher() { - } - - public void setExtractor(ArticleTextExtractor extractor) { - this.extractor = extractor; - } - - public ArticleTextExtractor getExtractor() { - return extractor; - } - - public HtmlFetcher setCache(SCache cache) { - this.cache = cache; - return this; - } - - public SCache getCache() { - return cache; - } - - public int getCacheCounter() { - return cacheCounter.get(); - } - - public HtmlFetcher clearCacheCounter() { - cacheCounter.set(0); - return this; - } - - public HtmlFetcher setMaxTextLength(int maxTextLength) { - this.maxTextLength = maxTextLength; - return this; - } - - public int getMaxTextLength() { - return maxTextLength; - } - - public void setAccept(String accept) { - this.accept = accept; - } - - public void setCharset(String charset) { - this.charset = charset; - } - - public void setCacheControl(String cacheControl) { - this.cacheControl = cacheControl; - } - - public String getLanguage() { - return language; - } - - public void setLanguage(String language) { - this.language = language; - } - - public String getReferrer() { - return referrer; - } - - public HtmlFetcher setReferrer(String referrer) { - this.referrer = referrer; - return this; - } - - public String getUserAgent() { - return userAgent; - } - - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } - - public String getAccept() { - return accept; - } - - public String getCacheControl() { - return cacheControl; - } - - public String getCharset() { - return charset; - } - - public JResult fetchAndExtract(String url, int timeout, boolean resolve) throws Exception { - return fetchAndExtract(url, timeout, resolve, 0, false); - } - - // main workhorse to call externally - @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") - private JResult fetchAndExtract(String url, int timeout, boolean resolve, - int maxContentSize, boolean forceReload) throws Exception { - String originalUrl = url; - url = SHelper.removeHashbang(url); - String gUrl = SHelper.getUrlFromUglyGoogleRedirect(url); - if (gUrl != null) - url = gUrl; - else { - gUrl = SHelper.getUrlFromUglyFacebookRedirect(url); - if (gUrl != null) - url = gUrl; - } - - if (resolve) { - // check if we can avoid resolving the URL (which hits the website!) - JResult res = getFromCache(url, originalUrl); - if (res != null) - return res; - - String resUrl = getResolvedUrl(url, timeout, 0); - if (resUrl.isEmpty()) { - - JResult result = new JResult(); - if (cache != null) - cache.put(url, result); - return result.setUrl(url); - } - - // if resolved url is different then use it! - if (!resUrl.equals(url)) { - // this is necessary e.g. for some homebaken url resolvers which return - // the resolved url relative to url! - url = SHelper.useDomainOfFirstArg4Second(url, resUrl); - } - } - - // check if we have the (resolved) URL in cache - JResult res = getFromCache(url, originalUrl); - if (res != null) - return res; - - JResult result = new JResult(); - // or should we use? - result.setUrl(url); - result.setOriginalUrl(originalUrl); - - // Immediately put the url into the cache as extracting content takes time. - if (cache != null) { - cache.put(originalUrl, result); - cache.put(url, result); - } - - // extract content to the extent appropriate for content type - String lowerUrl = url.toLowerCase(); - if (SHelper.isDoc(lowerUrl) || SHelper.isApp(lowerUrl) || SHelper.isPackage(lowerUrl)) { - // skip - } else if (SHelper.isVideo(lowerUrl) || SHelper.isAudio(lowerUrl)) { - result.setVideoUrl(url); - } else if (SHelper.isImage(lowerUrl)) { - result.setImageUrl(url); - } else { - try { - String urlToDownload = url; - if (forceReload) { - urlToDownload = getURLtoBreakCache(url); - } - extractor.extractContent(result, fetchAsString(urlToDownload, timeout), maxContentSize); - } catch (IOException io) { - // do nothing - } - if (result.getFaviconUrl().isEmpty()) - result.setFaviconUrl(SHelper.getDefaultFavicon(url)); - - // some links are relative to root and do not include the domain of the url :( - if (!result.getFaviconUrl().isEmpty()) - result.setFaviconUrl(fixUrl(url, result.getFaviconUrl())); - - if (!result.getImageUrl().isEmpty()) - result.setImageUrl(fixUrl(url, result.getImageUrl())); - - if (!result.getVideoUrl().isEmpty()) - result.setVideoUrl(fixUrl(url, result.getVideoUrl())); - - if (!result.getRssUrl().isEmpty()) - result.setRssUrl(fixUrl(url, result.getRssUrl())); - } - result.setText(lessText(result.getText())); - synchronized (result) { - result.notifyAll(); - } - return result; - } - - // Ugly hack to break free from any cached versions, a few URLs required this. - private static String getURLtoBreakCache(String url) { - try { - URL aURL = new URL(url); - if (aURL.getQuery() != null && aURL.getQuery().isEmpty()) { - return url + "?1"; - } else { - return url + "&1"; - } - } catch (MalformedURLException e) { - return url; - } - } - - private String lessText(String text) { - if (text == null) - return ""; - - if (maxTextLength >= 0 && text.length() > maxTextLength) - return text.substring(0, maxTextLength); - - return text; - } - - private static String fixUrl(String url, String urlOrPath) { - return SHelper.useDomainOfFirstArg4Second(url, urlOrPath); - } - - private String fetchAsString(String urlAsString, int timeout) - throws IOException { - return fetchAsString(urlAsString, timeout, true); - } - - // main routine to get raw webpage content - private String fetchAsString(String urlAsString, int timeout, boolean includeSomeGooseOptions) - throws IOException { - HttpURLConnection hConn = createUrlConnection(urlAsString, timeout, includeSomeGooseOptions); - hConn.setInstanceFollowRedirects(true); - String encoding = hConn.getContentEncoding(); - InputStream is; - if ("gzip".equalsIgnoreCase(encoding)) { - is = new GZIPInputStream(hConn.getInputStream()); - } else if ("deflate".equalsIgnoreCase(encoding)) { - is = new InflaterInputStream(hConn.getInputStream(), new Inflater(true)); - } else { - is = hConn.getInputStream(); - } - - String enc = Converter.extractEncoding(hConn.getContentType()); - return createConverter(urlAsString).streamToString(is, enc); - } - - private static Converter createConverter(String url) { - return new Converter(url); - } - - /** - * On some devices we have to hack: - * http://developers.sun.com/mobility/reference/techart/design_guidelines/http_redirection.html - * - * @param timeout Sets a specified timeout value, in milliseconds - * @return the resolved url if any. Or null if it couldn't resolve the url - * (within the specified time) or the same url if response code is OK - */ - private String getResolvedUrl(String urlAsString, int timeout, - int num_redirects) { - String newUrl; - int responseCode; - try { - HttpURLConnection hConn = createUrlConnection(urlAsString, timeout, true); - // force no follow - hConn.setInstanceFollowRedirects(false); - // the program doesn't care what the content actually is !! - // http://java.sun.com/developer/JDCTechTips/2003/tt0422.html - hConn.setRequestMethod("HEAD"); - hConn.connect(); - responseCode = hConn.getResponseCode(); - hConn.getInputStream().close(); - if (responseCode == HttpURLConnection.HTTP_OK) - return urlAsString; - - newUrl = hConn.getHeaderField("Location"); - // Note that the max recursion level is 5. - if (responseCode / 100 == 3 && newUrl != null && num_redirects < 5) { - newUrl = SPACE.matcher(newUrl).replaceAll("+"); - // some services use (none-standard) utf8 in their location header - if (urlAsString.contains("://bit.ly") - || urlAsString.contains("://is.gd")) - newUrl = encodeUriFromHeader(newUrl); - - // AP: This code is not longer need, instead we always follow - // multiple redirects. - // - // fix problems if shortened twice. as it is often the case after twitters' t.co bullshit - //if (furtherResolveNecessary.contains(SHelper.extractDomain(newUrl, true))) - // newUrl = getResolvedUrl(newUrl, timeout); - - // Add support for URLs with multiple levels of redirection, - // call getResolvedUrl until there is no more redirects or a - // max number of redirects is reached. - newUrl = SHelper.useDomainOfFirstArg4Second(urlAsString, newUrl); - newUrl = getResolvedUrl(newUrl, timeout, num_redirects + 1); - return newUrl; - } else - return urlAsString; - - } catch (Exception ex) { - return ""; - } - } - - /** - * Takes a URI that was decoded as ISO-8859-1 and applies percent-encoding - * to non-ASCII characters. Workaround for broken origin servers that send - * UTF-8 in the Location: header. - */ - private static String encodeUriFromHeader(String badLocation) { - StringBuilder sb = new StringBuilder(badLocation.length()); - - for (char ch : badLocation.toCharArray()) { - if (ch < (char) 128) { - sb.append(ch); - } else { - // this is ONLY valid if the uri was decoded using ISO-8859-1 - sb.append(String.format("%%%02X", (int) ch)); - } - } - - return sb.toString(); - } - - private HttpURLConnection createUrlConnection(String urlAsStr, int timeout, - boolean includeSomeGooseOptions) throws IOException { - URL url = new URL(urlAsStr); - //using proxy may increase latency - HttpURLConnection hConn = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); - hConn.setRequestProperty("User-Agent", userAgent); - hConn.setRequestProperty("Accept", accept); - - if (includeSomeGooseOptions) { - hConn.setRequestProperty("Accept-Language", language); - hConn.setRequestProperty("content-charset", charset); - hConn.addRequestProperty("Referer", referrer); - // avoid the cache for testing purposes only? - hConn.setRequestProperty("Cache-Control", cacheControl); - } - - // suggest respond to be gzipped or deflated (which is just another compression) - // http://stackoverflow.com/q/3932117 - hConn.setRequestProperty("Accept-Encoding", "gzip, deflate"); - hConn.setConnectTimeout(timeout); - hConn.setReadTimeout(timeout); - return hConn; - } - - private JResult getFromCache(String url, String originalUrl) { - if (cache != null) { - JResult res = cache.get(url); - if (res != null) { - // e.g. the cache returned a shortened url as original url now we want to store the - // current original url! Also it can be that the cache response to url but the JResult - // does not contain it so overwrite it: - res.setUrl(url); - res.setOriginalUrl(originalUrl); - cacheCounter.addAndGet(1); - return res; - } - } - return null; - } -} \ No newline at end of file diff --git a/app/src/main/java/org/purplei2p/lightning/reading/ImageResult.java b/app/src/main/java/org/purplei2p/lightning/reading/ImageResult.java deleted file mode 100644 index 922479d..0000000 --- a/app/src/main/java/org/purplei2p/lightning/reading/ImageResult.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.purplei2p.lightning.reading; - -import org.jsoup.nodes.Element; - -/** - * Class which encapsulates the data from an image found under an element - * - * @author Chris Alexander, chris@chris-alexander.co.uk - */ -class ImageResult { - - private final String src; - public final Integer weight; - private final String title; - private final int height; - private final int width; - private final String alt; - private final boolean noFollow; - public Element element; - - public ImageResult(String src, Integer weight, String title, int height, int width, String alt, - boolean noFollow) { - this.src = src; - this.weight = weight; - this.title = title; - this.height = height; - this.width = width; - this.alt = alt; - this.noFollow = noFollow; - } -} diff --git a/app/src/main/java/org/purplei2p/lightning/reading/JResult.java b/app/src/main/java/org/purplei2p/lightning/reading/JResult.java deleted file mode 100644 index be7fa08..0000000 --- a/app/src/main/java/org/purplei2p/lightning/reading/JResult.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright 2011 Peter Karich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.purplei2p.lightning.reading; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - - -/** - * Parsed result from web page containing important title, text and image. - * - * @author Peter Karich - */ -public class JResult implements Serializable { - - private String title; - private String url; - private String originalUrl; - private String canonicalUrl; - private String imageUrl; - private String videoUrl; - private String rssUrl; - private String text; - private String faviconUrl; - private String description; - private String authorName; - private String authorDescription; - private Date date; - private Collection keywords; - private List images = null; - private final List> links = new ArrayList<>(); - private String type; - private String sitename; - private String language; - - public JResult() { - } - - public String getUrl() { - if (url == null) - return ""; - return url; - } - - public JResult setUrl(String url) { - this.url = url; - return this; - } - - public JResult setOriginalUrl(String originalUrl) { - this.originalUrl = originalUrl; - return this; - } - - public String getOriginalUrl() { - return originalUrl; - } - - public JResult setCanonicalUrl(String canonicalUrl) { - this.canonicalUrl = canonicalUrl; - return this; - } - - public String getCanonicalUrl() { - return canonicalUrl; - } - - public String getFaviconUrl() { - if (faviconUrl == null) - return ""; - return faviconUrl; - } - - public JResult setFaviconUrl(String faviconUrl) { - this.faviconUrl = faviconUrl; - return this; - } - - public JResult setRssUrl(String rssUrl) { - this.rssUrl = rssUrl; - return this; - } - - public String getRssUrl() { - if (rssUrl == null) - return ""; - return rssUrl; - } - - public String getDescription() { - if (description == null) - return ""; - return description; - } - - public JResult setDescription(String description) { - this.description = description; - return this; - } - - public String getAuthorName() { - if (authorName == null) - return ""; - return authorName; - } - - public JResult setAuthorName(String authorName) { - this.authorName = authorName; - return this; - } - - public String getAuthorDescription() { - if (authorDescription == null) - return ""; - return authorDescription; - } - - public JResult setAuthorDescription(String authorDescription) { - this.authorDescription = authorDescription; - return this; - } - - public String getImageUrl() { - if (imageUrl == null) - return ""; - return imageUrl; - } - - public JResult setImageUrl(String imageUrl) { - this.imageUrl = imageUrl; - return this; - } - - public String getText() { - if (text == null) - return ""; - - return text; - } - - public JResult setText(String text) { - this.text = text; - return this; - } - - public String getTitle() { - if (title == null) - return ""; - return title; - } - - public JResult setTitle(String title) { - this.title = title; - return this; - } - - public String getVideoUrl() { - if (videoUrl == null) - return ""; - return videoUrl; - } - - public JResult setVideoUrl(String videoUrl) { - this.videoUrl = videoUrl; - return this; - } - - public JResult setDate(Date date) { - this.date = date; - return this; - } - - public Collection getKeywords() { - return keywords; - } - - public void setKeywords(Collection keywords) { - this.keywords = keywords; - } - - /** - * @return get date from url or guessed from text - */ - public Date getDate() { - return date; - } - - /** - * @return images list - */ - public List getImages() { - if (images == null) - return Collections.emptyList(); - return images; - } - - /** - * @return images count - */ - public int getImagesCount() { - if (images == null) - return 0; - return images.size(); - } - - /** - * set images list - */ - public void setImages(List images) { - this.images = images; - } - - public void addLink(String url, String text, Integer pos) { - Map link = new HashMap<>(); - link.put("url", url); - link.put("text", text); - link.put("offset", String.valueOf(pos)); - links.add(link); - } - - public List> getLinks() { - if (links == null) - return Collections.emptyList(); - return links; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getSitename() { - return sitename; - } - - public void setSitename(String sitename) { - this.sitename = sitename; - } - - public String getLanguage() { - return language; - } - - public void setLanguage(String language) { - this.language = language; - } - - @Override - public String toString() { - return "title:" + getTitle() + " imageUrl:" + getImageUrl() + " text:" + text; - } -} \ No newline at end of file diff --git a/app/src/main/java/org/purplei2p/lightning/reading/OutputFormatter.java b/app/src/main/java/org/purplei2p/lightning/reading/OutputFormatter.java deleted file mode 100644 index fd6d8fd..0000000 --- a/app/src/main/java/org/purplei2p/lightning/reading/OutputFormatter.java +++ /dev/null @@ -1,216 +0,0 @@ -package org.purplei2p.lightning.reading; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; - -import java.util.Arrays; -import java.util.List; -import java.util.regex.Pattern; - -import org.jsoup.nodes.Node; -import org.jsoup.nodes.TextNode; - -/** - * @author goose | jim - * @author karussell - *

- * this class will be responsible for taking our top node and stripping out junk - * we don't want and getting it ready for how we want it presented to the user - */ -public class OutputFormatter { - - private static final int MIN_FIRST_PARAGRAPH_TEXT = 50; // Min size of first paragraph - private static final int MIN_PARAGRAPH_TEXT = 30; // Min size of any other paragraphs - private static final List NODES_TO_REPLACE = Arrays.asList("strong", "b", "i"); - private Pattern unlikelyPattern = Pattern.compile("display:none|visibility:hidden"); - private final int minFirstParagraphText; - private final int minParagraphText; - private final List nodesToReplace; - private String nodesToKeepCssSelector = "p, ol"; - - public OutputFormatter() { - this(MIN_FIRST_PARAGRAPH_TEXT, MIN_PARAGRAPH_TEXT, NODES_TO_REPLACE); - } - - public OutputFormatter(int minParagraphText) { - this(minParagraphText, minParagraphText, NODES_TO_REPLACE); - } - - public OutputFormatter(int minFirstParagraphText, int minParagraphText) { - this(minFirstParagraphText, minParagraphText, NODES_TO_REPLACE); - } - - private OutputFormatter(int minFirstParagraphText, int minParagraphText, - List nodesToReplace) { - this.minFirstParagraphText = minFirstParagraphText; - this.minParagraphText = minParagraphText; - this.nodesToReplace = nodesToReplace; - } - - /** - * set elements to keep in output text - */ - public void setNodesToKeepCssSelector(String nodesToKeepCssSelector) { - this.nodesToKeepCssSelector = nodesToKeepCssSelector; - } - - /** - * takes an element and turns the P tags into \n\n - */ - public String getFormattedText(Element topNode) { - setParagraphIndex(topNode, nodesToKeepCssSelector); - removeNodesWithNegativeScores(topNode); - StringBuilder sb = new StringBuilder(); - int countOfP = append(topNode, sb, nodesToKeepCssSelector); - String str = SHelper.innerTrim(sb.toString()); - - int topNodeLength = topNode.text().length(); - if (topNodeLength == 0) { - topNodeLength = 1; - } - - - boolean lowTextRatio = ((str.length() / (topNodeLength * 1.0)) < 0.25); - if (str.length() > 100 && countOfP > 0 && !lowTextRatio) - return str; - - // no subelements - if (str.isEmpty() || (!topNode.text().isEmpty() - && str.length() <= topNode.ownText().length()) - || countOfP == 0 || lowTextRatio) { - str = topNode.text(); - } - - // if jsoup failed to parse the whole html now parse this smaller - // snippet again to avoid html tags disturbing our text: - return Jsoup.parse(str).text(); - } - - /** - * If there are elements inside our top node that have a negative gravity - * score remove them - */ - private void removeNodesWithNegativeScores(Element topNode) { - Elements gravityItems = topNode.select("*[gravityScore]"); - for (Element item : gravityItems) { - int score = getScore(item); - int paragraphIndex = getParagraphIndex(item); - if (score < 0 || item.text().length() < getMinParagraph(paragraphIndex)) { - item.remove(); - } - } - } - - private int append(Element node, StringBuilder sb, String tagName) { - int countOfP = 0; // Number of P elements in the article - int paragraphWithTextIndex = 0; - // is select more costly then getElementsByTag? - MAIN: - for (Element e : node.select(tagName)) { - Element tmpEl = e; - // check all elements until 'node' - while (tmpEl != null && !tmpEl.equals(node)) { - if (unlikely(tmpEl)) - continue MAIN; - tmpEl = tmpEl.parent(); - } - - String text = node2Text(e); - if (text.isEmpty() || text.length() < getMinParagraph(paragraphWithTextIndex) - || text.length() > SHelper.countLetters(text) * 2) { - continue; - } - - if (e.tagName().equals("p")) { - countOfP++; - } - - sb.append(text); - sb.append("\n\n"); - paragraphWithTextIndex += 1; - } - - return countOfP; - } - - private static void setParagraphIndex(Element node, String tagName) { - int paragraphIndex = 0; - for (Element e : node.select(tagName)) { - e.attr("paragraphIndex", Integer.toString(paragraphIndex++)); - } - } - - private int getMinParagraph(int paragraphIndex) { - if (paragraphIndex < 1) { - return minFirstParagraphText; - } else { - return minParagraphText; - } - } - - private static int getParagraphIndex(Element el) { - try { - return Integer.parseInt(el.attr("paragraphIndex")); - } catch (NumberFormatException ex) { - return -1; - } - } - - private static int getScore(Element el) { - try { - return Integer.parseInt(el.attr("gravityScore")); - } catch (Exception ex) { - return 0; - } - } - - private boolean unlikely(Node e) { - if (e.attr("class") != null && e.attr("class").toLowerCase().contains("caption")) - return true; - - String style = e.attr("style"); - String clazz = e.attr("class"); - return unlikelyPattern.matcher(style).find() || unlikelyPattern.matcher(clazz).find(); - } - - private void appendTextSkipHidden(Element e, StringBuilder accum, int indent) { - for (Node child : e.childNodes()) { - if (unlikely(child)) { - continue; - } - if (child instanceof TextNode) { - TextNode textNode = (TextNode) child; - String txt = textNode.text(); - accum.append(txt); - } else if (child instanceof Element) { - Element element = (Element) child; - if (accum.length() > 0 && element.isBlock() - && !lastCharIsWhitespace(accum)) - accum.append(' '); - else if (element.tagName().equals("br")) - accum.append(' '); - appendTextSkipHidden(element, accum, indent + 1); - } - } - } - - private static boolean lastCharIsWhitespace(StringBuilder accum) { - return accum.length() != 0 && Character.isWhitespace(accum.charAt(accum.length() - 1)); - } - - private String node2Text(Element el) { - StringBuilder sb = new StringBuilder(200); - appendTextSkipHidden(el, sb, 0); - return sb.toString(); - } - - private OutputFormatter setUnlikelyPattern(String unlikelyPattern) { - this.unlikelyPattern = Pattern.compile(unlikelyPattern); - return this; - } - - public OutputFormatter appendUnlikelyPattern(String str) { - return setUnlikelyPattern(unlikelyPattern.toString() + '|' + str); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/purplei2p/lightning/reading/SCache.java b/app/src/main/java/org/purplei2p/lightning/reading/SCache.java deleted file mode 100644 index 80f70be..0000000 --- a/app/src/main/java/org/purplei2p/lightning/reading/SCache.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2011 Peter Karich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.purplei2p.lightning.reading; - -/** - * - * @author Peter Karich - */ -public interface SCache { - - JResult get(String url); - - void put(String url, JResult res); - - int getSize(); -} diff --git a/app/src/main/java/org/purplei2p/lightning/reading/SHelper.java b/app/src/main/java/org/purplei2p/lightning/reading/SHelper.java deleted file mode 100644 index 25d984e..0000000 --- a/app/src/main/java/org/purplei2p/lightning/reading/SHelper.java +++ /dev/null @@ -1,451 +0,0 @@ -/* - * Copyright 2011 Peter Karich - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.purplei2p.lightning.reading; - -import org.jsoup.nodes.Element; - -import java.io.UnsupportedEncodingException; -import java.net.CookieHandler; -import java.net.CookieManager; -import java.net.CookiePolicy; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.security.SecureRandom; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Date; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -/** - * @author Peter Karich - */ -class SHelper { - - private static final String UTF8 = "UTF-8"; - private static final Pattern SPACE = Pattern.compile(" "); - - public static String replaceSpaces(String url) { - if (!url.isEmpty()) { - url = url.trim(); - if (url.contains(" ")) { - Matcher spaces = SPACE.matcher(url); - url = spaces.replaceAll("%20"); - } - } - return url; - } - - public static int count(String str, String substring) { - int c = 0; - int index1 = str.indexOf(substring); - if (index1 >= 0) { - c++; - c += count(str.substring(index1 + substring.length()), substring); - } - return c; - } - - /** - * remove more than two spaces or newlines - */ - public static String innerTrim(String str) { - if (str.isEmpty()) - return ""; - - StringBuilder sb = new StringBuilder(str.length()); - boolean previousSpace = false; - for (int i = 0, length = str.length(); i < length; i++) { - char c = str.charAt(i); - if (c == ' ' || (int) c == 9 || c == '\n') { - previousSpace = true; - continue; - } - - if (previousSpace) - sb.append(' '); - - previousSpace = false; - sb.append(c); - } - return sb.toString().trim(); - } - - /** - * Starts reading the encoding from the first valid character until an - * invalid encoding character occurs. - */ - public static String encodingCleanup(String str) { - StringBuilder sb = new StringBuilder(str.length()); - boolean startedWithCorrectString = false; - for (int i = 0; i < str.length(); i++) { - char c = str.charAt(i); - if (Character.isDigit(c) || Character.isLetter(c) || c == '-' || c == '_') { - startedWithCorrectString = true; - sb.append(c); - continue; - } - - if (startedWithCorrectString) - break; - } - return sb.toString().trim(); - } - - /** - * @return the longest substring as str1.substring(result[0], result[1]); - */ - public static String getLongestSubstring(String str1, String str2) { - int res[] = longestSubstring(str1, str2); - if (res == null || res[0] >= res[1]) - return ""; - - return str1.substring(res[0], res[1]); - } - - private static int[] longestSubstring(String str1, String str2) { - if (str1 == null || str1.isEmpty() || str2 == null || str2.isEmpty()) - return null; - - // dynamic programming => save already identical length into array - // to understand this algo simply print identical length in every entry of the array - // i+1, j+1 then reuses information from i,j - // java initializes them already with 0 - int[][] num = new int[str1.length()][str2.length()]; - int maxlen = 0; - int lastSubstrBegin = 0; - int endIndex = 0; - for (int i = 0; i < str1.length(); i++) { - for (int j = 0; j < str2.length(); j++) { - if (str1.charAt(i) == str2.charAt(j)) { - if ((i == 0) || (j == 0)) - num[i][j] = 1; - else - num[i][j] = 1 + num[i - 1][j - 1]; - - if (num[i][j] > maxlen) { - maxlen = num[i][j]; - // generate substring from str1 => i - lastSubstrBegin = i - num[i][j] + 1; - endIndex = i + 1; - } - } - } - } - return new int[]{lastSubstrBegin, endIndex}; - } - - public static String getDefaultFavicon(String url) { - return useDomainOfFirstArg4Second(url, "/favicon.ico"); - } - - /** - * @param urlForDomain extract the domain from this url - * @param path this url does not have a domain - * @return - */ - public static String useDomainOfFirstArg4Second(String urlForDomain, String path) { - try { - // See: http://stackoverflow.com/questions/1389184/building-an-absolute-url-from-a-relative-url-in-java - URL baseUrl = new URL(urlForDomain); - URL relativeurl = new URL(baseUrl, path); - return relativeurl.toString(); - } catch (MalformedURLException ex) { - return path; - } - } - - public static String extractHost(String url) { - return extractDomain(url, false); - } - - public static String extractDomain(String url, boolean aggressive) { - if (url.startsWith("http://")) - url = url.substring("http://".length()); - else if (url.startsWith("https://")) - url = url.substring("https://".length()); - - if (aggressive) { - if (url.startsWith("www.")) - url = url.substring("www.".length()); - - // strip mobile from start - if (url.startsWith("m.")) - url = url.substring("m.".length()); - } - - int slashIndex = url.indexOf('/'); - if (slashIndex > 0) - url = url.substring(0, slashIndex); - - return url; - } - - public static boolean isVideoLink(String url) { - url = extractDomain(url, true); - return url.startsWith("youtube.com") || url.startsWith("video.yahoo.com") - || url.startsWith("vimeo.com") || url.startsWith("blip.tv"); - } - - public static boolean isVideo(String url) { - return url.endsWith(".mpeg") || url.endsWith(".mpg") || url.endsWith(".avi") || url.endsWith(".mov") - || url.endsWith(".mpg4") || url.endsWith(".mp4") || url.endsWith(".flv") || url.endsWith(".wmv"); - } - - public static boolean isAudio(String url) { - return url.endsWith(".mp3") || url.endsWith(".ogg") || url.endsWith(".m3u") || url.endsWith(".wav"); - } - - public static boolean isDoc(String url) { - return url.endsWith(".pdf") || url.endsWith(".ppt") || url.endsWith(".doc") - || url.endsWith(".swf") || url.endsWith(".rtf") || url.endsWith(".xls"); - } - - public static boolean isPackage(String url) { - return url.endsWith(".gz") || url.endsWith(".tgz") || url.endsWith(".zip") - || url.endsWith(".rar") || url.endsWith(".deb") || url.endsWith(".rpm") || url.endsWith(".7z"); - } - - public static boolean isApp(String url) { - return url.endsWith(".exe") || url.endsWith(".bin") || url.endsWith(".bat") || url.endsWith(".dmg"); - } - - public static boolean isImage(String url) { - return url.endsWith(".png") || url.endsWith(".jpeg") || url.endsWith(".gif") - || url.endsWith(".jpg") || url.endsWith(".bmp") || url.endsWith(".ico") || url.endsWith(".eps"); - } - - /** - * @see "http://blogs.sun.com/CoreJavaTechTips/entry/cookie_handling_in_java_se" - */ - public static void enableCookieMgmt() { - CookieManager manager = new CookieManager(); - manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); - CookieHandler.setDefault(manager); - } - - /** - * @see "http://stackoverflow.com/questions/2529682/setting-user-agent-of-a-java-urlconnection" - */ - public static void enableUserAgentOverwrite() { - System.setProperty("http.agent", ""); - } - - public static String getUrlFromUglyGoogleRedirect(String url) { - if (url.startsWith("https://www.google.com/url?")) { - url = url.substring("https://www.google.com/url?".length()); - String arr[] = urlDecode(url).split("&"); - for (String str : arr) { - if (str.startsWith("q=")) - return str.substring("q=".length()); - } - } - - return null; - } - - public static String getUrlFromUglyFacebookRedirect(String url) { - if (url.startsWith("https://www.facebook.com/l.php?u=")) { - url = url.substring("https://www.facebook.com/l.php?u=".length()); - return urlDecode(url); - } - - return null; - } - - public static String urlEncode(String str) { - try { - return URLEncoder.encode(str, UTF8); - } catch (UnsupportedEncodingException ex) { - return str; - } - } - - private static String urlDecode(String str) { - try { - return URLDecoder.decode(str, UTF8); - } catch (UnsupportedEncodingException ex) { - return str; - } - } - - /** - * Popular sites uses the #! to indicate the importance of the following - * chars. Ugly but true. Such as: facebook, twitter, gizmodo, ... - */ - public static String removeHashbang(String url) { - return url.replaceFirst("#!", ""); - } - - public static String printNode(Element root) { - return printNode(root, 0); - } - - private static String printNode(Element root, int indentation) { - StringBuilder sb = new StringBuilder(indentation); - for (int i = 0; i < indentation; i++) { - sb.append(' '); - } - sb.append(root.tagName()); - sb.append(':'); - sb.append(root.ownText()); - sb.append('\n'); - for (Element el : root.children()) { - sb.append(printNode(el, indentation + 1)); - sb.append('\n'); - } - return sb.toString(); - } - - public static String estimateDate(String url) { - int index = url.indexOf("://"); - if (index > 0) - url = url.substring(index + 3); - - int year = -1; - int yearCounter = -1; - int month = -1; - int monthCounter = -1; - int day = -1; - String strs[] = url.split("/"); - for (int counter = 0; counter < strs.length; counter++) { - String str = strs[counter]; - if (str.length() == 4) { - try { - year = Integer.parseInt(str); - } catch (Exception ex) { - continue; - } - if (year < 1970 || year > 3000) { - year = -1; - continue; - } - yearCounter = counter; - } else if (str.length() == 2) { - if (monthCounter < 0 && counter == yearCounter + 1) { - try { - month = Integer.parseInt(str); - } catch (Exception ex) { - continue; - } - if (month < 1 || month > 12) { - month = -1; - continue; - } - monthCounter = counter; - } else if (counter == monthCounter + 1) { - try { - day = Integer.parseInt(str); - } catch (Exception ignored) { - // ignored - } - if (day < 1 || day > 31) { - day = -1; - continue; - } - break; - } - } - } - - if (year < 0) - return null; - - StringBuilder str = new StringBuilder(year); - if (month < 1) - return str.toString(); - - str.append('/'); - if (month < 10) - str.append('0'); - str.append(month); - if (day < 1) - return str.toString(); - - str.append('/'); - if (day < 10) - str.append('0'); - str.append(day); - return str.toString(); - } - - public static String completeDate(String dateStr) { - if (dateStr == null) - return null; - - int index = dateStr.indexOf('/'); - if (index > 0) { - index = dateStr.indexOf('/', index + 1); - if (index > 0) - return dateStr; - else - return dateStr + "/01"; - } - return dateStr + "/01/01"; - } - - // with the help of http://stackoverflow.com/questions/1828775/httpclient-and-ssl - public static void enableAnySSL() { - try { - SSLContext ctx = SSLContext.getInstance("TLS"); - ctx.init(new KeyManager[0], new TrustManager[]{new DefaultTrustManager()}, new SecureRandom()); - SSLContext.setDefault(ctx); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - - private static class DefaultTrustManager implements X509TrustManager { - - @Override - public void checkClientTrusted(X509Certificate[] certs, String arg1) throws CertificateException { - Date today = new Date(); - for (X509Certificate certificate : certs) { - certificate.checkValidity(today); - } - } - - @Override - public void checkServerTrusted(X509Certificate[] certs, String arg1) throws CertificateException { - Date today = new Date(); - for (X509Certificate certificate : certs) { - certificate.checkValidity(today); - } - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return null; - } - } - - public static int countLetters(String str) { - int len = str.length(); - int chars = 0; - for (int i = 0; i < len; i++) { - if (Character.isLetter(str.charAt(i))) - chars++; - } - return chars; - } -} \ No newline at end of file diff --git a/app/src/main/java/org/purplei2p/lightning/reading/activity/ReadingActivity.java b/app/src/main/java/org/purplei2p/lightning/reading/activity/ReadingActivity.java deleted file mode 100644 index b113870..0000000 --- a/app/src/main/java/org/purplei2p/lightning/reading/activity/ReadingActivity.java +++ /dev/null @@ -1,342 +0,0 @@ -package org.purplei2p.lightning.reading.activity; - -import android.animation.ObjectAnimator; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.graphics.PorterDuff; -import android.graphics.drawable.ColorDrawable; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import javax.inject.Inject; - -import org.purplei2p.lightning.R; -import org.purplei2p.lightning.BrowserApp; -import org.purplei2p.lightning.constant.Constants; -import org.purplei2p.lightning.dialog.BrowserDialog; -import org.purplei2p.lightning.preference.PreferenceManager; - -import com.anthonycr.bonsai.Schedulers; -import com.anthonycr.bonsai.Single; -import com.anthonycr.bonsai.SingleAction; -import com.anthonycr.bonsai.SingleOnSubscribe; -import com.anthonycr.bonsai.SingleSubscriber; -import com.anthonycr.bonsai.Subscription; - -import org.purplei2p.lightning.reading.HtmlFetcher; -import org.purplei2p.lightning.reading.JResult; -import org.purplei2p.lightning.utils.ThemeUtils; -import org.purplei2p.lightning.utils.Utils; -import butterknife.BindView; -import butterknife.ButterKnife; - -public class ReadingActivity extends AppCompatActivity { - - private static final String TAG = "ReadingActivity"; - - @BindView(R.id.textViewTitle) TextView mTitle; - @BindView(R.id.textViewBody) TextView mBody; - - @Inject PreferenceManager mPreferences; - - private boolean mInvert; - private String mUrl = null; - private int mTextSize; - private ProgressDialog mProgressDialog; - private Subscription mPageLoaderSubscription; - - private static final float XXLARGE = 30.0f; - private static final float XLARGE = 26.0f; - private static final float LARGE = 22.0f; - private static final float MEDIUM = 18.0f; - private static final float SMALL = 14.0f; - private static final float XSMALL = 10.0f; - - @Override - protected void onCreate(Bundle savedInstanceState) { - BrowserApp.getAppComponent().inject(this); - - overridePendingTransition(R.anim.slide_in_from_right, R.anim.fade_out_scale); - mInvert = mPreferences.getInvertColors(); - final int color; - if (mInvert) { - setTheme(R.style.Theme_SettingsTheme_Dark); - color = ThemeUtils.getPrimaryColorDark(this); - getWindow().setBackgroundDrawable(new ColorDrawable(color)); - } else { - setTheme(R.style.Theme_SettingsTheme); - color = ThemeUtils.getPrimaryColor(this); - getWindow().setBackgroundDrawable(new ColorDrawable(color)); - } - super.onCreate(savedInstanceState); - setContentView(R.layout.reading_view); - ButterKnife.bind(this); - - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - - if (getSupportActionBar() != null) - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - mTextSize = mPreferences.getReadingTextSize(); - mBody.setTextSize(getTextSize(mTextSize)); - mTitle.setText(getString(R.string.untitled)); - mBody.setText(getString(R.string.loading)); - - mTitle.setVisibility(View.INVISIBLE); - mBody.setVisibility(View.INVISIBLE); - - Intent intent = getIntent(); - if (!loadPage(intent)) { - setText(getString(R.string.untitled), getString(R.string.loading_failed)); - } - } - - private static float getTextSize(int size) { - switch (size) { - case 0: - return XSMALL; - case 1: - return SMALL; - case 2: - return MEDIUM; - case 3: - return LARGE; - case 4: - return XLARGE; - case 5: - return XXLARGE; - default: - return MEDIUM; - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.reading, menu); - MenuItem invert = menu.findItem(R.id.invert_item); - MenuItem textSize = menu.findItem(R.id.text_size_item); - - int iconColor = ThemeUtils.getIconThemeColor(this, mInvert); - - if (invert != null && invert.getIcon() != null) { - invert.getIcon().mutate().setColorFilter(iconColor, PorterDuff.Mode.SRC_IN); - } - - if (textSize != null && textSize.getIcon() != null) { - textSize.getIcon().mutate().setColorFilter(iconColor, PorterDuff.Mode.SRC_IN); - } - - return super.onCreateOptionsMenu(menu); - } - - private boolean loadPage(Intent intent) { - if (intent == null) { - return false; - } - mUrl = intent.getStringExtra(Constants.LOAD_READING_URL); - if (mUrl == null) { - return false; - } - if (getSupportActionBar() != null) - getSupportActionBar().setTitle(Utils.getDomainName(mUrl)); - mPageLoaderSubscription = loadPage(mUrl).subscribeOn(Schedulers.worker()) - .observeOn(Schedulers.main()) - .subscribe(new SingleOnSubscribe() { - @Override - public void onStart() { - mProgressDialog = new ProgressDialog(ReadingActivity.this); - mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); - mProgressDialog.setCancelable(false); - mProgressDialog.setIndeterminate(true); - mProgressDialog.setMessage(getString(R.string.loading)); - mProgressDialog.show(); - BrowserDialog.setDialogSize(ReadingActivity.this, mProgressDialog); - } - - @Override - public void onItem(@Nullable ReaderInfo item) { - if (item == null || item.getTitle().isEmpty() || item.getBody().isEmpty()) { - setText(getString(R.string.untitled), getString(R.string.loading_failed)); - } else { - setText(item.getTitle(), item.getBody()); - } - } - - @Override - public void onError(@NonNull Throwable throwable) { - setText(getString(R.string.untitled), getString(R.string.loading_failed)); - if (mProgressDialog != null && mProgressDialog.isShowing()) { - mProgressDialog.dismiss(); - mProgressDialog = null; - } - } - - @Override - public void onComplete() { - if (mProgressDialog != null && mProgressDialog.isShowing()) { - mProgressDialog.dismiss(); - mProgressDialog = null; - } - } - }); - return true; - } - - private static Single loadPage(@NonNull final String url) { - return Single.create(new SingleAction() { - @Override - public void onSubscribe(@NonNull SingleSubscriber subscriber) { - HtmlFetcher fetcher = new HtmlFetcher(); - try { - JResult result = fetcher.fetchAndExtract(url, 2500, true); - subscriber.onItem(new ReaderInfo(result.getTitle(), result.getText())); - } catch (Exception e) { - subscriber.onError(new Throwable("Encountered exception")); - Log.e(TAG, "Error parsing page", e); - } catch (OutOfMemoryError e) { - System.gc(); - subscriber.onError(new Throwable("Out of memory")); - Log.e(TAG, "Out of memory", e); - } - subscriber.onComplete(); - } - }); - } - - private static class ReaderInfo { - @NonNull private final String mTitleText; - @NonNull private final String mBodyText; - - public ReaderInfo(@NonNull String title, @NonNull String body) { - mTitleText = title; - mBodyText = body; - } - - @NonNull - public String getTitle() { - return mTitleText; - } - - @NonNull - public String getBody() { - return mBodyText; - } - } - - private void setText(String title, String body) { - if (mTitle == null || mBody == null) - return; - if (mTitle.getVisibility() == View.INVISIBLE) { - mTitle.setAlpha(0.0f); - mTitle.setVisibility(View.VISIBLE); - mTitle.setText(title); - ObjectAnimator animator = ObjectAnimator.ofFloat(mTitle, "alpha", 1.0f); - animator.setDuration(300); - animator.start(); - } else { - mTitle.setText(title); - } - - if (mBody.getVisibility() == View.INVISIBLE) { - mBody.setAlpha(0.0f); - mBody.setVisibility(View.VISIBLE); - mBody.setText(body); - ObjectAnimator animator = ObjectAnimator.ofFloat(mBody, "alpha", 1.0f); - animator.setDuration(300); - animator.start(); - } else { - mBody.setText(body); - } - } - - @Override - protected void onDestroy() { - mPageLoaderSubscription.unsubscribe(); - - if (mProgressDialog != null && mProgressDialog.isShowing()) { - mProgressDialog.dismiss(); - mProgressDialog = null; - } - super.onDestroy(); - } - - @Override - protected void onPause() { - super.onPause(); - if (isFinishing()) { - overridePendingTransition(R.anim.fade_in_scale, R.anim.slide_out_to_right); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.invert_item: - mPreferences.setInvertColors(!mInvert); - Intent read = new Intent(this, ReadingActivity.class); - read.putExtra(Constants.LOAD_READING_URL, mUrl); - startActivity(read); - finish(); - break; - case R.id.text_size_item: - - View view = LayoutInflater.from(this).inflate(R.layout.dialog_seek_bar, null); - final SeekBar bar = view.findViewById(R.id.text_size_seekbar); - bar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { - - @Override - public void onProgressChanged(SeekBar view, int size, boolean user) { - mBody.setTextSize(getTextSize(size)); - } - - @Override - public void onStartTrackingTouch(SeekBar arg0) { - } - - @Override - public void onStopTrackingTouch(SeekBar arg0) { - } - - }); - bar.setMax(5); - bar.setProgress(mTextSize); - - AlertDialog.Builder builder = new AlertDialog.Builder(this) - .setView(view) - .setTitle(R.string.size) - .setPositiveButton(android.R.string.ok, new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int arg1) { - mTextSize = bar.getProgress(); - mBody.setTextSize(getTextSize(mTextSize)); - mPreferences.setReadingTextSize(bar.getProgress()); - } - - }); - Dialog dialog = builder.show(); - BrowserDialog.setDialogSize(this, dialog); - break; - default: - finish(); - break; - } - return super.onOptionsItemSelected(item); - } -} diff --git a/app/src/main/java/org/purplei2p/lightning/search/SuggestionsAdapter.java b/app/src/main/java/org/purplei2p/lightning/search/SuggestionsAdapter.java index a1715f9..b2050da 100644 --- a/app/src/main/java/org/purplei2p/lightning/search/SuggestionsAdapter.java +++ b/app/src/main/java/org/purplei2p/lightning/search/SuggestionsAdapter.java @@ -47,378 +47,365 @@ import org.purplei2p.lightning.utils.ThemeUtils; public class SuggestionsAdapter extends BaseAdapter implements Filterable { - private static final Scheduler FILTER_SCHEDULER = Schedulers.newSingleThreadedScheduler(); - - private static final String CACHE_FILE_TYPE = ".sgg"; - - private final List mFilteredList = new ArrayList<>(5); - - private final List mHistory = new ArrayList<>(5); - private final List mBookmarks = new ArrayList<>(5); - private final List mSuggestions = new ArrayList<>(5); - - private static final int MAX_SUGGESTIONS = 5; - - @NonNull private final Drawable mSearchDrawable; - @NonNull private final Drawable mHistoryDrawable; - @NonNull private final Drawable mBookmarkDrawable; - - private final Comparator mFilterComparator = new SuggestionsComparator(); - - @Inject BookmarkModel mBookmarkManager; - @Inject PreferenceManager mPreferenceManager; - @Inject HistoryModel mHistoryModel; - @Inject Application mApplication; - - private final List mAllBookmarks = new ArrayList<>(5); - - private final boolean mDarkTheme; - private boolean mIsIncognito = true; - @NonNull private final Context mContext; - private PreferenceManager.Suggestion mSuggestionChoice; - - public SuggestionsAdapter(@NonNull Context context, boolean dark, boolean incognito) { - super(); - BrowserApp.getAppComponent().inject(this); - mContext = context; - mDarkTheme = dark || incognito; - mIsIncognito = incognito; - - refreshPreferences(); - - 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); - } - - public void refreshPreferences() { - mSuggestionChoice = mPreferenceManager.getSearchSuggestionChoice(); - } - - public void clearCache() { - // We don't need these cache files anymore - Schedulers.io().execute(new ClearCacheRunnable(mApplication)); - } - - public void refreshBookmarks() { - mBookmarkManager.getAllBookmarks() - .subscribeOn(Schedulers.io()) - .subscribe(new SingleOnSubscribe>() { - @Override - public void onItem(@Nullable List item) { - Preconditions.checkNonNull(item); - mAllBookmarks.clear(); - mAllBookmarks.addAll(item); - } - }); - } - - @Override - public int getCount() { - return mFilteredList.size(); - } - - @Nullable - @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 { - - SuggestionHolder(@NonNull View view) { - mTitle = view.findViewById(R.id.title); - mUrl = view.findViewById(R.id.url); - mImage = view.findViewById(R.id.suggestionIcon); - } - - @NonNull final ImageView mImage; - @NonNull final TextView mTitle; - @NonNull final TextView mUrl; - - } - - @Nullable - @Override - public View getView(int position, @Nullable 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()); - - if (mDarkTheme) { - holder.mTitle.setTextColor(Color.WHITE); - } - - Drawable image; - switch (web.getImageId()) { - case R.drawable.ic_bookmark: { - image = mBookmarkDrawable; - break; - } - case R.drawable.ic_search: { - image = mSearchDrawable; - break; - } - case R.drawable.ic_history: { - image = mHistoryDrawable; - break; - } - default: - image = mSearchDrawable; - break; - } - - holder.mImage.setImageDrawable(image); - - return convertView; - } - - @NonNull - @Override - public Filter getFilter() { - return new SearchFilter(this, mHistoryModel); - } - - private synchronized void publishResults(@NonNull List list) { - mFilteredList.clear(); - mFilteredList.addAll(list); - notifyDataSetChanged(); - } - - private void clearSuggestions() { - Completable.create(new CompletableAction() { - @Override - public void onSubscribe(@NonNull CompletableSubscriber subscriber) { - mBookmarks.clear(); - mHistory.clear(); - mSuggestions.clear(); - subscriber.onComplete(); - } - }).subscribeOn(FILTER_SCHEDULER) - .observeOn(Schedulers.main()) - .subscribe(); - } - - private void combineResults(final @Nullable List bookmarkList, - final @Nullable List historyList, - final @Nullable List suggestionList) { - Single.create(new SingleAction>() { - @Override - public void onSubscribe(@NonNull SingleSubscriber> subscriber) { - List list = new ArrayList<>(5); - if (bookmarkList != null) { - mBookmarks.clear(); - mBookmarks.addAll(bookmarkList); - } - if (historyList != null) { - mHistory.clear(); - mHistory.addAll(historyList); - } - if (suggestionList != null) { - mSuggestions.clear(); - mSuggestions.addAll(suggestionList); - } - Iterator bookmark = mBookmarks.iterator(); - Iterator history = mHistory.iterator(); - Iterator suggestion = mSuggestions.listIterator(); - while (list.size() < MAX_SUGGESTIONS) { - if (!bookmark.hasNext() && !suggestion.hasNext() && !history.hasNext()) { - break; - } - if (bookmark.hasNext()) { - list.add(bookmark.next()); - } - if (suggestion.hasNext() && list.size() < MAX_SUGGESTIONS) { - list.add(suggestion.next()); - } - if (history.hasNext() && list.size() < MAX_SUGGESTIONS) { - list.add(history.next()); - } - } - - Collections.sort(list, mFilterComparator); - subscriber.onItem(list); - subscriber.onComplete(); - } - }).subscribeOn(FILTER_SCHEDULER) - .observeOn(Schedulers.main()) - .subscribe(new SingleOnSubscribe>() { - @Override - public void onItem(@Nullable List item) { - Preconditions.checkNonNull(item); - publishResults(item); - } - }); - } - - @NonNull - private Single> getBookmarksForQuery(@NonNull final String query) { - return Single.create(new SingleAction>() { - @Override - public void onSubscribe(@NonNull SingleSubscriber> subscriber) { - List bookmarks = new ArrayList<>(5); - int counter = 0; - for (int n = 0; n < mAllBookmarks.size(); n++) { - if (counter >= 5) { - break; - } - if (mAllBookmarks.get(n).getTitle().toLowerCase(Locale.getDefault()) - .startsWith(query)) { - bookmarks.add(mAllBookmarks.get(n)); - counter++; - } else if (mAllBookmarks.get(n).getUrl().contains(query)) { - bookmarks.add(mAllBookmarks.get(n)); - counter++; - } - } - subscriber.onItem(bookmarks); - subscriber.onComplete(); - } - }); - } - - @NonNull - private Single> getSuggestionsForQuery(@NonNull final String query) { - if (mSuggestionChoice == PreferenceManager.Suggestion.SUGGESTION_LEGWORK) { - return SuggestionsManager.createLegworkQueryObservable(query, mApplication); - } else if (mSuggestionChoice == PreferenceManager.Suggestion.SUGGESTION_DUCK) { - return SuggestionsManager.createDuckQueryObservable(query, mApplication); - } else { - return Single.empty(); - } - } - - private boolean shouldRequestNetwork() { - return !mIsIncognito && mSuggestionChoice != PreferenceManager.Suggestion.SUGGESTION_NONE; - } - - private static class SearchFilter extends Filter { - - @NonNull private final SuggestionsAdapter mSuggestionsAdapter; - @NonNull private final HistoryModel mHistoryModel; - - SearchFilter(@NonNull SuggestionsAdapter suggestionsAdapter, - @NonNull HistoryModel historyModel) { - mSuggestionsAdapter = suggestionsAdapter; - mHistoryModel = historyModel; - } - - @NonNull - @Override - protected FilterResults performFiltering(@Nullable CharSequence constraint) { - FilterResults results = new FilterResults(); - if (constraint == null || constraint.length() == 0) { - mSuggestionsAdapter.clearSuggestions(); - return results; - } - String query = constraint.toString().toLowerCase(Locale.getDefault()).trim(); - - if (mSuggestionsAdapter.shouldRequestNetwork() && !SuggestionsManager.isRequestInProgress()) { - mSuggestionsAdapter.getSuggestionsForQuery(query) - .subscribeOn(Schedulers.worker()) - .observeOn(Schedulers.main()) - .subscribe(new SingleOnSubscribe>() { - @Override - public void onItem(@Nullable List item) { - mSuggestionsAdapter.combineResults(null, null, item); - } - }); - } - - mSuggestionsAdapter.getBookmarksForQuery(query) - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.main()) - .subscribe(new SingleOnSubscribe>() { - @Override - public void onItem(@Nullable List item) { - mSuggestionsAdapter.combineResults(item, null, null); - } - }); - - mHistoryModel.findHistoryItemsContaining(query) - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.main()) - .subscribe(new SingleOnSubscribe>() { - @Override - public void onItem(@Nullable List item) { - mSuggestionsAdapter.combineResults(null, item, null); - } - }); - results.count = 1; - return results; - } - - @NonNull - @Override - public CharSequence convertResultToString(@NonNull Object resultValue) { - return ((HistoryItem) resultValue).getUrl(); - } - - @Override - protected void publishResults(CharSequence constraint, FilterResults results) { - mSuggestionsAdapter.combineResults(null, null, null); - } - } - - private static class ClearCacheRunnable implements Runnable { - - @NonNull - private final Application app; - - ClearCacheRunnable(@NonNull Application app) { - this.app = app; - } - - @Override - public void run() { - File dir = new File(app.getCacheDir().toString()); - String[] fileList = dir.list(new NameFilter()); - for (String fileName : fileList) { - File file = new File(dir.getPath() + fileName); - file.delete(); - } - } - - private static class NameFilter implements FilenameFilter { - - @Override - public boolean accept(File dir, @NonNull String filename) { - return filename.endsWith(CACHE_FILE_TYPE); - } - } - } - - private static class SuggestionsComparator implements Comparator { - - @Override - public int compare(@NonNull HistoryItem lhs, @NonNull HistoryItem rhs) { - if (lhs.getImageId() == rhs.getImageId()) return 0; - if (lhs.getImageId() == R.drawable.ic_bookmark) return -1; - if (rhs.getImageId() == R.drawable.ic_bookmark) return 1; - if (lhs.getImageId() == R.drawable.ic_history) return -1; - return 1; - } - } -} + private static final Scheduler FILTER_SCHEDULER = Schedulers.newSingleThreadedScheduler(); + + private static final String CACHE_FILE_TYPE = ".sgg"; + + private final List mFilteredList = new ArrayList<>(5); + + private final List mHistory = new ArrayList<>(5); + private final List mBookmarks = new ArrayList<>(5); + private final List mSuggestions = new ArrayList<>(5); + + private static final int MAX_SUGGESTIONS = 5; + + @NonNull private final Drawable mSearchDrawable; + @NonNull private final Drawable mHistoryDrawable; + @NonNull private final Drawable mBookmarkDrawable; + + private final Comparator mFilterComparator = new SuggestionsComparator(); + + @Inject BookmarkModel mBookmarkManager; + @Inject PreferenceManager mPreferenceManager; + @Inject HistoryModel mHistoryModel; + @Inject Application mApplication; + + private final List mAllBookmarks = new ArrayList<>(5); + + private final boolean mDarkTheme; + private boolean mIsIncognito = true; + @NonNull private final Context mContext; + + public SuggestionsAdapter(@NonNull Context context, boolean dark, boolean incognito) { + super(); + BrowserApp.getAppComponent().inject(this); + mContext = context; + mDarkTheme = dark || incognito; + mIsIncognito = incognito; + + 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); + } + + public void clearCache() { + // We don't need these cache files anymore + Schedulers.io().execute(new ClearCacheRunnable(mApplication)); + } + + public void refreshBookmarks() { + mBookmarkManager.getAllBookmarks() + .subscribeOn(Schedulers.io()) + .subscribe(new SingleOnSubscribe>() { + @Override + public void onItem(@Nullable List item) { + Preconditions.checkNonNull(item); + mAllBookmarks.clear(); + mAllBookmarks.addAll(item); + } + }); + } + + @Override + public int getCount() { + return mFilteredList.size(); + } + + @Nullable + @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 { + + SuggestionHolder(@NonNull View view) { + mTitle = view.findViewById(R.id.title); + mUrl = view.findViewById(R.id.url); + mImage = view.findViewById(R.id.suggestionIcon); + } + + @NonNull final ImageView mImage; + @NonNull final TextView mTitle; + @NonNull final TextView mUrl; + + } + + @Nullable + @Override + public View getView(int position, @Nullable 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()); + + if (mDarkTheme) { + holder.mTitle.setTextColor(Color.WHITE); + } + + Drawable image; + switch (web.getImageId()) { + case R.drawable.ic_bookmark: { + image = mBookmarkDrawable; + break; + } + case R.drawable.ic_search: { + image = mSearchDrawable; + break; + } + case R.drawable.ic_history: { + image = mHistoryDrawable; + break; + } + default: + image = mSearchDrawable; + break; + } + + holder.mImage.setImageDrawable(image); + + return convertView; + } + + @NonNull + @Override + public Filter getFilter() { + return new SearchFilter(this, mHistoryModel); + } + + private synchronized void publishResults(@NonNull List list) { + mFilteredList.clear(); + mFilteredList.addAll(list); + notifyDataSetChanged(); + } + + private void clearSuggestions() { + Completable.create(new CompletableAction() { + @Override + public void onSubscribe(@NonNull CompletableSubscriber subscriber) { + mBookmarks.clear(); + mHistory.clear(); + mSuggestions.clear(); + subscriber.onComplete(); + } + }).subscribeOn(FILTER_SCHEDULER) + .observeOn(Schedulers.main()) + .subscribe(); + } + + private void combineResults(final @Nullable List bookmarkList, + final @Nullable List historyList, + final @Nullable List suggestionList) { + Single.create(new SingleAction>() { + @Override + public void onSubscribe(@NonNull SingleSubscriber> subscriber) { + List list = new ArrayList<>(5); + if (bookmarkList != null) { + mBookmarks.clear(); + mBookmarks.addAll(bookmarkList); + } + if (historyList != null) { + mHistory.clear(); + mHistory.addAll(historyList); + } + if (suggestionList != null) { + mSuggestions.clear(); + mSuggestions.addAll(suggestionList); + } + Iterator bookmark = mBookmarks.iterator(); + Iterator history = mHistory.iterator(); + Iterator suggestion = mSuggestions.listIterator(); + while (list.size() < MAX_SUGGESTIONS) { + if (!bookmark.hasNext() && !suggestion.hasNext() && !history.hasNext()) { + break; + } + if (bookmark.hasNext()) { + list.add(bookmark.next()); + } + if (suggestion.hasNext() && list.size() < MAX_SUGGESTIONS) { + list.add(suggestion.next()); + } + if (history.hasNext() && list.size() < MAX_SUGGESTIONS) { + list.add(history.next()); + } + } + + Collections.sort(list, mFilterComparator); + subscriber.onItem(list); + subscriber.onComplete(); + } + }).subscribeOn(FILTER_SCHEDULER) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe>() { + @Override + public void onItem(@Nullable List item) { + Preconditions.checkNonNull(item); + publishResults(item); + } + }); + } + + @NonNull + private Single> getBookmarksForQuery(@NonNull final String query) { + return Single.create(new SingleAction>() { + @Override + public void onSubscribe(@NonNull SingleSubscriber> subscriber) { + List bookmarks = new ArrayList<>(5); + int counter = 0; + for (int n = 0; n < mAllBookmarks.size(); n++) { + if (counter >= 5) { + break; + } + if (mAllBookmarks.get(n).getTitle().toLowerCase(Locale.getDefault()) + .startsWith(query)) { + bookmarks.add(mAllBookmarks.get(n)); + counter++; + } else if (mAllBookmarks.get(n).getUrl().contains(query)) { + bookmarks.add(mAllBookmarks.get(n)); + counter++; + } + } + subscriber.onItem(bookmarks); + subscriber.onComplete(); + } + }); + } + + @NonNull + private Single> getSuggestionsForQuery(@NonNull final String query) { + return Single.empty(); + } + + private boolean shouldRequestNetwork() { + return !mIsIncognito; + } + + private static class SearchFilter extends Filter { + + @NonNull private final SuggestionsAdapter mSuggestionsAdapter; + @NonNull private final HistoryModel mHistoryModel; + + SearchFilter(@NonNull SuggestionsAdapter suggestionsAdapter, + @NonNull HistoryModel historyModel) { + mSuggestionsAdapter = suggestionsAdapter; + mHistoryModel = historyModel; + } + + @NonNull + @Override + protected FilterResults performFiltering(@Nullable CharSequence constraint) { + FilterResults results = new FilterResults(); + if (constraint == null || constraint.length() == 0) { + mSuggestionsAdapter.clearSuggestions(); + return results; + } + String query = constraint.toString().toLowerCase(Locale.getDefault()).trim(); + + if (mSuggestionsAdapter.shouldRequestNetwork()) { + mSuggestionsAdapter.getSuggestionsForQuery(query) + .subscribeOn(Schedulers.worker()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe>() { + @Override + public void onItem(@Nullable List item) { + mSuggestionsAdapter.combineResults(null, null, item); + } + }); + } + + mSuggestionsAdapter.getBookmarksForQuery(query) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe>() { + @Override + public void onItem(@Nullable List item) { + mSuggestionsAdapter.combineResults(item, null, null); + } + }); + + mHistoryModel.findHistoryItemsContaining(query) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe>() { + @Override + public void onItem(@Nullable List item) { + mSuggestionsAdapter.combineResults(null, item, null); + } + }); + results.count = 1; + return results; + } + + @NonNull + @Override + public CharSequence convertResultToString(@NonNull Object resultValue) { + return ((HistoryItem) resultValue).getUrl(); + } + + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + mSuggestionsAdapter.combineResults(null, null, null); + } + } + + private static class ClearCacheRunnable implements Runnable { + + @NonNull + private final Application app; + + ClearCacheRunnable(@NonNull Application app) { + this.app = app; + } + + @Override + public void run() { + File dir = new File(app.getCacheDir().toString()); + String[] fileList = dir.list(new NameFilter()); + for (String fileName : fileList) { + File file = new File(dir.getPath() + fileName); + file.delete(); + } + } + + private static class NameFilter implements FilenameFilter { + + @Override + public boolean accept(File dir, @NonNull String filename) { + return filename.endsWith(CACHE_FILE_TYPE); + } + } + } + + private static class SuggestionsComparator implements Comparator { + + @Override + public int compare(@NonNull HistoryItem lhs, @NonNull HistoryItem rhs) { + if (lhs.getImageId() == rhs.getImageId()) return 0; + if (lhs.getImageId() == R.drawable.ic_bookmark) return -1; + if (rhs.getImageId() == R.drawable.ic_bookmark) return 1; + if (lhs.getImageId() == R.drawable.ic_history) return -1; + return 1; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/purplei2p/lightning/search/SuggestionsManager.kt b/app/src/main/java/org/purplei2p/lightning/search/SuggestionsManager.kt deleted file mode 100644 index cdb6fbb..0000000 --- a/app/src/main/java/org/purplei2p/lightning/search/SuggestionsManager.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.purplei2p.lightning.search - -import org.purplei2p.lightning.database.HistoryItem -import org.purplei2p.lightning.search.suggestions.DuckSuggestionsModel -import org.purplei2p.lightning.search.suggestions.LegworkSuggestionsModel -import android.app.Application -import com.anthonycr.bonsai.Single -import com.anthonycr.bonsai.SingleAction - -internal object SuggestionsManager { - - @JvmStatic - @Volatile var isRequestInProgress: Boolean = false - - @JvmStatic - fun createLegworkQueryObservable(query: String, application: Application) = - Single.create(SingleAction> { subscriber -> - isRequestInProgress = true - val results = LegworkSuggestionsModel(application).fetchResults(query) - subscriber.onItem(results) - subscriber.onComplete() - isRequestInProgress = false - }) - - @JvmStatic - fun createDuckQueryObservable(query: String, application: Application) = - Single.create(SingleAction> { subscriber -> - isRequestInProgress = true - val results = DuckSuggestionsModel(application).fetchResults(query) - subscriber.onItem(results) - subscriber.onComplete() - isRequestInProgress = false - }) - -} diff --git a/app/src/main/java/org/purplei2p/lightning/search/engine/BaseSearchEngine.java b/app/src/main/java/org/purplei2p/lightning/search/engine/BaseSearchEngine.java new file mode 100644 index 0000000..b7333c0 --- /dev/null +++ b/app/src/main/java/org/purplei2p/lightning/search/engine/BaseSearchEngine.java @@ -0,0 +1,51 @@ +package org.purplei2p.lightning.search.engine; + +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; + +import org.purplei2p.lightning.utils.Preconditions; + +/** + * A class representative of a search engine. + *

+ * Contains three key pieces of information: + *

    + *
  • The icon shown for the search engine, should point to a local assets URL.
  • + *
  • The query URL for the search engine, the query will be appended to the end.
  • + *
  • The title string resource for the search engine.
  • + *
+*/ +public class BaseSearchEngine { + + @NonNull private final String mIconUrl; + @NonNull private final String mQueryUrl; + @StringRes private final int mTitleRes; + + BaseSearchEngine(@NonNull String iconUrl, + @NonNull String queryUrl, + @StringRes int titleRes) { + + Preconditions.checkNonNull(iconUrl); + Preconditions.checkNonNull(queryUrl); + + mIconUrl = iconUrl; + mQueryUrl = queryUrl; + mTitleRes = titleRes; + } + + @NonNull + public final String getIconUrl() { + return mIconUrl; + } + + @NonNull + public final String getQueryUrl() { + return mQueryUrl; + } + + @StringRes + public final int getTitleRes() { + return mTitleRes; + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/purplei2p/lightning/search/engine/BaseSearchEngine.kt b/app/src/main/java/org/purplei2p/lightning/search/engine/BaseSearchEngine.kt deleted file mode 100644 index 6a86cbe..0000000 --- a/app/src/main/java/org/purplei2p/lightning/search/engine/BaseSearchEngine.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.purplei2p.lightning.search.engine - -import android.support.annotation.StringRes - -/** - * A class representative of a search engine. - * - * Contains three key pieces of information: - * * The icon shown for the search engine, should point to a local assets URL. - * * The query URL for the search engine, the query will be appended to the end. - * * The title string resource for the search engine. - */ -open class BaseSearchEngine internal constructor(val iconUrl: String, - val queryUrl: String, - @StringRes val titleRes: Int) diff --git a/app/src/main/java/org/purplei2p/lightning/search/engine/CustomSearch.java b/app/src/main/java/org/purplei2p/lightning/search/engine/CustomSearch.java new file mode 100644 index 0000000..3d030fa --- /dev/null +++ b/app/src/main/java/org/purplei2p/lightning/search/engine/CustomSearch.java @@ -0,0 +1,16 @@ +package org.purplei2p.lightning.search.engine; + +import android.support.annotation.NonNull; + +import org.purplei2p.lightning.R; + +/** + * A custom search engine. + */ +public class CustomSearch extends BaseSearchEngine { + + public CustomSearch(@NonNull String queryUrl) { + super("file:///android_asset/lightning.png", queryUrl, R.string.search_engine_custom); + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/purplei2p/lightning/search/engine/CustomSearch.kt b/app/src/main/java/org/purplei2p/lightning/search/engine/CustomSearch.kt deleted file mode 100644 index a889bf7..0000000 --- a/app/src/main/java/org/purplei2p/lightning/search/engine/CustomSearch.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.purplei2p.lightning.search.engine - -import org.purplei2p.lightning.R - -/** - * A custom search engine. - */ -class CustomSearch(queryUrl: String) : BaseSearchEngine( - "file:///android_asset/lightning.png", - queryUrl, - R.string.search_engine_custom -) diff --git a/app/src/main/java/org/purplei2p/lightning/search/engine/DuckLiteSearch.java b/app/src/main/java/org/purplei2p/lightning/search/engine/DuckLiteSearch.java new file mode 100644 index 0000000..bf5dd92 --- /dev/null +++ b/app/src/main/java/org/purplei2p/lightning/search/engine/DuckLiteSearch.java @@ -0,0 +1,17 @@ +package org.purplei2p.lightning.search.engine; + +import org.purplei2p.lightning.R; +import org.purplei2p.lightning.constant.Constants; + +/** + * The DuckDuckGo Lite search engine. + *

+ * See https://duckduckgo.com/assets/logo_homepage.normal.v101.png for the icon. + */ +public class DuckLiteSearch extends BaseSearchEngine { + + public DuckLiteSearch() { + super("file:///android_asset/duckduckgo.png", Constants.DUCK_LITE_SEARCH, R.string.search_engine_duckduckgo_lite); + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/purplei2p/lightning/search/engine/DuckLiteSearch.kt b/app/src/main/java/org/purplei2p/lightning/search/engine/DuckLiteSearch.kt deleted file mode 100644 index 32dc121..0000000 --- a/app/src/main/java/org/purplei2p/lightning/search/engine/DuckLiteSearch.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.purplei2p.lightning.search.engine - -import org.purplei2p.lightning.R -import org.purplei2p.lightning.constant.Constants - -/** - * The DuckDuckGo Lite search engine. - * - * See https://duckduckgo.com/assets/logo_homepage.normal.v101.png for the icon. - */ -class DuckLiteSearch : BaseSearchEngine( - "file:///android_asset/duckduckgo.png", - Constants.DUCK_LITE_SEARCH, - R.string.search_engine_duckduckgo_lite -) diff --git a/app/src/main/java/org/purplei2p/lightning/search/engine/DuckSearch.java b/app/src/main/java/org/purplei2p/lightning/search/engine/DuckSearch.java new file mode 100644 index 0000000..18bfbda --- /dev/null +++ b/app/src/main/java/org/purplei2p/lightning/search/engine/DuckSearch.java @@ -0,0 +1,17 @@ +package org.purplei2p.lightning.search.engine; + +import org.purplei2p.lightning.R; +import org.purplei2p.lightning.constant.Constants; + +/** + * The DuckDuckGo search engine. + *

+ * See https://duckduckgo.com/assets/logo_homepage.normal.v101.png for the icon. + */ +public class DuckSearch extends BaseSearchEngine { + + public DuckSearch() { + super("file:///android_asset/duckduckgo.png", Constants.DUCK_SEARCH, R.string.search_engine_duckduckgo); + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/purplei2p/lightning/search/engine/DuckSearch.kt b/app/src/main/java/org/purplei2p/lightning/search/engine/DuckSearch.kt deleted file mode 100644 index ca323fe..0000000 --- a/app/src/main/java/org/purplei2p/lightning/search/engine/DuckSearch.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.purplei2p.lightning.search.engine - -import org.purplei2p.lightning.R -import org.purplei2p.lightning.constant.Constants - -/** - * The DuckDuckGo search engine. - * - * See https://duckduckgo.com/assets/logo_homepage.normal.v101.png for the icon. - */ -class DuckSearch : BaseSearchEngine( - "file:///android_asset/duckduckgo.png", - Constants.DUCK_SEARCH, - R.string.search_engine_duckduckgo -) diff --git a/app/src/main/java/org/purplei2p/lightning/search/engine/LegworkSearch.java b/app/src/main/java/org/purplei2p/lightning/search/engine/LegworkSearch.java new file mode 100644 index 0000000..35f7591 --- /dev/null +++ b/app/src/main/java/org/purplei2p/lightning/search/engine/LegworkSearch.java @@ -0,0 +1,17 @@ +package org.purplei2p.lightning.search.engine; + +import org.purplei2p.lightning.R; +import org.purplei2p.lightning.constant.Constants; + +/** + * The Legwork.I2P search engine. + *

+ * See http://legwork.i2p/env/grafics/LegworkLogo_200.png for the icon. +*/ +public class LegworkSearch extends BaseSearchEngine { + + public LegworkSearch() { + super("file:///android_asset/legwork.png", Constants.LEGWORK_SEARCH, R.string.search_engine_legwork); + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/purplei2p/lightning/search/engine/LegworkSearch.kt b/app/src/main/java/org/purplei2p/lightning/search/engine/LegworkSearch.kt deleted file mode 100644 index 4d73c24..0000000 --- a/app/src/main/java/org/purplei2p/lightning/search/engine/LegworkSearch.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.purplei2p.lightning.search.engine - -import org.purplei2p.lightning.R -import org.purplei2p.lightning.constant.Constants - -/** - * The Legwork.I2P search engine. - * - * See http://legwork.i2p/env/grafics/LegworkLogo_200.png for the icon. - */ -class LegworkSearch : BaseSearchEngine( - "file:///android_asset/legwork.png", - Constants.LEGWORK_SEARCH, - R.string.search_engine_legwork -) diff --git a/app/src/main/java/org/purplei2p/lightning/search/suggestions/BaseSuggestionsModel.java b/app/src/main/java/org/purplei2p/lightning/search/suggestions/BaseSuggestionsModel.java deleted file mode 100644 index b8a2bc3..0000000 --- a/app/src/main/java/org/purplei2p/lightning/search/suggestions/BaseSuggestionsModel.java +++ /dev/null @@ -1,165 +0,0 @@ -package org.purplei2p.lightning.search.suggestions; - -import android.app.Application; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.TextUtils; -import android.util.Log; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -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 org.purplei2p.lightning.database.HistoryItem; -import org.purplei2p.lightning.utils.FileUtils; -import org.purplei2p.lightning.utils.Utils; -import okhttp3.Cache; -import okhttp3.CacheControl; -import okhttp3.Interceptor; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; - -/** - * The base search suggestions API. Provides common - * fetching and caching functionality for each potential - * suggestions provider. - */ -public abstract class BaseSuggestionsModel { - - private static final String TAG = "BaseSuggestionsModel"; - - static final int MAX_RESULTS = 5; - private static final long INTERVAL_DAY = TimeUnit.DAYS.toSeconds(1); - @NonNull private static final String DEFAULT_LANGUAGE = "en"; - - @NonNull private final OkHttpClient mHttpClient; - @NonNull private final CacheControl mCacheControl; - @NonNull private final String mEncoding; - @NonNull private final String mLanguage; - - /** - * Create a URL for the given query in the given language. - * - * @param query the query that was made. - * @param language the locale of the user. - * @return should return a URL that can be fetched using a GET. - */ - @NonNull - protected abstract String createQueryUrl(@NonNull String query, @NonNull String language); - - /** - * Parse the results of an input stream into a list of {@link HistoryItem}. - * - * @param inputStream the raw input to parse. - * @param results the list to populate. - * @throws Exception throw an exception if anything goes wrong. - */ - protected abstract void parseResults(@NonNull InputStream inputStream, @NonNull List results) throws Exception; - - BaseSuggestionsModel(@NonNull Application application, @NonNull String encoding) { - mEncoding = encoding; - mLanguage = getLanguage(); - File suggestionsCache = new File(application.getCacheDir(), "suggestion_responses"); - mHttpClient = new OkHttpClient.Builder() - .cache(new Cache(suggestionsCache, FileUtils.megabytesToBytes(1))) - .addNetworkInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR) - .build(); - mCacheControl = new CacheControl.Builder().maxStale(1, TimeUnit.DAYS).build(); - } - - /** - * Retrieves the results for a query. - * - * @param rawQuery the raw query to retrieve the results for. - * @return a list of history items for the query. - */ - @NonNull - public final List fetchResults(@NonNull final String rawQuery) { - List filter = new ArrayList<>(5); - - String query; - try { - query = URLEncoder.encode(rawQuery, mEncoding); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "Unable to encode the URL", e); - - return filter; - } - - InputStream inputStream = downloadSuggestionsForQuery(query, mLanguage); - if (inputStream == null) { - // There are no suggestions for this query, return an empty list. - return filter; - } - try { - parseResults(inputStream, filter); - } catch (Exception e) { - Log.e(TAG, "Unable to parse results", e); - } finally { - Utils.close(inputStream); - } - - return filter; - } - - /** - * This method downloads the search suggestions for the specific query. - * NOTE: This is a blocking operation, do not fetchResults on the UI thread. - * - * @param query the query to get suggestions for - * @return the cache file containing the suggestions - */ - @Nullable - private InputStream downloadSuggestionsForQuery(@NonNull String query, @NonNull String language) { - String queryUrl = createQueryUrl(query, language); - - try { - URL url = new URL(queryUrl); - - // OkHttp automatically gzips requests - Request suggestionsRequest = new Request.Builder().url(url) - .addHeader("Accept-Charset", mEncoding) - .cacheControl(mCacheControl) - .build(); - - Response suggestionsResponse = mHttpClient.newCall(suggestionsRequest).execute(); - - ResponseBody responseBody = suggestionsResponse.body(); - return responseBody != null ? responseBody.byteStream() : null; - } catch (IOException exception) { - Log.e(TAG, "Problem getting search suggestions", exception); - } - - return null; - } - - @NonNull - private static String getLanguage() { - String language = Locale.getDefault().getLanguage(); - if (TextUtils.isEmpty(language)) { - language = DEFAULT_LANGUAGE; - } - return language; - } - - @NonNull - private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() { - @Override - public Response intercept(@NonNull Chain chain) throws IOException { - Response originalResponse = chain.proceed(chain.request()); - return originalResponse.newBuilder() - .header("cache-control", "max-age=" + INTERVAL_DAY + ", max-stale=" + INTERVAL_DAY) - .build(); - } - }; - -} diff --git a/app/src/main/java/org/purplei2p/lightning/search/suggestions/DuckSuggestionsModel.java b/app/src/main/java/org/purplei2p/lightning/search/suggestions/DuckSuggestionsModel.java deleted file mode 100644 index f1f8067..0000000 --- a/app/src/main/java/org/purplei2p/lightning/search/suggestions/DuckSuggestionsModel.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.purplei2p.lightning.search.suggestions; - -import android.app.Application; -import android.support.annotation.NonNull; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.io.InputStream; -import java.util.List; - -import org.purplei2p.lightning.R; -import org.purplei2p.lightning.database.HistoryItem; -import org.purplei2p.lightning.utils.FileUtils; - -/** - * The search suggestions provider for the DuckDuckGo search engine. - */ -public final class DuckSuggestionsModel extends BaseSuggestionsModel { - - @NonNull private static final String ENCODING = "UTF-8"; - @NonNull private final String mSearchSubtitle; - - public DuckSuggestionsModel(@NonNull Application application) { - super(application, ENCODING); - mSearchSubtitle = application.getString(R.string.suggestion); - } - - @NonNull - @Override - protected String createQueryUrl(@NonNull String query, @NonNull String language) { - return "https://duckduckgo.com/ac/?q=" + query; - } - - @Override - protected void parseResults(@NonNull InputStream inputStream, @NonNull List results) throws Exception { - String content = FileUtils.readStringFromStream(inputStream, ENCODING); - JSONArray jsonArray = new JSONArray(content); - int counter = 0; - for (int n = 0, size = jsonArray.length(); n < size; n++) { - JSONObject object = jsonArray.getJSONObject(n); - String suggestion = object.getString("phrase"); - results.add(new HistoryItem(mSearchSubtitle + " \"" + suggestion + '"', - suggestion, R.drawable.ic_search)); - counter++; - if (counter >= MAX_RESULTS) { - break; - } - } - } - -} diff --git a/app/src/main/java/org/purplei2p/lightning/search/suggestions/LegworkSuggestionsModel.java b/app/src/main/java/org/purplei2p/lightning/search/suggestions/LegworkSuggestionsModel.java deleted file mode 100644 index 30c04c3..0000000 --- a/app/src/main/java/org/purplei2p/lightning/search/suggestions/LegworkSuggestionsModel.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.purplei2p.lightning.search.suggestions; - -import android.app.Application; -import android.support.annotation.NonNull; - -import org.json.JSONArray; - -import java.io.InputStream; -import java.util.List; - -import org.purplei2p.lightning.R; -import org.purplei2p.lightning.database.HistoryItem; -import org.purplei2p.lightning.utils.FileUtils; - -/** - * The search suggestions provider for the DuckDuckGo search engine. - */ -public final class LegworkSuggestionsModel extends BaseSuggestionsModel { - - @NonNull private static final String ENCODING = "UTF-8"; - @NonNull private final String mSearchSubtitle; - - public LegworkSuggestionsModel(@NonNull Application application) { - super(application, ENCODING); - mSearchSubtitle = application.getString(R.string.suggestion); - } - - @NonNull - @Override - protected String createQueryUrl(@NonNull String query, @NonNull String language) { - return "http://legwork.i2p/suggest.json?query=" + query; - } - - @Override - protected void parseResults(@NonNull InputStream inputStream, @NonNull List results) throws Exception { - String content = FileUtils.readStringFromStream(inputStream, ENCODING); - JSONArray respArray = new JSONArray(content); - JSONArray jsonArray = respArray.getJSONArray(1); - int counter = 0; - for (int n = 0, size = jsonArray.length(); n < size; n++) { - String suggestion = jsonArray.getString(n); - results.add(new HistoryItem(mSearchSubtitle + " \"" + suggestion + '"', - suggestion, R.drawable.ic_search)); - counter++; - if (counter >= MAX_RESULTS) { - break; - } - } - } - -} diff --git a/app/src/main/java/org/purplei2p/lightning/settings/fragment/GeneralSettingsFragment.java b/app/src/main/java/org/purplei2p/lightning/settings/fragment/GeneralSettingsFragment.java index 7c2fad1..09aa055 100644 --- a/app/src/main/java/org/purplei2p/lightning/settings/fragment/GeneralSettingsFragment.java +++ b/app/src/main/java/org/purplei2p/lightning/settings/fragment/GeneralSettingsFragment.java @@ -38,8 +38,6 @@ import org.purplei2p.lightning.utils.ProxyUtils; import org.purplei2p.lightning.utils.ThemeUtils; import org.purplei2p.lightning.utils.Utils; -import static org.purplei2p.lightning.preference.PreferenceManager.Suggestion; - public class GeneralSettingsFragment extends LightningPreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { private static final String SETTINGS_PROXY = "proxy"; @@ -50,11 +48,10 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme private static final String SETTINGS_DOWNLOAD = "download"; private static final String SETTINGS_HOME = "home"; private static final String SETTINGS_SEARCHENGINE = "search"; - private static final String SETTINGS_SUGGESTIONS = "suggestions_choice"; private Activity mActivity; private static final int API = android.os.Build.VERSION.SDK_INT; private CharSequence[] mProxyChoices; - private Preference proxy, useragent, downloadloc, home, searchengine, searchsSuggestions; + private Preference proxy, useragent, downloadloc, home, searchengine; private String mDownloadLocation; private int mAgentChoice; private String mHomepage; @@ -80,7 +77,6 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme downloadloc = findPreference(SETTINGS_DOWNLOAD); home = findPreference(SETTINGS_HOME); searchengine = findPreference(SETTINGS_SEARCHENGINE); - searchsSuggestions = findPreference(SETTINGS_SUGGESTIONS); CheckBoxPreference cbImages = (CheckBoxPreference) findPreference(SETTINGS_IMAGES); CheckBoxPreference cbJsScript = (CheckBoxPreference) findPreference(SETTINGS_JAVASCRIPT); @@ -90,7 +86,6 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme useragent.setOnPreferenceClickListener(this); downloadloc.setOnPreferenceClickListener(this); home.setOnPreferenceClickListener(this); - searchsSuggestions.setOnPreferenceClickListener(this); searchengine.setOnPreferenceClickListener(this); cbImages.setOnPreferenceChangeListener(this); cbJsScript.setOnPreferenceChangeListener(this); @@ -114,18 +109,6 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme downloadloc.setSummary(mDownloadLocation); - switch (mPreferenceManager.getSearchSuggestionChoice()) { - case SUGGESTION_LEGWORK: - searchsSuggestions.setSummary(R.string.powered_by_legwork); - break; - case SUGGESTION_DUCK: - searchsSuggestions.setSummary(R.string.powered_by_duck); - break; - case SUGGESTION_NONE: - searchsSuggestions.setSummary(R.string.search_suggestions_off); - break; - } - if (mHomepage.contains(Constants.SCHEME_HOMEPAGE)) { home.setSummary(getResources().getString(R.string.action_homepage)); @@ -341,49 +324,6 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme BrowserDialog.setDialogSize(mActivity, dialog); } - private void suggestionsDialog() { - AlertDialog.Builder picker = new AlertDialog.Builder(mActivity); - picker.setTitle(getResources().getString(R.string.search_suggestions)); - - int currentChoice = 2; - - switch (mPreferenceManager.getSearchSuggestionChoice()) { - case SUGGESTION_LEGWORK: - currentChoice = 0; - break; - case SUGGESTION_DUCK: - currentChoice = 1; - break; - case SUGGESTION_NONE: - currentChoice = 2; - break; - } - - picker.setSingleChoiceItems(R.array.suggestions, currentChoice, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case 0: - mPreferenceManager.setSearchSuggestionChoice(Suggestion.SUGGESTION_LEGWORK); - searchsSuggestions.setSummary(R.string.powered_by_legwork); - break; - case 1: - mPreferenceManager.setSearchSuggestionChoice(Suggestion.SUGGESTION_DUCK); - searchsSuggestions.setSummary(R.string.powered_by_duck); - break; - case 2: - mPreferenceManager.setSearchSuggestionChoice(Suggestion.SUGGESTION_NONE); - searchsSuggestions.setSummary(R.string.search_suggestions_off); - break; - } - } - }); - picker.setPositiveButton(getResources().getString(R.string.action_ok), null); - Dialog dialog = picker.show(); - BrowserDialog.setDialogSize(mActivity, dialog); - } - private void homePicker() { String currentHomepage; mHomepage = mPreferenceManager.getHomepage(); @@ -539,9 +479,6 @@ public class GeneralSettingsFragment extends LightningPreferenceFragment impleme case SETTINGS_SEARCHENGINE: searchDialog(); return true; - case SETTINGS_SUGGESTIONS: - suggestionsDialog(); - return true; default: return false; } diff --git a/app/src/main/res/layout/bookmark_drawer.xml b/app/src/main/res/layout/bookmark_drawer.xml index 265a23d..25035ff 100644 --- a/app/src/main/res/layout/bookmark_drawer.xml +++ b/app/src/main/res/layout/bookmark_drawer.xml @@ -100,26 +100,5 @@ android:paddingTop="4dp" app:srcCompat="@drawable/ic_action_star"/> - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu-large/incognito.xml b/app/src/main/res/menu-large/incognito.xml index 19afb5f..784ea1e 100644 --- a/app/src/main/res/menu-large/incognito.xml +++ b/app/src/main/res/menu-large/incognito.xml @@ -33,8 +33,5 @@ - \ No newline at end of file diff --git a/app/src/main/res/menu-large/main.xml b/app/src/main/res/menu-large/main.xml index d07de47..f344b92 100644 --- a/app/src/main/res/menu-large/main.xml +++ b/app/src/main/res/menu-large/main.xml @@ -65,9 +65,6 @@ - diff --git a/app/src/main/res/menu-xlarge/incognito.xml b/app/src/main/res/menu-xlarge/incognito.xml index 19afb5f..784ea1e 100644 --- a/app/src/main/res/menu-xlarge/incognito.xml +++ b/app/src/main/res/menu-xlarge/incognito.xml @@ -33,8 +33,5 @@ - \ No newline at end of file diff --git a/app/src/main/res/menu-xlarge/main.xml b/app/src/main/res/menu-xlarge/main.xml index d07de47..f344b92 100644 --- a/app/src/main/res/menu-xlarge/main.xml +++ b/app/src/main/res/menu-xlarge/main.xml @@ -65,9 +65,6 @@ - diff --git a/app/src/main/res/menu/incognito.xml b/app/src/main/res/menu/incognito.xml index f836164..44e8079 100644 --- a/app/src/main/res/menu/incognito.xml +++ b/app/src/main/res/menu/incognito.xml @@ -16,9 +16,5 @@ - - \ No newline at end of file diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml index e9be4dc..96e0eca 100644 --- a/app/src/main/res/menu/main.xml +++ b/app/src/main/res/menu/main.xml @@ -47,9 +47,6 @@ - diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 015ac22..259d210 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -119,10 +119,6 @@ Сообщение сервера: %s Имя пользователя Пароль - Подсказки поиска - Используя Legwork - Используя DuckDuckGo - Без поисковых подсказок HTTP прокси Нет @@ -170,7 +166,6 @@ Закрыть остальные вкладки Блокировать куки со сторонних сайтов Включить цветной режим - Режим чтения Загрузка… Не удалось загрузить страницу. Snacktory diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 4cbe6e8..8001138 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -19,12 +19,6 @@ @string/action_webpage - - @string/powered_by_legwork - @string/powered_by_duck - @string/search_suggestions_off - - @string/light_theme @string/dark_theme diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 436e761..ba998e5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -119,10 +119,6 @@ Server message: %s Username Password - Search suggestions - Powered by Legwork - Powered by DuckDuckGo - No search suggestions HTTP proxy None @@ -170,7 +166,6 @@ Close other tabs Block 3rd party cookies Enable color mode - Reader mode Loading… Couldn\'t load anything from the page. Snacktory diff --git a/app/src/main/res/xml/preference_general.xml b/app/src/main/res/xml/preference_general.xml index f7e6482..8b08825 100644 --- a/app/src/main/res/xml/preference_general.xml +++ b/app/src/main/res/xml/preference_general.xml @@ -29,8 +29,5 @@ - \ No newline at end of file diff --git a/build.gradle b/build.gradle index deed3e9..195593b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,10 @@ buildscript { - ext.kotlin_version = '1.1.3' repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.3' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -23,6 +21,6 @@ ext { targetSdkVersion = 26 buildToolsVersion = '26.0.3' - versionName = '4.5.1' - versionCode = 96 + versionName = '0.1.2' + versionCode = 3 }