diff --git a/.travis.yml b/.travis.yml index 8f769e4..193142d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,8 @@ language: android sudo: false android: components: + - build-tools-23.0.1 - build-tools-22.0.1 - - build-tools-23.0.0 - android-23 - android-22 - extra-android-support @@ -17,3 +17,4 @@ before_install: install: - ./gradlew script: + - ./gradlew assembleRelease diff --git a/README.md b/README.md index 5c76c02..411e2cc 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,11 @@ ####Download * [Download APK from here](https://github.com/anthonycr/Lightning-Browser/releases) -* [Download from Google Play](https://play.google.com/store/apps/details?id=acr.browser.barebones) +* [Download from F-Droid](https://f-droid.org/repository/browse/?fdfilter=lightning&fdid=acr.browser.lightning) + +* [Download Free from Google Play](https://play.google.com/store/apps/details?id=acr.browser.barebones) + +* [Download Paid from Google Play](https://play.google.com/store/apps/details?id=acr.browser.lightning) ####Master Branch * [![Build Status](https://travis-ci.org/anthonycr/Lightning-Browser.svg?branch=master)](https://travis-ci.org/anthonycr/Lightning-Browser) @@ -46,12 +50,14 @@ * Please add translations/translation fixes as you see need ####Contributing +* [The Trello Board](https://trello.com/b/Gwjx8MC3/lightning-browser) * Contributions are always welcome * If you want a feature and can code, feel free to fork and add the change yourself and make a pull request * PLEASE use the ````dev```` branch when contributing as the ````master```` branch is supposed to be for stable builds. I will not reject your pull request if you make it on master, but it will annoy me and make my life harder. * Code Style - * Standard Java camel case - * Member variables are preceded with an 'm' + * Hungarian Notation + * Prefix member variables with 'm' + * Prefix static member variables with 's' * Use 4 spaces instead of a tab (\t) ####Setting Up the Project diff --git a/app/build.gradle b/app/build.gradle index 8453c1a..70d3e98 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,6 @@ apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt' +apply plugin: 'com.getkeepsafe.dexcount' android { compileSdkVersion 23 @@ -7,7 +8,7 @@ android { defaultConfig { minSdkVersion 14 targetSdkVersion 23 - versionName "4.2.0a" + versionName "4.2.3.1" } sourceSets { lightningPlus.setRoot('src/LightningPlus') @@ -30,40 +31,64 @@ android { lightningPlus { buildConfigField "boolean", "FULL_VERSION", "true" applicationId "acr.browser.lightning" - versionCode 81 + versionCode 85 } lightningLite { buildConfigField "boolean", "FULL_VERSION", "false" applicationId "acr.browser.barebones" - versionCode 82 + versionCode 86 } } lintOptions { abortOnError true } + packagingOptions { + exclude '.readme' + } +} + +dexcount { + includeClasses = false + includeFieldCount = false + printAsTree = true + orderByMethodCount = true + verbose = false } dependencies { - compile 'com.android.support:palette-v7:23.0.1' - compile 'com.android.support:appcompat-v7:23.0.1' - compile 'com.android.support:design:23.0.1' - compile 'com.android.support:recyclerview-v7:23.0.1' + // support libraries + compile 'com.android.support:palette-v7:23.1.0' + compile 'com.android.support:appcompat-v7:23.1.0' + compile 'com.android.support:design:23.1.0' + compile 'com.android.support:recyclerview-v7:23.1.0' + + // html parsing fo reading mode compile 'org.jsoup:jsoup:1.8.3' + + // event bus compile 'com.squareup:otto:1.3.8' + + // dependency injection compile 'com.google.dagger:dagger:2.0.1' apt 'com.google.dagger:dagger-compiler:2.0.1' + + // view binding compile 'com.jakewharton:butterknife:7.0.1' - // Only Lightning Plus needs the proxy libraries - compile 'net.i2p.android:client:0.7' + // permissions + compile 'com.anthonycr.grant:permissions:1.0' + // proxy support + compile 'net.i2p.android:client:0.7' // Use the following code to update the libnetcipher submodule // git submodule foreach git reset --hard // git submodule update --remote compile(project(':libnetcipher')) + // memory leak analysis debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' provided 'javax.annotation:jsr250-api:1.0' + } \ No newline at end of file diff --git a/app/proguard-project.txt b/app/proguard-project.txt index edc15dc..40cddda 100644 --- a/app/proguard-project.txt +++ b/app/proguard-project.txt @@ -50,6 +50,18 @@ public static *** i(...); } +-keep class butterknife.** { *; } +-dontwarn butterknife.internal.** +-keep class **$$ViewBinder { *; } + +-keepclasseswithmembernames class * { + @butterknife.* ; +} + +-keepclasseswithmembernames class * { + @butterknife.* ; +} + # this will fix a force close in ReadingActivity -keep public class org.jsoup.** { public *; diff --git a/app/src/LightningLite/java/acr/browser/lightning/utils/ProxyUtils.java b/app/src/LightningLite/java/acr/browser/lightning/utils/ProxyUtils.java index d49f9f2..ed8211b 100644 --- a/app/src/LightningLite/java/acr/browser/lightning/utils/ProxyUtils.java +++ b/app/src/LightningLite/java/acr/browser/lightning/utils/ProxyUtils.java @@ -6,10 +6,13 @@ import android.content.DialogInterface; import android.support.v7.app.AlertDialog; import android.util.Log; +import com.squareup.otto.Bus; + import net.i2p.android.ui.I2PAndroidHelper; import acr.browser.lightning.R; import acr.browser.lightning.app.BrowserApp; +import acr.browser.lightning.bus.BrowserEvents; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.preference.PreferenceManager; import info.guardianproject.netcipher.proxy.OrbotHelper; @@ -26,8 +29,11 @@ public class ProxyUtils { private final PreferenceManager mPreferences; private static ProxyUtils mInstance; + private final Bus mEventBus; + private ProxyUtils(Context context) { - mPreferences = PreferenceManager.getInstance(); + mPreferences = BrowserApp.getAppComponent().getPreferenceManager(); + mEventBus = BrowserApp.getAppComponent().getBus(); mI2PHelper = new I2PAndroidHelper(context.getApplicationContext()); } @@ -143,13 +149,13 @@ public class ProxyUtils { } - public boolean isProxyReady(Activity activity) { + public boolean isProxyReady() { if (mPreferences.getProxyChoice() == Constants.PROXY_I2P) { if (!mI2PHelper.isI2PAndroidRunning()) { - Utils.showSnackbar(activity, R.string.i2p_not_running); + mEventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.i2p_not_running)); return false; } else if (!mI2PHelper.areTunnelsActive()) { - Utils.showSnackbar(activity, R.string.i2p_tunnels_not_ready); + mEventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.i2p_tunnels_not_ready)); return false; } } diff --git a/app/src/LightningPlus/java/acr/browser/lightning/utils/ProxyUtils.java b/app/src/LightningPlus/java/acr/browser/lightning/utils/ProxyUtils.java index d49f9f2..f450692 100644 --- a/app/src/LightningPlus/java/acr/browser/lightning/utils/ProxyUtils.java +++ b/app/src/LightningPlus/java/acr/browser/lightning/utils/ProxyUtils.java @@ -10,6 +10,7 @@ import net.i2p.android.ui.I2PAndroidHelper; import acr.browser.lightning.R; import acr.browser.lightning.app.BrowserApp; +import acr.browser.lightning.bus.BrowserEvents; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.preference.PreferenceManager; import info.guardianproject.netcipher.proxy.OrbotHelper; @@ -27,7 +28,7 @@ public class ProxyUtils { private static ProxyUtils mInstance; private ProxyUtils(Context context) { - mPreferences = PreferenceManager.getInstance(); + mPreferences = BrowserApp.getAppComponent().getPreferenceManager(); mI2PHelper = new I2PAndroidHelper(context.getApplicationContext()); } @@ -143,13 +144,15 @@ public class ProxyUtils { } - public boolean isProxyReady(Activity activity) { + public boolean isProxyReady() { if (mPreferences.getProxyChoice() == Constants.PROXY_I2P) { if (!mI2PHelper.isI2PAndroidRunning()) { - Utils.showSnackbar(activity, R.string.i2p_not_running); + BrowserApp.getAppComponent().getBus() + .post(new BrowserEvents.ShowSnackBarMessage(R.string.i2p_not_running)); return false; } else if (!mI2PHelper.areTunnelsActive()) { - Utils.showSnackbar(activity, R.string.i2p_tunnels_not_ready); + BrowserApp.getAppComponent().getBus() + .post(new BrowserEvents.ShowSnackBarMessage(R.string.i2p_tunnels_not_ready)); return false; } } @@ -200,7 +203,7 @@ public class ProxyUtils { break; case Constants.PROXY_I2P: - I2PAndroidHelper ih = new I2PAndroidHelper(activity.getApplicationContext()); + I2PAndroidHelper ih = new I2PAndroidHelper(BrowserApp.getAppContext()); if (!ih.isI2PAndroidInstalled()) { choice = Constants.NO_PROXY; ih.promptToInstall(activity); diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c3d47ec..7e0481e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -47,6 +47,18 @@ + + + + + + + + + + + + @@ -88,7 +100,6 @@ - diff --git a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java index 39a0812..136b843 100644 --- a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java @@ -17,15 +17,8 @@ import android.content.Intent; import android.content.IntentFilter; import android.database.sqlite.SQLiteException; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.Paint; import android.graphics.PorterDuff; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.media.MediaPlayer; @@ -35,24 +28,22 @@ import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.provider.MediaStore; +import android.support.annotation.ColorInt; import android.support.annotation.IdRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.FragmentManager; import android.support.v4.content.ContextCompat; import android.support.v4.view.GravityCompat; -import android.support.v4.view.ViewCompat; import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout.DrawerListener; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.graphics.Palette; import android.support.v7.graphics.drawable.DrawerArrowDrawable; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; @@ -76,7 +67,6 @@ import android.webkit.ValueCallback; import android.webkit.WebChromeClient.CustomViewCallback; import android.webkit.WebIconDatabase; import android.webkit.WebView; -import android.webkit.WebView.HitTestResult; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; @@ -85,7 +75,6 @@ import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; @@ -95,11 +84,7 @@ import com.squareup.otto.Bus; import com.squareup.otto.Subscribe; import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import javax.inject.Inject; @@ -107,18 +92,20 @@ import acr.browser.lightning.R; import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.bus.BookmarkEvents; import acr.browser.lightning.bus.BrowserEvents; +import acr.browser.lightning.bus.NavigationEvents; +import acr.browser.lightning.bus.TabEvents; import acr.browser.lightning.constant.BookmarkPage; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.constant.HistoryPage; -import acr.browser.lightning.controller.BrowserController; +import acr.browser.lightning.controller.UIController; import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.HistoryDatabase; -import acr.browser.lightning.dialog.BookmarksDialogBuilder; -import acr.browser.lightning.object.ClickHandler; +import acr.browser.lightning.dialog.LightningDialogBuilder; +import acr.browser.lightning.fragment.BookmarksFragment; +import acr.browser.lightning.fragment.TabsFragment; import acr.browser.lightning.object.SearchAdapter; -import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.receiver.NetworkReceiver; -import acr.browser.lightning.utils.PermissionsManager; +import com.anthonycr.grant.PermissionsManager; import acr.browser.lightning.utils.ProxyUtils; import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.UrlUtils; @@ -129,7 +116,7 @@ import acr.browser.lightning.view.LightningView; import butterknife.Bind; import butterknife.ButterKnife; -public abstract class BrowserActivity extends ThemableBrowserActivity implements BrowserController, OnClickListener, OnLongClickListener { +public abstract class BrowserActivity extends ThemableBrowserActivity implements UIController, OnClickListener, OnLongClickListener { // Static Layout @Bind(R.id.drawer_layout) @@ -157,12 +144,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements RelativeLayout mSearchBar; - // Browser Views - private final List mWebViewList = new ArrayList<>(); - private LightningView mCurrentView; - private WebView mWebView; - private RecyclerView mTabListView; - // Toolbar Views private AutoCompleteTextView mSearch; private ImageView mArrowImage; @@ -173,29 +154,24 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements private View mCustomView; // Adapter - private LightningViewAdapter mTabAdapter; private SearchAdapter mSearchAdapter; // Callback - private ClickHandler mClickHandler; private CustomViewCallback mCustomViewCallback; private ValueCallback mUploadMessage; private ValueCallback mFilePathCallback; // Primatives - private boolean mFullScreen, mColorMode, mDarkTheme, - mIsNewIntent = false, - mIsFullScreen = false, - mIsImmersive = false, - mShowTabsInDrawer; + private boolean mFullScreen; + private boolean mDarkTheme; + private boolean mIsNewIntent = false; + private boolean mIsFullScreen = false; + private boolean mIsImmersive = false; + private boolean mShowTabsInDrawer; private int mOriginalOrientation, mBackgroundColor, mIdGenerator, mIconColor, mCurrentUiColor = Color.BLACK; private String mSearchText, mUntitledTitle, mHomepage, mCameraPhotoPath; - // Storage - private HistoryDatabase mHistoryDatabase; - private final PreferenceManager mPreferences = PreferenceManager.getInstance(); - // The singleton BookmarkManager @Inject BookmarkManager mBookmarkManager; @@ -208,7 +184,15 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements BookmarkPage mBookmarkPage; @Inject - BookmarksDialogBuilder mBookmarksDialogBuilder; + LightningDialogBuilder bookmarksDialogBuilder; + + @Inject + TabsManager mTabsManager; + + // Preference manager was moved on ThemeableBrowserActivity + + @Inject + HistoryDatabase mHistoryDatabase; // Image private Bitmap mWebpageBitmap; @@ -227,13 +211,11 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements private static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); - public abstract boolean isIncognito(); - - abstract void initializeTabs(); + protected abstract boolean isIncognito(); abstract void closeActivity(); - public abstract void updateHistory(final String title, final String url); + public abstract void updateHistory(@Nullable final String title, @NonNull final String url); abstract void updateCookiePreference(); @@ -257,18 +239,12 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mIconColor = mDarkTheme ? ThemeUtils.getIconDarkThemeColor(this) : ThemeUtils.getIconLightThemeColor(this); mShowTabsInDrawer = mPreferences.getShowTabsInDrawer(!isTablet()); - mWebViewList.clear(); - - mClickHandler = new ClickHandler(this); // initialize background ColorDrawable mBackground.setColor(((ColorDrawable) mToolbarLayout.getBackground()).getColor()); - setupFrameLayoutButton(R.id.new_tab_button, R.id.icon_plus); // Drawer stutters otherwise mDrawerLeft.setLayerType(View.LAYER_TYPE_HARDWARE, null); mDrawerRight.setLayerType(View.LAYER_TYPE_HARDWARE, null); - ImageView tabTitleImage = (ImageView) findViewById(R.id.plusIcon); - tabTitleImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !mShowTabsInDrawer) { getWindow().setStatusBarColor(Color.BLACK); @@ -281,31 +257,28 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mHomepage = mPreferences.getHomepage(); - RecyclerView horizontalListView = (RecyclerView) findViewById(R.id.twv_list); - - + final TabsFragment tabsFragment = new TabsFragment(); + final int containerId = mShowTabsInDrawer ? R.id.left_drawer : R.id.tabs_toolbar_container; + final Bundle tabsFragmentArguments = new Bundle(); + tabsFragmentArguments.putBoolean(TabsFragment.IS_INCOGNITO, isIncognito()); + tabsFragmentArguments.putBoolean(TabsFragment.VERTICAL_MODE, mShowTabsInDrawer); + tabsFragment.setArguments(tabsFragmentArguments); + + final BookmarksFragment bookmarksFragment = new BookmarksFragment(); + final Bundle bookmarksFragmentArguments = new Bundle(); + bookmarksFragmentArguments.putBoolean(BookmarksFragment.INCOGNITO_MODE, isIncognito()); + bookmarksFragment.setArguments(bookmarksFragmentArguments); + + final FragmentManager fragmentManager = getSupportFragmentManager(); + fragmentManager + .beginTransaction() + .add(containerId, tabsFragment) + .add(R.id.right_drawer, bookmarksFragment) + .commit(); if (mShowTabsInDrawer) { - mTabAdapter = new LightningViewAdapter(this, R.layout.tab_list_item, mWebViewList); - mTabListView = (RecyclerView) findViewById(R.id.left_drawer_list); - mTabListView.setOverScrollMode(View.OVER_SCROLL_IF_CONTENT_SCROLLS); - RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); - mTabListView.setLayoutManager(layoutManager); - mTabListView.setHasFixedSize(true); - mToolbarLayout.removeView(horizontalListView); - } else { - mTabAdapter = new LightningViewAdapter(this, R.layout.tab_list_item_horizontal, mWebViewList); - mTabListView = horizontalListView; - mTabListView.setOverScrollMode(View.OVER_SCROLL_NEVER); - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, mDrawerLeft); - RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); - mTabListView.setLayoutManager(layoutManager); - mTabListView.setHasFixedSize(true); + mToolbarLayout.removeView(findViewById(R.id.tabs_toolbar_container)); } - mTabListView.setAdapter(mTabAdapter); - - mHistoryDatabase = HistoryDatabase.getInstance(); - if (actionBar == null) return; @@ -336,11 +309,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mProxyUtils = ProxyUtils.getInstance(); - setupFrameLayoutButton(R.id.action_back, R.id.icon_back); - setupFrameLayoutButton(R.id.action_forward, R.id.icon_forward); - setupFrameLayoutButton(R.id.action_toggle_desktop, R.id.icon_desktop); - setupFrameLayoutButton(R.id.action_reading, R.id.icon_reading); - // create the search EditText in the ToolBar mSearch = (AutoCompleteTextView) customView.findViewById(R.id.search); mUntitledTitle = getString(R.string.untitled); @@ -378,7 +346,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements WebIconDatabase.getInstance().open(getDir("icons", MODE_PRIVATE).getPath()); } - initializeTabs(); + mTabsManager.restoreTabsAndHandleIntent(this, getIntent(), isIncognito()); + // At this point we always have at least a tab in the tab manager + showTab(0); mProxyUtils.checkForProxy(this); } @@ -393,8 +363,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mSearch.getWindowToken(), 0); searchTheWeb(mSearch.getText().toString()); - if (mCurrentView != null) { - mCurrentView.requestFocus(); + final LightningView currentView = mTabsManager.getCurrentTab(); + if (currentView != null) { + currentView.requestFocus(); } return true; default: @@ -415,8 +386,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mSearch.getWindowToken(), 0); searchTheWeb(mSearch.getText().toString()); - if (mCurrentView != null) { - mCurrentView.requestFocus(); + final LightningView currentView = mTabsManager.getCurrentTab(); + if (currentView != null) { + currentView.requestFocus(); } return true; } @@ -425,19 +397,19 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements @Override public void onFocusChange(View v, final boolean hasFocus) { - if (!hasFocus && mCurrentView != null) { - setIsLoading(mCurrentView.getProgress() < 100); - updateUrl(mCurrentView.getUrl(), true); - } else if (hasFocus) { - String url = mCurrentView.getUrl(); - if (url.startsWith(Constants.FILE)) { + final LightningView currentView = mTabsManager.getCurrentTab(); + if (!hasFocus && currentView != null) { + setIsLoading(currentView.getProgress() < 100); + updateUrl(currentView.getUrl(), true); + } else if (hasFocus && currentView != null) { + String url = currentView.getUrl(); + if (UrlUtils.isSpecialUrl(url)) { mSearch.setText(""); } else { mSearch.setText(url); } - ((AutoCompleteTextView) v).selectAll(); // Hack to make sure - // the text gets - // selected + // Hack to make sure the text gets selected + ((AutoCompleteTextView) v).selectAll(); mIcon = mClearIcon; mSearch.setCompoundDrawables(null, null, mClearIcon, null); } @@ -580,54 +552,17 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } } - void restoreOrNewTab() { - mIdGenerator = 0; - - String url = null; - if (getIntent() != null) { - url = getIntent().getDataString(); - if (url != null) { - if (url.startsWith(Constants.FILE)) { - Utils.showSnackbar(this, R.string.message_blocked_local); - url = null; - } - } - } - if (mPreferences.getRestoreLostTabsEnabled()) { - String mem = mPreferences.getMemoryUrl(); - mPreferences.setMemoryUrl(""); - String[] array = Utils.getArray(mem); - int count = 0; - for (String urlString : array) { - if (!urlString.isEmpty()) { - if (url != null && url.compareTo(urlString) == 0) { - url = null; - } - newTab(urlString, true); - count++; - } - } - if (url != null) { - newTab(url, true); - } else if (count == 0) { - newTab(null, true); - } - } else { - newTab(url, true); - } - } - private void initializePreferences() { + final LightningView currentView = mTabsManager.getCurrentTab(); + final WebView currentWebView = mTabsManager.getCurrentWebView(); mFullScreen = mPreferences.getFullScreenEnabled(); - mColorMode = mPreferences.getColorModeEnabled(); - mColorMode &= !mDarkTheme; - if (!isIncognito() && !mColorMode && !mDarkTheme && mWebpageBitmap != null) { + boolean colorMode = mPreferences.getColorModeEnabled(); + colorMode &= !mDarkTheme; + if (!isIncognito() && !colorMode && !mDarkTheme && mWebpageBitmap != null) { changeToolbarBackground(mWebpageBitmap, null); - mTabAdapter.notifyDataSetChanged(); - } else if (!isIncognito() && mCurrentView != null && !mDarkTheme - && mCurrentView.getFavicon() != null) { - changeToolbarBackground(mCurrentView.getFavicon(), null); - mTabAdapter.notifyDataSetChanged(); + } else if (!isIncognito() && currentView != null && !mDarkTheme + && currentView.getFavicon() != null) { + changeToolbarBackground(currentView.getFavicon(), null); } if (mFullScreen) { @@ -637,8 +572,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mToolbarLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); height = mToolbarLayout.getMeasuredHeight(); } - if (mWebView != null) - mWebView.setTranslationY(height); + if (currentWebView != null) + currentWebView.setTranslationY(height); mBrowserFrame.setLayoutTransition(null); if (mBrowserFrame.findViewById(R.id.toolbar_layout) == null) { mUiLayout.removeView(mToolbarLayout); @@ -652,8 +587,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mUiLayout.addView(mToolbarLayout, 0); } mBrowserFrame.setLayoutTransition(new LayoutTransition()); - if (mWebView != null) - mWebView.setTranslationY(0); + if (currentWebView != null) + currentWebView.setTranslationY(0); } setFullscreen(mPreferences.getHideStatusBarEnabled(), false); @@ -730,6 +665,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements @Override public boolean onOptionsItemSelected(MenuItem item) { + final LightningView currentView = mTabsManager.getCurrentTab(); // Handle action buttons switch (item.getItemId()) { case android.R.id.home: @@ -738,13 +674,13 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } return true; case R.id.action_back: - if (mCurrentView != null && mCurrentView.canGoBack()) { - mCurrentView.goBack(); + if (currentView != null && currentView.canGoBack()) { + currentView.goBack(); } return true; case R.id.action_forward: - if (mCurrentView != null && mCurrentView.canGoForward()) { - mCurrentView.goForward(); + if (currentView != null && currentView.canGoForward()) { + currentView.goForward(); } return true; case R.id.action_new_tab: @@ -755,11 +691,11 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements overridePendingTransition(R.anim.slide_up_in, R.anim.fade_out_scale); return true; case R.id.action_share: - if (mCurrentView != null && !mCurrentView.getUrl().startsWith(Constants.FILE)) { + if (currentView != null && !UrlUtils.isSpecialUrl(currentView.getUrl())) { Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("text/plain"); - shareIntent.putExtra(Intent.EXTRA_SUBJECT, mCurrentView.getTitle()); - shareIntent.putExtra(Intent.EXTRA_TEXT, mCurrentView.getUrl()); + shareIntent.putExtra(Intent.EXTRA_SUBJECT, currentView.getTitle()); + shareIntent.putExtra(Intent.EXTRA_TEXT, currentView.getUrl()); startActivity(Intent.createChooser(shareIntent, getResources().getString(R.string.dialog_title_share))); } return true; @@ -767,9 +703,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements openBookmarks(); return true; case R.id.action_copy: - if (mCurrentView != null && !mCurrentView.getUrl().startsWith(Constants.FILE)) { + if (currentView != null && !UrlUtils.isSpecialUrl(currentView.getUrl())) { ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("label", mCurrentView.getUrl()); + ClipData clip = ClipData.newPlainText("label", currentView.getUrl()); clipboard.setPrimaryClip(clip); Utils.showSnackbar(this, R.string.message_link_copied); } @@ -781,18 +717,20 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements openHistory(); return true; case R.id.action_add_bookmark: - if (mCurrentView != null && !mCurrentView.getUrl().startsWith(Constants.FILE)) { - mEventBus.post(new BrowserEvents.AddBookmark(mCurrentView.getTitle(), - mCurrentView.getUrl())); + if (currentView != null && !UrlUtils.isSpecialUrl(currentView.getUrl())) { + mEventBus.post(new BrowserEvents.AddBookmark(currentView.getTitle(), + currentView.getUrl())); } return true; case R.id.action_find: findInPage(); return true; case R.id.action_reading_mode: - Intent read = new Intent(this, ReadingActivity.class); - read.putExtra(Constants.LOAD_READING_URL, mCurrentView.getUrl()); - startActivity(read); + if (currentView != null) { + Intent read = new Intent(this, ReadingActivity.class); + read.putExtra(Constants.LOAD_READING_URL, currentView.getUrl()); + startActivity(read); + } return true; default: return super.onOptionsItemSelected(item); @@ -823,8 +761,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } private void showSearchInterfaceBar(String text) { - if (mCurrentView != null) { - mCurrentView.find(text); + final LightningView currentView = mTabsManager.getCurrentTab(); + if (currentView != null) { + currentView.find(text); } mSearchBar.setVisibility(View.VISIBLE); @@ -869,66 +808,53 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements builder.show(); } - /** - * The click listener for ListView in the navigation drawer - */ - private class DrawerItemClickListener implements OnClickListener { - - @Override - public void onClick(View v) { - int position = mTabListView.getChildAdapterPosition(v); - if (mCurrentView != mWebViewList.get(position)) { - mIsNewIntent = false; - showTab(mWebViewList.get(position)); - } - } - } - - /** - * long click listener for Navigation Drawer - */ - private class DrawerItemLongClickListener implements OnLongClickListener { - - @Override - public boolean onLongClick(View v) { - int position = mTabListView.getChildAdapterPosition(v); - showCloseDialog(position); - return true; - } - } - /** * displays the WebView contained in the LightningView Also handles the * removal of previous views * - * @param view the LightningView to show + * @param position the poition of the tab to display */ - private synchronized void showTab(LightningView view) { + private synchronized void showTab(final int position) { + final LightningView currentView = mTabsManager.getCurrentTab(); + final LightningView newView = mTabsManager.switchToTab(position); + switchTabs(currentView, newView); + } + + // This is commodity to breack the flow between regular tab management and the CLIQZ's search + // interface. + private void switchTabs(final LightningView currentView, final LightningView newView) { + final WebView currentWebView = currentView != null ? currentView.getWebView() : null; + final WebView newWebView = newView != null ? newView.getWebView() : null; + if (newView == null || newWebView == null) { + return; + } + // Set the background color so the color mode color doesn't show through mBrowserFrame.setBackgroundColor(mBackgroundColor); - if (view == null || (view == mCurrentView && !mCurrentView.isShown())) { + if (newView == currentView && currentView.isShown()) { return; } + mIsNewIntent = false; + final float translation = mToolbarLayout.getTranslationY(); mBrowserFrame.removeAllViews(); - if (mCurrentView != null) { - mCurrentView.setForegroundTab(false); - mCurrentView.onPause(); - } - mCurrentView = view; - mWebView = view.getWebView(); - mCurrentView.setForegroundTab(true); - if (mWebView != null) { - updateUrl(mCurrentView.getUrl(), true); - updateProgress(mCurrentView.getProgress()); + if (currentView != null) { + currentView.setForegroundTab(false); + currentView.onPause(); + } + newView.setForegroundTab(true); + if (currentWebView != null) { + updateUrl(newView.getUrl(), true); + updateProgress(newView.getProgress()); } else { updateUrl("", true); updateProgress(0); } - mBrowserFrame.addView(mWebView, MATCH_PARENT); - mCurrentView.requestFocus(); - mCurrentView.onResume(); + removeViewFromParent(newWebView); + mBrowserFrame.addView(newWebView, MATCH_PARENT); + newView.requestFocus(); + newView.onResume(); if (mFullScreen) { // mToolbarLayout has already been removed @@ -940,10 +866,10 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mToolbarLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); height = mToolbarLayout.getMeasuredHeight(); } - mWebView.setTranslationY(translation + height); + newWebView.setTranslationY(translation + height); mToolbarLayout.setTranslationY(translation); } else { - mWebView.setTranslationY(0); + newWebView.setTranslationY(0); } showActionBar(); @@ -959,55 +885,79 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements }, 200); // Should update the bookmark status in BookmarksFragment - mEventBus.post(new BrowserEvents.CurrentPageUrl(mCurrentView.getUrl())); + mEventBus.post(new BrowserEvents.CurrentPageUrl(newView.getUrl())); -// new Handler().postDelayed(new Runnable() { -// @Override -// public void run() { + // new Handler().postDelayed(new Runnable() { + // @Override + // public void run() { // Remove browser frame background to reduce overdraw //TODO evaluate performance -// mBrowserFrame.setBackgroundColor(Color.TRANSPARENT); -// } -// }, 300); + // mBrowserFrame.setBackgroundColor(Color.TRANSPARENT); + // } + // }, 300); } - void handleNewIntent(Intent intent) { + private static void removeViewFromParent(View view) { + ViewGroup parent = ((ViewGroup) view.getParent()); + if (parent != null) { + parent.removeView(view); + } + } - String url = null; + void handleNewIntent(Intent intent) { + final String url; if (intent != null) { url = intent.getDataString(); + } else { + url = null; } int num = 0; - String source = null; + final String source; if (intent != null && intent.getExtras() != null) { num = intent.getExtras().getInt(getPackageName() + ".Origin"); source = intent.getExtras().getString("SOURCE"); + } else { + source = null; } if (num == 1) { loadUrlInCurrentView(url); } else if (url != null) { if (url.startsWith(Constants.FILE)) { - Utils.showSnackbar(this, R.string.message_blocked_local); - url = null; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setCancelable(true) + .setTitle(R.string.title_warning) + .setMessage(R.string.message_blocked_local) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.action_open, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + newTab(url, true); + } + }) + .show(); + } else { + newTab(url, true); } - newTab(url, true); mIsNewIntent = (source == null); } } private void loadUrlInCurrentView(final String url) { - if (mCurrentView == null) { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab == null) { + // This is a problem, probably an assert will be better than a return return; } - mCurrentView.loadUrl(url); + currentTab.loadUrl(url); mEventBus.post(new BrowserEvents.CurrentPageUrl(url)); } @Override public void closeEmptyTab() { - if (mWebView != null && mWebView.copyBackForwardList().getSize() == 0) { + final WebView currentWebView = mTabsManager.getCurrentWebView(); + if (currentWebView != null && currentWebView.copyBackForwardList().getSize() == 0) { closeCurrentTab(); } } @@ -1020,101 +970,74 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements public void onTrimMemory(int level) { if (level > TRIM_MEMORY_MODERATE && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { Log.d(Constants.TAG, "Low Memory, Free Memory"); - for (int n = 0, size = mWebViewList.size(); n < size; n++) { - mWebViewList.get(n).freeMemory(); - } + mTabsManager.freeMemory(); } } - synchronized boolean newTab(String url, boolean show) { + private synchronized boolean newTab(String url, boolean show) { // Limit number of tabs for limited version of app - if (!Constants.FULL_VERSION && mWebViewList.size() >= 10) { + if (!Constants.FULL_VERSION && mTabsManager.size() >= 10) { Utils.showSnackbar(this, R.string.max_tabs); return false; } mIsNewIntent = false; - LightningView startingTab = new LightningView(this, url, mDarkTheme, isIncognito(), this); + LightningView startingTab = mTabsManager.newTab(this, url, isIncognito()); if (mIdGenerator == 0) { startingTab.resumeTimers(); } mIdGenerator++; - mWebViewList.add(startingTab); if (show) { - showTab(startingTab); + showTab(mTabsManager.size() - 1); } - updateTabs(); - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - mTabListView.smoothScrollToPosition(mWebViewList.size() - 1); - } - }, 300); + // TODO Check is this is callable directly from LightningView + mEventBus.post(new BrowserEvents.TabsChanged()); + + // TODO Restore this + // new Handler().postDelayed(new Runnable() { + // @Override + // public void run() { + // mDrawerListLeft.smoothScrollToPosition(mTabsManager.size() - 1); + // } + // }, 300); return true; } private synchronized void deleteTab(int position) { - if (position >= mWebViewList.size()) { - return; - } + final LightningView tabToDelete = mTabsManager.getTabAtPosition(position); - int current = mWebViewList.indexOf(mCurrentView); - if (current < 0) { + if (tabToDelete == null) { return; } - LightningView reference = mWebViewList.get(position); - if (reference == null) { - return; - } - if (!reference.getUrl().startsWith(Constants.FILE) && !isIncognito()) { - mPreferences.setSavedUrl(reference.getUrl()); + + if (!UrlUtils.isSpecialUrl(tabToDelete.getUrl()) && !isIncognito()) { + mPreferences.setSavedUrl(tabToDelete.getUrl()); } - boolean isShown = reference.isShown(); + final boolean isShown = tabToDelete.isShown(); + boolean shouldClose = mIsNewIntent && isShown; if (isShown) { mBrowserFrame.setBackgroundColor(mBackgroundColor); } - if (current > position) { - mWebViewList.remove(position); - updateTabs(); - reference.onDestroy(); - } else if (mWebViewList.size() > position + 1) { - if (current == position) { - showTab(mWebViewList.get(position + 1)); - mWebViewList.remove(position); - updateTabs(); - } else { - mWebViewList.remove(position); - } - - reference.onDestroy(); - } else if (mWebViewList.size() > 1) { - if (current == position) { - showTab(mWebViewList.get(position - 1)); - mWebViewList.remove(position); - updateTabs(); - } else { - mWebViewList.remove(position); - } - - reference.onDestroy(); - } else { - if (mCurrentView.getUrl().startsWith(Constants.FILE) || mCurrentView.getUrl().equals(mHomepage)) { - closeActivity(); - } else { - mWebViewList.remove(position); - performExitCleanUp(); - reference.pauseTimers(); - reference.onDestroy(); - mCurrentView = null; - mWebView = null; - mTabAdapter.notifyDataSetChanged(); - finish(); - } + final LightningView currentTab = mTabsManager.getCurrentTab(); + mTabsManager.deleteTab(position); + final LightningView afterTab = mTabsManager.getCurrentTab(); + if (afterTab == null) { +// if (currentTab != null && (UrlUtils.isSpecialUrl(currentTab.getUrl()) +// || currentTab.getUrl().equals(mPreferenceManager.getHomepage()))) { +// closeActivity(); +// } else { + performExitCleanUp(); + finish(); +// } + } else if (afterTab != currentTab) { + switchTabs(currentTab, afterTab); + currentTab.pauseTimers(); } - mTabAdapter.notifyDataSetChanged(); - if (mIsNewIntent && isShown) { + mEventBus.post(new BrowserEvents.TabsChanged()); + + if (shouldClose) { mIsNewIntent = false; closeActivity(); } @@ -1123,8 +1046,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } private void performExitCleanUp() { - if (mPreferences.getClearCacheExit() && mCurrentView != null && !isIncognito()) { - WebUtils.clearCache(mCurrentView.getWebView()); + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (mPreferences.getClearCacheExit() && currentTab != null && !isIncognito()) { + WebUtils.clearCache(currentTab.getWebView()); Log.d(Constants.TAG, "Cache Cleared"); } if (mPreferences.getClearHistoryExitEnabled() && !isIncognito()) { @@ -1145,8 +1069,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements @Override public boolean onKeyLongPress(int keyCode, KeyEvent event) { + final LightningView currentTab = mTabsManager.getCurrentTab(); if (keyCode == KeyEvent.KEYCODE_BACK) { - showCloseDialog(mWebViewList.indexOf(mCurrentView)); + showCloseDialog(mTabsManager.positionOf(currentTab)); } return true; } @@ -1154,38 +1079,35 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements private void closeBrowser() { mBrowserFrame.setBackgroundColor(mBackgroundColor); performExitCleanUp(); - mCurrentView = null; - mWebView = null; - for (int n = 0, size = mWebViewList.size(); n < size; n++) { - if (mWebViewList.get(n) != null) { - mWebViewList.get(n).onDestroy(); - } - } - mWebViewList.clear(); - mTabAdapter.notifyDataSetChanged(); + mTabsManager.shutdown(); + mEventBus.post(new BrowserEvents.TabsChanged()); finish(); } @Override - public void onBackPressed() { + public synchronized void onBackPressed() { + final LightningView currentTab = mTabsManager.getCurrentTab(); if (mDrawerLayout.isDrawerOpen(mDrawerLeft)) { mDrawerLayout.closeDrawer(mDrawerLeft); } else if (mDrawerLayout.isDrawerOpen(mDrawerRight)) { - mEventBus - .post(new BrowserEvents.UserPressedBack()); + mEventBus.post(new BrowserEvents.UserPressedBack()); } else { - if (mCurrentView != null) { + if (currentTab != null) { Log.d(Constants.TAG, "onBackPressed"); if (mSearch.hasFocus()) { - mCurrentView.requestFocus(); - } else if (mCurrentView.canGoBack()) { - if (!mCurrentView.isShown()) { + currentTab.requestFocus(); + } else if (currentTab.canGoBack()) { + if (!currentTab.isShown()) { onHideCustomView(); } else { - mCurrentView.goBack(); + currentTab.goBack(); } } else { - deleteTab(mWebViewList.indexOf(mCurrentView)); + if (mCustomView != null || mCustomViewCallback != null) { + onHideCustomView(); + } else { + deleteTab(mTabsManager.positionOf(currentTab)); + } } } else { Log.e(Constants.TAG, "This shouldn't happen ever"); @@ -1197,10 +1119,11 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements @Override protected void onPause() { super.onPause(); + final LightningView currentTab = mTabsManager.getCurrentTab(); Log.d(Constants.TAG, "onPause"); - if (mCurrentView != null) { - mCurrentView.pauseTimers(); - mCurrentView.onPause(); + if (currentTab != null) { + currentTab.pauseTimers(); + currentTab.onPause(); } try { unregisterReceiver(mNetworkReceiver); @@ -1216,13 +1139,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements void saveOpenTabs() { if (mPreferences.getRestoreLostTabsEnabled()) { - StringBuilder s = new StringBuilder(mWebViewList.size() * 50); - for (int n = 0, size = mWebViewList.size(); n < size; n++) { - if (!mWebViewList.get(n).getUrl().isEmpty()) { - s.append(mWebViewList.get(n).getUrl()).append("|$|SEPARATOR|$|"); - } - } - mPreferences.setMemoryUrl(s.toString()); + final String s = mTabsManager.tabsString(); + mPreferences.setMemoryUrl(s); } } @@ -1251,24 +1169,18 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements @Override protected void onResume() { super.onResume(); + final LightningView currentTab = mTabsManager.getCurrentTab(); Log.d(Constants.TAG, "onResume"); if (mSearchAdapter != null) { mSearchAdapter.refreshPreferences(); mSearchAdapter.refreshBookmarks(); } - if (mCurrentView != null) { - mCurrentView.resumeTimers(); - mCurrentView.onResume(); + if (currentTab != null) { + currentTab.resumeTimers(); + currentTab.onResume(); } - mHistoryDatabase = HistoryDatabase.getInstance(); initializePreferences(); - for (int n = 0, size = mWebViewList.size(); n < size; n++) { - if (mWebViewList.get(n) != null) { - mWebViewList.get(n).initializePreferences(null, this); - } else { - mWebViewList.remove(n); - } - } + mTabsManager.resume(this); supportInvalidateOptionsMenu(); @@ -1284,173 +1196,18 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements * 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(); - mCurrentView.stopLoading(); - if (mCurrentView != null) { + if (currentTab != null) { + currentTab.stopLoading(); loadUrlInCurrentView(UrlUtils.smartUrlFilter(query, true, searchUrl)); } } - private class LightningViewAdapter extends RecyclerView.Adapter { - - private final Context context; - private final int layoutResourceId; - private List data = null; - private final CloseTabListener mExitListener; - private final Drawable mBackgroundTabDrawable; - private final Drawable mForegroundTabDrawable; - private final Bitmap mForegroundTabBitmap; - private final DrawerItemClickListener mClickListener; - private final DrawerItemLongClickListener mLongClickListener; - private ColorMatrix mColorMatrix; - private Paint mPaint; - private ColorFilter mFilter; - private static final float DESATURATED = 0.5f; - - public LightningViewAdapter(Context context, int layoutResourceId, List data) { - this.layoutResourceId = layoutResourceId; - this.context = context; - this.data = data; - this.mExitListener = new CloseTabListener(); - this.mClickListener = new DrawerItemClickListener(); - this.mLongClickListener = new DrawerItemLongClickListener(); - - if (mShowTabsInDrawer) { - mBackgroundTabDrawable = null; - mForegroundTabBitmap = null; - mForegroundTabDrawable = ThemeUtils.getSelectedBackground(context, mDarkTheme); - } else { - int backgroundColor = Utils.mixTwoColors(ThemeUtils.getPrimaryColor(context), Color.BLACK, 0.75f); - Bitmap backgroundTabBitmap = Bitmap.createBitmap(Utils.dpToPx(175), Utils.dpToPx(30), Bitmap.Config.ARGB_8888); - Utils.drawTrapezoid(new Canvas(backgroundTabBitmap), backgroundColor, true); - mBackgroundTabDrawable = new BitmapDrawable(getResources(), backgroundTabBitmap); - - int foregroundColor = ThemeUtils.getPrimaryColor(context); - mForegroundTabBitmap = Bitmap.createBitmap(Utils.dpToPx(175), Utils.dpToPx(30), Bitmap.Config.ARGB_8888); - Utils.drawTrapezoid(new Canvas(mForegroundTabBitmap), foregroundColor, false); - mForegroundTabDrawable = null; - } - } - - @Override - public LightningViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { - LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext()); - View view = inflater.inflate(layoutResourceId, viewGroup, false); - return new LightningViewHolder(view); - } - - @Override - public void onBindViewHolder(final LightningViewHolder holder, int position) { - holder.exitButton.setTag(position); - holder.exitButton.setOnClickListener(mExitListener); - holder.layout.setOnClickListener(mClickListener); - holder.layout.setOnLongClickListener(mLongClickListener); - - ViewCompat.jumpDrawablesToCurrentState(holder.exitButton); - - LightningView web = data.get(position); - holder.txtTitle.setText(web.getTitle()); - - final Bitmap favicon = web.getFavicon(); - if (web.isForegroundTab()) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - holder.txtTitle.setTextAppearance(R.style.boldText); - } else { - //noinspection deprecation - holder.txtTitle.setTextAppearance(context, R.style.boldText); - } - Drawable foregroundDrawable; - if (!mShowTabsInDrawer) { - foregroundDrawable = new BitmapDrawable(getResources(), mForegroundTabBitmap); - if (!isIncognito() && mColorMode) { - foregroundDrawable.setColorFilter(mCurrentUiColor, PorterDuff.Mode.SRC_IN); - } - } else { - foregroundDrawable = mForegroundTabDrawable; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - holder.layout.setBackground(foregroundDrawable); - } else { - //noinspection deprecation - holder.layout.setBackgroundDrawable(foregroundDrawable); - } - if (!isIncognito() && mColorMode) { - changeToolbarBackground(favicon, foregroundDrawable); - } - holder.favicon.setImageBitmap(favicon); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - holder.txtTitle.setTextAppearance(R.style.normalText); - } else { - //noinspection deprecation - holder.txtTitle.setTextAppearance(context, R.style.normalText); - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - holder.layout.setBackground(mBackgroundTabDrawable); - } else { - //noinspection deprecation - holder.layout.setBackgroundDrawable(mBackgroundTabDrawable); - } - - holder.favicon.setImageBitmap(getDesaturatedBitmap(favicon)); - } - } - - @Override - public int getItemCount() { - return (data != null) ? data.size() : 0; - } - - public Bitmap getDesaturatedBitmap(Bitmap favicon) { - Bitmap grayscaleBitmap = Bitmap.createBitmap(favicon.getWidth(), - favicon.getHeight(), Bitmap.Config.ARGB_8888); - - Canvas c = new Canvas(grayscaleBitmap); - if (mColorMatrix == null || mFilter == null || mPaint == null) { - mPaint = new Paint(); - mColorMatrix = new ColorMatrix(); - mColorMatrix.setSaturation(DESATURATED); - mFilter = new ColorMatrixColorFilter(mColorMatrix); - mPaint.setColorFilter(mFilter); - } - - c.drawBitmap(favicon, 0, 0, mPaint); - return grayscaleBitmap; - } - - public class LightningViewHolder extends RecyclerView.ViewHolder { - - public LightningViewHolder(View view) { - super(view); - txtTitle = (TextView) view.findViewById(R.id.textTab); - favicon = (ImageView) view.findViewById(R.id.faviconTab); - exit = (ImageView) view.findViewById(R.id.deleteButton); - layout = (LinearLayout) view.findViewById(R.id.tab_item_background); - exitButton = (FrameLayout) view.findViewById(R.id.deleteAction); - exit.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); - } - - final TextView txtTitle; - final ImageView favicon; - final ImageView exit; - final FrameLayout exitButton; - final LinearLayout layout; - } - } - - private class CloseTabListener implements OnClickListener { - - @Override - public void onClick(View v) { - deleteTab((int) v.getTag()); - } - - } - /** * 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 @@ -1459,7 +1216,8 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements * @param favicon the Bitmap to extract the color from * @param tabBackground the optional LinearLayout to color */ - private void changeToolbarBackground(@NonNull Bitmap favicon, @Nullable final Drawable tabBackground) { + @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; @@ -1508,12 +1266,24 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } @Override - public void updateUrl(String url, boolean shortUrl) { + public boolean getUseDarkTheme() { + return mDarkTheme; + } + + @ColorInt + @Override + public int getUiColor() { + return mCurrentUiColor; + } + + @Override + public void updateUrl(@Nullable String url, boolean shortUrl) { if (url == null || mSearch == null || mSearch.hasFocus()) { return; } + final LightningView currentTab = mTabsManager.getCurrentTab(); mEventBus.post(new BrowserEvents.CurrentPageUrl(url)); - if (shortUrl && !url.startsWith(Constants.FILE)) { + if (shortUrl && !UrlUtils.isSpecialUrl(url)) { switch (mPreferences.getUrlBoxContentChoice()) { case 0: // Default, show only the domain url = url.replaceFirst(Constants.HTTP, ""); @@ -1524,15 +1294,15 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mSearch.setText(url); break; case 2: // Title, show the page's title - if (mCurrentView != null && !mCurrentView.getTitle().isEmpty()) { - mSearch.setText(mCurrentView.getTitle()); + if (currentTab != null && !currentTab.getTitle().isEmpty()) { + mSearch.setText(currentTab.getTitle()); } else { mSearch.setText(mUntitledTitle); } break; } } else { - if (url.startsWith(Constants.FILE)) { + if (UrlUtils.isSpecialUrl(url)) { url = ""; } mSearch.setText(url); @@ -1545,14 +1315,11 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mProgressBar.setProgress(n); } - void addItemToHistory(final String title, final String url) { + void addItemToHistory(@Nullable final String title, @NonNull final String url) { Runnable update = new Runnable() { @Override public void run() { try { - if (mHistoryDatabase == null) { - mHistoryDatabase = HistoryDatabase.getInstance(); - } mHistoryDatabase.visitHistoryItem(url, title); } catch (IllegalStateException e) { Log.e(Constants.TAG, "IllegalStateException in updateHistory", e); @@ -1563,7 +1330,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } } }; - if (url != null && !url.startsWith(Constants.FILE)) { + if (!UrlUtils.isSpecialUrl(url)) { new Thread(update).start(); } } @@ -1573,30 +1340,34 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements * previously searched URLs */ private void initializeSearchSuggestions(final AutoCompleteTextView getUrl) { - getUrl.setThreshold(1); getUrl.setDropDownWidth(-1); getUrl.setDropDownAnchor(R.id.toolbar_layout); getUrl.setOnItemClickListener(new OnItemClickListener() { @Override - public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) { - try { - String url; - url = ((TextView) arg1.findViewById(R.id.url)).getText().toString(); - if (url.startsWith(BrowserActivity.this.getString(R.string.suggestion))) { - url = ((TextView) arg1.findViewById(R.id.title)).getText().toString(); - } else { - getUrl.setText(url); - } - searchTheWeb(url); - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(getUrl.getWindowToken(), 0); - if (mCurrentView != null) { - mCurrentView.requestFocus(); + 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(BrowserActivity.this.getString(R.string.suggestion))) { + CharSequence searchString = ((TextView) view.findViewById(R.id.title)).getText(); + if (searchString != null) { + url = searchString.toString(); } - } catch (NullPointerException e) { - Log.e("Browser Error: ", "NullPointerException on item click"); + } + if (url == null) { + return; + } + getUrl.setText(url); + searchTheWeb(url); + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(getUrl.getWindowToken(), 0); + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null) { + currentTab.requestFocus(); } } @@ -1607,11 +1378,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements getUrl.setAdapter(mSearchAdapter); } - @Override - public boolean proxyIsNotReady() { - return !mProxyUtils.isProxyReady(this); - } - /** * function that opens the HTML history page in the browser */ @@ -1654,36 +1420,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements return super.onCreateOptionsMenu(menu); } - /** - * open the HTML bookmarks page, parameter view is the WebView that should show the page - */ - @Override - public void openBookmarkPage(WebView view) { - if (view == null) - return; - Bitmap folderIcon = ThemeUtils.getThemedBitmap(this, R.drawable.ic_folder, false); - FileOutputStream outputStream = null; - File image = new File(this.getCacheDir(), "folder.png"); - try { - outputStream = new FileOutputStream(image); - folderIcon.compress(Bitmap.CompressFormat.PNG, 100, outputStream); - folderIcon.recycle(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } finally { - Utils.close(outputStream); - } - File bookmarkWebPage = new File(this.getFilesDir(), Constants.BOOKMARKS_FILENAME); - - mBookmarkPage.buildBookmarkPage(null, mBookmarkManager.getBookmarksFromFolder(null, true)); - view.loadUrl(Constants.FILE + bookmarkWebPage); - } - - @Override - public void updateTabs() { - mTabAdapter.notifyDataSetChanged(); - } - /** * opens a file chooser * param ValueCallback is the message from the WebView indicating a file chooser @@ -1785,33 +1521,26 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser"); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray); - this.startActivityForResult(chooserIntent, 1); + startActivityForResult(chooserIntent, 1); } - /** - * handles long presses for the browser, tries to get the - * url of the item that was clicked and sends it (it can be null) - * to the click handler that does cool stuff with it - */ @Override - public void onLongPress() { - if (mClickHandler == null) { - mClickHandler = new ClickHandler(this); - } - Message click = mClickHandler.obtainMessage(); - if (click != null) { - click.setTarget(mClickHandler); - mWebView.requestFocusNodeHref(click); - } + public synchronized void onShowCustomView(View view, CustomViewCallback callback) { + int requestedOrientation = mOriginalOrientation = getRequestedOrientation(); + onShowCustomView(view, callback, requestedOrientation); } @Override - public void onShowCustomView(View view, CustomViewCallback callback) { - if (view == null) { - return; - } - if (mCustomView != null && callback != null) { - callback.onCustomViewHidden(); + 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(Constants.TAG, "Error hiding custom view", e); + } + } return; } try { @@ -1820,56 +1549,81 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements Log.e(Constants.TAG, "WebView is not allowed to keep the screen on"); } mOriginalOrientation = getRequestedOrientation(); - FrameLayout decor = (FrameLayout) getWindow().getDecorView(); + 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)); - mCustomView = view; - mFullscreenContainer.addView(mCustomView, COVER_SCREEN_PARAMS); - decor.addView(mFullscreenContainer, COVER_SCREEN_PARAMS); - setFullscreen(true, true); - mCurrentView.setVisibility(View.GONE); 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); } - mCustomViewCallback = callback; } @Override public void onHideCustomView() { - if (mCustomView == null || mCustomViewCallback == null || mCurrentView == null) { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (mCustomView == null || mCustomViewCallback == null || currentTab == null) { + if (mCustomViewCallback != null) { + try { + mCustomViewCallback.onCustomViewHidden(); + } catch (Exception e) { + Log.e(Constants.TAG, "Error hiding custom view", e); + } + mCustomViewCallback = null; + } return; } Log.d(Constants.TAG, "onHideCustomView"); - mCurrentView.setVisibility(View.VISIBLE); + currentTab.setVisibility(View.VISIBLE); try { mCustomView.setKeepScreenOn(false); } catch (SecurityException e) { Log.e(Constants.TAG, "WebView is not allowed to keep the screen on"); } setFullscreen(mPreferences.getHideStatusBarEnabled(), false); - FrameLayout decor = (FrameLayout) getWindow().getDecorView(); - if (decor != null) { - decor.removeView(mFullscreenContainer); - } - - if (API < Build.VERSION_CODES.KITKAT) { - try { - mCustomViewCallback.onCustomViewHidden(); - } catch (Throwable ignored) { - + 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(Constants.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(Constants.TAG, "Error hiding custom view", e); + } + } + mCustomViewCallback = null; setRequestedOrientation(mOriginalOrientation); } @@ -1929,32 +1683,6 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } } - /** - * This interface method is used by the LightningView to obtain an - * image that is displayed as a placeholder on a video until the video - * has initialized and can begin loading. - * - * @return a Bitmap that can be used as a place holder for videos. - */ - @Override - public Bitmap getDefaultVideoPoster() { - return BitmapFactory.decodeResource(getResources(), android.R.drawable.spinner_background); - } - - /** - * An interface method so that we can inflate a view to send to - * a LightningView when it needs to display a video and has to - * show a loading dialog. Inflates a progress view and returns it. - * - * @return A view that should be used to display the state - * of a video's loading progress. - */ - @Override - public View getVideoLoadingProgressView() { - LayoutInflater inflater = LayoutInflater.from(this); - return inflater.inflate(R.layout.video_loading_progress, null); - } - /** * This method handles the JavaScript callback to create a new tab. * Basically this handles the event that JavaScript needs to create @@ -1964,14 +1692,20 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements * the newly created WebView. */ @Override - public void onCreateWindow(Message resultMsg) { + public synchronized void onCreateWindow(Message resultMsg) { if (resultMsg == null) { return; } if (newTab("", true)) { - WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; - transport.setWebView(mWebView); - resultMsg.sendToTarget(); + 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(); + } + } } } @@ -1985,7 +1719,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements */ @Override public void onCloseWindow(LightningView view) { - deleteTab(mWebViewList.indexOf(view)); + deleteTab(mTabsManager.positionOf(view)); } /** @@ -1995,6 +1729,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements */ @Override public void hideActionBar() { + final WebView currentWebView = mTabsManager.getCurrentWebView(); if (mFullScreen) { if (mBrowserFrame.findViewById(R.id.toolbar_layout) == null) { mUiLayout.removeView(mToolbarLayout); @@ -2002,26 +1737,26 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mToolbarLayout.bringToFront(); Log.d(Constants.TAG, "Move view to browser frame"); mToolbarLayout.setTranslationY(0); - mWebView.setTranslationY(mToolbarLayout.getHeight()); + if (currentWebView != null) { + currentWebView.setTranslationY(mToolbarLayout.getHeight()); + } } - if (mToolbarLayout == null || mCurrentView == null) + if (mToolbarLayout == null || currentWebView == null) return; final int height = mToolbarLayout.getHeight(); - final WebView view = mWebView; if (mToolbarLayout.getTranslationY() > -0.01f) { Animation show = new Animation() { @Override protected void applyTransformation(float interpolatedTime, Transformation t) { float trans = (1.0f - interpolatedTime) * height; mToolbarLayout.setTranslationY(trans - height); - if (view != null) - view.setTranslationY(trans); + currentWebView.setTranslationY(trans); } }; show.setDuration(250); show.setInterpolator(new DecelerateInterpolator()); - mWebView.startAnimation(show); + currentWebView.startAnimation(show); } } } @@ -2034,6 +1769,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements @Override public void showActionBar() { if (mFullScreen) { + final WebView view = mTabsManager.getCurrentWebView(); if (mToolbarLayout == null) return; @@ -2050,12 +1786,14 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements mToolbarLayout.bringToFront(); Log.d(Constants.TAG, "Move view to browser frame"); mToolbarLayout.setTranslationY(0); - mWebView.setTranslationY(height); + if (view != null) { + view.setTranslationY(height); + } } - if (mCurrentView == null) + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab == null) return; - final WebView view = mWebView; final int totalHeight = height; if (mToolbarLayout.getTranslationY() < -(height - 0.01f)) { Animation show = new Animation() { @@ -2070,192 +1808,13 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements }; show.setDuration(250); show.setInterpolator(new DecelerateInterpolator()); - mWebView.startAnimation(show); - } - } - } - - /** - * Handle a long-press on the current web page. The WebView provides - * a URL to this method that it thinks is what the user has long-pressed. - * This may be different than what the user actually long-pressed, e.g. in - * the case of an image link you may want to provide options for both the - * image and the link. This method also handles the event that the WebView - * was not able to get a URL from the event in which case we need to drill - * down using {@link HitTestResult} and obtain the URL that is at the root - * of their press. This method is responsible with delegating the dialog - * creation to one of the several other methods in this class. - * - * @param url the URL that was retrieved from the WebView in - * the long-press event. - */ - @Override - public void longClickPage(final String url) { - HitTestResult result = null; - String currentUrl = null; - if (mWebView != null) { - result = mWebView.getHitTestResult(); - currentUrl = mWebView.getUrl(); - } - if (currentUrl != null && currentUrl.startsWith(Constants.FILE)) { - if (currentUrl.endsWith(HistoryPage.FILENAME)) { - if (url != null) { - longPressHistoryLink(url); - } else if (result != null && result.getExtra() != null) { - final String newUrl = result.getExtra(); - longPressHistoryLink(newUrl); - } - } else if (currentUrl.endsWith(Constants.BOOKMARKS_FILENAME)) { - if (url != null) { - mBookmarksDialogBuilder.showLongPressedDialogForUrl(this, url); - } else if (result != null && result.getExtra() != null) { - final String newUrl = result.getExtra(); - mBookmarksDialogBuilder.showLongPressedDialogForUrl(this, newUrl); - } - } - } else { - if (url != null) { - if (result != null) { - if (result.getType() == HitTestResult.SRC_IMAGE_ANCHOR_TYPE || result.getType() == HitTestResult.IMAGE_TYPE) { - longPressImage(url); - } else { - longPressLink(url); - } - } else { - longPressLink(url); - } - } else if (result != null && result.getExtra() != null) { - final String newUrl = result.getExtra(); - if (result.getType() == HitTestResult.SRC_IMAGE_ANCHOR_TYPE || result.getType() == HitTestResult.IMAGE_TYPE) { - longPressImage(newUrl); - } else { - longPressLink(newUrl); + if (view != null) { + view.startAnimation(show); } } } } - /** - * Handle the event that the user has long-pressed an item on the {@link HistoryPage}. - * In this case we wish to present the user with a dialog with a few options: - * open the history item in a new tab, delete the history item, or open the - * item in the current tab. - * - * @param url the URL of the history item. - */ - private void longPressHistoryLink(final String url) { - DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - newTab(url, false); - mDrawerLayout.closeDrawers(); - break; - - case DialogInterface.BUTTON_NEGATIVE: - mHistoryDatabase.deleteHistoryItem(url); - openHistory(); - break; - - case DialogInterface.BUTTON_NEUTRAL: - if (mCurrentView != null) { - loadUrlInCurrentView(url); - } - break; - } - } - }; - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.action_history) - .setMessage(R.string.dialog_history_long_press) - .setCancelable(true) - .setPositiveButton(R.string.action_new_tab, dialogClickListener) - .setNegativeButton(R.string.action_delete, dialogClickListener) - .setNeutralButton(R.string.action_open, dialogClickListener) - .show(); - } - - /** - * Handle the event that the user has long-pressed on an image link - * and provide them with various options for things to do with that image. - * Options include opening the image in a new tab, loading the image in the - * current tab, or downloading the image. - * - * @param url the URL of the image that was retrieved from long-press. - */ - private void longPressImage(final String url) { - DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - newTab(url, false); - break; - - case DialogInterface.BUTTON_NEGATIVE: - loadUrlInCurrentView(url); - break; - - case DialogInterface.BUTTON_NEUTRAL: - Utils.downloadFile(BrowserActivity.this, url, - mCurrentView.getUserAgent(), "attachment"); - break; - } - } - }; - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(url.replace(Constants.HTTP, "")) - .setCancelable(true) - .setMessage(R.string.dialog_image) - .setPositiveButton(R.string.action_new_tab, dialogClickListener) - .setNegativeButton(R.string.action_open, dialogClickListener) - .setNeutralButton(R.string.action_download, dialogClickListener) - .show(); - } - - /** - * Handle the event that the user has long-pressed a link on a web page. - * It creates a dialog with a few options on what the user can do with the - * URL that has been retrieved from their long-press, namely, create a - * new tab, load the URL in the current tab, or copy the link. - * - * @param url the URL that has been retrieved in the long-press event - */ - private void longPressLink(final String url) { - DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - newTab(url, false); - break; - - case DialogInterface.BUTTON_NEGATIVE: - loadUrlInCurrentView(url); - break; - - case DialogInterface.BUTTON_NEUTRAL: - ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("label", url); - clipboard.setPrimaryClip(clip); - break; - } - } - }; - - AlertDialog.Builder builder = new AlertDialog.Builder(this); // dialog - builder.setTitle(url) - .setCancelable(true) - .setMessage(R.string.dialog_link) - .setPositiveButton(R.string.action_new_tab, dialogClickListener) - .setNegativeButton(R.string.action_open, dialogClickListener) - .setNeutralButton(R.string.action_copy, dialogClickListener) - .show(); - } - /** * 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 @@ -2274,11 +1833,12 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements * See setIsFinishedLoading and setIsLoading for displaying the correct icon */ private void refreshOrStop() { - if (mCurrentView != null) { - if (mCurrentView.getProgress() < 100) { - mCurrentView.stopLoading(); + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null) { + if (currentTab.getProgress() < 100) { + currentTab.stopLoading(); } else { - mCurrentView.reload(); + currentTab.reload(); } } } @@ -2292,53 +1852,38 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements */ @Override public void onClick(View v) { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab == null) { + return; + } switch (v.getId()) { - case R.id.action_back: - if (mCurrentView != null) { - if (mCurrentView.canGoBack()) { - mCurrentView.goBack(); - } else { - deleteTab(mWebViewList.indexOf(mCurrentView)); - } - } - break; - case R.id.action_forward: - if (mCurrentView != null) { - if (mCurrentView.canGoForward()) { - mCurrentView.goForward(); - } - } - break; case R.id.arrow_button: if (mSearch != null && mSearch.hasFocus()) { - mCurrentView.requestFocus(); + currentTab.requestFocus(); } else if (mShowTabsInDrawer) { mDrawerLayout.openDrawer(mDrawerLeft); - } else if (mCurrentView != null) { - mCurrentView.loadHomepage(); + } else { + currentTab.loadHomepage(); } break; - case R.id.new_tab_button: - newTab(null, true); - break; case R.id.button_next: - mWebView.findNext(false); + currentTab.findNext(); break; case R.id.button_back: - mWebView.findNext(true); + currentTab.findPrevious(); break; case R.id.button_quit: - mWebView.clearMatches(); + 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, mCurrentView.getUrl()); + read.putExtra(Constants.LOAD_READING_URL, currentTab.getUrl()); startActivity(read); break; case R.id.action_toggle_desktop: - mCurrentView.toggleDesktopUA(this); - mCurrentView.reload(); + currentTab.toggleDesktopUA(this); + currentTab.reload(); closeDrawers(); break; } @@ -2356,19 +1901,11 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements */ @Override public boolean onLongClick(View view) { - switch (view.getId()) { - case R.id.new_tab_button: - String url = mPreferences.getSavedUrl(); - if (url != null) { - newTab(url, true); - Utils.showSnackbar(this, R.string.deleted_tab); - } - mPreferences.setSavedUrl(null); - break; - } return true; } + // TODO Check if all the calls are relative to TabsFragement + /** * A utility method that creates a FrameLayout button with the given ID and * sets the image of the button to the given image ID. The OnClick and OnLongClick @@ -2381,10 +1918,10 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements * @param imageId the image to set as the button image */ private void setupFrameLayoutButton(@IdRes int buttonId, @IdRes int imageId) { - View frameButton = findViewById(buttonId); + final View frameButton = findViewById(buttonId); + final ImageView buttonImage = (ImageView) findViewById(imageId); frameButton.setOnClickListener(this); frameButton.setOnLongClickListener(this); - ImageView buttonImage = (ImageView) findViewById(imageId); buttonImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); } @@ -2400,11 +1937,7 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements super.onReceive(context, intent); boolean isConnected = isConnected(context); Log.d(Constants.TAG, "Network Connected: " + String.valueOf(isConnected)); - for (int n = 0, size = mWebViewList.size(); n < size; n++) { - WebView view = mWebViewList.get(n).getWebView(); - if (view != null) - view.setNetworkAvailable(isConnected); - } + mTabsManager.notifyConnectionStatus(isConnected); } }; @@ -2419,21 +1952,22 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - PermissionsManager.getInstance().notifyPermissionsChange(permissions); + PermissionsManager.getInstance().notifyPermissionsChange(permissions, grantResults); super.onRequestPermissionsResult(requestCode, permissions, grantResults); } private final Object mBusEventListener = new Object() { /** - * Load the given bookmark in the current tab, used by the the - * {@link acr.browser.lightning.fragment.BookmarksFragment} + * Load the given url in the current tab, used by the the + * {@link acr.browser.lightning.fragment.BookmarksFragment} and by the + * {@link LightningDialogBuilder} * * @param event Bus event indicating that the user has clicked a bookmark */ @Subscribe - public void loadBookmarkInCurrentTab(final BookmarkEvents.Clicked event) { - loadUrlInCurrentView(event.bookmark.getUrl()); + public void loadUrlInCurrentTab(final BrowserEvents.OpenUrlInCurrentTab event) { + loadUrlInCurrentView(event.url); // keep any jank from happening when the drawer is closed after the // URL starts to load final Handler handler = new Handler(); @@ -2446,15 +1980,16 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements } /** - * Load the given bookmark in a new tab, used by the the - * {@link acr.browser.lightning.fragment.BookmarksFragment} + * Load the given url in a new tab, used by the the + * {@link acr.browser.lightning.fragment.BookmarksFragment} and by the + * {@link LightningDialogBuilder} * * @param event Bus event indicating that the user wishes * to open a bookmark in a new tab */ @Subscribe - public void loadBookmarkInNewTab(final BookmarkEvents.AsNewTab event) { - newTab(event.bookmark.getUrl(), true); + public void loadUrlInNewTab(final BrowserEvents.OpenUrlInNewTab event) { + BrowserActivity.this.newTab(event.url, true); mDrawerLayout.closeDrawers(); } @@ -2467,8 +2002,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements */ @Subscribe public void bookmarkCurrentPage(final BookmarkEvents.WantToBookmarkCurrentPage event) { - if (mCurrentView != null) { - mEventBus.post(new BrowserEvents.AddBookmark(mCurrentView.getTitle(), mCurrentView.getUrl())); + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null) { + mEventBus.post(new BrowserEvents.AddBookmark(currentTab.getTitle(), currentTab.getUrl())); } } @@ -2490,12 +2026,13 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements */ @Subscribe public void bookmarkChanged(final BookmarkEvents.BookmarkChanged event) { - if (mCurrentView != null && mCurrentView.getUrl().startsWith(Constants.FILE) - && mCurrentView.getUrl().endsWith(Constants.BOOKMARKS_FILENAME)) { - openBookmarkPage(mWebView); + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null && currentTab.getUrl().startsWith(Constants.FILE) + && currentTab.getUrl().endsWith(BookmarkPage.FILENAME)) { + currentTab.loadBookmarkpage(); } - if (mCurrentView != null) { - mEventBus.post(new BrowserEvents.CurrentPageUrl(mCurrentView.getUrl())); + if (currentTab != null) { + mEventBus.post(new BrowserEvents.CurrentPageUrl(currentTab.getUrl())); } } @@ -2506,12 +2043,13 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements */ @Subscribe public void bookmarkDeleted(final BookmarkEvents.Deleted event) { - if (mCurrentView != null && mCurrentView.getUrl().startsWith(Constants.FILE) - && mCurrentView.getUrl().endsWith(Constants.BOOKMARKS_FILENAME)) { - openBookmarkPage(mWebView); + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null && currentTab.getUrl().startsWith(Constants.FILE) + && currentTab.getUrl().endsWith(BookmarkPage.FILENAME)) { + currentTab.loadBookmarkpage(); } - if (mCurrentView != null) { - mEventBus.post(new BrowserEvents.CurrentPageUrl(mCurrentView.getUrl())); + if (currentTab != null) { + mEventBus.post(new BrowserEvents.CurrentPageUrl(currentTab.getUrl())); } } @@ -2527,5 +2065,111 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements public void closeBookmarks(final BookmarkEvents.CloseBookmarks event) { mDrawerLayout.closeDrawer(mDrawerRight); } + + /** + * The user wants to close a tab + * + * @param event contains the position inside the tabs adapter + */ + @Subscribe + public void closeTab(final TabEvents.CloseTab event) { + deleteTab(event.position); + } + + /** + * The user clicked on a tab, let's show it + * + * @param event contains the tab position in the tabs adapter + */ + @Subscribe + public void showTab(final TabEvents.ShowTab event) { + BrowserActivity.this.showTab(event.position); + } + + /** + * The user long pressed on a tab, ask him if he want to close the tab + * + * @param event contains the tab position in the tabs adapter + */ + @Subscribe + public void showCloseDialog(final TabEvents.ShowCloseDialog event) { + BrowserActivity.this.showCloseDialog(event.position); + } + + /** + * The user wants to create a new tab + * + * @param event a marker + */ + @Subscribe + public void newTab(final TabEvents.NewTab event) { + BrowserActivity.this.newTab(null, true); + } + + /** + * The user wants to go back on current tab + * + * @param event a marker + */ + @Subscribe + public void goBack(final NavigationEvents.GoBack event) { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null) { + if (currentTab.canGoBack()) { + currentTab.goBack(); + } else { + deleteTab(mTabsManager.positionOf(currentTab)); + } + } + } + + /** + * The user wants to go forward on current tab + * + * @param event a marker + */ + @Subscribe + public void goForward(final NavigationEvents.GoForward event) { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null) { + if (currentTab.canGoForward()) { + currentTab.goForward(); + } + } + } + + @Subscribe + public void goHome(final NavigationEvents.GoHome event) { + final LightningView currentTab = mTabsManager.getCurrentTab(); + if (currentTab != null) { + currentTab.loadHomepage(); + closeDrawers(); + } + } + + /** + * The user long pressed the new tab button + * + * @param event a marker + */ + @Subscribe + public void newTabLongPress(final TabEvents.NewTabLongPress event) { + String url = mPreferences.getSavedUrl(); + if (url != null) { + BrowserActivity.this.newTab(url, true); + + Utils.showSnackbar(BrowserActivity.this, R.string.deleted_tab); + } + mPreferences.setSavedUrl(null); + } + + @Subscribe + public void displayInSnackbar(final BrowserEvents.ShowSnackBarMessage event) { + if (event.message != null) { + Utils.showSnackbar(BrowserActivity.this, event.message); + } else { + Utils.showSnackbar(BrowserActivity.this, event.stringRes); + } + } }; } diff --git a/app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java b/app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java index b7be9d3..f9e44b9 100644 --- a/app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/IncognitoActivity.java @@ -2,12 +2,13 @@ package acr.browser.lightning.activity; import android.content.Intent; import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.Menu; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import acr.browser.lightning.R; -import acr.browser.lightning.preference.PreferenceManager; @SuppressWarnings("deprecation") public class IncognitoActivity extends BrowserActivity { @@ -18,13 +19,13 @@ public class IncognitoActivity extends BrowserActivity { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { CookieSyncManager.createInstance(this); } - cookieManager.setAcceptCookie(PreferenceManager.getInstance().getIncognitoCookiesEnabled()); + cookieManager.setAcceptCookie(mPreferences.getIncognitoCookiesEnabled()); } - @Override - public synchronized void initializeTabs() { - newTab(null, true); - } +// @Override +// public synchronized void initializeTabs() { +// newTab(null, true); +// } @Override public boolean onCreateOptionsMenu(Menu menu) { @@ -45,7 +46,7 @@ public class IncognitoActivity extends BrowserActivity { } @Override - public void updateHistory(String title, String url) { + public void updateHistory(@Nullable String title, @NonNull String url) { // addItemToHistory(title, url); } diff --git a/app/src/main/java/acr/browser/lightning/activity/MainActivity.java b/app/src/main/java/acr/browser/lightning/activity/MainActivity.java index 68d6022..4d45d15 100644 --- a/app/src/main/java/acr/browser/lightning/activity/MainActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/MainActivity.java @@ -2,12 +2,13 @@ package acr.browser.lightning.activity; import android.content.Intent; import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.Menu; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import acr.browser.lightning.R; -import acr.browser.lightning.preference.PreferenceManager; @SuppressWarnings("deprecation") public class MainActivity extends BrowserActivity { @@ -18,14 +19,14 @@ public class MainActivity extends BrowserActivity { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { CookieSyncManager.createInstance(this); } - cookieManager.setAcceptCookie(PreferenceManager.getInstance().getCookiesEnabled()); + cookieManager.setAcceptCookie(mPreferences.getCookiesEnabled()); } - @Override - public synchronized void initializeTabs() { - restoreOrNewTab(); - // if incognito mode use newTab(null, true); instead - } +// @Override +// public synchronized void initializeTabs() { +// // restoreOrNewTab(); +// // if incognito mode use newTab(null, true); instead +// } @Override public boolean onCreateOptionsMenu(Menu menu) { @@ -46,7 +47,7 @@ public class MainActivity extends BrowserActivity { } @Override - public void updateHistory(String title, String url) { + public void updateHistory(@Nullable String title, @NonNull String url) { addItemToHistory(title, url); } diff --git a/app/src/main/java/acr/browser/lightning/activity/ReadingActivity.java b/app/src/main/java/acr/browser/lightning/activity/ReadingActivity.java index 8474e85..d2733f5 100644 --- a/app/src/main/java/acr/browser/lightning/activity/ReadingActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/ReadingActivity.java @@ -21,7 +21,10 @@ import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; +import java.lang.ref.WeakReference; + import acr.browser.lightning.R; +import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.reading.HtmlFetcher; @@ -38,6 +41,7 @@ public class ReadingActivity extends AppCompatActivity { private PreferenceManager mPreferences; private int mTextSize; private ProgressDialog mProgressDialog; + private PageLoader mLoaderReference; private static final float XXLARGE = 30.0f; private static final float XLARGE = 26.0f; @@ -49,7 +53,7 @@ public class ReadingActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { overridePendingTransition(R.anim.slide_in_from_right, R.anim.fade_out_scale); - mPreferences = PreferenceManager.getInstance(); + mPreferences = BrowserApp.getAppComponent().getPreferenceManager(); mInvert = mPreferences.getInvertColors(); final int color; if (mInvert) { @@ -129,29 +133,33 @@ public class ReadingActivity extends AppCompatActivity { } if (getSupportActionBar() != null) getSupportActionBar().setTitle(Utils.getDomainName(mUrl)); - new PageLoader(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mUrl); + mLoaderReference = new PageLoader(this); + mLoaderReference.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mUrl); return true; } private class PageLoader extends AsyncTask { - private final Activity mActivity; + private final WeakReference mActivityReference; private String mTitleText; private String mBodyText; public PageLoader(Activity activity) { - mActivity = activity; + mActivityReference = new WeakReference<>(activity); } @Override protected void onPreExecute() { super.onPreExecute(); - mProgressDialog = new ProgressDialog(mActivity); - mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); - mProgressDialog.setCancelable(false); - mProgressDialog.setIndeterminate(true); - mProgressDialog.setMessage(mActivity.getString(R.string.loading)); - mProgressDialog.show(); + Activity activity = mActivityReference.get(); + if (activity != null) { + mProgressDialog = new ProgressDialog(activity); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + mProgressDialog.setCancelable(false); + mProgressDialog.setIndeterminate(true); + mProgressDialog.setMessage(activity.getString(R.string.loading)); + mProgressDialog.show(); + } } @Override @@ -223,6 +231,7 @@ public class ReadingActivity extends AppCompatActivity { mProgressDialog.dismiss(); mProgressDialog = null; } + mLoaderReference.cancel(true); super.onDestroy(); } diff --git a/app/src/main/java/acr/browser/lightning/activity/SettingsActivity.java b/app/src/main/java/acr/browser/lightning/activity/SettingsActivity.java index d97c0da..512804c 100644 --- a/app/src/main/java/acr/browser/lightning/activity/SettingsActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/SettingsActivity.java @@ -15,7 +15,7 @@ import java.util.ArrayList; import java.util.List; import acr.browser.lightning.R; -import acr.browser.lightning.utils.PermissionsManager; +import com.anthonycr.grant.PermissionsManager; public class SettingsActivity extends ThemableSettingsActivity { @@ -61,7 +61,7 @@ public class SettingsActivity extends ThemableSettingsActivity { @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - PermissionsManager.getInstance().notifyPermissionsChange(permissions); + PermissionsManager.getInstance().notifyPermissionsChange(permissions, grantResults); super.onRequestPermissionsResult(requestCode, permissions, grantResults); } } diff --git a/app/src/main/java/acr/browser/lightning/activity/TabsManager.java b/app/src/main/java/acr/browser/lightning/activity/TabsManager.java new file mode 100644 index 0000000..6f14bb8 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/activity/TabsManager.java @@ -0,0 +1,289 @@ +package acr.browser.lightning.activity; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.support.annotation.Nullable; +import android.support.v7.app.AlertDialog; +import android.util.Log; +import android.webkit.WebView; + +import com.squareup.otto.Bus; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import acr.browser.lightning.R; +import acr.browser.lightning.bus.BrowserEvents; +import acr.browser.lightning.constant.Constants; +import acr.browser.lightning.preference.PreferenceManager; +import acr.browser.lightning.utils.UrlUtils; +import acr.browser.lightning.utils.Utils; +import acr.browser.lightning.view.LightningView; + +/** + * @author Stefano Pacifici + * @date 2015/09/14 + */ +@Singleton +public class TabsManager { + + private static final String TAG = TabsManager.class.getSimpleName(); + private final List mWebViewList = new ArrayList<>(); + private LightningView mCurrentTab; + + @Inject + PreferenceManager mPreferenceManager; + + @Inject + Bus mEventBus; + + @Inject + public TabsManager() {} + + public synchronized void restoreTabsAndHandleIntent(final Activity activity, + final Intent intent, + final boolean incognito) { + String url = null; + if (intent != null) { + url = intent.getDataString(); + } + mWebViewList.clear(); + mCurrentTab = null; + if (!incognito && mPreferenceManager.getRestoreLostTabsEnabled()) { + final String mem = mPreferenceManager.getMemoryUrl(); + mPreferenceManager.setMemoryUrl(""); + String[] array = Utils.getArray(mem); + for (String urlString : array) { + if (!urlString.isEmpty()) { + newTab(activity, urlString, incognito); + } + } + } + if (url != null) { + if (url.startsWith(Constants.FILE)) { + final String urlToLoad = url; + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + 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, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + newTab(activity, urlToLoad, incognito); + } + }) + .show(); + } else { + newTab(activity, url, incognito); + } + } + if (mWebViewList.size() == 0) { + newTab(activity, null, incognito); + } + } + + /** + * Return a clone of the current tabs list. The list will not be updated, the user has to fetch + * a new copy when notified. + * + * @return a copy of the current tabs list + */ + public List getTabsList() { + return new ArrayList<>(mWebViewList); + } + + /** + * Return the tab at the given position in tabs list, or null if position is not in tabs list + * range. + * + * @param position the index in tabs list + * @return the corespondent {@link LightningView}, or null if the index is invalid + */ + @Nullable + public synchronized LightningView getTabAtPosition(final int position) { + if (position < 0 || position >= mWebViewList.size()) { + return null; + } + + return mWebViewList.get(position); + } + + /** + * Try to low memory pressure + */ + public synchronized void freeMemory() { + for (LightningView tab : mWebViewList) { + tab.freeMemory(); + } + } + + /** + * Shutdown the manager + */ + public synchronized void shutdown() { + for (LightningView tab : mWebViewList) { + tab.onDestroy(); + } + mWebViewList.clear(); + mCurrentTab = null; + } + + /** + * Resume the tabs + * + * @param context + */ + public synchronized void resume(final Context context) { + for (LightningView tab : mWebViewList) { + tab.initializePreferences(null, context); + } + } + + /** + * Forward network connection status to the webviews. + * + * @param isConnected + */ + public synchronized void notifyConnectionStatus(final boolean isConnected) { + for (LightningView tab : mWebViewList) { + final WebView webView = tab.getWebView(); + if (webView != null) { + webView.setNetworkAvailable(isConnected); + } + } + } + + /** + * @return The number of currently opened tabs + */ + public synchronized int size() { + return mWebViewList.size(); + } + + /** + * Create and return a new tab. The tab is automatically added to the tabs list. + * + * @param activity + * @param url + * @param isIncognito + * @return + */ + public synchronized LightningView newTab(final Activity activity, + final String url, + final boolean isIncognito) { + final LightningView tab = new LightningView(activity, url, isIncognito); + mWebViewList.add(tab); + return tab; + } + + /** + * Remove a tab and return its reference or null if the position is not in tabs range + * + * @param position The position of the tab to remove + * @return The removed tab reference or null + */ + @Nullable + public synchronized LightningView removeTab(final int position) { + if (position >= mWebViewList.size()) { + return null; + } + final LightningView tab = mWebViewList.remove(position); + if (mCurrentTab == tab) { + mCurrentTab = null; + } + tab.onDestroy(); + Log.d(Constants.TAG, tab.toString()); + return tab; + } + + public synchronized void deleteTab(int position) { + final LightningView currentTab = getCurrentTab(); + int current = positionOf(currentTab); + + if (current == position) { + if (size() == 1) { + mCurrentTab = null; + } else if (current < size() - 1 ) { + // There is another tab after this one + mCurrentTab = getTabAtPosition(current + 1); + } else { + mCurrentTab = getTabAtPosition(current - 1); + } + removeTab(current); + } else { + removeTab(position); + } + } + + /** + * Return the position of the given tab. + * + * @param tab the tab to look for + * @return the position of the tab or -1 if the tab is not in the list + */ + public synchronized int positionOf(final LightningView tab) { + return mWebViewList.indexOf(tab); + } + + /** + * @return A string representation of the currently opened tabs + */ + public String tabsString() { + final StringBuilder builder = new StringBuilder(); + for (LightningView tab : mWebViewList) { + final String url = tab.getUrl(); + if (!url.isEmpty()) { + builder.append(url).append("|$|SEPARATOR|$|"); + } + } + return builder.toString(); + } + + /** + * Return the {@link WebView} associated to the current tab, or null if there is no current tab + * + * @return a {@link WebView} or null + */ + @Nullable + public synchronized WebView getCurrentWebView() { + return mCurrentTab != null ? mCurrentTab.getWebView() : null; + } + + /** + * TODO We should remove also this, but probably not + * + * @return + */ + @Nullable + public synchronized LightningView getCurrentTab() { + return mCurrentTab; + } + + /** + * Switch the current tab to the one at the given position. It returns the selected. After this + * call {@link TabsManager#getCurrentTab()} return the same reference returned by this method if + * position is valid. + * + * @return the selected tab or null if position is out of tabs range + */ + @Nullable + public synchronized LightningView switchToTab(final int position) { + if (position < 0 || position >= mWebViewList.size()) { + Log.e(TAG, "Returning a null LightningView requested for position: " + position); + return null; + } else { + final LightningView tab = mWebViewList.get(position); + if (tab != null) { + mCurrentTab = tab; + } + return tab; + } + } + +} diff --git a/app/src/main/java/acr/browser/lightning/activity/ThemableBrowserActivity.java b/app/src/main/java/acr/browser/lightning/activity/ThemableBrowserActivity.java index 88a01dc..13f210e 100644 --- a/app/src/main/java/acr/browser/lightning/activity/ThemableBrowserActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/ThemableBrowserActivity.java @@ -5,18 +5,25 @@ import android.content.res.Configuration; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; +import javax.inject.Inject; + import acr.browser.lightning.R; +import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.preference.PreferenceManager; public abstract class ThemableBrowserActivity extends AppCompatActivity { + @Inject + PreferenceManager mPreferences; + private int mTheme; private boolean mShowTabsInDrawer; @Override protected void onCreate(Bundle savedInstanceState) { - mTheme = PreferenceManager.getInstance().getUseTheme(); - mShowTabsInDrawer = PreferenceManager.getInstance().getShowTabsInDrawer(!isTablet()); + BrowserApp.getAppComponent().inject(this); + mTheme = mPreferences.getUseTheme(); + mShowTabsInDrawer = mPreferences.getShowTabsInDrawer(!isTablet()); // set the theme if (mTheme == 1) { @@ -30,8 +37,8 @@ public abstract class ThemableBrowserActivity extends AppCompatActivity { @Override protected void onResume() { super.onResume(); - int theme = PreferenceManager.getInstance().getUseTheme(); - boolean drawerTabs = PreferenceManager.getInstance().getShowTabsInDrawer(!isTablet()); + int theme = mPreferences.getUseTheme(); + boolean drawerTabs = mPreferences.getShowTabsInDrawer(!isTablet()); if (theme != mTheme || mShowTabsInDrawer != drawerTabs) { restart(); } diff --git a/app/src/main/java/acr/browser/lightning/activity/ThemableSettingsActivity.java b/app/src/main/java/acr/browser/lightning/activity/ThemableSettingsActivity.java index c5a4573..50abecb 100644 --- a/app/src/main/java/acr/browser/lightning/activity/ThemableSettingsActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/ThemableSettingsActivity.java @@ -4,7 +4,7 @@ import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import acr.browser.lightning.R; -import acr.browser.lightning.preference.PreferenceManager; +import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.utils.ThemeUtils; public abstract class ThemableSettingsActivity extends AppCompatPreferenceActivity { @@ -13,7 +13,7 @@ public abstract class ThemableSettingsActivity extends AppCompatPreferenceActivi @Override protected void onCreate(Bundle savedInstanceState) { - mTheme = PreferenceManager.getInstance().getUseTheme(); + mTheme = BrowserApp.getAppComponent().getPreferenceManager().getUseTheme(); // set the theme if (mTheme == 0) { @@ -32,7 +32,7 @@ public abstract class ThemableSettingsActivity extends AppCompatPreferenceActivi @Override protected void onResume() { super.onResume(); - if (PreferenceManager.getInstance().getUseTheme() != mTheme) { + if (BrowserApp.getAppComponent().getPreferenceManager().getUseTheme() != mTheme) { restart(); } } diff --git a/app/src/main/java/acr/browser/lightning/app/AppComponent.java b/app/src/main/java/acr/browser/lightning/app/AppComponent.java index dbaf0bc..6be83ca 100644 --- a/app/src/main/java/acr/browser/lightning/app/AppComponent.java +++ b/app/src/main/java/acr/browser/lightning/app/AppComponent.java @@ -1,13 +1,23 @@ package acr.browser.lightning.app; +import android.content.Context; + +import com.squareup.otto.Bus; + import javax.inject.Singleton; import acr.browser.lightning.activity.BrowserActivity; +import acr.browser.lightning.activity.ThemableBrowserActivity; import acr.browser.lightning.constant.BookmarkPage; -import acr.browser.lightning.dialog.BookmarksDialogBuilder; +import acr.browser.lightning.database.HistoryDatabase; +import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.fragment.BookmarkSettingsFragment; import acr.browser.lightning.fragment.BookmarksFragment; +import acr.browser.lightning.fragment.LightningPreferenceFragment; +import acr.browser.lightning.fragment.TabsFragment; import acr.browser.lightning.object.SearchAdapter; +import acr.browser.lightning.preference.PreferenceManager; +import acr.browser.lightning.view.LightningView; import dagger.Component; /** @@ -25,7 +35,25 @@ public interface AppComponent { void inject(SearchAdapter adapter); - void inject(BookmarksDialogBuilder builder); + void inject(LightningDialogBuilder builder); void inject(BookmarkPage bookmarkPage); + + void inject(TabsFragment fragment); + + PreferenceManager getPreferenceManager(); + + void inject(LightningPreferenceFragment fragment); + + BookmarkPage getBookmarkPage(); + + Bus getBus(); + + HistoryDatabase getHistoryDatabase(); + + Context getApplicationContext(); + + void inject(LightningView lightningView); + + void inject(ThemableBrowserActivity activity); } diff --git a/app/src/main/java/acr/browser/lightning/app/BrowserApp.java b/app/src/main/java/acr/browser/lightning/app/BrowserApp.java index e5f00fa..61be9df 100644 --- a/app/src/main/java/acr/browser/lightning/app/BrowserApp.java +++ b/app/src/main/java/acr/browser/lightning/app/BrowserApp.java @@ -7,19 +7,24 @@ import com.squareup.leakcanary.LeakCanary; public class BrowserApp extends Application { - private static Context context; + private static Context sContext; private static AppComponent appComponent; @Override public void onCreate() { super.onCreate(); - context = getApplicationContext(); LeakCanary.install(this); buildDepencyGraph(); } + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + sContext = base; + } + public static Context getAppContext() { - return context; + return sContext; } public static AppComponent getAppComponent() { diff --git a/app/src/main/java/acr/browser/lightning/bus/BookmarkEvents.java b/app/src/main/java/acr/browser/lightning/bus/BookmarkEvents.java index 3331eb7..1bff1cb 100644 --- a/app/src/main/java/acr/browser/lightning/bus/BookmarkEvents.java +++ b/app/src/main/java/acr/browser/lightning/bus/BookmarkEvents.java @@ -11,28 +11,6 @@ public final class BookmarkEvents { // No instances } - /** - * A bookmark was clicked - */ - public final static class Clicked { - public final HistoryItem bookmark; - - public Clicked(final HistoryItem bookmark) { - this.bookmark = bookmark; - } - } - - /** - * The user ask to open the bookmark as new tab - */ - public final static class AsNewTab { - public final HistoryItem bookmark; - - public AsNewTab(final HistoryItem bookmark) { - this.bookmark = bookmark; - } - } - /** * The user ask to delete the selected bookmark */ @@ -61,13 +39,6 @@ public final class BookmarkEvents { } } - /** - * The {@link acr.browser.lightning.fragment.BookmarksFragment} want to know the url (and title) - * of the currently shown web page. - */ - // public static class WantInfoAboutCurrentPage { - // } - /** * Sended by the {@link acr.browser.lightning.fragment.BookmarksFragment} when it wants to close * itself (generally in reply to a {@link acr.browser.lightning.bus.BrowserEvents.UserPressedBack} diff --git a/app/src/main/java/acr/browser/lightning/bus/BrowserEvents.java b/app/src/main/java/acr/browser/lightning/bus/BrowserEvents.java index 15ff373..478dbce 100644 --- a/app/src/main/java/acr/browser/lightning/bus/BrowserEvents.java +++ b/app/src/main/java/acr/browser/lightning/bus/BrowserEvents.java @@ -1,5 +1,7 @@ package acr.browser.lightning.bus; +import android.support.annotation.StringRes; + /** * Created by Stefano Pacifici on 26/08/15. */ @@ -24,9 +26,8 @@ public final class BrowserEvents { } /** - * Used to reply to {@link acr.browser.lightning.fragment.BookmarksFragment} message - * {@link acr.browser.lightning.bus.BookmarkEvents.WantInfoAboutCurrentPage}. This is generally - * used to update the {@link acr.browser.lightning.fragment.BookmarksFragment} interface. + * Notify the current page has a new url. This is generally used to update the + * {@link acr.browser.lightning.fragment.BookmarksFragment} interface. */ public static class CurrentPageUrl { public final String url; @@ -41,4 +42,55 @@ public final class BrowserEvents { */ public static class UserPressedBack { } + + /** + * Notify that the user closed or opened a tab + */ + public static class TabsChanged { + } + + /** + * + */ + + /** + * Notify the Browser to display a SnackBar in the main activity + */ + public static class ShowSnackBarMessage { + public final String message; + @StringRes + public final int stringRes; + + public ShowSnackBarMessage(final String message) { + this.message = message; + this.stringRes = -1; + } + + public ShowSnackBarMessage(@StringRes final int stringRes) { + this.message = null; + this.stringRes = stringRes; + } + } + + /** + * The user want to open the given url in the current tab + */ + public final static class OpenUrlInCurrentTab { + public final String url; + + public OpenUrlInCurrentTab(final String url) { + this.url = url; + } + } + + /** + * The user ask to open the given url as new tab + */ + public final static class OpenUrlInNewTab { + public final String url; + + public OpenUrlInNewTab(final String url) { + this.url = url; + } + } } diff --git a/app/src/main/java/acr/browser/lightning/bus/NavigationEvents.java b/app/src/main/java/acr/browser/lightning/bus/NavigationEvents.java new file mode 100644 index 0000000..5f7519f --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/bus/NavigationEvents.java @@ -0,0 +1,34 @@ +package acr.browser.lightning.bus; + +/** + * Collections of navigation events, like go back or go forward + * + * @author Stefano Pacifici + * @date 2015/09/15 + */ +public class NavigationEvents { + private NavigationEvents() { + // No instances please + } + + /** + * Fired by {@link acr.browser.lightning.fragment.TabsFragment} when the user presses back + * button. + */ + public static class GoBack { + } + + /** + * Fired by {@link acr.browser.lightning.fragment.TabsFragment} when the user presses forward + * button. + */ + public static class GoForward { + } + + /** + * Fired by {@link acr.browser.lightning.fragment.TabsFragment} when the user presses the home + * button. + */ + public static class GoHome { + } +} diff --git a/app/src/main/java/acr/browser/lightning/bus/TabEvents.java b/app/src/main/java/acr/browser/lightning/bus/TabEvents.java new file mode 100644 index 0000000..a2bac5c --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/bus/TabEvents.java @@ -0,0 +1,65 @@ +package acr.browser.lightning.bus; + +/** + * A collection of events been sent by {@link acr.browser.lightning.fragment.TabsFragment} + * + * @author Stefano Pacifici + * @date 2015/09/14 + */ +public final class TabEvents { + + private TabEvents() { + // No instances + } + + + /** + * Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user click on the + * tab exit button + */ + public static class CloseTab { + public final int position; + + public CloseTab(int position) { + this.position = position; + } + } + + /** + * Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user click on the + * tab itself. + */ + public static class ShowTab { + public final int position; + + public ShowTab(int position) { + this.position = position; + } + } + + /** + * Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user long press on the + * tab itself. + */ + public static class ShowCloseDialog { + public final int position; + + public ShowCloseDialog(int position) { + this.position = position; + } + } + + /** + * Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user want to create a + * new tab. + */ + public static class NewTab { + } + + /** + * Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user long presses on + * new tab button. + */ + public static class NewTabLongPress { + } +} diff --git a/app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java b/app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java index 67c1765..d5f6401 100644 --- a/app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java +++ b/app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java @@ -20,11 +20,16 @@ import acr.browser.lightning.utils.Utils; public final class BookmarkPage { + /** + * The bookmark page standard suffix + */ + public static final String FILENAME = "bookmarks.html"; + private static final String HEADING = "\n" + "\n" + "\n" + "\n" + - "\n" + + "\n" + "" + BrowserApp.getAppContext().getString(R.string.action_bookmarks) + "\n" + @@ -62,12 +67,13 @@ public final class BookmarkPage { CACHE_DIR = context.getCacheDir(); } - public void buildBookmarkPage(final String folder, final List list) { + public void buildBookmarkPage(final String folder) { + final List list = manager.getBookmarksFromFolder(folder, true); final File bookmarkWebPage; if (folder == null || folder.isEmpty()) { - bookmarkWebPage = new File(FILES_DIR, Constants.BOOKMARKS_FILENAME); + bookmarkWebPage = new File(FILES_DIR, FILENAME); } else { - bookmarkWebPage = new File(FILES_DIR, folder + '-' + Constants.BOOKMARKS_FILENAME); + bookmarkWebPage = new File(FILES_DIR, folder + '-' + FILENAME); } final StringBuilder bookmarkBuilder = new StringBuilder(BookmarkPage.HEADING); @@ -76,14 +82,14 @@ public final class BookmarkPage { final HistoryItem item = list.get(n); bookmarkBuilder.append(BookmarkPage.PART1); if (item.isFolder()) { - final File folderPage = new File(FILES_DIR, item.getTitle() + '-' + Constants.BOOKMARKS_FILENAME); + final File folderPage = new File(FILES_DIR, item.getTitle() + '-' + FILENAME); bookmarkBuilder.append(Constants.FILE).append(folderPage); bookmarkBuilder.append(BookmarkPage.PART2); bookmarkBuilder.append(folderIconPath); new Thread(new Runnable() { @Override public void run() { - buildBookmarkPage(item.getTitle(), manager.getBookmarksFromFolder(item.getTitle(), true)); + buildBookmarkPage(item.getTitle()); } }).run(); } else { diff --git a/app/src/main/java/acr/browser/lightning/constant/Constants.java b/app/src/main/java/acr/browser/lightning/constant/Constants.java index 33bc973..46feeec 100644 --- a/app/src/main/java/acr/browser/lightning/constant/Constants.java +++ b/app/src/main/java/acr/browser/lightning/constant/Constants.java @@ -43,11 +43,6 @@ public final class Constants { public static final int PROXY_I2P = 2; public static final int PROXY_MANUAL = 3; - /** - * The bookmark page standard suffix - */ - public static final String BOOKMARKS_FILENAME = "bookmarks.html"; - public static final String DEFAULT_ENCODING = "UTF-8"; public static final String[] TEXT_ENCODINGS = {"ISO-8859-1", "UTF-8", "GBK", "Big5", "ISO-2022-JP", "SHIFT_JS", "EUC-JP", "EUC-KR"}; diff --git a/app/src/main/java/acr/browser/lightning/constant/HistoryPage.java b/app/src/main/java/acr/browser/lightning/constant/HistoryPage.java index 41dc907..8e7d936 100644 --- a/app/src/main/java/acr/browser/lightning/constant/HistoryPage.java +++ b/app/src/main/java/acr/browser/lightning/constant/HistoryPage.java @@ -66,7 +66,7 @@ public class HistoryPage { } private static List getWebHistory(Context context) { - HistoryDatabase databaseHandler = HistoryDatabase.getInstance(); + HistoryDatabase databaseHandler = BrowserApp.getAppComponent().getHistoryDatabase(); return databaseHandler.getLastHundredItems(); } } diff --git a/app/src/main/java/acr/browser/lightning/constant/StartPage.java b/app/src/main/java/acr/browser/lightning/constant/StartPage.java index 35b5453..b13d426 100644 --- a/app/src/main/java/acr/browser/lightning/constant/StartPage.java +++ b/app/src/main/java/acr/browser/lightning/constant/StartPage.java @@ -16,7 +16,7 @@ import acr.browser.lightning.utils.Utils; public class StartPage { - private static final String FILENAME = "homepage.html"; + public static final String FILENAME = "homepage.html"; private static final String HEAD = "" + "" @@ -57,11 +57,12 @@ public class StartPage { StringBuilder homepageBuilder = new StringBuilder(StartPage.HEAD); String icon; String searchUrl; - switch (PreferenceManager.getInstance().getSearchChoice()) { + final PreferenceManager preferenceManager = BrowserApp.getAppComponent().getPreferenceManager(); + switch (preferenceManager.getSearchChoice()) { case 0: // CUSTOM SEARCH icon = "file:///android_asset/lightning.png"; - searchUrl = PreferenceManager.getInstance().getSearchUrl(); + searchUrl = preferenceManager.getSearchUrl(); break; case 1: // GOOGLE_SEARCH; diff --git a/app/src/main/java/acr/browser/lightning/controller/BrowserController.java b/app/src/main/java/acr/browser/lightning/controller/UIController.java similarity index 57% rename from app/src/main/java/acr/browser/lightning/controller/BrowserController.java rename to app/src/main/java/acr/browser/lightning/controller/UIController.java index 73c8e7f..2dbafd2 100644 --- a/app/src/main/java/acr/browser/lightning/controller/BrowserController.java +++ b/app/src/main/java/acr/browser/lightning/controller/UIController.java @@ -4,36 +4,40 @@ package acr.browser.lightning.controller; import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Message; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.view.View; import android.webkit.ValueCallback; import android.webkit.WebChromeClient.CustomViewCallback; -import android.webkit.WebView; import acr.browser.lightning.view.LightningView; -public interface BrowserController { +public interface UIController { - void updateUrl(String title, boolean shortUrl); + void changeToolbarBackground(@NonNull Bitmap favicon, @Nullable Drawable drawable); - void updateProgress(int n); + @ColorInt + int getUiColor(); - void updateHistory(String title, String url); + boolean getUseDarkTheme(); - void openFileChooser(ValueCallback uploadMsg); + void updateUrl(@Nullable String title, boolean shortUrl); - void updateTabs(); + void updateProgress(int n); - void onLongPress(); + void updateHistory(@Nullable String title, @NonNull String url); - void onShowCustomView(View view, CustomViewCallback callback); + void openFileChooser(ValueCallback uploadMsg); - void onHideCustomView(); + void onShowCustomView(View view, CustomViewCallback callback); - Bitmap getDefaultVideoPoster(); + void onShowCustomView(View view, CustomViewCallback callback, int requestedOrienation); - View getVideoLoadingProgressView(); + void onHideCustomView(); void onCreateWindow(Message resultMsg); @@ -43,16 +47,8 @@ public interface BrowserController { void showActionBar(); - void longClickPage(String url); - - void openBookmarkPage(WebView view); - void showFileChooser(ValueCallback filePathCallback); void closeEmptyTab(); - boolean proxyIsNotReady(); - - // void updateBookmarkIndicator(String url); - } diff --git a/app/src/main/java/acr/browser/lightning/database/BookmarkLocalSync.java b/app/src/main/java/acr/browser/lightning/database/BookmarkLocalSync.java index 1c1b8c1..2857570 100644 --- a/app/src/main/java/acr/browser/lightning/database/BookmarkLocalSync.java +++ b/app/src/main/java/acr/browser/lightning/database/BookmarkLocalSync.java @@ -26,7 +26,7 @@ public class BookmarkLocalSync { private final Context mContext; - public BookmarkLocalSync(Context context) { + public BookmarkLocalSync(@NonNull Context context) { mContext = context; } @@ -72,7 +72,7 @@ public class BookmarkLocalSync { if (!isChromeSupported()) { return list; } - Cursor cursor = getStockCursor(); + Cursor cursor = getChromeCursor(); try { if (cursor != null) { for (int n = 0; n < cursor.getColumnCount(); n++) { diff --git a/app/src/main/java/acr/browser/lightning/database/HistoryDatabase.java b/app/src/main/java/acr/browser/lightning/database/HistoryDatabase.java index 82801c5..bc1895d 100644 --- a/app/src/main/java/acr/browser/lightning/database/HistoryDatabase.java +++ b/app/src/main/java/acr/browser/lightning/database/HistoryDatabase.java @@ -8,13 +8,18 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; +import javax.inject.Singleton; + import acr.browser.lightning.R; -import acr.browser.lightning.app.BrowserApp; +@Singleton public class HistoryDatabase extends SQLiteOpenHelper { // All Static variables @@ -35,18 +40,8 @@ public class HistoryDatabase extends SQLiteOpenHelper { private SQLiteDatabase mDatabase; - private static HistoryDatabase mInstance; - - private boolean mLock; - - public static HistoryDatabase getInstance() { - if (mInstance == null || mInstance.isClosed()) { - mInstance = new HistoryDatabase(BrowserApp.getAppContext()); - } - return mInstance; - } - - private HistoryDatabase(Context context) { + @Inject + public HistoryDatabase(Context context) { super(context.getApplicationContext(), DATABASE_NAME, null, DATABASE_VERSION); mDatabase = this.getWritableDatabase(); } @@ -69,70 +64,61 @@ public class HistoryDatabase extends SQLiteOpenHelper { onCreate(db); } - public void deleteHistory() { + public synchronized void deleteHistory() { mDatabase.delete(TABLE_HISTORY, null, null); mDatabase.close(); mDatabase = this.getWritableDatabase(); } - public boolean isClosed() { + private synchronized boolean isClosed() { return mDatabase == null || !mDatabase.isOpen(); } @Override public synchronized void close() { - if (!mLock) { - if (mDatabase != null) { - mDatabase.close(); - mDatabase = null; - } + if (mDatabase != null) { + mDatabase.close(); + mDatabase = null; } super.close(); } private void openIfNecessary() { - if (mDatabase == null) { + if (isClosed()) { mDatabase = this.getWritableDatabase(); } } public synchronized void deleteHistoryItem(String url) { - mLock = true; openIfNecessary(); mDatabase.delete(TABLE_HISTORY, KEY_URL + " = ?", new String[]{url}); - mLock = false; } - public synchronized void visitHistoryItem(String url, String title) { - mLock = true; + public synchronized void visitHistoryItem(@NonNull String url, @Nullable String title) { openIfNecessary(); ContentValues values = new ContentValues(); - values.put(KEY_TITLE, title); + values.put(KEY_TITLE, title == null ? "" : title); values.put(KEY_TIME_VISITED, System.currentTimeMillis()); Cursor q = mDatabase.query(false, TABLE_HISTORY, new String[]{KEY_URL}, KEY_URL + " = ?", new String[]{url}, null, null, null, "1"); if (q.getCount() > 0) { mDatabase.update(TABLE_HISTORY, values, KEY_URL + " = ?", new String[]{url}); } else { - addHistoryItem(new HistoryItem(url, title)); + addHistoryItem(new HistoryItem(url, title == null ? "" : title)); } q.close(); - mLock = false; } - private synchronized void addHistoryItem(HistoryItem item) { - mLock = true; + private synchronized void addHistoryItem(@NonNull HistoryItem item) { openIfNecessary(); ContentValues values = new ContentValues(); values.put(KEY_URL, item.getUrl()); values.put(KEY_TITLE, item.getTitle()); values.put(KEY_TIME_VISITED, System.currentTimeMillis()); mDatabase.insert(TABLE_HISTORY, null, values); - mLock = false; } - String getHistoryItem(String url) { - mLock = true; + synchronized String getHistoryItem(String url) { openIfNecessary(); Cursor cursor = mDatabase.query(TABLE_HISTORY, new String[]{KEY_ID, KEY_URL, KEY_TITLE}, KEY_URL + " = ?", new String[]{url}, null, null, null, null); @@ -143,14 +129,15 @@ public class HistoryDatabase extends SQLiteOpenHelper { cursor.close(); } - mLock = false; return m; } - public List findItemsContaining(String search) { - mLock = true; + public synchronized List findItemsContaining(@Nullable String search) { openIfNecessary(); List itemList = new ArrayList<>(5); + if (search == null) { + return itemList; + } String selectQuery = "SELECT * FROM " + TABLE_HISTORY + " WHERE " + KEY_TITLE + " LIKE '%" + search + "%' OR " + KEY_URL + " LIKE '%" + search + "%' " + "ORDER BY " + KEY_TIME_VISITED + " DESC LIMIT 5"; @@ -168,12 +155,10 @@ public class HistoryDatabase extends SQLiteOpenHelper { } while (cursor.moveToNext() && n < 5); } cursor.close(); - mLock = false; return itemList; } - public List getLastHundredItems() { - mLock = true; + public synchronized List getLastHundredItems() { openIfNecessary(); List itemList = new ArrayList<>(100); String selectQuery = "SELECT * FROM " + TABLE_HISTORY + " ORDER BY " + KEY_TIME_VISITED @@ -192,12 +177,10 @@ public class HistoryDatabase extends SQLiteOpenHelper { } while (cursor.moveToNext() && counter < 100); } cursor.close(); - mLock = false; return itemList; } - public List getAllHistoryItems() { - mLock = true; + public synchronized List getAllHistoryItems() { openIfNecessary(); List itemList = new ArrayList<>(); String selectQuery = "SELECT * FROM " + TABLE_HISTORY + " ORDER BY " + KEY_TIME_VISITED @@ -215,18 +198,15 @@ public class HistoryDatabase extends SQLiteOpenHelper { } while (cursor.moveToNext()); } cursor.close(); - mLock = false; return itemList; } - public int getHistoryItemsCount() { - mLock = true; + public synchronized int getHistoryItemsCount() { openIfNecessary(); String countQuery = "SELECT * FROM " + TABLE_HISTORY; Cursor cursor = mDatabase.rawQuery(countQuery, null); int n = cursor.getCount(); cursor.close(); - mLock = false; return n; } } diff --git a/app/src/main/java/acr/browser/lightning/dialog/BookmarksDialogBuilder.java b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java similarity index 60% rename from app/src/main/java/acr/browser/lightning/dialog/BookmarksDialogBuilder.java rename to app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java index ec8282c..02c3f8e 100644 --- a/app/src/main/java/acr/browser/lightning/dialog/BookmarksDialogBuilder.java +++ b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java @@ -1,8 +1,12 @@ package acr.browser.lightning.dialog; +import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; import android.content.DialogInterface; import android.net.Uri; +import android.support.annotation.NonNull; import android.support.v7.app.AlertDialog; import android.view.View; import android.widget.ArrayAdapter; @@ -19,24 +23,33 @@ import javax.inject.Inject; import acr.browser.lightning.R; import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.bus.BookmarkEvents; +import acr.browser.lightning.bus.BrowserEvents; +import acr.browser.lightning.constant.BookmarkPage; import acr.browser.lightning.constant.Constants; +import acr.browser.lightning.constant.HistoryPage; import acr.browser.lightning.database.BookmarkManager; +import acr.browser.lightning.database.HistoryDatabase; import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.utils.Utils; /** + * TODO Rename this class it doesn't build dialogs only for bookmarks + * * Created by Stefano Pacifici on 02/09/15, based on Anthony C. Restaino's code. */ -public class BookmarksDialogBuilder { +public class LightningDialogBuilder { @Inject BookmarkManager bookmarkManager; + @Inject + HistoryDatabase mHistoryDatabase; + @Inject Bus eventBus; @Inject - public BookmarksDialogBuilder() { + public LightningDialogBuilder() { BrowserApp.getAppComponent().inject(this); } @@ -46,13 +59,13 @@ public class BookmarksDialogBuilder { * @param context used to show the dialog * @param url the long pressed url */ - public void showLongPressedDialogForUrl(final Context context, final String url) { + public void showLongPressedDialogForBookmarkUrl(final Context context, final String url) { final HistoryItem item; - if (url.startsWith(Constants.FILE) && url.endsWith(Constants.BOOKMARKS_FILENAME)) { + if (url.startsWith(Constants.FILE) && url.endsWith(BookmarkPage.FILENAME)) { // TODO hacky, make a better bookmark mechanism in the future final Uri uri = Uri.parse(url); final String filename = uri.getLastPathSegment(); - final String folderTitle = filename.substring(0, filename.length() - Constants.BOOKMARKS_FILENAME.length() - 1); + final String folderTitle = filename.substring(0, filename.length() - BookmarkPage.FILENAME.length() - 1); item = new HistoryItem(); item.setIsFolder(true); item.setTitle(folderTitle); @@ -65,19 +78,19 @@ public class BookmarksDialogBuilder { if (item.isFolder()) { showBookmarkFolderLongPressedDialog(context, item); } else { - showLongPressedDialogForUrl(context, item); + showLongPressedDialogForBookmarkUrl(context, item); } } } - public void showLongPressedDialogForUrl(final Context context, final HistoryItem item) { + public void showLongPressedDialogForBookmarkUrl(final Context context, final HistoryItem item) { final DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_POSITIVE: - eventBus.post(new BookmarkEvents.AsNewTab(item)); + eventBus.post(new BrowserEvents.OpenUrlInNewTab(item.getUrl())); break; case DialogInterface.BUTTON_NEGATIVE: if (bookmarkManager.deleteBookmark(item)) { @@ -197,4 +210,100 @@ public class BookmarksDialogBuilder { }); editFolderDialog.show(); } + + public void showLongPressedHistoryLinkDialog(final Context context, final String url) { + DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + eventBus.post(new BrowserEvents.OpenUrlInNewTab(url)); + break; + case DialogInterface.BUTTON_NEGATIVE: + mHistoryDatabase.deleteHistoryItem(url); + // openHistory(); + eventBus.post(new BrowserEvents.OpenUrlInCurrentTab(HistoryPage.getHistoryPage(context))); + break; + case DialogInterface.BUTTON_NEUTRAL: + eventBus.post(new BrowserEvents.OpenUrlInCurrentTab(url)); + break; + default: + break; + } + } + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.action_history) + .setMessage(R.string.dialog_history_long_press) + .setCancelable(true) + .setPositiveButton(R.string.action_new_tab, dialogClickListener) + .setNegativeButton(R.string.action_delete, dialogClickListener) + .setNeutralButton(R.string.action_open, dialogClickListener) + .show(); + } + + // TODO There should be a way in which we do not need an activity reference to dowload a file + public void showLongPressImageDialog(@NonNull final Activity activity, @NonNull final String url, + @NonNull final String userAgent) { + DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + eventBus.post(new BrowserEvents.OpenUrlInNewTab(url)); + break; + case DialogInterface.BUTTON_NEGATIVE: + eventBus.post(new BrowserEvents.OpenUrlInCurrentTab(url)); + break; + case DialogInterface.BUTTON_NEUTRAL: + Utils.downloadFile(activity, url, + userAgent, "attachment"); + break; + } + } + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(url.replace(Constants.HTTP, "")) + .setCancelable(true) + .setMessage(R.string.dialog_image) + .setPositiveButton(R.string.action_new_tab, dialogClickListener) + .setNegativeButton(R.string.action_open, dialogClickListener) + .setNeutralButton(R.string.action_download, dialogClickListener) + .show(); + } + + public void showLongPressLinkDialog(final Context context, final String url) { + DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + eventBus.post(new BrowserEvents.OpenUrlInNewTab(url)); + break; + + case DialogInterface.BUTTON_NEGATIVE: + eventBus.post(new BrowserEvents.OpenUrlInCurrentTab(url)); + break; + + case DialogInterface.BUTTON_NEUTRAL: + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("label", url); + clipboard.setPrimaryClip(clip); + break; + } + } + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(context); // dialog + builder.setTitle(url) + .setCancelable(true) + .setMessage(R.string.dialog_link) + .setPositiveButton(R.string.action_new_tab, dialogClickListener) + .setNegativeButton(R.string.action_open, dialogClickListener) + .setNeutralButton(R.string.action_copy, dialogClickListener) + .show(); + } + } diff --git a/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java b/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java index 82d25aa..7e2f9eb 100644 --- a/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java +++ b/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java @@ -3,15 +3,14 @@ */ package acr.browser.lightning.download; -import android.app.Activity; import android.app.DownloadManager; import android.content.ActivityNotFoundException; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; +import android.os.Build; import android.os.Environment; import android.support.v7.app.AlertDialog; import android.text.TextUtils; @@ -19,13 +18,17 @@ import android.util.Log; import android.webkit.CookieManager; import android.webkit.URLUtil; +import com.squareup.otto.Bus; + import java.io.File; import java.io.IOException; +import acr.browser.lightning.BuildConfig; import acr.browser.lightning.R; +import acr.browser.lightning.activity.MainActivity; +import acr.browser.lightning.app.BrowserApp; +import acr.browser.lightning.bus.BrowserEvents; import acr.browser.lightning.constant.Constants; -import acr.browser.lightning.preference.PreferenceManager; -import acr.browser.lightning.utils.Utils; /** * Handle download requests @@ -44,13 +47,13 @@ public class DownloadHandler { * Notify the host application a download should be done, or that the data * should be streamed if a streaming viewer is available. * - * @param activity Activity requesting the download. + * @param context The context in which the download was requested. * @param url The full url to the content that should be downloaded * @param userAgent User agent of the downloading application. * @param contentDisposition Content-disposition http header, if present. * @param mimetype The mimetype of the content reported by the server */ - public static void onDownloadStart(Activity activity, String url, String userAgent, + public static void onDownloadStart(Context context, String url, String userAgent, String contentDisposition, String mimetype) { // if we're dealing wih A/V content that's not explicitly marked // for download, check if it's streamable. @@ -61,18 +64,22 @@ public class DownloadHandler { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse(url), mimetype); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - ResolveInfo info = activity.getPackageManager().resolveActivity(intent, + intent.addCategory(Intent.CATEGORY_BROWSABLE); + intent.setComponent(null); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { + intent.setSelector(null); + } + ResolveInfo info = context.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); if (info != null) { - ComponentName myName = activity.getComponentName(); // If we resolved to ourselves, we don't want to attempt to // load the url only to try and download it again. - if (!myName.getPackageName().equals(info.activityInfo.packageName) - || !myName.getClassName().equals(info.activityInfo.name)) { + if (BuildConfig.APPLICATION_ID.equals(info.activityInfo.packageName) + || MainActivity.class.getName().equals(info.activityInfo.name)) { // someone (other than us) knows how to handle this mime // type with this scheme, don't download. try { - activity.startActivity(intent); + context.startActivity(intent); return; } catch (ActivityNotFoundException ex) { // Best behavior is to fall back to a download in this @@ -81,8 +88,7 @@ public class DownloadHandler { } } } - onDownloadStartNoStream(activity, url, userAgent, contentDisposition, mimetype - ); + onDownloadStartNoStream(context, url, userAgent, contentDisposition, mimetype); } // This is to work around the fact that java.net.URI throws Exceptions @@ -119,17 +125,17 @@ public class DownloadHandler { * Notify the host application a download should be done, even if there is a * streaming viewer available for thise type. * - * @param activity Activity requesting the download. + * @param context The context in which the download is requested. * @param url The full url to the content that should be downloaded * @param userAgent User agent of the downloading application. * @param contentDisposition Content-disposition http header, if present. * @param mimetype The mimetype of the content reported by the server */ /* package */ - private static void onDownloadStartNoStream(final Activity activity, String url, String userAgent, + private static void onDownloadStartNoStream(final Context context, String url, String userAgent, String contentDisposition, String mimetype) { - - String filename = URLUtil.guessFileName(url, contentDisposition, mimetype); + final Bus eventBus = BrowserApp.getAppComponent().getBus(); + final String filename = URLUtil.guessFileName(url, contentDisposition, mimetype); // Check to see if we have an SDCard String status = Environment.getExternalStorageState(); @@ -139,14 +145,14 @@ public class DownloadHandler { // Check to see if the SDCard is busy, same as the music app if (status.equals(Environment.MEDIA_SHARED)) { - msg = activity.getString(R.string.download_sdcard_busy_dlg_msg); + msg = context.getString(R.string.download_sdcard_busy_dlg_msg); title = R.string.download_sdcard_busy_dlg_title; } else { - msg = activity.getString(R.string.download_no_sdcard_dlg_msg, filename); + msg = context.getString(R.string.download_no_sdcard_dlg_msg, filename); title = R.string.download_no_sdcard_dlg_title; } - new AlertDialog.Builder(activity).setTitle(title) + new AlertDialog.Builder(context).setTitle(title) .setIcon(android.R.drawable.ic_dialog_alert).setMessage(msg) .setPositiveButton(R.string.action_ok, null).show(); return; @@ -162,7 +168,7 @@ public class DownloadHandler { // This only happens for very bad urls, we want to catch the // exception here Log.e(TAG, "Exception while trying to parse url '" + url + '\'', e); - Utils.showSnackbar(activity, R.string.problem_download); + eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.problem_download)); return; } @@ -172,7 +178,7 @@ public class DownloadHandler { try { request = new DownloadManager.Request(uri); } catch (IllegalArgumentException e) { - Utils.showSnackbar(activity, R.string.cannot_download); + eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.cannot_download)); return; } request.setMimeType(mimetype); @@ -180,7 +186,7 @@ public class DownloadHandler { // or, should it be set to one of several Environment.DIRECTORY* dirs // depending on mimetype? - String location = PreferenceManager.getInstance().getDownloadDirectory(); + String location = BrowserApp.getAppComponent().getPreferenceManager().getDownloadDirectory(); Uri downloadFolder; if (location != null) { location = addNecessarySlashes(location); @@ -188,18 +194,18 @@ public class DownloadHandler { } else { location = addNecessarySlashes(DEFAULT_DOWNLOAD_PATH); downloadFolder = Uri.parse(location); - PreferenceManager.getInstance().setDownloadDirectory(location); + BrowserApp.getAppComponent().getPreferenceManager().setDownloadDirectory(location); } File dir = new File(downloadFolder.getPath()); if (!dir.isDirectory() && !dir.mkdirs()) { // Cannot make the directory - Utils.showSnackbar(activity, R.string.problem_location_download); + eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.problem_location_download)); return; } if (!isWriteAccessAvailable(downloadFolder)) { - Utils.showSnackbar(activity, R.string.problem_location_download); + eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.problem_location_download)); return; } request.setDestinationUri(Uri.parse(Constants.FILE + location + filename)); @@ -219,9 +225,9 @@ public class DownloadHandler { } // We must have long pressed on a link or image to download it. We // are not sure of the mimetype in this case, so do a head request - new FetchUrlMimeType(activity, request, addressString, cookies, userAgent).start(); + new FetchUrlMimeType(context, request, addressString, cookies, userAgent).start(); } else { - final DownloadManager manager = (DownloadManager) activity + final DownloadManager manager = (DownloadManager) context .getSystemService(Context.DOWNLOAD_SERVICE); new Thread() { @Override @@ -231,15 +237,16 @@ public class DownloadHandler { } catch (IllegalArgumentException e) { // Probably got a bad URL or something e.printStackTrace(); - Utils.showSnackbar(activity, R.string.cannot_download); + eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.cannot_download)); } catch (SecurityException e) { // TODO write a download utility that downloads files rather than rely on the system // because the system can only handle Environment.getExternal... as a path - Utils.showSnackbar(activity, R.string.problem_location_download); + eventBus.post(new BrowserEvents.ShowSnackBarMessage(R.string.problem_location_download)); } } }.start(); - Utils.showSnackbar(activity, activity.getString(R.string.download_pending) + ' ' + filename); + eventBus.post(new BrowserEvents.ShowSnackBarMessage( + context.getString(R.string.download_pending) + ' ' + filename)); } } diff --git a/app/src/main/java/acr/browser/lightning/download/FetchUrlMimeType.java b/app/src/main/java/acr/browser/lightning/download/FetchUrlMimeType.java index 39da66f..bf34f3d 100644 --- a/app/src/main/java/acr/browser/lightning/download/FetchUrlMimeType.java +++ b/app/src/main/java/acr/browser/lightning/download/FetchUrlMimeType.java @@ -3,19 +3,23 @@ */ package acr.browser.lightning.download; -import android.app.Activity; import android.app.DownloadManager; import android.content.Context; import android.os.Environment; +import android.os.Handler; +import android.os.Looper; import android.webkit.MimeTypeMap; import android.webkit.URLUtil; +import com.squareup.otto.Bus; + import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import acr.browser.lightning.R; -import acr.browser.lightning.utils.Utils; +import acr.browser.lightning.app.BrowserApp; +import acr.browser.lightning.bus.BrowserEvents; /** * This class is used to pull down the http headers of a given URL so that we @@ -27,7 +31,7 @@ import acr.browser.lightning.utils.Utils; */ class FetchUrlMimeType extends Thread { - private final Activity mActivity; + private final Context mContext; private final DownloadManager.Request mRequest; @@ -37,9 +41,9 @@ class FetchUrlMimeType extends Thread { private final String mUserAgent; - public FetchUrlMimeType(Activity activity, DownloadManager.Request request, String uri, + public FetchUrlMimeType(Context context, DownloadManager.Request request, String uri, String cookies, String userAgent) { - mActivity = activity; + mContext = context; mRequest = request; mUri = uri; mCookies = cookies; @@ -50,6 +54,7 @@ class FetchUrlMimeType extends Thread { public void run() { // User agent is likely to be null, though the AndroidHttpClient // seems ok with that. + final Bus eventBus = BrowserApp.getAppComponent().getBus(); String mimeType = null; String contentDisposition = null; HttpURLConnection connection = null; @@ -101,9 +106,16 @@ class FetchUrlMimeType extends Thread { } // Start the download - DownloadManager manager = (DownloadManager) mActivity + DownloadManager manager = (DownloadManager) mContext .getSystemService(Context.DOWNLOAD_SERVICE); manager.enqueue(mRequest); - Utils.showSnackbar(mActivity, mActivity.getString(R.string.download_pending) + ' ' + filename); + Handler handler = new Handler(Looper.getMainLooper()); + final String file = filename; + handler.post(new Runnable() { + @Override + public void run() { + eventBus.post(new BrowserEvents.ShowSnackBarMessage(mContext.getString(R.string.download_pending) + ' ' + file)); + } + }); } } diff --git a/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java b/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java index 72f3ab8..cb26211 100644 --- a/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java +++ b/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java @@ -3,6 +3,7 @@ */ package acr.browser.lightning.download; +import android.Manifest; import android.app.Activity; import android.content.DialogInterface; import android.support.v7.app.AlertDialog; @@ -12,42 +13,55 @@ import android.webkit.URLUtil; import acr.browser.lightning.R; import acr.browser.lightning.constant.Constants; +import com.anthonycr.grant.PermissionsManager; +import com.anthonycr.grant.PermissionsResultAction; public class LightningDownloadListener implements DownloadListener { private final Activity mActivity; - public LightningDownloadListener(Activity activity) { - mActivity = activity; + public LightningDownloadListener(Activity context) { + mActivity = context; } @Override public void onDownloadStart(final String url, final String userAgent, - final String contentDisposition, final String mimetype, long contentLength) { - String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype); - DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - DownloadHandler.onDownloadStart(mActivity, url, userAgent, - contentDisposition, mimetype); - break; - - case DialogInterface.BUTTON_NEGATIVE: - break; - } - } - }; - - AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); // dialog - builder.setTitle(fileName) - .setMessage(mActivity.getResources().getString(R.string.dialog_download)) - .setPositiveButton(mActivity.getResources().getString(R.string.action_download), - dialogClickListener) - .setNegativeButton(mActivity.getResources().getString(R.string.action_cancel), - dialogClickListener).show(); - Log.i(Constants.TAG, "Downloading" + fileName); + final String contentDisposition, final String mimetype, long contentLength) { + PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(mActivity, + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, + new PermissionsResultAction() { + @Override + public void onGranted() { + String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype); + DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + DownloadHandler.onDownloadStart(mActivity, url, userAgent, + contentDisposition, mimetype); + break; + case DialogInterface.BUTTON_NEGATIVE: + break; + } + } + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); // dialog + builder.setTitle(fileName) + .setMessage(mActivity.getResources().getString(R.string.dialog_download)) + .setPositiveButton(mActivity.getResources().getString(R.string.action_download), + dialogClickListener) + .setNegativeButton(mActivity.getResources().getString(R.string.action_cancel), + dialogClickListener).show(); + Log.i(Constants.TAG, "Downloading" + fileName); + } + + @Override + public void onDenied(String permission) { + //TODO show message + } + }); } } diff --git a/app/src/main/java/acr/browser/lightning/download/WebAddress.java b/app/src/main/java/acr/browser/lightning/download/WebAddress.java index 96852a6..ed45299 100644 --- a/app/src/main/java/acr/browser/lightning/download/WebAddress.java +++ b/app/src/main/java/acr/browser/lightning/download/WebAddress.java @@ -35,11 +35,11 @@ class WebAddress { private static final int MATCH_GROUP_PORT = 4; private static final int MATCH_GROUP_PATH = 5; private static final Pattern sAddressPattern = Pattern.compile( - /* scheme */"(?:(http|https|file)\\:\\/\\/)?" + - /* authority */"(?:([-A-Za-z0-9$_.+!*'(),;?&=]+(?:\\:[-A-Za-z0-9$_.+!*'(),;?&=]+)?)@)?" + + /* scheme */"(?:(http|https|file)://)?" + + /* authority */"(?:([-A-Za-z0-9$_.+!*'(),;?&=]+(?::[-A-Za-z0-9$_.+!*'(),;?&=]+)?)@)?" + /* host */"([" + GOOD_IRI_CHAR + "%_-][" + GOOD_IRI_CHAR + "%_\\.-]*|\\[[0-9a-fA-F:\\.]+\\])?" + - /* port */"(?:\\:([0-9]*))?" + - /* path */"(\\/?[^#]*)?" + + /* port */"(?::([0-9]*))?" + + /* path */"(/?[^#]*)?" + /* anchor */".*", Pattern.CASE_INSENSITIVE); /** diff --git a/app/src/main/java/acr/browser/lightning/fragment/AdvancedSettingsFragment.java b/app/src/main/java/acr/browser/lightning/fragment/AdvancedSettingsFragment.java index 4f4b7a6..a7a4b1c 100644 --- a/app/src/main/java/acr/browser/lightning/fragment/AdvancedSettingsFragment.java +++ b/app/src/main/java/acr/browser/lightning/fragment/AdvancedSettingsFragment.java @@ -8,7 +8,6 @@ import android.content.DialogInterface; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.Preference; -import android.preference.PreferenceFragment; import android.support.v7.app.AlertDialog; import java.util.Arrays; @@ -16,9 +15,8 @@ import java.util.List; import acr.browser.lightning.R; import acr.browser.lightning.constant.Constants; -import acr.browser.lightning.preference.PreferenceManager; -public class AdvancedSettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { +public class AdvancedSettingsFragment extends LightningPreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { private static final String SETTINGS_NEWWINDOW = "allow_new_window"; private static final String SETTINGS_ENABLECOOKIES = "allow_cookies"; @@ -29,7 +27,6 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref private static final String SETTINGS_TEXTENCODING = "text_encoding"; private Activity mActivity; - private PreferenceManager mPreferences; private CheckBoxPreference cbAllowPopups, cbenablecookies, cbcookiesInkognito, cbrestoreTabs; private Preference renderingmode, urlcontent, textEncoding; private CharSequence[] mUrlOptions; @@ -46,8 +43,6 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref } private void initPrefs() { - // mPreferences storage - mPreferences = PreferenceManager.getInstance(); renderingmode = findPreference(SETTINGS_RENDERINGMODE); textEncoding = findPreference(SETTINGS_TEXTENCODING); @@ -65,7 +60,7 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref cbcookiesInkognito.setOnPreferenceChangeListener(this); cbrestoreTabs.setOnPreferenceChangeListener(this); - switch (mPreferences.getRenderingMode()) { + switch (mPreferenceManager.getRenderingMode()) { case 0: renderingmode.setSummary(getString(R.string.name_normal)); break; @@ -80,16 +75,16 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref break; } - textEncoding.setSummary(mPreferences.getTextEncoding()); + textEncoding.setSummary(mPreferenceManager.getTextEncoding()); mUrlOptions = getResources().getStringArray(R.array.url_content_array); - int option = mPreferences.getUrlBoxContentChoice(); + int option = mPreferenceManager.getUrlBoxContentChoice(); urlcontent.setSummary(mUrlOptions[option]); - cbAllowPopups.setChecked(mPreferences.getPopupsEnabled()); - cbenablecookies.setChecked(mPreferences.getCookiesEnabled()); - cbcookiesInkognito.setChecked(mPreferences.getIncognitoCookiesEnabled()); - cbrestoreTabs.setChecked(mPreferences.getRestoreLostTabsEnabled()); + cbAllowPopups.setChecked(mPreferenceManager.getPopupsEnabled()); + cbenablecookies.setChecked(mPreferenceManager.getCookiesEnabled()); + cbcookiesInkognito.setChecked(mPreferenceManager.getIncognitoCookiesEnabled()); + cbrestoreTabs.setChecked(mPreferenceManager.getRestoreLostTabsEnabled()); } @Override @@ -114,19 +109,19 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref // switch preferences switch (preference.getKey()) { case SETTINGS_NEWWINDOW: - mPreferences.setPopupsEnabled((Boolean) newValue); + mPreferenceManager.setPopupsEnabled((Boolean) newValue); cbAllowPopups.setChecked((Boolean) newValue); return true; case SETTINGS_ENABLECOOKIES: - mPreferences.setCookiesEnabled((Boolean) newValue); + mPreferenceManager.setCookiesEnabled((Boolean) newValue); cbenablecookies.setChecked((Boolean) newValue); return true; case SETTINGS_COOKIESINKOGNITO: - mPreferences.setIncognitoCookiesEnabled((Boolean) newValue); + mPreferenceManager.setIncognitoCookiesEnabled((Boolean) newValue); cbcookiesInkognito.setChecked((Boolean) newValue); return true; case SETTINGS_RESTORETABS: - mPreferences.setRestoreLostTabsEnabled((Boolean) newValue); + mPreferenceManager.setRestoreLostTabsEnabled((Boolean) newValue); cbrestoreTabs.setChecked((Boolean) newValue); return true; default: @@ -142,12 +137,12 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref mActivity.getString(R.string.name_grayscale), mActivity.getString(R.string.name_inverted_grayscale)}; - int n = mPreferences.getRenderingMode(); + int n = mPreferenceManager.getRenderingMode(); picker.setSingleChoiceItems(chars, n, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mPreferences.setRenderingMode(which); + mPreferenceManager.setRenderingMode(which); switch (which) { case 0: renderingmode.setSummary(getString(R.string.name_normal)); @@ -172,12 +167,12 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref AlertDialog.Builder picker = new AlertDialog.Builder(mActivity); picker.setTitle(getResources().getString(R.string.text_encoding)); final List textEncodingList = Arrays.asList(Constants.TEXT_ENCODINGS); - int n = textEncodingList.indexOf(mPreferences.getTextEncoding()); + int n = textEncodingList.indexOf(mPreferenceManager.getTextEncoding()); picker.setSingleChoiceItems(Constants.TEXT_ENCODINGS, n, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mPreferences.setTextEncoding(Constants.TEXT_ENCODINGS[which]); + mPreferenceManager.setTextEncoding(Constants.TEXT_ENCODINGS[which]); textEncoding.setSummary(Constants.TEXT_ENCODINGS[which]); } }); @@ -189,12 +184,12 @@ public class AdvancedSettingsFragment extends PreferenceFragment implements Pref AlertDialog.Builder picker = new AlertDialog.Builder(mActivity); picker.setTitle(getResources().getString(R.string.url_contents)); - int n = mPreferences.getUrlBoxContentChoice(); + int n = mPreferenceManager.getUrlBoxContentChoice(); picker.setSingleChoiceItems(mUrlOptions, n, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mPreferences.setUrlBoxContentChoice(which); + mPreferenceManager.setUrlBoxContentChoice(which); if (which < mUrlOptions.length) { urlcontent.setSummary(mUrlOptions[which]); } diff --git a/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java b/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java index 8698603..0656f39 100644 --- a/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java +++ b/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java @@ -15,6 +15,7 @@ import android.preference.PreferenceFragment; import android.support.v7.app.AlertDialog; import java.io.File; +import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Comparator; import java.util.List; @@ -26,7 +27,8 @@ import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.database.BookmarkLocalSync; import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.HistoryItem; -import acr.browser.lightning.utils.PermissionsManager; +import com.anthonycr.grant.PermissionsManager; +import com.anthonycr.grant.PermissionsResultAction; import acr.browser.lightning.utils.Utils; public class BookmarkSettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener { @@ -45,9 +47,17 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }; + private ImportBookmarksTask mImportTaskReference; private static final File mPath = new File(Environment.getExternalStorageDirectory().toString()); private class ImportBookmarksTask extends AsyncTask { + + private final WeakReference mActivityReference; + + public ImportBookmarksTask(Activity activity) { + mActivityReference = new WeakReference<>(activity); + } + @Override protected Integer doInBackground(Void... params) { List list = null; @@ -67,10 +77,11 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref @Override protected void onPostExecute(Integer num) { super.onPostExecute(num); - if (mActivity != null) { + Activity activity = mActivityReference.get(); + if (activity != null) { int number = num; - final String message = mActivity.getResources().getString(R.string.message_import); - Utils.showSnackbar(mActivity, number + " " + message); + final String message = activity.getResources().getString(R.string.message_import); + Utils.showSnackbar(activity, number + " " + message); } } } @@ -88,7 +99,7 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref PermissionsManager permissionsManager = PermissionsManager.getInstance(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - permissionsManager.requestPermissionsIfNecessary(getActivity(), REQUIRED_PERMISSIONS); + permissionsManager.requestPermissionsIfNecessaryForResult(getActivity(), REQUIRED_PERMISSIONS, null); } } @@ -96,6 +107,9 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref public void onDestroy() { super.onDestroy(); mActivity = null; + if (mImportTaskReference != null) { + mImportTaskReference.cancel(false); + } } private void initPrefs() { @@ -124,18 +138,37 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref public boolean onPreferenceClick(Preference preference) { switch (preference.getKey()) { case SETTINGS_EXPORT: - if (PermissionsManager.checkPermissions(getActivity(), REQUIRED_PERMISSIONS)) { - mBookmarkManager.exportBookmarks(getActivity()); - } + PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(getActivity(), REQUIRED_PERMISSIONS, + new PermissionsResultAction() { + @Override + public void onGranted() { + mBookmarkManager.exportBookmarks(getActivity()); + } + + @Override + public void onDenied(String permission) { + //TODO Show message + } + }); return true; case SETTINGS_IMPORT: - if (PermissionsManager.checkPermissions(getActivity(), REQUIRED_PERMISSIONS)) { - loadFileList(null); - createDialog(); - } + PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(getActivity(), REQUIRED_PERMISSIONS, + new PermissionsResultAction() { + @Override + public void onGranted() { + loadFileList(null); + createDialog(); + } + + @Override + public void onDenied(String permission) { + //TODO Show message + } + }); return true; case SETTINGS_IMPORT_BROWSER: - new ImportBookmarksTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + mImportTaskReference = new ImportBookmarksTask(getActivity()); + mImportTaskReference.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); return true; default: return false; diff --git a/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java b/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java index 80aa3f9..d7584cb 100644 --- a/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java +++ b/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java @@ -1,7 +1,7 @@ package acr.browser.lightning.fragment; -import android.app.Activity; import android.content.Context; +import android.content.Intent; import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.os.Bundle; @@ -36,23 +36,31 @@ import java.util.List; import javax.inject.Inject; import acr.browser.lightning.R; -import acr.browser.lightning.activity.BrowserActivity; +import acr.browser.lightning.activity.ReadingActivity; +import acr.browser.lightning.activity.TabsManager; import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.async.AsyncExecutor; import acr.browser.lightning.bus.BookmarkEvents; import acr.browser.lightning.bus.BrowserEvents; +import acr.browser.lightning.constant.Constants; import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.HistoryItem; -import acr.browser.lightning.dialog.BookmarksDialogBuilder; +import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.async.ImageDownloadTask; import acr.browser.lightning.utils.ThemeUtils; +import acr.browser.lightning.utils.UrlUtils; +import acr.browser.lightning.view.LightningView; /** * Created by Stefano Pacifici on 25/08/15. Based on Anthony C. Restaino's code. */ public class BookmarksFragment extends Fragment implements View.OnClickListener, View.OnLongClickListener { + private final static String TAG = BookmarksFragment.class.getSimpleName(); + + public final static String INCOGNITO_MODE = TAG + ".INCOGNITO_MODE"; + // Managers @Inject BookmarkManager mBookmarkManager; @@ -63,7 +71,13 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, // Dialog builder @Inject - BookmarksDialogBuilder mBookmarksDialogBuilder; + LightningDialogBuilder mBookmarksDialogBuilder; + + @Inject + PreferenceManager mPreferenceManager; + + @Inject + TabsManager mTabsManager; // Adapter private BookmarkViewAdapter mBookmarkAdapter; @@ -96,6 +110,14 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); BrowserApp.getAppComponent().inject(this); + final Bundle arguments = getArguments(); + final Context context = getContext(); + boolean isIncognito = arguments.getBoolean(INCOGNITO_MODE, false); + boolean darkTheme = mPreferenceManager.getUseTheme() != 0 || isIncognito; + mWebpageBitmap = ThemeUtils.getThemedBitmap(context, R.drawable.ic_webpage, darkTheme); + mFolderBitmap = ThemeUtils.getThemedBitmap(context, R.drawable.ic_folder, darkTheme); + mIconColor = darkTheme ? ThemeUtils.getIconDarkThemeColor(context) : + ThemeUtils.getIconLightThemeColor(context); } // Handle bookmark click @@ -107,7 +129,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, mScrollIndex = mBookmarksListView.getFirstVisiblePosition(); setBookmarkDataSet(mBookmarkManager.getBookmarksFromFolder(item.getTitle(), true), true); } else { - mEventBus.post(new BookmarkEvents.Clicked(item)); + mEventBus.post(new BrowserEvents.OpenUrlInCurrentTab(item.getUrl())); } } }; @@ -135,6 +157,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, mBookmarksListView.setOnItemClickListener(mItemClickListener); mBookmarksListView.setOnItemLongClickListener(mItemLongClickListener); mBookmarkTitleImage = (ImageView) view.findViewById(R.id.starIcon); + mBookmarkTitleImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); mBookmarkImage = (ImageView) view.findViewById(R.id.icon_star); final View backView = view.findViewById(R.id.bookmark_back_button); backView.setOnClickListener(new View.OnClickListener() { @@ -148,26 +171,14 @@ 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); // Must be called here, only here we have a reference to the ListView new Thread(mInitBookmarkManager).run(); return view; } - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - // TODO remove dependency on BrowserActivity - super.onActivityCreated(savedInstanceState); - final Activity activity = getActivity(); - final PreferenceManager preferenceManager = PreferenceManager.getInstance(); - boolean darkTheme = preferenceManager.getUseTheme() != 0 || ((BrowserActivity) activity).isIncognito(); - mWebpageBitmap = ThemeUtils.getThemedBitmap(activity, R.drawable.ic_webpage, darkTheme); - mFolderBitmap = ThemeUtils.getThemedBitmap(activity, R.drawable.ic_folder, darkTheme); - mIconColor = darkTheme ? ThemeUtils.getIconDarkThemeColor(activity) : - ThemeUtils.getIconLightThemeColor(activity); - mBookmarkTitleImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); - } - @Override public void onStart() { super.onStart(); @@ -183,12 +194,14 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, @Subscribe public void addBookmark(final BrowserEvents.AddBookmark event) { final HistoryItem item = new HistoryItem(event.url, event.title); - if (mBookmarkManager.addBookmark(item)) { - mBookmarks.add(item); - Collections.sort(mBookmarks, new BookmarkManager.SortIgnoreCase()); - mBookmarkAdapter.notifyDataSetChanged(); - mEventBus.post(new BookmarkEvents.Added(item)); - updateBookmarkIndicator(event.url); + if (!UrlUtils.isSpecialUrl(item.getUrl())) { + if (mBookmarkManager.addBookmark(item)) { + mBookmarks.add(item); + Collections.sort(mBookmarks, new BookmarkManager.SortIgnoreCase()); + mBookmarkAdapter.notifyDataSetChanged(); + mEventBus.post(new BookmarkEvents.Added(item)); + updateBookmarkIndicator(event.url); + } } } @@ -295,7 +308,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, if (item.isFolder()) { mBookmarksDialogBuilder.showBookmarkFolderLongPressedDialog(getContext(), item); } else { - mBookmarksDialogBuilder.showLongPressedDialogForUrl(getContext(), item); + mBookmarksDialogBuilder.showLongPressedDialogForBookmarkUrl(getContext(), item); } } @@ -305,6 +318,22 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, case R.id.action_add_bookmark: mEventBus.post(new BookmarkEvents.WantToBookmarkCurrentPage()); break; + case R.id.action_reading: + LightningView currentTab = mTabsManager.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 = mTabsManager.getCurrentTab(); + if (current != null) { + current.toggleDesktopUA(getActivity()); + current.reload(); + // TODO add back drawer closing + } + break; default: break; } diff --git a/app/src/main/java/acr/browser/lightning/fragment/DisplaySettingsFragment.java b/app/src/main/java/acr/browser/lightning/fragment/DisplaySettingsFragment.java index 23180e9..a5ddaa1 100644 --- a/app/src/main/java/acr/browser/lightning/fragment/DisplaySettingsFragment.java +++ b/app/src/main/java/acr/browser/lightning/fragment/DisplaySettingsFragment.java @@ -8,7 +8,6 @@ import android.content.DialogInterface; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.Preference; -import android.preference.PreferenceFragment; import android.support.v7.app.AlertDialog; import android.view.Gravity; import android.view.LayoutInflater; @@ -19,9 +18,8 @@ import android.widget.SeekBar; import android.widget.TextView; import acr.browser.lightning.R; -import acr.browser.lightning.preference.PreferenceManager; -public class DisplaySettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { +public class DisplaySettingsFragment extends LightningPreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { private static final String SETTINGS_HIDESTATUSBAR = "fullScreenOption"; private static final String SETTINGS_FULLSCREEN = "fullscreen"; @@ -38,7 +36,6 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe private static final float XSMALL = 10.0f; private Activity mActivity; - private PreferenceManager mPreferences; private CheckBoxPreference cbstatus, cbfullscreen, cbviewport, cboverview, cbreflow; private Preference theme; private String[] mThemeOptions; @@ -57,9 +54,8 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe private void initPrefs() { // mPreferences storage - mPreferences = PreferenceManager.getInstance(); mThemeOptions = this.getResources().getStringArray(R.array.themes); - mCurrentTheme = mPreferences.getUseTheme(); + mCurrentTheme = mPreferenceManager.getUseTheme(); theme = findPreference(SETTINGS_THEME); Preference textsize = findPreference(SETTINGS_TEXTSIZE); @@ -77,13 +73,13 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe cboverview.setOnPreferenceChangeListener(this); cbreflow.setOnPreferenceChangeListener(this); - cbstatus.setChecked(mPreferences.getHideStatusBarEnabled()); - cbfullscreen.setChecked(mPreferences.getFullScreenEnabled()); - cbviewport.setChecked(mPreferences.getUseWideViewportEnabled()); - cboverview.setChecked(mPreferences.getOverviewModeEnabled()); - cbreflow.setChecked(mPreferences.getTextReflowEnabled()); + cbstatus.setChecked(mPreferenceManager.getHideStatusBarEnabled()); + cbfullscreen.setChecked(mPreferenceManager.getFullScreenEnabled()); + cbviewport.setChecked(mPreferenceManager.getUseWideViewportEnabled()); + cboverview.setChecked(mPreferenceManager.getOverviewModeEnabled()); + cbreflow.setChecked(mPreferenceManager.getTextReflowEnabled()); - theme.setSummary(mThemeOptions[mPreferences.getUseTheme()]); + theme.setSummary(mThemeOptions[mPreferenceManager.getUseTheme()]); } @Override @@ -105,23 +101,23 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe // switch preferences switch (preference.getKey()) { case SETTINGS_HIDESTATUSBAR: - mPreferences.setHideStatusBarEnabled((Boolean) newValue); + mPreferenceManager.setHideStatusBarEnabled((Boolean) newValue); cbstatus.setChecked((Boolean) newValue); return true; case SETTINGS_FULLSCREEN: - mPreferences.setFullScreenEnabled((Boolean) newValue); + mPreferenceManager.setFullScreenEnabled((Boolean) newValue); cbfullscreen.setChecked((Boolean) newValue); return true; case SETTINGS_VIEWPORT: - mPreferences.setUseWideViewportEnabled((Boolean) newValue); + mPreferenceManager.setUseWideViewportEnabled((Boolean) newValue); cbviewport.setChecked((Boolean) newValue); return true; case SETTINGS_OVERVIEWMODE: - mPreferences.setOverviewModeEnabled((Boolean) newValue); + mPreferenceManager.setOverviewModeEnabled((Boolean) newValue); cboverview.setChecked((Boolean) newValue); return true; case SETTINGS_REFLOW: - mPreferences.setTextReflowEnabled((Boolean) newValue); + mPreferenceManager.setTextReflowEnabled((Boolean) newValue); cbreflow.setChecked((Boolean) newValue); return true; default: @@ -142,14 +138,14 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe bar.setOnSeekBarChangeListener(new TextSeekBarListener(sample)); final int MAX = 5; bar.setMax(MAX); - bar.setProgress(MAX - mPreferences.getTextSize()); + bar.setProgress(MAX - mPreferenceManager.getTextSize()); builder.setView(view); builder.setTitle(R.string.title_text_size); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface arg0, int arg1) { - mPreferences.setTextSize(MAX - bar.getProgress()); + mPreferenceManager.setTextSize(MAX - bar.getProgress()); } }); @@ -179,12 +175,12 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe AlertDialog.Builder picker = new AlertDialog.Builder(mActivity); picker.setTitle(getResources().getString(R.string.theme)); - int n = mPreferences.getUseTheme(); + int n = mPreferenceManager.getUseTheme(); picker.setSingleChoiceItems(mThemeOptions, n, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mPreferences.setUseTheme(which); + mPreferenceManager.setUseTheme(which); if (which < mThemeOptions.length) { theme.setSummary(mThemeOptions[which]); } @@ -195,7 +191,7 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe @Override public void onClick(DialogInterface dialog, int which) { - if (mCurrentTheme != mPreferences.getUseTheme()) { + if (mCurrentTheme != mPreferenceManager.getUseTheme()) { getActivity().onBackPressed(); } } @@ -203,7 +199,7 @@ public class DisplaySettingsFragment extends PreferenceFragment implements Prefe picker.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { - if (mCurrentTheme != mPreferences.getUseTheme()) { + if (mCurrentTheme != mPreferenceManager.getUseTheme()) { getActivity().onBackPressed(); } } diff --git a/app/src/main/java/acr/browser/lightning/fragment/GeneralSettingsFragment.java b/app/src/main/java/acr/browser/lightning/fragment/GeneralSettingsFragment.java index 951b6b9..1272810 100644 --- a/app/src/main/java/acr/browser/lightning/fragment/GeneralSettingsFragment.java +++ b/app/src/main/java/acr/browser/lightning/fragment/GeneralSettingsFragment.java @@ -5,11 +5,11 @@ package acr.browser.lightning.fragment; import android.app.Activity; import android.content.DialogInterface; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.preference.CheckBoxPreference; import android.preference.Preference; -import android.preference.PreferenceFragment; import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.text.Editable; @@ -23,12 +23,11 @@ import android.widget.LinearLayout; import acr.browser.lightning.R; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.download.DownloadHandler; -import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.utils.ProxyUtils; import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.Utils; -public class GeneralSettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { +public class GeneralSettingsFragment extends LightningPreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { private static final String SETTINGS_PROXY = "proxy"; private static final String SETTINGS_FLASH = "cb_flash"; @@ -45,7 +44,6 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe private Activity mActivity; private static final int API = android.os.Build.VERSION.SDK_INT; - private PreferenceManager mPreferences; private CharSequence[] mProxyChoices; private Preference proxy, useragent, downloadloc, home, searchengine; private String mDownloadLocation; @@ -65,9 +63,6 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe } private void initPrefs() { - // mPreferences storage - mPreferences = PreferenceManager.getInstance(); - proxy = findPreference(SETTINGS_PROXY); useragent = findPreference(SETTINGS_USERAGENT); downloadloc = findPreference(SETTINGS_DOWNLOAD); @@ -95,23 +90,23 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe cbgooglesuggest.setOnPreferenceChangeListener(this); cbDrawerTabs.setOnPreferenceChangeListener(this); - mAgentChoice = mPreferences.getUserAgentChoice(); - mHomepage = mPreferences.getHomepage(); - mDownloadLocation = mPreferences.getDownloadDirectory(); + mAgentChoice = mPreferenceManager.getUserAgentChoice(); + mHomepage = mPreferenceManager.getHomepage(); + mDownloadLocation = mPreferenceManager.getDownloadDirectory(); mProxyChoices = getResources().getStringArray(R.array.proxy_choices_array); - int choice = mPreferences.getProxyChoice(); + int choice = mPreferenceManager.getProxyChoice(); if (choice == Constants.PROXY_MANUAL) { - proxy.setSummary(mPreferences.getProxyHost() + ':' + mPreferences.getProxyPort()); + proxy.setSummary(mPreferenceManager.getProxyHost() + ':' + mPreferenceManager.getProxyPort()); } else { proxy.setSummary(mProxyChoices[choice]); } - if (API >= 19) { - mPreferences.setFlashSupport(0); + if (API >= Build.VERSION_CODES.KITKAT) { + mPreferenceManager.setFlashSupport(0); } - setSearchEngineSummary(mPreferences.getSearchChoice()); + setSearchEngineSummary(mPreferenceManager.getSearchChoice()); downloadloc.setSummary(mDownloadLocation); @@ -139,28 +134,27 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe useragent.setSummary(getResources().getString(R.string.agent_custom)); } - int flashNum = mPreferences.getFlashSupport(); - boolean imagesBool = mPreferences.getBlockImagesEnabled(); - boolean enableJSBool = mPreferences.getJavaScriptEnabled(); + int flashNum = mPreferenceManager.getFlashSupport(); + boolean imagesBool = mPreferenceManager.getBlockImagesEnabled(); + boolean enableJSBool = mPreferenceManager.getJavaScriptEnabled(); -// proxy.setEnabled(Constants.FULL_VERSION); cbAds.setEnabled(Constants.FULL_VERSION); - cbFlash.setEnabled(API < 19); + cbFlash.setEnabled(API < Build.VERSION_CODES.KITKAT); cbImages.setChecked(imagesBool); cbJsScript.setChecked(enableJSBool); cbFlash.setChecked(flashNum > 0); - cbAds.setChecked(Constants.FULL_VERSION && mPreferences.getAdBlockEnabled()); - cbColorMode.setChecked(mPreferences.getColorModeEnabled()); - cbgooglesuggest.setChecked(mPreferences.getGoogleSearchSuggestionsEnabled()); - cbDrawerTabs.setChecked(mPreferences.getShowTabsInDrawer(true)); + cbAds.setChecked(Constants.FULL_VERSION && mPreferenceManager.getAdBlockEnabled()); + cbColorMode.setChecked(mPreferenceManager.getColorModeEnabled()); + cbgooglesuggest.setChecked(mPreferenceManager.getGoogleSearchSuggestionsEnabled()); + cbDrawerTabs.setChecked(mPreferenceManager.getShowTabsInDrawer(true)); } private void searchUrlPicker() { final AlertDialog.Builder urlPicker = new AlertDialog.Builder(mActivity); urlPicker.setTitle(getResources().getString(R.string.custom_url)); final EditText getSearchUrl = new EditText(mActivity); - String mSearchUrl = mPreferences.getSearchUrl(); + String mSearchUrl = mPreferenceManager.getSearchUrl(); getSearchUrl.setText(mSearchUrl); urlPicker.setView(getSearchUrl); urlPicker.setPositiveButton(getResources().getString(R.string.action_ok), @@ -168,7 +162,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe @Override public void onClick(DialogInterface dialog, int which) { String text = getSearchUrl.getText().toString(); - mPreferences.setSearchUrl(text); + mPreferenceManager.setSearchUrl(text); searchengine.setSummary(getResources().getString(R.string.custom_url) + ": " + text); } @@ -185,7 +179,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { - mPreferences.setFlashSupport(1); + mPreferenceManager.setFlashSupport(1); } }) .setNegativeButton(getResources().getString(R.string.action_auto), @@ -193,13 +187,13 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe @Override public void onClick(DialogInterface dialog, int which) { - mPreferences.setFlashSupport(2); + mPreferenceManager.setFlashSupport(2); } }).setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { - mPreferences.setFlashSupport(0); + mPreferenceManager.setFlashSupport(0); } }); @@ -210,7 +204,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe private void proxyChoicePicker() { AlertDialog.Builder picker = new AlertDialog.Builder(mActivity); picker.setTitle(getResources().getString(R.string.http_proxy)); - picker.setSingleChoiceItems(mProxyChoices, mPreferences.getProxyChoice(), + picker.setSingleChoiceItems(mProxyChoices, mPreferenceManager.getProxyChoice(), new DialogInterface.OnClickListener() { @Override @@ -235,7 +229,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe break; } - mPreferences.setProxyChoice(choice); + mPreferenceManager.setProxyChoice(choice); if (choice < mProxyChoices.length) proxy.setSummary(mProxyChoices[choice]); } @@ -253,8 +247,8 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe filterArray[0] = new InputFilter.LengthFilter(maxCharacters - 1); eProxyPort.setFilters(filterArray); - eProxyHost.setText(mPreferences.getProxyHost()); - eProxyPort.setText(Integer.toString(mPreferences.getProxyPort())); + eProxyHost.setText(mPreferenceManager.getProxyHost()); + eProxyPort.setText(Integer.toString(mPreferenceManager.getProxyPort())); new AlertDialog.Builder(mActivity) .setTitle(R.string.manual_proxy) @@ -269,10 +263,10 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe // larger than max integer proxyPort = Integer.parseInt(eProxyPort.getText().toString()); } catch (NumberFormatException ignored) { - proxyPort = mPreferences.getProxyPort(); + proxyPort = mPreferenceManager.getProxyPort(); } - mPreferences.setProxyHost(proxyHost); - mPreferences.setProxyPort(proxyPort); + mPreferenceManager.setProxyHost(proxyHost); + mPreferenceManager.setProxyPort(proxyPort); proxy.setSummary(proxyHost + ':' + proxyPort); } }).show(); @@ -286,13 +280,13 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe "DuckDuckGo (Privacy)", "DuckDuckGo Lite (Privacy)", "Baidu (Chinese)", "Yandex (Russian)"}; - int n = mPreferences.getSearchChoice(); + int n = mPreferenceManager.getSearchChoice(); picker.setSingleChoiceItems(chars, n, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mPreferences.setSearchChoice(which); + mPreferenceManager.setSearchChoice(which); setSearchEngineSummary(which); } }); @@ -303,7 +297,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe private void homepageDialog() { AlertDialog.Builder picker = new AlertDialog.Builder(mActivity); picker.setTitle(getResources().getString(R.string.home)); - mHomepage = mPreferences.getHomepage(); + mHomepage = mPreferenceManager.getHomepage(); int n; if (mHomepage.contains("about:home")) { n = 1; @@ -321,15 +315,15 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe public void onClick(DialogInterface dialog, int which) { switch (which + 1) { case 1: - mPreferences.setHomepage("about:home"); + mPreferenceManager.setHomepage("about:home"); home.setSummary(getResources().getString(R.string.action_homepage)); break; case 2: - mPreferences.setHomepage("about:blank"); + mPreferenceManager.setHomepage("about:blank"); home.setSummary(getResources().getString(R.string.action_blank)); break; case 3: - mPreferences.setHomepage("about:bookmarks"); + mPreferenceManager.setHomepage("about:bookmarks"); home.setSummary(getResources().getString(R.string.action_bookmarks)); break; case 4: @@ -346,11 +340,12 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe final AlertDialog.Builder homePicker = new AlertDialog.Builder(mActivity); homePicker.setTitle(getResources().getString(R.string.title_custom_homepage)); final EditText getHome = new EditText(mActivity); - mHomepage = mPreferences.getHomepage(); + mHomepage = mPreferenceManager.getHomepage(); if (!mHomepage.startsWith("about:")) { getHome.setText(mHomepage); } else { - getHome.setText("http://www.google.com"); + String defaultUrl = "http://www.google.com"; + getHome.setText(defaultUrl); } homePicker.setView(getHome); homePicker.setPositiveButton(getResources().getString(R.string.action_ok), @@ -358,7 +353,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe @Override public void onClick(DialogInterface dialog, int which) { String text = getHome.getText().toString(); - mPreferences.setHomepage(text); + mPreferenceManager.setHomepage(text); home.setSummary(text); } }); @@ -368,7 +363,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe private void downloadLocDialog() { AlertDialog.Builder picker = new AlertDialog.Builder(mActivity); picker.setTitle(getResources().getString(R.string.title_download_location)); - mDownloadLocation = mPreferences.getDownloadDirectory(); + mDownloadLocation = mPreferenceManager.getDownloadDirectory(); int n; if (mDownloadLocation.contains(Environment.DIRECTORY_DOWNLOADS)) { n = 0; @@ -382,7 +377,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe public void onClick(DialogInterface dialog, int which) { switch (which) { case 0: - mPreferences.setDownloadDirectory(DownloadHandler.DEFAULT_DOWNLOAD_PATH); + mPreferenceManager.setDownloadDirectory(DownloadHandler.DEFAULT_DOWNLOAD_PATH); downloadloc.setSummary(DownloadHandler.DEFAULT_DOWNLOAD_PATH); break; case 1: @@ -398,12 +393,12 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe private void agentDialog() { AlertDialog.Builder agentPicker = new AlertDialog.Builder(mActivity); agentPicker.setTitle(getResources().getString(R.string.title_user_agent)); - mAgentChoice = mPreferences.getUserAgentChoice(); + mAgentChoice = mPreferenceManager.getUserAgentChoice(); agentPicker.setSingleChoiceItems(R.array.user_agent, mAgentChoice - 1, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - mPreferences.setUserAgentChoice(which + 1); + mPreferenceManager.setUserAgentChoice(which + 1); switch (which + 1) { case 1: useragent.setSummary(getResources().getString(R.string.agent_default)); @@ -435,7 +430,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe @Override public void onClick(DialogInterface dialog, int which) { String text = getAgent.getText().toString(); - mPreferences.setUserAgentString(text); + mPreferenceManager.setUserAgentString(text); useragent.setSummary(getResources().getString(R.string.agent_custom)); } }); @@ -449,12 +444,12 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe final EditText getDownload = new EditText(mActivity); getDownload.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - getDownload.setText(PreferenceManager.getInstance().getDownloadDirectory()); + getDownload.setText(mPreferenceManager.getDownloadDirectory()); final int errorColor = ContextCompat.getColor(getActivity(), R.color.error_red); final int regularColor = ThemeUtils.getTextColor(getActivity()); getDownload.setTextColor(regularColor); getDownload.addTextChangedListener(new DownloadLocationTextWatcher(getDownload, errorColor, regularColor)); - getDownload.setText(mPreferences.getDownloadDirectory()); + getDownload.setText(mPreferenceManager.getDownloadDirectory()); layout.addView(getDownload); downLocationPicker.setView(layout); @@ -464,7 +459,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe public void onClick(DialogInterface dialog, int which) { String text = getDownload.getText().toString(); text = DownloadHandler.addNecessarySlashes(text); - mPreferences.setDownloadDirectory(text); + mPreferenceManager.setDownloadDirectory(text); downloadloc.setSummary(text); } }); @@ -533,44 +528,41 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - // switch preferences + boolean checked = false; + if (newValue instanceof Boolean) { + checked = (Boolean) newValue; + } switch (preference.getKey()) { case SETTINGS_FLASH: - if (cbFlash.isChecked()) { - getFlashChoice(); - } else { - mPreferences.setFlashSupport(0); - } - if (!Utils.isFlashInstalled(mActivity) && cbFlash.isChecked()) { + if (!Utils.isFlashInstalled(mActivity) && checked) { Utils.createInformativeDialog(mActivity, R.string.title_warning, R.string.dialog_adobe_not_installed); - cbFlash.setEnabled(false); - mPreferences.setFlashSupport(0); + mPreferenceManager.setFlashSupport(0); + return false; + } else { + if (checked) { + getFlashChoice(); + } else { + mPreferenceManager.setFlashSupport(0); + } } - cbFlash.setChecked((Boolean) newValue); return true; case SETTINGS_ADS: - mPreferences.setAdBlockEnabled((Boolean) newValue); - cbAds.setChecked((Boolean) newValue); + mPreferenceManager.setAdBlockEnabled(checked); return true; case SETTINGS_IMAGES: - mPreferences.setBlockImagesEnabled((Boolean) newValue); - cbImages.setChecked((Boolean) newValue); + mPreferenceManager.setBlockImagesEnabled(checked); return true; case SETTINGS_JAVASCRIPT: - mPreferences.setJavaScriptEnabled((Boolean) newValue); - cbJsScript.setChecked((Boolean) newValue); + mPreferenceManager.setJavaScriptEnabled(checked); return true; case SETTINGS_COLORMODE: - mPreferences.setColorModeEnabled((Boolean) newValue); - cbColorMode.setChecked((Boolean) newValue); + mPreferenceManager.setColorModeEnabled(checked); return true; case SETTINGS_GOOGLESUGGESTIONS: - mPreferences.setGoogleSearchSuggestionsEnabled((Boolean) newValue); - cbgooglesuggest.setChecked((Boolean) newValue); + mPreferenceManager.setGoogleSearchSuggestionsEnabled(checked); return true; case SETTINGS_DRAWERTABS: - mPreferences.setShowTabsInDrawer((Boolean) newValue); - cbDrawerTabs.setChecked((Boolean) newValue); + mPreferenceManager.setShowTabsInDrawer(checked); default: return false; } diff --git a/app/src/main/java/acr/browser/lightning/fragment/LightningPreferenceFragment.java b/app/src/main/java/acr/browser/lightning/fragment/LightningPreferenceFragment.java new file mode 100644 index 0000000..ab7c48d --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/fragment/LightningPreferenceFragment.java @@ -0,0 +1,27 @@ +package acr.browser.lightning.fragment; + +import android.os.Bundle; +import android.preference.PreferenceFragment; + +import javax.inject.Inject; + +import acr.browser.lightning.app.BrowserApp; +import acr.browser.lightning.preference.PreferenceManager; + +/** + * Simplify {@link PreferenceManager} inject in all the PreferenceFragments + * + * @author Stefano Pacifici + * @date 2015/09/16 + */ +public class LightningPreferenceFragment extends PreferenceFragment { + + @Inject + PreferenceManager mPreferenceManager; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + BrowserApp.getAppComponent().inject(this); + } +} diff --git a/app/src/main/java/acr/browser/lightning/fragment/PrivacySettingsFragment.java b/app/src/main/java/acr/browser/lightning/fragment/PrivacySettingsFragment.java index 9fc225c..4b76aa9 100644 --- a/app/src/main/java/acr/browser/lightning/fragment/PrivacySettingsFragment.java +++ b/app/src/main/java/acr/browser/lightning/fragment/PrivacySettingsFragment.java @@ -11,16 +11,15 @@ import android.os.Handler; import android.os.Message; import android.preference.CheckBoxPreference; import android.preference.Preference; -import android.preference.PreferenceFragment; import android.support.v7.app.AlertDialog; import android.webkit.WebView; import acr.browser.lightning.R; -import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.WebUtils; +import acr.browser.lightning.view.LightningView; -public class PrivacySettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { +public class PrivacySettingsFragment extends LightningPreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { private static final String SETTINGS_LOCATION = "location"; private static final String SETTINGS_THIRDPCOOKIES = "third_party"; @@ -33,11 +32,10 @@ public class PrivacySettingsFragment extends PreferenceFragment implements Prefe private static final String SETTINGS_CLEARCOOKIES = "clear_cookies"; private static final String SETTINGS_CLEARWEBSTORAGE = "clear_webstorage"; private static final String SETTINGS_WEBSTORAGEEXIT = "clear_webstorage_exit"; + private static final String SETTINGS_DONOTTRACK = "do_not_track"; + private static final String SETTINGS_IDENTIFYINGHEADERS = "remove_identifying_headers"; private Activity mActivity; - private PreferenceManager mPreferences; - private CheckBoxPreference cblocation, cb3cookies, cbsavepasswords, cbcacheexit, cbhistoryexit, - cbcookiesexit, cbwebstorageexit; private Handler messageHandler; @Override @@ -52,21 +50,20 @@ public class PrivacySettingsFragment extends PreferenceFragment implements Prefe } private void initPrefs() { - // mPreferences storage - mPreferences = PreferenceManager.getInstance(); - Preference clearcache = findPreference(SETTINGS_CLEARCACHE); Preference clearhistory = findPreference(SETTINGS_CLEARHISTORY); Preference clearcookies = findPreference(SETTINGS_CLEARCOOKIES); Preference clearwebstorage = findPreference(SETTINGS_CLEARWEBSTORAGE); - cblocation = (CheckBoxPreference) findPreference(SETTINGS_LOCATION); - cb3cookies = (CheckBoxPreference) findPreference(SETTINGS_THIRDPCOOKIES); - cbsavepasswords = (CheckBoxPreference) findPreference(SETTINGS_SAVEPASSWORD); - cbcacheexit = (CheckBoxPreference) findPreference(SETTINGS_CACHEEXIT); - cbhistoryexit = (CheckBoxPreference) findPreference(SETTINGS_HISTORYEXIT); - cbcookiesexit = (CheckBoxPreference) findPreference(SETTINGS_COOKIEEXIT); - cbwebstorageexit = (CheckBoxPreference) findPreference(SETTINGS_WEBSTORAGEEXIT); + CheckBoxPreference cblocation = (CheckBoxPreference) findPreference(SETTINGS_LOCATION); + CheckBoxPreference cb3cookies = (CheckBoxPreference) findPreference(SETTINGS_THIRDPCOOKIES); + CheckBoxPreference cbsavepasswords = (CheckBoxPreference) findPreference(SETTINGS_SAVEPASSWORD); + CheckBoxPreference cbcacheexit = (CheckBoxPreference) findPreference(SETTINGS_CACHEEXIT); + CheckBoxPreference cbhistoryexit = (CheckBoxPreference) findPreference(SETTINGS_HISTORYEXIT); + CheckBoxPreference cbcookiesexit = (CheckBoxPreference) findPreference(SETTINGS_COOKIEEXIT); + CheckBoxPreference cbwebstorageexit = (CheckBoxPreference) findPreference(SETTINGS_WEBSTORAGEEXIT); + CheckBoxPreference cbDoNotTrack = (CheckBoxPreference) findPreference(SETTINGS_DONOTTRACK); + CheckBoxPreference cbIdentifyingHeaders = (CheckBoxPreference) findPreference(SETTINGS_IDENTIFYINGHEADERS); clearcache.setOnPreferenceClickListener(this); clearhistory.setOnPreferenceClickListener(this); @@ -80,14 +77,21 @@ public class PrivacySettingsFragment extends PreferenceFragment implements Prefe cbhistoryexit.setOnPreferenceChangeListener(this); cbcookiesexit.setOnPreferenceChangeListener(this); cbwebstorageexit.setOnPreferenceChangeListener(this); - - cblocation.setChecked(mPreferences.getLocationEnabled()); - cbsavepasswords.setChecked(mPreferences.getSavePasswordsEnabled()); - cbcacheexit.setChecked(mPreferences.getClearCacheExit()); - cbhistoryexit.setChecked(mPreferences.getClearHistoryExitEnabled()); - cbcookiesexit.setChecked(mPreferences.getClearCookiesExitEnabled()); - cb3cookies.setChecked(mPreferences.getBlockThirdPartyCookiesEnabled()); - cbwebstorageexit.setChecked(mPreferences.getClearWebStorageExitEnabled()); + cbDoNotTrack.setOnPreferenceChangeListener(this); + cbIdentifyingHeaders.setOnPreferenceChangeListener(this); + + cblocation.setChecked(mPreferenceManager.getLocationEnabled()); + cbsavepasswords.setChecked(mPreferenceManager.getSavePasswordsEnabled()); + cbcacheexit.setChecked(mPreferenceManager.getClearCacheExit()); + cbhistoryexit.setChecked(mPreferenceManager.getClearHistoryExitEnabled()); + cbcookiesexit.setChecked(mPreferenceManager.getClearCookiesExitEnabled()); + cb3cookies.setChecked(mPreferenceManager.getBlockThirdPartyCookiesEnabled()); + cbwebstorageexit.setChecked(mPreferenceManager.getClearWebStorageExitEnabled()); + cbDoNotTrack.setChecked(mPreferenceManager.getDoNotTrackEnabled()); + cbIdentifyingHeaders.setChecked(mPreferenceManager.getRemoveIdentifyingHeadersEnabled()); + + String identifyingHeadersSummary = LightningView.HEADER_REQUESTED_WITH + ", " + LightningView.HEADER_WAP_PROFILE; + cbIdentifyingHeaders.setSummary(identifyingHeadersSummary); cb3cookies.setEnabled(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP); @@ -200,35 +204,33 @@ public class PrivacySettingsFragment extends PreferenceFragment implements Prefe @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - // switch preferences switch (preference.getKey()) { case SETTINGS_LOCATION: - mPreferences.setLocationEnabled((Boolean) newValue); - cblocation.setChecked((Boolean) newValue); + mPreferenceManager.setLocationEnabled((Boolean) newValue); return true; case SETTINGS_THIRDPCOOKIES: - mPreferences.setBlockThirdPartyCookiesEnabled((Boolean) newValue); - cb3cookies.setChecked((Boolean) newValue); + mPreferenceManager.setBlockThirdPartyCookiesEnabled((Boolean) newValue); return true; case SETTINGS_SAVEPASSWORD: - mPreferences.setSavePasswordsEnabled((Boolean) newValue); - cbsavepasswords.setChecked((Boolean) newValue); + mPreferenceManager.setSavePasswordsEnabled((Boolean) newValue); return true; case SETTINGS_CACHEEXIT: - mPreferences.setClearCacheExit((Boolean) newValue); - cbcacheexit.setChecked((Boolean) newValue); + mPreferenceManager.setClearCacheExit((Boolean) newValue); return true; case SETTINGS_HISTORYEXIT: - mPreferences.setClearHistoryExitEnabled((Boolean) newValue); - cbhistoryexit.setChecked((Boolean) newValue); + mPreferenceManager.setClearHistoryExitEnabled((Boolean) newValue); return true; case SETTINGS_COOKIEEXIT: - mPreferences.setClearCookiesExitEnabled((Boolean) newValue); - cbcookiesexit.setChecked((Boolean) newValue); + mPreferenceManager.setClearCookiesExitEnabled((Boolean) newValue); return true; case SETTINGS_WEBSTORAGEEXIT: - mPreferences.setClearWebStorageExitEnabled((Boolean) newValue); - cbwebstorageexit.setChecked((Boolean) newValue); + mPreferenceManager.setClearWebStorageExitEnabled((Boolean) newValue); + return true; + case SETTINGS_DONOTTRACK: + mPreferenceManager.setDoNotTrackEnabled((Boolean) newValue); + return true; + case SETTINGS_IDENTIFYINGHEADERS: + mPreferenceManager.setRemoveIdentifyingHeadersEnabled((Boolean) newValue); return true; default: return false; diff --git a/app/src/main/java/acr/browser/lightning/fragment/TabsFragment.java b/app/src/main/java/acr/browser/lightning/fragment/TabsFragment.java new file mode 100644 index 0000000..8377c1f --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/fragment/TabsFragment.java @@ -0,0 +1,359 @@ +package acr.browser.lightning.fragment; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.view.ViewCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.LayoutManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.squareup.otto.Bus; +import com.squareup.otto.Subscribe; + +import javax.inject.Inject; + +import acr.browser.lightning.R; +import acr.browser.lightning.activity.TabsManager; +import acr.browser.lightning.app.BrowserApp; +import acr.browser.lightning.bus.BrowserEvents; +import acr.browser.lightning.bus.NavigationEvents; +import acr.browser.lightning.bus.TabEvents; +import acr.browser.lightning.controller.UIController; +import acr.browser.lightning.preference.PreferenceManager; +import acr.browser.lightning.utils.ThemeUtils; +import acr.browser.lightning.utils.Utils; +import acr.browser.lightning.view.LightningView; + +/** + * A fragment that holds and manages the tabs and interaction with the tabs. + * It is reliant on the BrowserController in order to get the current UI state + * of the browser. It also uses the BrowserController to signal that the UI needs + * to change. This class contains the adapter used by both the drawer tabs and + * the desktop tabs. It delegates touch events for the tab UI appropriately. + */ +public class TabsFragment extends Fragment implements View.OnClickListener, View.OnLongClickListener { + + private static final String TAG = TabsFragment.class.getSimpleName(); + + /** + * Arguments boolean to tell the fragment it is displayed in the drawner or on the tab strip + * If true, the fragment is in the left drawner in the strip otherwise. + */ + public static final String VERTICAL_MODE = TAG + ".VERTICAL_MODE"; + public static final String IS_INCOGNITO = TAG + ".IS_INCOGNITO"; + + private boolean mIsIncognito, mDarkTheme; + private int mIconColor; + private boolean mColorMode = true; + private boolean mShowInNavigationDrawer; + + private RecyclerView mRecyclerView; + private LightningViewAdapter mTabsAdapter; + private UIController mUiController; + + @Inject + TabsManager tabsManager; + + @Inject + Bus mBus; + + @Inject + PreferenceManager mPreferences; + + public TabsFragment() { + BrowserApp.getAppComponent().inject(this); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Bundle arguments = getArguments(); + final Context context = getContext(); + mUiController = (UIController) getActivity(); + mIsIncognito = arguments.getBoolean(IS_INCOGNITO, false); + mShowInNavigationDrawer = arguments.getBoolean(VERTICAL_MODE, true); + mDarkTheme = mPreferences.getUseTheme() != 0 || mIsIncognito; + mColorMode = mPreferences.getColorModeEnabled(); + mColorMode &= !mDarkTheme; + mIconColor = mDarkTheme ? + ThemeUtils.getIconDarkThemeColor(context) : + ThemeUtils.getIconLightThemeColor(context); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View view; + final LayoutManager layoutManager; + if (mShowInNavigationDrawer) { + view = inflater.inflate(R.layout.tab_drawer, container, false); + layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false); + setupFrameLayoutButton(view, R.id.tab_header_button, R.id.plusIcon); + setupFrameLayoutButton(view, R.id.new_tab_button, R.id.icon_plus); + setupFrameLayoutButton(view, R.id.action_back, R.id.icon_back); + setupFrameLayoutButton(view, R.id.action_forward, R.id.icon_forward); + setupFrameLayoutButton(view, R.id.action_home, R.id.icon_home); + } else { + view = inflater.inflate(R.layout.tab_strip, container, false); + layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false); + } + mRecyclerView = (RecyclerView) view.findViewById(R.id.tabs_list); + mRecyclerView.setLayoutManager(layoutManager); + mTabsAdapter = new LightningViewAdapter(mShowInNavigationDrawer); + mRecyclerView.setAdapter(mTabsAdapter); + mRecyclerView.setHasFixedSize(true); + return view; + } + + private void setupFrameLayoutButton(@NonNull final View root, @IdRes final int buttonId, + @IdRes final int imageId) { + final View frameButton = root.findViewById(buttonId); + final ImageView buttonImage = (ImageView) root.findViewById(imageId); + frameButton.setOnClickListener(this); + frameButton.setOnLongClickListener(this); + buttonImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + mRecyclerView = null; + mTabsAdapter = null; + } + + @Override + public void onStart() { + super.onStart(); + mBus.register(this); + } + + @Override + public void onResume() { + super.onResume(); + // Force adapter refresh + mTabsAdapter.notifyDataSetChanged(); + } + + @Override + public void onStop() { + super.onStop(); + mBus.unregister(this); + } + + @Subscribe + public void tabsChanged(final BrowserEvents.TabsChanged event) { + if (mTabsAdapter != null) { + mTabsAdapter.notifyDataSetChanged(); + } + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.new_tab_button: + mBus.post(new TabEvents.NewTab()); + break; + case R.id.action_back: + mBus.post(new NavigationEvents.GoBack()); + break; + case R.id.action_forward: + mBus.post(new NavigationEvents.GoForward()); + break; + case R.id.action_home: + mBus.post(new NavigationEvents.GoHome()); + default: + break; + } + } + + @Override + public boolean onLongClick(View v) { + switch (v.getId()) { + case R.id.action_new_tab: + mBus.post(new TabEvents.NewTabLongPress()); + break; + default: + break; + } + return true; + } + + public class LightningViewAdapter extends RecyclerView.Adapter { + + private final int mLayoutResourceId; + private final Drawable mBackgroundTabDrawable; + private final Drawable mForegroundTabDrawable; + private final Bitmap mForegroundTabBitmap; + private ColorMatrix mColorMatrix; + private Paint mPaint; + private ColorFilter mFilter; + private static final float DESATURATED = 0.5f; + + private final boolean mDrawerTabs; + + public LightningViewAdapter(final boolean vertical) { + this.mLayoutResourceId = vertical ? R.layout.tab_list_item : R.layout.tab_list_item_horizontal; + this.mDrawerTabs = vertical; + + if (vertical) { + mBackgroundTabDrawable = null; + mForegroundTabBitmap = null; + mForegroundTabDrawable = ThemeUtils.getSelectedBackground(getContext(), mDarkTheme); + } else { + int backgroundColor = Utils.mixTwoColors(ThemeUtils.getPrimaryColor(getContext()), Color.BLACK, 0.75f); + Bitmap backgroundTabBitmap = Bitmap.createBitmap(Utils.dpToPx(175), Utils.dpToPx(30), Bitmap.Config.ARGB_8888); + Utils.drawTrapezoid(new Canvas(backgroundTabBitmap), backgroundColor, true); + mBackgroundTabDrawable = new BitmapDrawable(getResources(), backgroundTabBitmap); + + int foregroundColor = ThemeUtils.getPrimaryColor(getContext()); + mForegroundTabBitmap = Bitmap.createBitmap(Utils.dpToPx(175), Utils.dpToPx(30), Bitmap.Config.ARGB_8888); + Utils.drawTrapezoid(new Canvas(mForegroundTabBitmap), foregroundColor, false); + mForegroundTabDrawable = null; + } + } + + @Override + public LightningViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext()); + View view = inflater.inflate(mLayoutResourceId, viewGroup, false); + return new LightningViewHolder(view); + } + + @Override + public void onBindViewHolder(final LightningViewHolder holder, int position) { + holder.exitButton.setTag(position); + + ViewCompat.jumpDrawablesToCurrentState(holder.exitButton); + + LightningView web = tabsManager.getTabAtPosition(position); + if (web == null) { + return; + } + holder.txtTitle.setText(web.getTitle()); + + final Bitmap favicon = web.getFavicon(); + if (web.isForegroundTab()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + holder.txtTitle.setTextAppearance(R.style.boldText); + } else { + holder.txtTitle.setTextAppearance(getContext(), R.style.boldText); + } + Drawable foregroundDrawable; + if (!mDrawerTabs) { + foregroundDrawable = new BitmapDrawable(getResources(), mForegroundTabBitmap); + if (!mIsIncognito && mColorMode) { + foregroundDrawable.setColorFilter(mUiController.getUiColor(), PorterDuff.Mode.SRC_IN); + } + } else { + foregroundDrawable = mForegroundTabDrawable; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + holder.layout.setBackground(foregroundDrawable); + } else { + holder.layout.setBackgroundDrawable(foregroundDrawable); + } + if (!mIsIncognito && mColorMode) { + mUiController.changeToolbarBackground(favicon, foregroundDrawable); + } + holder.favicon.setImageBitmap(favicon); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + holder.txtTitle.setTextAppearance(R.style.normalText); + } else { + holder.txtTitle.setTextAppearance(getContext(), R.style.normalText); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + holder.layout.setBackground(mBackgroundTabDrawable); + } else { + holder.layout.setBackgroundDrawable(mBackgroundTabDrawable); + } + holder.favicon.setImageBitmap(getDesaturatedBitmap(favicon)); + } + } + + @Override + public int getItemCount() { + return tabsManager.size(); + } + + public Bitmap getDesaturatedBitmap(Bitmap favicon) { + Bitmap grayscaleBitmap = Bitmap.createBitmap(favicon.getWidth(), + favicon.getHeight(), Bitmap.Config.ARGB_8888); + + Canvas c = new Canvas(grayscaleBitmap); + if (mColorMatrix == null || mFilter == null || mPaint == null) { + mPaint = new Paint(); + mColorMatrix = new ColorMatrix(); + mColorMatrix.setSaturation(DESATURATED); + mFilter = new ColorMatrixColorFilter(mColorMatrix); + mPaint.setColorFilter(mFilter); + } + + c.drawBitmap(favicon, 0, 0, mPaint); + return grayscaleBitmap; + } + + public class LightningViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { + + public LightningViewHolder(View view) { + super(view); + txtTitle = (TextView) view.findViewById(R.id.textTab); + favicon = (ImageView) view.findViewById(R.id.faviconTab); + exit = (ImageView) view.findViewById(R.id.deleteButton); + layout = (LinearLayout) view.findViewById(R.id.tab_item_background); + exitButton = (FrameLayout) view.findViewById(R.id.deleteAction); + exit.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); + + exitButton.setOnClickListener(this); + layout.setOnClickListener(this); + layout.setOnLongClickListener(this); + } + + final TextView txtTitle; + final ImageView favicon; + final ImageView exit; + final FrameLayout exitButton; + final LinearLayout layout; + + @Override + public void onClick(View v) { + if (v == exitButton) { + // Close tab + mBus.post(new TabEvents.CloseTab(getAdapterPosition())); + } + if (v == layout) { + mBus.post(new TabEvents.ShowTab(getAdapterPosition())); + } + } + + @Override + public boolean onLongClick(View v) { + // Show close dialog + mBus.post(new TabEvents.ShowCloseDialog(getAdapterPosition())); + return true; + } + } + } +} diff --git a/app/src/main/java/acr/browser/lightning/object/ClickHandler.java b/app/src/main/java/acr/browser/lightning/object/ClickHandler.java deleted file mode 100644 index 841c03f..0000000 --- a/app/src/main/java/acr/browser/lightning/object/ClickHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2014 A.C.R. Development - */ -package acr.browser.lightning.object; - -import android.content.Context; -import android.os.Handler; -import android.os.Message; - -import acr.browser.lightning.controller.BrowserController; - -public class ClickHandler extends Handler { - - private BrowserController mBrowserController; - - public ClickHandler(Context context) { - try { - mBrowserController = (BrowserController) context; - } catch (ClassCastException e) { - throw new ClassCastException(context + " must implement BrowserController"); - } - } - - @Override - public void handleMessage(Message msg) { - super.handleMessage(msg); - String url = msg.getData().getString("url"); - mBrowserController.longClickPage(url); - } -} diff --git a/app/src/main/java/acr/browser/lightning/object/SearchAdapter.java b/app/src/main/java/acr/browser/lightning/object/SearchAdapter.java index 6db5ef1..4a2830c 100644 --- a/app/src/main/java/acr/browser/lightning/object/SearchAdapter.java +++ b/app/src/main/java/acr/browser/lightning/object/SearchAdapter.java @@ -56,16 +56,14 @@ public class SearchAdapter extends BaseAdapter implements Filterable { private final List mFilteredList = new ArrayList<>(5); private final List mAllBookmarks = new ArrayList<>(5); private final Object mLock = new Object(); - private HistoryDatabase mDatabaseHandler; private final Context mContext; private boolean mUseGoogle = true; private boolean mIsExecuting = false; private final boolean mDarkTheme; private final boolean mIncognito; - @Inject - BookmarkManager mBookmarkManager; private static final String CACHE_FILE_TYPE = ".sgg"; private static final String ENCODING = "ISO-8859-1"; + private static final String DEFAULT_LANGUAGE = "en"; private static final long INTERVAL_DAY = 86400000; private static final int MAX_SUGGESTIONS = 5; private static final SuggestionsComparator mComparator = new SuggestionsComparator(); @@ -75,11 +73,19 @@ public class SearchAdapter extends BaseAdapter implements Filterable { private final Drawable mHistoryDrawable; private final Drawable mBookmarkDrawable; + @Inject + HistoryDatabase mDatabaseHandler; + + @Inject + BookmarkManager mBookmarkManager; + + @Inject + PreferenceManager mPreferenceManager; + public SearchAdapter(Context context, boolean dark, boolean incognito) { BrowserApp.getAppComponent().inject(this); - mDatabaseHandler = HistoryDatabase.getInstance(); mAllBookmarks.addAll(mBookmarkManager.getAllBookmarks(true)); - mUseGoogle = PreferenceManager.getInstance().getGoogleSearchSuggestionsEnabled(); + mUseGoogle = mPreferenceManager.getGoogleSearchSuggestionsEnabled(); mContext = context; mSearchSubtitle = mContext.getString(R.string.suggestion); mDarkTheme = dark || incognito; @@ -114,13 +120,12 @@ public class SearchAdapter extends BaseAdapter implements Filterable { } public void refreshPreferences() { - mUseGoogle = PreferenceManager.getInstance().getGoogleSearchSuggestionsEnabled(); + mUseGoogle = mPreferenceManager.getGoogleSearchSuggestionsEnabled(); if (!mUseGoogle) { synchronized (mSuggestions) { mSuggestions.clear(); } } - mDatabaseHandler = HistoryDatabase.getInstance(); } public void refreshBookmarks() { @@ -244,13 +249,10 @@ public class SearchAdapter extends BaseAdapter implements Filterable { mBookmarks.add(mAllBookmarks.get(n)); counter++; } - } } } - if (mDatabaseHandler == null || mDatabaseHandler.isClosed()) { - mDatabaseHandler = HistoryDatabase.getInstance(); - } + List historyList = mDatabaseHandler.findItemsContaining(constraint.toString()); synchronized (mHistory) { mHistory.clear(); @@ -373,9 +375,13 @@ public class SearchAdapter extends BaseAdapter implements Filterable { } InputStream in = null; FileOutputStream fos = null; + String language = Locale.getDefault().getLanguage(); + if (language.isEmpty()) { + language = DEFAULT_LANGUAGE; + } try { URL url = new URL("http://google.com/complete/search?q=" + query - + "&output=toolbar&hl=en"); + + "&output=toolbar&hl=" + language); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoInput(true); connection.connect(); @@ -413,7 +419,7 @@ public class SearchAdapter extends BaseAdapter implements Filterable { return connectivity.getActiveNetworkInfo(); } - private List getFilteredList() { + private synchronized List getFilteredList() { List list = new ArrayList<>(5); synchronized (mBookmarks) { synchronized (mHistory) { diff --git a/app/src/main/java/acr/browser/lightning/preference/PreferenceManager.java b/app/src/main/java/acr/browser/lightning/preference/PreferenceManager.java index 0fda30e..d948e42 100644 --- a/app/src/main/java/acr/browser/lightning/preference/PreferenceManager.java +++ b/app/src/main/java/acr/browser/lightning/preference/PreferenceManager.java @@ -1,11 +1,15 @@ package acr.browser.lightning.preference; +import android.content.Context; import android.content.SharedPreferences; -import acr.browser.lightning.app.BrowserApp; +import javax.inject.Inject; +import javax.inject.Singleton; + import acr.browser.lightning.constant.Constants; import acr.browser.lightning.download.DownloadHandler; +@Singleton public class PreferenceManager { private static class Name { @@ -47,6 +51,8 @@ public class PreferenceManager { public static final String TEXT_ENCODING = "textEncoding"; public static final String CLEAR_WEBSTORAGE_EXIT = "clearWebStorageExit"; public static final String SHOW_TABS_IN_DRAWER = "showTabsInDrawer"; + public static final String DO_NOT_TRACK = "doNotTrack"; + public static final String IDENTIFYING_HEADERS = "removeIdentifyingHeaders"; public static final String USE_PROXY = "useProxy"; public static final String PROXY_CHOICE = "proxyChoice"; @@ -56,20 +62,13 @@ public class PreferenceManager { public static final String INITIAL_CHECK_FOR_I2P = "checkForI2P"; } - private static PreferenceManager mInstance; private final SharedPreferences mPrefs; private static final String PREFERENCES = "settings"; - public static PreferenceManager getInstance() { - if (mInstance == null) { - mInstance = new PreferenceManager(); - } - return mInstance; - } - - private PreferenceManager() { - mPrefs = BrowserApp.getAppContext().getSharedPreferences(PREFERENCES, 0); + @Inject + PreferenceManager(final Context context) { + mPrefs = context.getSharedPreferences(PREFERENCES, 0); } public boolean getAdBlockEnabled() { @@ -248,6 +247,14 @@ public class PreferenceManager { return mPrefs.getBoolean(Name.SHOW_TABS_IN_DRAWER, defaultValue); } + public boolean getDoNotTrackEnabled() { + return mPrefs.getBoolean(Name.DO_NOT_TRACK, false); + } + + public boolean getRemoveIdentifyingHeadersEnabled(){ + return mPrefs.getBoolean(Name.IDENTIFYING_HEADERS, false); + } + private void putBoolean(String name, boolean value) { mPrefs.edit().putBoolean(name, value).apply(); } @@ -260,6 +267,14 @@ public class PreferenceManager { mPrefs.edit().putString(name, value).apply(); } + public void setRemoveIdentifyingHeadersEnabled(boolean enabled){ + putBoolean(Name.IDENTIFYING_HEADERS, enabled); + } + + public void setDoNotTrackEnabled(boolean doNotTrack) { + putBoolean(Name.DO_NOT_TRACK, doNotTrack); + } + public void setShowTabsInDrawer(boolean show) { putBoolean(Name.SHOW_TABS_IN_DRAWER, show); } diff --git a/app/src/main/java/acr/browser/lightning/reading/ArticleTextExtractor.java b/app/src/main/java/acr/browser/lightning/reading/ArticleTextExtractor.java index 07eb8ab..1de0507 100644 --- a/app/src/main/java/acr/browser/lightning/reading/ArticleTextExtractor.java +++ b/app/src/main/java/acr/browser/lightning/reading/ArticleTextExtractor.java @@ -491,8 +491,7 @@ public class ArticleTextExtractor { Element el = elems.get(0); if (el.hasAttr("content")) { dateStr = el.attr("content"); - Date parsedDate = parseDate(dateStr); - return parsedDate; + return parseDate(dateStr); } } diff --git a/app/src/main/java/acr/browser/lightning/reading/HtmlFetcher.java b/app/src/main/java/acr/browser/lightning/reading/HtmlFetcher.java index 1af5d3b..3c58342 100644 --- a/app/src/main/java/acr/browser/lightning/reading/HtmlFetcher.java +++ b/app/src/main/java/acr/browser/lightning/reading/HtmlFetcher.java @@ -33,6 +33,8 @@ import java.util.zip.GZIPInputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; +import acr.browser.lightning.utils.Utils; + /** * Class to fetch articles. This class is thread safe. * @@ -49,28 +51,34 @@ public class HtmlFetcher { } public static void main(String[] args) throws Exception { - BufferedReader 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"; - BufferedWriter writer = new BufferedWriter(new FileWriter(outFile)); - writer.write(html); - writer.close(); + BufferedReader reader = null; + BufferedWriter writer = null; + try { + + 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"; + writer = new BufferedWriter(new FileWriter(outFile)); + writer.write(html); + } + } finally { + Utils.close(reader); + Utils.close(writer); } - reader.close(); } private String referrer = "http://jetsli.de/crawler"; diff --git a/app/src/main/java/acr/browser/lightning/reading/OutputFormatter.java b/app/src/main/java/acr/browser/lightning/reading/OutputFormatter.java index f57a1da..fe63ca4 100644 --- a/app/src/main/java/acr/browser/lightning/reading/OutputFormatter.java +++ b/app/src/main/java/acr/browser/lightning/reading/OutputFormatter.java @@ -23,7 +23,7 @@ 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 Pattern unlikelyPattern = Pattern.compile("display:none|visibility:hidden"); private final int minFirstParagraphText; private final int minParagraphText; private final List nodesToReplace; diff --git a/app/src/main/java/acr/browser/lightning/reading/SHelper.java b/app/src/main/java/acr/browser/lightning/reading/SHelper.java index 6e2ed97..7293f3a 100644 --- a/app/src/main/java/acr/browser/lightning/reading/SHelper.java +++ b/app/src/main/java/acr/browser/lightning/reading/SHelper.java @@ -253,7 +253,7 @@ class SHelper { public static String getUrlFromUglyGoogleRedirect(String url) { if (url.startsWith("http://www.google.com/url?")) { url = url.substring("http://www.google.com/url?".length()); - String arr[] = urlDecode(url).split("\\&"); + String arr[] = urlDecode(url).split("&"); for (String str : arr) { if (str.startsWith("q=")) return str.substring("q=".length()); diff --git a/app/src/main/java/acr/browser/lightning/utils/AdBlock.java b/app/src/main/java/acr/browser/lightning/utils/AdBlock.java index 706596f..82e9b61 100644 --- a/app/src/main/java/acr/browser/lightning/utils/AdBlock.java +++ b/app/src/main/java/acr/browser/lightning/utils/AdBlock.java @@ -13,8 +13,8 @@ import java.util.HashSet; import java.util.Locale; import java.util.Set; +import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.constant.Constants; -import acr.browser.lightning.preference.PreferenceManager; public class AdBlock { @@ -44,11 +44,11 @@ public class AdBlock { if (mBlockedDomainsList.isEmpty() && Constants.FULL_VERSION) { loadHostsFile(context); } - mBlockAds = PreferenceManager.getInstance().getAdBlockEnabled(); + mBlockAds = BrowserApp.getAppComponent().getPreferenceManager().getAdBlockEnabled(); } public void updatePreference() { - mBlockAds = PreferenceManager.getInstance().getAdBlockEnabled(); + mBlockAds = BrowserApp.getAppComponent().getPreferenceManager().getAdBlockEnabled(); } private void loadBlockedDomainsList(final Context context) { diff --git a/app/src/main/java/acr/browser/lightning/utils/IntentUtils.java b/app/src/main/java/acr/browser/lightning/utils/IntentUtils.java index 21e6791..db29ee9 100644 --- a/app/src/main/java/acr/browser/lightning/utils/IntentUtils.java +++ b/app/src/main/java/acr/browser/lightning/utils/IntentUtils.java @@ -7,6 +7,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; +import android.os.Build; import android.util.Log; import android.webkit.WebView; @@ -23,7 +24,7 @@ public class IntentUtils { + // switch on case insensitive matching '(' + // begin group for schema - "(?:http|https|file):\\/\\/" + "|(?:inline|data|about|javascript):" + "|(?:.*:.*@)" + "(?:http|https|file)://" + "|(?:inline|data|about|javascript):" + "|(?:.*:.*@)" + ')' + "(.*)"); public IntentUtils(Activity activity) { @@ -39,6 +40,12 @@ public class IntentUtils { return false; } + intent.addCategory(Intent.CATEGORY_BROWSABLE); + intent.setComponent(null); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { + intent.setSelector(null); + } + if (mActivity.getPackageManager().resolveActivity(intent, 0) == null) { String packagename = intent.getPackage(); if (packagename != null) { @@ -51,8 +58,6 @@ public class IntentUtils { return false; } } - intent.addCategory(Intent.CATEGORY_BROWSABLE); - intent.setComponent(null); if (tab != null) { intent.putExtra(mActivity.getPackageName() + ".Origin", 1); } diff --git a/app/src/main/java/acr/browser/lightning/utils/PermissionsManager.java b/app/src/main/java/acr/browser/lightning/utils/PermissionsManager.java deleted file mode 100644 index c0cc104..0000000 --- a/app/src/main/java/acr/browser/lightning/utils/PermissionsManager.java +++ /dev/null @@ -1,75 +0,0 @@ -package acr.browser.lightning.utils; - -import android.app.Activity; -import android.content.pm.PackageManager; -import android.os.Build; -import android.support.annotation.NonNull; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Copyright 8/22/2015 Anthony Restaino - */ -public class PermissionsManager { - - private static PermissionsManager mInstance; - private final Set mPendingRequests = new HashSet<>(); - - public static PermissionsManager getInstance() { - if (mInstance == null) { - mInstance = new PermissionsManager(); - } - return mInstance; - } - - public void requestPermissionsIfNecessary(Activity activity, @NonNull String[] permissions) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || activity == null) { - return; - } - List permList = new ArrayList<>(); - for (String perm : permissions) { - if (activity.checkSelfPermission(perm) != PackageManager.PERMISSION_GRANTED - && !mPendingRequests.contains(perm)) { - permList.add(perm); - } - } - if (!permList.isEmpty()) { - String[] permsToRequest = permList.toArray(new String[permList.size()]); - mPendingRequests.addAll(permList); - activity.requestPermissions(permsToRequest, 1); - } - - } - - public static boolean checkPermission(Activity activity, @NonNull String permission) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return true; - } else if (activity == null) { - return false; - } - return activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; - } - - public static boolean checkPermissions(Activity activity, @NonNull String[] permissions) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return true; - } else if (activity == null) { - return false; - } - boolean permissionsNecessary = true; - for (String perm : permissions) { - permissionsNecessary &= activity.checkSelfPermission(perm) == PackageManager.PERMISSION_GRANTED; - } - return permissionsNecessary; - } - - public void notifyPermissionsChange(String[] permissions) { - for (String perm : permissions) { - mPendingRequests.remove(perm); - } - } - -} diff --git a/app/src/main/java/acr/browser/lightning/utils/UrlUtils.java b/app/src/main/java/acr/browser/lightning/utils/UrlUtils.java index 1cb5b9b..06b5c7c 100644 --- a/app/src/main/java/acr/browser/lightning/utils/UrlUtils.java +++ b/app/src/main/java/acr/browser/lightning/utils/UrlUtils.java @@ -21,6 +21,11 @@ import android.webkit.URLUtil; import java.util.regex.Matcher; import java.util.regex.Pattern; +import acr.browser.lightning.constant.BookmarkPage; +import acr.browser.lightning.constant.Constants; +import acr.browser.lightning.constant.HistoryPage; +import acr.browser.lightning.constant.StartPage; + /** * Utility methods for Url manipulation */ @@ -28,7 +33,7 @@ public class UrlUtils { private static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile( "(?i)" + // switch on case insensitive matching '(' + // begin group for schema - "(?:http|https|file):\\/\\/" + + "(?:http|https|file)://" + "|(?:inline|data|about|javascript):" + "|(?:.*:.*@)" + ')' + @@ -145,4 +150,14 @@ public class UrlUtils { } return inUrl; } + + /** + * Returns whether the given url is the bookmarks/history page or a normal website + */ + public static boolean isSpecialUrl(String url) { + return url != null && url.startsWith(Constants.FILE) && + (url.endsWith(BookmarkPage.FILENAME) || + url.endsWith(HistoryPage.FILENAME) || + url.endsWith(StartPage.FILENAME)); + } } \ No newline at end of file diff --git a/app/src/main/java/acr/browser/lightning/utils/Utils.java b/app/src/main/java/acr/browser/lightning/utils/Utils.java index c5c5985..43bd90a 100644 --- a/app/src/main/java/acr/browser/lightning/utils/Utils.java +++ b/app/src/main/java/acr/browser/lightning/utils/Utils.java @@ -3,6 +3,7 @@ */ package acr.browser.lightning.utils; +import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; @@ -39,15 +40,29 @@ import java.util.Date; import acr.browser.lightning.R; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.download.DownloadHandler; +import com.anthonycr.grant.PermissionsManager; +import com.anthonycr.grant.PermissionsResultAction; public final class Utils { public static void downloadFile(final Activity activity, final String url, final String userAgent, final String contentDisposition) { - String fileName = URLUtil.guessFileName(url, null, null); - DownloadHandler.onDownloadStart(activity, url, userAgent, contentDisposition, null - ); - Log.i(Constants.TAG, "Downloading" + fileName); + PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE}, new PermissionsResultAction() { + @Override + public void onGranted() { + String fileName = URLUtil.guessFileName(url, null, null); + DownloadHandler.onDownloadStart(activity, url, userAgent, contentDisposition, null + ); + Log.i(Constants.TAG, "Downloading" + fileName); + } + + @Override + public void onDenied(String permission) { + // TODO Show Message + } + }); + } public static Intent newEmailIntent(String address, String subject, diff --git a/app/src/main/java/acr/browser/lightning/utils/WebUtils.java b/app/src/main/java/acr/browser/lightning/utils/WebUtils.java index a5e5fd8..609bedf 100644 --- a/app/src/main/java/acr/browser/lightning/utils/WebUtils.java +++ b/app/src/main/java/acr/browser/lightning/utils/WebUtils.java @@ -10,7 +10,7 @@ import android.webkit.WebStorage; import android.webkit.WebView; import android.webkit.WebViewDatabase; -import acr.browser.lightning.database.HistoryDatabase; +import acr.browser.lightning.app.BrowserApp; /** * Copyright 8/4/2015 Anthony Restaino @@ -34,7 +34,7 @@ public class WebUtils { } public static void clearHistory(@NonNull Context context) { - HistoryDatabase.getInstance().deleteHistory(); + BrowserApp.getAppComponent().getHistoryDatabase().deleteHistory(); WebViewDatabase m = WebViewDatabase.getInstance(context); m.clearFormData(); m.clearHttpAuthUsernamePassword(); diff --git a/app/src/main/java/acr/browser/lightning/view/IconCacheTask.java b/app/src/main/java/acr/browser/lightning/view/IconCacheTask.java new file mode 100644 index 0000000..c4845d2 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/view/IconCacheTask.java @@ -0,0 +1,44 @@ +package acr.browser.lightning.view; + +import android.graphics.Bitmap; +import android.net.Uri; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import acr.browser.lightning.app.BrowserApp; +import acr.browser.lightning.constant.Constants; +import acr.browser.lightning.utils.Utils; + +/** + * @author Anthony C. Restaino + * @date 2015/09/29 + */ +class IconCacheTask implements Runnable{ + private final Uri uri; + private final Bitmap icon; + + public IconCacheTask(Uri uri, Bitmap icon) { + this.uri = uri; + this.icon = icon; + } + + @Override + public void run() { + String hash = String.valueOf(uri.getHost().hashCode()); + Log.d(Constants.TAG, "Caching icon for " + uri.getHost()); + FileOutputStream fos = null; + try { + File image = new File(BrowserApp.getAppContext().getCacheDir(), hash + ".png"); + fos = new FileOutputStream(image); + icon.compress(Bitmap.CompressFormat.PNG, 100, fos); + fos.flush(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + Utils.close(fos); + } + } +} diff --git a/app/src/main/java/acr/browser/lightning/view/LightningChromeClient.java b/app/src/main/java/acr/browser/lightning/view/LightningChromeClient.java new file mode 100644 index 0000000..393c0c5 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/view/LightningChromeClient.java @@ -0,0 +1,233 @@ +package acr.browser.lightning.view; + +import android.Manifest; +import android.app.Activity; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Message; +import android.support.v7.app.AlertDialog; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.webkit.GeolocationPermissions; +import android.webkit.ValueCallback; +import android.webkit.WebChromeClient; +import android.webkit.WebView; + +import com.squareup.otto.Bus; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import acr.browser.lightning.R; +import acr.browser.lightning.app.BrowserApp; +import acr.browser.lightning.bus.BrowserEvents; +import acr.browser.lightning.constant.Constants; +import acr.browser.lightning.controller.UIController; +import com.anthonycr.grant.PermissionsManager; +import com.anthonycr.grant.PermissionsResultAction; +import acr.browser.lightning.utils.Utils; + +/** + * @author Stefano Pacifici based on Anthony C. Restaino code + * @date 2015/09/21 + */ +class LightningChromeClient extends WebChromeClient { + + private static final String[] PERMISSIONS = new String[]{Manifest.permission.ACCESS_FINE_LOCATION}; + + private final Activity mActivity; + private final LightningView mLightningView; + private final UIController mUIController; + private final Bus eventBus; + + LightningChromeClient(Activity activity, LightningView lightningView) { + mActivity = activity; + mUIController = (UIController) activity; + mLightningView = lightningView; + eventBus = BrowserApp.getAppComponent().getBus(); + } + + @Override + public void onProgressChanged(WebView view, int newProgress) { + if (mLightningView.isShown()) { + mUIController.updateProgress(newProgress); + } + } + + @Override + public void onReceivedIcon(WebView view, Bitmap icon) { + mLightningView.mTitle.setFavicon(icon); + eventBus.post(new BrowserEvents.TabsChanged()); + cacheFavicon(view.getUrl(), icon); + } + + /** + * Naive caching of the favicon according to the domain name of the URL + * + * @param icon the icon to cache + */ + private static void cacheFavicon(final String url, final Bitmap icon) { + if (icon == null) return; + final Uri uri = Uri.parse(url); + if (uri.getHost() == null) { + return; + } + new Thread(new Runnable() { + @Override + public void run() { + String hash = String.valueOf(uri.getHost().hashCode()); + Log.d(Constants.TAG, "Caching icon for " + uri.getHost()); + FileOutputStream fos = null; + try { + File image = new File(BrowserApp.getAppContext().getCacheDir(), hash + ".png"); + fos = new FileOutputStream(image); + icon.compress(Bitmap.CompressFormat.PNG, 100, fos); + fos.flush(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + Utils.close(fos); + } + } + }).start(); + } + + + @Override + public void onReceivedTitle(WebView view, String title) { + if (title != null && !title.isEmpty()) { + mLightningView.mTitle.setTitle(title); + } else { + mLightningView.mTitle.setTitle(mActivity.getString(R.string.untitled)); + } + eventBus.post(new BrowserEvents.TabsChanged()); + if (view != null) { + mUIController.updateHistory(title, view.getUrl()); + } + } + + @Override + public void onGeolocationPermissionsShowPrompt(final String origin, + final GeolocationPermissions.Callback callback) { + PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(mActivity, PERMISSIONS, new PermissionsResultAction() { + @Override + public void onGranted() { + final boolean remember = true; + AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); + builder.setTitle(mActivity.getString(R.string.location)); + String org; + if (origin.length() > 50) { + org = origin.subSequence(0, 50) + "..."; + } else { + org = origin; + } + builder.setMessage(org + mActivity.getString(R.string.message_location)) + .setCancelable(true) + .setPositiveButton(mActivity.getString(R.string.action_allow), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + callback.invoke(origin, true, remember); + } + }) + .setNegativeButton(mActivity.getString(R.string.action_dont_allow), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + callback.invoke(origin, false, remember); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + } + + @Override + public void onDenied(String permission) { + //TODO show message and/or turn off setting + } + }); + } + + @Override + public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, + Message resultMsg) { + mUIController.onCreateWindow(resultMsg); + return true; + } + + @Override + public void onCloseWindow(WebView window) { + mUIController.onCloseWindow(mLightningView); + } + + @SuppressWarnings("unused") + public void openFileChooser(ValueCallback uploadMsg) { + mUIController.openFileChooser(uploadMsg); + } + + @SuppressWarnings("unused") + public void openFileChooser(ValueCallback uploadMsg, String acceptType) { + mUIController.openFileChooser(uploadMsg); + } + + @SuppressWarnings("unused") + public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) { + mUIController.openFileChooser(uploadMsg); + } + + public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, + WebChromeClient.FileChooserParams fileChooserParams) { + mUIController.showFileChooser(filePathCallback); + return true; + } + + /** + * Obtain an image that is displayed as a placeholder on a video until the video has initialized + * and can begin loading. + * + * @return a Bitmap that can be used as a place holder for videos. + */ + @Override + public Bitmap getDefaultVideoPoster() { + if (mActivity == null) { + return null; + } + final Resources resources = mActivity.getResources(); + return BitmapFactory.decodeResource(resources, android.R.drawable.spinner_background); + } + + /** + * Inflate a view to send to a LightningView when it needs to display a video and has to + * show a loading dialog. Inflates a progress view and returns it. + * + * @return A view that should be used to display the state + * of a video's loading progress. + */ + @Override + public View getVideoLoadingProgressView() { + LayoutInflater inflater = LayoutInflater.from(mActivity); + return inflater.inflate(R.layout.video_loading_progress, null); + } + + @Override + public void onHideCustomView() { + mUIController.onHideCustomView(); + } + + @Override + public void onShowCustomView(View view, CustomViewCallback callback) { + mUIController.onShowCustomView(view, callback); + } + + @SuppressWarnings("deprecation") + @Override + public void onShowCustomView(View view, int requestedOrientation, + CustomViewCallback callback) { + mUIController.onShowCustomView(view, callback, requestedOrientation); + } +} diff --git a/app/src/main/java/acr/browser/lightning/view/LightningView.java b/app/src/main/java/acr/browser/lightning/view/LightningView.java index 2c9a9e1..ff80b8b 100644 --- a/app/src/main/java/acr/browser/lightning/view/LightningView.java +++ b/app/src/main/java/acr/browser/lightning/view/LightningView.java @@ -4,28 +4,21 @@ package acr.browser.lightning.view; -import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; -import android.content.ActivityNotFoundException; import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; -import android.net.MailTo; import android.net.Uri; -import android.net.http.SslError; import android.os.Build; +import android.os.Handler; import android.os.Message; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; -import android.text.InputType; -import android.text.method.PasswordTransformationMethod; +import android.support.v4.util.ArrayMap; import android.util.Log; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; @@ -34,58 +27,53 @@ import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewConfiguration; import android.webkit.CookieManager; -import android.webkit.GeolocationPermissions; -import android.webkit.HttpAuthHandler; -import android.webkit.SslErrorHandler; -import android.webkit.ValueCallback; -import android.webkit.WebChromeClient; -import android.webkit.WebResourceRequest; -import android.webkit.WebResourceResponse; import android.webkit.WebSettings; import android.webkit.WebSettings.LayoutAlgorithm; import android.webkit.WebSettings.PluginState; import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.EditText; -import android.widget.LinearLayout; -import java.io.ByteArrayInputStream; +import com.squareup.otto.Bus; + import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.IOException; -import java.net.URISyntaxException; +import java.lang.ref.WeakReference; +import java.util.Map; + +import javax.inject.Inject; import acr.browser.lightning.R; import acr.browser.lightning.app.BrowserApp; +import acr.browser.lightning.bus.BrowserEvents; +import acr.browser.lightning.constant.BookmarkPage; import acr.browser.lightning.constant.Constants; +import acr.browser.lightning.constant.HistoryPage; import acr.browser.lightning.constant.StartPage; -import acr.browser.lightning.controller.BrowserController; +import acr.browser.lightning.controller.UIController; +import acr.browser.lightning.dialog.LightningDialogBuilder; import acr.browser.lightning.download.LightningDownloadListener; import acr.browser.lightning.preference.PreferenceManager; -import acr.browser.lightning.utils.AdBlock; -import acr.browser.lightning.utils.IntentUtils; -import acr.browser.lightning.utils.PermissionsManager; +import acr.browser.lightning.utils.ProxyUtils; import acr.browser.lightning.utils.ThemeUtils; +import acr.browser.lightning.utils.UrlUtils; import acr.browser.lightning.utils.Utils; public class LightningView { - private final Title mTitle; + public static final String HEADER_REQUESTED_WITH = "X-Requested-With"; + public static final String HEADER_WAP_PROFILE = "X-Wap-Profile"; + public static final String HEADER_DNT = "DNT"; + + final LightningViewTitle mTitle; private WebView mWebView; - private final boolean mIsIncognitoTab; - private final BrowserController mBrowserController; + final boolean mIsIncognitoTab; + private final UIController mUIController; private final GestureDetector mGestureDetector; private final Activity mActivity; private static String mHomepage; private static String mDefaultUserAgent; - // TODO fix so that mWebpageBitmap can be static - static changes the icon when switching from light to dark and then back to light - private final Bitmap mWebpageBitmap; - private static PreferenceManager mPreferences; - private final AdBlock mAdBlock; - private final IntentUtils mIntentUtils; private final Paint mPaint = new Paint(); private boolean isForegroundTab; - private boolean mTextReflow = false; private boolean mInvertPage = false; private boolean mToggleDesktop = false; private static float mMaxFling; @@ -97,26 +85,29 @@ public class LightningView { 0, 0, -1.0f, 0, 255, // blue 0, 0, 0, 1.0f, 0 // alpha }; - private final PermissionsManager mPermissionsManager; - private static final String[] PERMISSIONS = new String[]{Manifest.permission.ACCESS_FINE_LOCATION}; + private final WebViewHandler mWebViewHandler = new WebViewHandler(this); + private final Map mRequestHeaders = new ArrayMap<>(); - @SuppressLint("NewApi") - public LightningView(Activity activity, String url, boolean darkTheme, boolean isIncognito, BrowserController controller) { + @Inject + Bus mEventBus; + @Inject + PreferenceManager mPreferences; + + @Inject + LightningDialogBuilder mBookmarksDialogBuilder; + + @SuppressLint("NewApi") + public LightningView(Activity activity, String url, boolean isIncognito) { + BrowserApp.getAppComponent().inject(this); mActivity = activity; + mUIController = (UIController) activity; mWebView = new WebView(activity); mIsIncognitoTab = isIncognito; - mTitle = new Title(activity, darkTheme); - mAdBlock = AdBlock.getInstance(activity.getApplicationContext()); - mPermissionsManager = PermissionsManager.getInstance(); - - mWebpageBitmap = mTitle.mDefaultIcon; + mTitle = new LightningViewTitle(activity, mUIController.getUseDarkTheme()); mMaxFling = ViewConfiguration.get(activity).getScaledMaximumFlingVelocity(); - mBrowserController = controller; - - mIntentUtils = new IntentUtils(mActivity); mWebView.setDrawingCacheBackgroundColor(Color.WHITE); mWebView.setFocusableInTouchMode(true); mWebView.setFocusable(true); @@ -133,8 +124,8 @@ public class LightningView { mWebView.setScrollbarFadingEnabled(true); mWebView.setSaveEnabled(true); mWebView.setNetworkAvailable(true); - mWebView.setWebChromeClient(new LightningChromeClient(activity)); - mWebView.setWebViewClient(new LightningWebClient(activity)); + mWebView.setWebChromeClient(new LightningChromeClient(activity, this)); + mWebView.setWebViewClient(new LightningWebClient(activity, this)); mWebView.setDownloadListener(new LightningDownloadListener(activity)); mGestureDetector = new GestureDetector(activity, new CustomGestureListener()); mWebView.setOnTouchListener(new TouchListener()); @@ -144,7 +135,7 @@ public class LightningView { if (url != null) { if (!url.trim().isEmpty()) { - mWebView.loadUrl(url); + mWebView.loadUrl(url, mRequestHeaders); } else { // don't load anything, the user is looking for a blank tab } @@ -158,14 +149,39 @@ public class LightningView { return; } if (mHomepage.startsWith("about:home")) { - mWebView.loadUrl(StartPage.getHomepage(mActivity)); + mWebView.loadUrl(StartPage.getHomepage(mActivity), mRequestHeaders); } else if (mHomepage.startsWith("about:bookmarks")) { - mBrowserController.openBookmarkPage(mWebView); + loadBookmarkpage(); } else { - mWebView.loadUrl(mHomepage); + mWebView.loadUrl(mHomepage, mRequestHeaders); } } + /** + * Load the HTML bookmarks page in this view + */ + public void loadBookmarkpage() { + if (mWebView == null) + return; + Bitmap folderIcon = ThemeUtils.getThemedBitmap(mActivity, R.drawable.ic_folder, false); + FileOutputStream outputStream = null; + File image = new File(mActivity.getCacheDir(), "folder.png"); + try { + outputStream = new FileOutputStream(image); + folderIcon.compress(Bitmap.CompressFormat.PNG, 100, outputStream); + folderIcon.recycle(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } finally { + Utils.close(outputStream); + } + File bookmarkWebPage = new File(mActivity.getFilesDir(), BookmarkPage.FILENAME); + + BrowserApp.getAppComponent().getBookmarkPage().buildBookmarkPage(null); + mWebView.loadUrl(Constants.FILE + bookmarkWebPage, mRequestHeaders); + + } + /** * Initialize the preference driven settings of the WebView * @@ -173,26 +189,34 @@ public class LightningView { * if you don't have a reference to them * @param context the context in which the WebView was created */ - @SuppressLint("NewApi") + @SuppressLint({"NewApi", "SetJavaScriptEnabled"}) public synchronized void initializePreferences(@Nullable WebSettings settings, Context context) { if (settings == null && mWebView == null) { return; } else if (settings == null) { settings = mWebView.getSettings(); } - mPreferences = PreferenceManager.getInstance(); + + if (mPreferences.getDoNotTrackEnabled()) { + mRequestHeaders.put(HEADER_DNT, "1"); + } else { + mRequestHeaders.remove(HEADER_DNT); + } + + if (mPreferences.getRemoveIdentifyingHeadersEnabled()) { + mRequestHeaders.put(HEADER_REQUESTED_WITH, ""); + mRequestHeaders.put(HEADER_WAP_PROFILE, ""); + } else { + mRequestHeaders.remove(HEADER_REQUESTED_WITH); + mRequestHeaders.remove(HEADER_WAP_PROFILE); + } settings.setDefaultTextEncodingName(mPreferences.getTextEncoding()); mHomepage = mPreferences.getHomepage(); - mAdBlock.updatePreference(); - setColorMode(mPreferences.getRenderingMode()); if (!mIsIncognitoTab) { settings.setGeolocationEnabled(mPreferences.getLocationEnabled()); - if (mPreferences.getLocationEnabled() && !PermissionsManager.checkPermissions(mActivity, PERMISSIONS)) { - mPermissionsManager.requestPermissionsIfNecessary(mActivity, PERMISSIONS); - } } else { settings.setGeolocationEnabled(false); } @@ -240,7 +264,6 @@ public class LightningView { } if (mPreferences.getTextReflowEnabled()) { - mTextReflow = true; settings.setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS); if (API >= android.os.Build.VERSION_CODES.KITKAT) { try { @@ -252,7 +275,6 @@ public class LightningView { } } } else { - mTextReflow = false; settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL); } @@ -383,6 +405,11 @@ public class LightningView { } } + @NonNull + protected Map getRequestHeaders() { + return mRequestHeaders; + } + public boolean isShown() { return mWebView != null && mWebView.isShown(); } @@ -406,7 +433,7 @@ public class LightningView { public void setForegroundTab(boolean isForeground) { isForegroundTab = isForeground; - mBrowserController.updateTabs(); + mEventBus.post(new BrowserEvents.TabsChanged()); } public boolean isForegroundTab() { @@ -509,7 +536,7 @@ public class LightningView { public synchronized void reload() { // Check if configured proxy is available - if (mBrowserController.proxyIsNotReady()) { + if (!ProxyUtils.getInstance().isProxyReady()) { // User has been notified return; } @@ -521,6 +548,7 @@ public class LightningView { /** * Naive caching of the favicon according to the domain name of the URL + * * @param icon the icon to cache */ private void cacheFavicon(final Bitmap icon) { @@ -552,7 +580,10 @@ public class LightningView { mWebView.setVisibility(View.GONE); mWebView.removeAllViews(); mWebView.destroyDrawingCache(); - // mWebView.destroy(); //this is causing the segfault + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + //this is causing the segfault occasionally below 4.2 + mWebView.destroy(); + } mWebView = null; } } @@ -563,7 +594,7 @@ public class LightningView { } } - public String getUserAgent() { + private String getUserAgent() { if (mWebView != null) { return mWebView.getSettings().getUserAgentString(); } else { @@ -577,6 +608,79 @@ public class LightningView { } } + public synchronized void findNext() { + if (mWebView != null) { + mWebView.findNext(true); + } + } + + public synchronized void findPrevious() { + if (mWebView != null) { + mWebView.findNext(false); + } + } + + public synchronized void clearFindMatches() { + if (mWebView != null) { + mWebView.clearMatches(); + } + } + + /** + * Used by {@link LightningWebClient} + * + * @return true if the page is in inverted mode, false otherwise + */ + public boolean getInvertePage() { + return mInvertPage; + } + + /** + * handles a long click on the page, parameter String url + * is the url that should have been obtained from the WebView touch node + * thingy, if it is null, this method tries to deal with it and find a workaround + */ + private void longClickPage(final String url) { + final WebView.HitTestResult result = mWebView.getHitTestResult(); + String currentUrl = mWebView.getUrl(); + if (currentUrl != null && UrlUtils.isSpecialUrl(currentUrl)) { + if (currentUrl.endsWith(HistoryPage.FILENAME)) { + if (url != null) { + mBookmarksDialogBuilder.showLongPressedHistoryLinkDialog(mActivity, url); + } else if (result != null && result.getExtra() != null) { + final String newUrl = result.getExtra(); + mBookmarksDialogBuilder.showLongPressedHistoryLinkDialog(mActivity, newUrl); + } + } else if (currentUrl.endsWith(BookmarkPage.FILENAME)) { + if (url != null) { + mBookmarksDialogBuilder.showLongPressedDialogForBookmarkUrl(mActivity, url); + } else if (result != null && result.getExtra() != null) { + final String newUrl = result.getExtra(); + mBookmarksDialogBuilder.showLongPressedDialogForBookmarkUrl(mActivity, newUrl); + } + } + } else { + if (url != null) { + if (result != null) { + if (result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE || result.getType() == WebView.HitTestResult.IMAGE_TYPE) { + mBookmarksDialogBuilder.showLongPressImageDialog(mActivity, url, getUserAgent()); + } else { + mBookmarksDialogBuilder.showLongPressLinkDialog(mActivity, url); + } + } else { + mBookmarksDialogBuilder.showLongPressLinkDialog(mActivity, url); + } + } else if (result != null && result.getExtra() != null) { + final String newUrl = result.getExtra(); + if (result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE || result.getType() == WebView.HitTestResult.IMAGE_TYPE) { + mBookmarksDialogBuilder.showLongPressImageDialog(mActivity, newUrl, getUserAgent()); + } else { + mBookmarksDialogBuilder.showLongPressLinkDialog(mActivity, newUrl); + } + } + } + } + public boolean canGoBack() { return mWebView != null && mWebView.canGoBack(); } @@ -586,7 +690,7 @@ public class LightningView { } @Nullable - public WebView getWebView() { + public synchronized WebView getWebView() { return mWebView; } @@ -596,13 +700,12 @@ public class LightningView { public synchronized void loadUrl(String url) { // Check if configured proxy is available - if (mBrowserController.proxyIsNotReady()) { - // User has been notified + if (!ProxyUtils.getInstance().isProxyReady()) { return; } if (mWebView != null) { - mWebView.loadUrl(url); + mWebView.loadUrl(url, mRequestHeaders); } } @@ -619,470 +722,6 @@ public class LightningView { } } - private static class IconCacheTask implements Runnable { - private final Uri uri; - private final Bitmap icon; - - public IconCacheTask(Uri uri, Bitmap icon) { - this.uri = uri; - this.icon = icon; - } - - @Override - public void run() { - String hash = String.valueOf(uri.getHost().hashCode()); - Log.d(Constants.TAG, "Caching icon for " + uri.getHost()); - FileOutputStream fos = null; - try { - File image = new File(BrowserApp.getAppContext().getCacheDir(), hash + ".png"); - fos = new FileOutputStream(image); - icon.compress(Bitmap.CompressFormat.PNG, 100, fos); - fos.flush(); - } catch (IOException e) { - e.printStackTrace(); - } finally { - Utils.close(fos); - } - } - } - - public class LightningWebClient extends WebViewClient { - - final Activity mActivity; - - LightningWebClient(Activity activity) { - mActivity = activity; - } - - @Override - public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (mAdBlock.isAd(request.getUrl().toString())) { - ByteArrayInputStream EMPTY = new ByteArrayInputStream("".getBytes()); - return new WebResourceResponse("text/plain", "utf-8", EMPTY); - } - } - return super.shouldInterceptRequest(view, request); - } - - @SuppressWarnings("deprecation") - @Override - public WebResourceResponse shouldInterceptRequest(WebView view, String url) { - if (mAdBlock.isAd(url)) { - ByteArrayInputStream EMPTY = new ByteArrayInputStream("".getBytes()); - return new WebResourceResponse("text/plain", "utf-8", EMPTY); - } - return null; - } - - @SuppressLint("NewApi") - @Override - public void onPageFinished(WebView view, String url) { - if (view.isShown()) { - mBrowserController.updateUrl(url, true); - view.postInvalidate(); - } - if (view.getTitle() == null || view.getTitle().isEmpty()) { - mTitle.setTitle(mActivity.getString(R.string.untitled)); - } else { - mTitle.setTitle(view.getTitle()); - } - if (API >= android.os.Build.VERSION_CODES.KITKAT && mInvertPage) { - view.evaluateJavascript(Constants.JAVASCRIPT_INVERT_PAGE, null); - } - mBrowserController.updateTabs(); - } - - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - if (isShown()) { - mBrowserController.updateUrl(url, false); - mBrowserController.showActionBar(); - } - mTitle.setFavicon(mWebpageBitmap); - mBrowserController.updateTabs(); - } - - @Override - public void onReceivedHttpAuthRequest(final WebView view, @NonNull final HttpAuthHandler handler, - final String host, final String realm) { - - AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); - final EditText name = new EditText(mActivity); - final EditText password = new EditText(mActivity); - LinearLayout passLayout = new LinearLayout(mActivity); - passLayout.setOrientation(LinearLayout.VERTICAL); - - passLayout.addView(name); - passLayout.addView(password); - - name.setHint(mActivity.getString(R.string.hint_username)); - name.setSingleLine(); - password.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD); - password.setSingleLine(); - password.setTransformationMethod(new PasswordTransformationMethod()); - password.setHint(mActivity.getString(R.string.hint_password)); - builder.setTitle(mActivity.getString(R.string.title_sign_in)); - builder.setView(passLayout); - builder.setCancelable(true) - .setPositiveButton(mActivity.getString(R.string.title_sign_in), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - String user = name.getText().toString(); - String pass = password.getText().toString(); - handler.proceed(user.trim(), pass.trim()); - Log.d(Constants.TAG, "Request Login"); - - } - }) - .setNegativeButton(mActivity.getString(R.string.action_cancel), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - handler.cancel(); - - } - }); - AlertDialog alert = builder.create(); - alert.show(); - - } - - private boolean mIsRunning = false; - private float mZoomScale = 0.0f; - - @SuppressLint("NewApi") - @Override - public void onScaleChanged(final WebView view, final float oldScale, final float newScale) { - if (view.isShown() && mTextReflow && API >= android.os.Build.VERSION_CODES.KITKAT) { - if (mIsRunning) - return; - if (Math.abs(mZoomScale - newScale) > 0.01f) { - mIsRunning = view.postDelayed(new Runnable() { - - @Override - public void run() { - mZoomScale = newScale; - view.evaluateJavascript(Constants.JAVASCRIPT_TEXT_REFLOW, null); - mIsRunning = false; - } - - }, 100); - } - - } - } - - @Override - public void onReceivedSslError(WebView view, @NonNull final SslErrorHandler handler, SslError error) { - AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); - builder.setTitle(mActivity.getString(R.string.title_warning)); - builder.setMessage(mActivity.getString(R.string.message_untrusted_certificate)) - .setCancelable(true) - .setPositiveButton(mActivity.getString(R.string.action_yes), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - handler.proceed(); - } - }) - .setNegativeButton(mActivity.getString(R.string.action_no), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - handler.cancel(); - } - }); - AlertDialog alert = builder.create(); - if (error.getPrimaryError() == SslError.SSL_UNTRUSTED) { - alert.show(); - } else { - handler.proceed(); - } - - } - - @Override - public void onFormResubmission(WebView view, @NonNull final Message dontResend, final Message resend) { - AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); - builder.setTitle(mActivity.getString(R.string.title_form_resubmission)); - builder.setMessage(mActivity.getString(R.string.message_form_resubmission)) - .setCancelable(true) - .setPositiveButton(mActivity.getString(R.string.action_yes), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - - resend.sendToTarget(); - } - }) - .setNegativeButton(mActivity.getString(R.string.action_no), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - - dontResend.sendToTarget(); - } - }); - AlertDialog alert = builder.create(); - alert.show(); - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - // Check if configured proxy is available - if (mBrowserController.proxyIsNotReady()) { - // User has been notified - return true; - } - - if (mIsIncognitoTab) { - return super.shouldOverrideUrlLoading(view, url); - } - if (url.startsWith("about:")) { - return super.shouldOverrideUrlLoading(view, url); - } - if (url.contains("mailto:")) { - MailTo mailTo = MailTo.parse(url); - Intent i = Utils.newEmailIntent(mailTo.getTo(), mailTo.getSubject(), - mailTo.getBody(), mailTo.getCc()); - mActivity.startActivity(i); - view.reload(); - return true; - } else if (url.startsWith("intent://")) { - Intent intent; - try { - intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); - } catch (URISyntaxException ex) { - return false; - } - if (intent != null) { - try { - mActivity.startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.e(Constants.TAG, "ActivityNotFoundException"); - } - return true; - } - } - return mIntentUtils.startActivityForUrl(mWebView, url); - } - } - - public class LightningChromeClient extends WebChromeClient { - - final Activity mActivity; - - LightningChromeClient(Activity activity) { - mActivity = activity; - } - - @Override - public void onProgressChanged(WebView view, int newProgress) { - if (isShown()) { - mBrowserController.updateProgress(newProgress); - } - } - - @Override - public void onReceivedIcon(WebView view, Bitmap icon) { - if (icon == null) - return; - mTitle.setFavicon(icon); - mBrowserController.updateTabs(); - cacheFavicon(icon); - } - - @Override - public void onReceivedTitle(WebView view, String title) { - if (title != null && !title.isEmpty()) { - mTitle.setTitle(title); - } else { - mTitle.setTitle(mActivity.getString(R.string.untitled)); - } - mBrowserController.updateTabs(); - if (view != null) - mBrowserController.updateHistory(title, view.getUrl()); - } - - @Override - public void onGeolocationPermissionsShowPrompt(final String origin, - final GeolocationPermissions.Callback callback) { - mPermissionsManager.requestPermissionsIfNecessary(mActivity, PERMISSIONS); - final boolean remember = true; - AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); - builder.setTitle(mActivity.getString(R.string.location)); - String org; - if (origin.length() > 50) { - org = origin.subSequence(0, 50) + "..."; - } else { - org = origin; - } - builder.setMessage(org + mActivity.getString(R.string.message_location)) - .setCancelable(true) - .setPositiveButton(mActivity.getString(R.string.action_allow), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - callback.invoke(origin, true, remember); - } - }) - .setNegativeButton(mActivity.getString(R.string.action_dont_allow), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int id) { - callback.invoke(origin, false, remember); - } - }); - AlertDialog alert = builder.create(); - alert.show(); - - } - - @Override - public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, - Message resultMsg) { - mBrowserController.onCreateWindow(resultMsg); - return true; - } - - @Override - public void onCloseWindow(WebView window) { - mBrowserController.onCloseWindow(LightningView.this); - } - - @SuppressWarnings("unused") - public void openFileChooser(ValueCallback uploadMsg) { - mBrowserController.openFileChooser(uploadMsg); - } - - @SuppressWarnings("unused") - public void openFileChooser(ValueCallback uploadMsg, String acceptType) { - mBrowserController.openFileChooser(uploadMsg); - } - - @SuppressWarnings("unused") - public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) { - mBrowserController.openFileChooser(uploadMsg); - } - - public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, - WebChromeClient.FileChooserParams fileChooserParams) { - mBrowserController.showFileChooser(filePathCallback); - return true; - } - - @Override - public Bitmap getDefaultVideoPoster() { - return mBrowserController.getDefaultVideoPoster(); - } - - @Override - public View getVideoLoadingProgressView() { - return mBrowserController.getVideoLoadingProgressView(); - } - - @Override - public void onHideCustomView() { - mBrowserController.onHideCustomView(); - super.onHideCustomView(); - } - - @Override - public void onShowCustomView(View view, CustomViewCallback callback) { - // While these lines might look like they work, in practice, - // Full-screen videos won't work correctly. I may test this out some - // more - // if (view instanceof FrameLayout) { - // FrameLayout frame = (FrameLayout) view; - // if (frame.getFocusedChild() instanceof VideoView) { - // VideoView video = (VideoView) frame.getFocusedChild(); - // video.stopPlayback(); - // frame.removeView(video); - // video.setVisibility(View.GONE); - // } - // } else { - mBrowserController.onShowCustomView(view, callback); - - // } - - super.onShowCustomView(view, callback); - } - - @SuppressWarnings("deprecation") - @Override - @Deprecated - public void onShowCustomView(View view, int requestedOrientation, - CustomViewCallback callback) { - // While these lines might look like they work, in practice, - // Full-screen videos won't work correctly. I may test this out some - // more - // if (view instanceof FrameLayout) { - // FrameLayout frame = (FrameLayout) view; - // if (frame.getFocusedChild() instanceof VideoView) { - // VideoView video = (VideoView) frame.getFocusedChild(); - // video.stopPlayback(); - // frame.removeView(video); - // video.setVisibility(View.GONE); - // } - // } else { - mBrowserController.onShowCustomView(view, callback); - - // } - - super.onShowCustomView(view, requestedOrientation, callback); - } - } - - public class Title { - - private Bitmap mFavicon; - private String mTitle; - private final Bitmap mDefaultIcon; - - public Title(Context context, boolean darkTheme) { - mDefaultIcon = ThemeUtils.getThemedBitmap(context, R.drawable.ic_webpage, darkTheme); - mFavicon = mDefaultIcon; - mTitle = mActivity.getString(R.string.action_new_tab); - } - - public void setFavicon(Bitmap favicon) { - if (favicon == null) { - mFavicon = mDefaultIcon; - } else { - mFavicon = Utils.padFavicon(favicon); - } - } - - public void setTitle(String title) { - if (title == null) { - mTitle = ""; - } else { - mTitle = title; - } - } - - public void setTitleAndFavicon(String title, Bitmap favicon) { - mTitle = title; - - if (favicon == null) { - mFavicon = mDefaultIcon; - } else { - mFavicon = Utils.padFavicon(favicon); - } - } - - public String getTitle() { - return mTitle; - } - - public Bitmap getFavicon() { - return mFavicon; - } - - } - private class TouchListener implements OnTouchListener { float mLocation; @@ -1105,9 +744,9 @@ public class LightningView { } else if (mAction == MotionEvent.ACTION_UP) { final float distance = (mY - mLocation); if (distance > SCROLL_UP_THRESHOLD && view.getScrollY() < SCROLL_UP_THRESHOLD) { - mBrowserController.showActionBar(); + mUIController.showActionBar(); } else if (distance < -SCROLL_UP_THRESHOLD) { - mBrowserController.hideActionBar(); + mUIController.hideActionBar(); } mLocation = 0; } @@ -1122,9 +761,9 @@ public class LightningView { public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { int power = (int) (velocityY * 100 / mMaxFling); if (power < -10) { - mBrowserController.hideActionBar(); + mUIController.hideActionBar(); } else if (power > 15) { - mBrowserController.showActionBar(); + mUIController.showActionBar(); } return super.onFling(e1, e2, velocityX, velocityY); } @@ -1140,8 +779,13 @@ public class LightningView { @Override public void onLongPress(MotionEvent e) { - if (mCanTriggerLongPress) - mBrowserController.onLongPress(); + if (mCanTriggerLongPress) { + Message msg = mWebViewHandler.obtainMessage(); + if (msg != null) { + msg.setTarget(mWebViewHandler); + mWebView.requestFocusNodeHref(msg); + } + } } /** @@ -1163,4 +807,23 @@ public class LightningView { mCanTriggerLongPress = true; } } + + private static class WebViewHandler extends Handler { + + private final WeakReference mReference; + + public WebViewHandler(LightningView view) { + mReference = new WeakReference<>(view); + } + + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + final String url = msg.getData().getString("url"); + LightningView view = mReference.get(); + if (view != null) { + view.longClickPage(url); + } + } + } } diff --git a/app/src/main/java/acr/browser/lightning/view/LightningViewTitle.java b/app/src/main/java/acr/browser/lightning/view/LightningViewTitle.java new file mode 100644 index 0000000..f20dc86 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/view/LightningViewTitle.java @@ -0,0 +1,66 @@ +package acr.browser.lightning.view; + +import android.content.Context; +import android.graphics.Bitmap; + +import acr.browser.lightning.R; +import acr.browser.lightning.utils.ThemeUtils; +import acr.browser.lightning.utils.Utils; + +/** + * @author Stefano Pacifici base on Anthony C. Restaino's code + * @date 2015/09/21 + */ +class LightningViewTitle { + + private static Bitmap DEFAULT_ICON = null; + + private Bitmap mFavicon; + private String mTitle; + + public LightningViewTitle(Context context, boolean darkTheme) { + if (DEFAULT_ICON == null) { + DEFAULT_ICON = ThemeUtils.getThemedBitmap(context, R.drawable.ic_webpage, darkTheme); + } + mFavicon = DEFAULT_ICON; + mTitle = context.getString(R.string.action_new_tab); + } + + public void setFavicon(Bitmap favicon) { + if (favicon == null) { + mFavicon = DEFAULT_ICON; + } else { + mFavicon = Utils.padFavicon(favicon); + } + } + + public void setTitle(String title) { + if (title == null) { + mTitle = ""; + } else { + mTitle = title; + } + } + + public void setTitleAndFavicon(String title, Bitmap favicon) { + mTitle = title; + + if (favicon == null) { + mFavicon = DEFAULT_ICON; + } else { + mFavicon = Utils.padFavicon(favicon); + } + } + + public String getTitle() { + return mTitle; + } + + public Bitmap getFavicon() { + return mFavicon; + } + + public static Bitmap getDefaultIcon() { + return DEFAULT_ICON; + } +} diff --git a/app/src/main/java/acr/browser/lightning/view/LightningWebClient.java b/app/src/main/java/acr/browser/lightning/view/LightningWebClient.java new file mode 100644 index 0000000..5e48f16 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/view/LightningWebClient.java @@ -0,0 +1,319 @@ +package acr.browser.lightning.view; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.MailTo; +import android.net.http.SslError; +import android.os.Build; +import android.os.Message; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.text.InputType; +import android.text.method.PasswordTransformationMethod; +import android.util.Log; +import android.webkit.HttpAuthHandler; +import android.webkit.SslErrorHandler; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.EditText; +import android.widget.LinearLayout; + +import com.squareup.otto.Bus; + +import java.io.ByteArrayInputStream; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import acr.browser.lightning.R; +import acr.browser.lightning.app.BrowserApp; +import acr.browser.lightning.bus.BrowserEvents; +import acr.browser.lightning.constant.Constants; +import acr.browser.lightning.controller.UIController; +import acr.browser.lightning.utils.AdBlock; +import acr.browser.lightning.utils.IntentUtils; +import acr.browser.lightning.utils.ProxyUtils; +import acr.browser.lightning.utils.Utils; + +class LightningWebClient extends WebViewClient { + + + private final Activity mActivity; + private final LightningView mLightningView; + private final UIController mUIController; + private final AdBlock mAdBlock; + private final Bus mEventBus; + private final IntentUtils mIntentUtils; + + LightningWebClient(Activity activity, LightningView lightningView) { + mActivity = activity; + mUIController = (UIController) activity; + mLightningView = lightningView; + mAdBlock = AdBlock.getInstance(activity); + mAdBlock.updatePreference(); + mEventBus = BrowserApp.getAppComponent().getBus(); + mIntentUtils = new IntentUtils(activity); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + if (mAdBlock.isAd(request.getUrl().toString())) { + ByteArrayInputStream EMPTY = new ByteArrayInputStream("".getBytes()); + return new WebResourceResponse("text/plain", "utf-8", EMPTY); + } + return super.shouldInterceptRequest(view, request); + } + + @SuppressWarnings("deprecation") + @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, String url) { + if (mAdBlock.isAd(url)) { + ByteArrayInputStream EMPTY = new ByteArrayInputStream("".getBytes()); + return new WebResourceResponse("text/plain", "utf-8", EMPTY); + } + return null; + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + @Override + public void onPageFinished(WebView view, String url) { + if (view.isShown()) { + mUIController.updateUrl(url, true); + view.postInvalidate(); + } + if (view.getTitle() == null || view.getTitle().isEmpty()) { + mLightningView.mTitle.setTitle(mActivity.getString(R.string.untitled)); + } else { + mLightningView.mTitle.setTitle(view.getTitle()); + } + if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && + mLightningView.getInvertePage()) { + view.evaluateJavascript(Constants.JAVASCRIPT_INVERT_PAGE, null); + } + mEventBus.post(new BrowserEvents.TabsChanged()); + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + mLightningView.mTitle.setFavicon(null); + if (mLightningView.isShown()) { + mUIController.updateUrl(url, false); + mUIController.showActionBar(); + } + mEventBus.post(new BrowserEvents.TabsChanged()); + } + + @Override + public void onReceivedHttpAuthRequest(final WebView view, @NonNull final HttpAuthHandler handler, + final String host, final String realm) { + + AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); + final EditText name = new EditText(mActivity); + final EditText password = new EditText(mActivity); + LinearLayout passLayout = new LinearLayout(mActivity); + passLayout.setOrientation(LinearLayout.VERTICAL); + + passLayout.addView(name); + passLayout.addView(password); + + name.setHint(mActivity.getString(R.string.hint_username)); + name.setSingleLine(); + password.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD); + password.setSingleLine(); + password.setTransformationMethod(new PasswordTransformationMethod()); + password.setHint(mActivity.getString(R.string.hint_password)); + builder.setTitle(mActivity.getString(R.string.title_sign_in)); + builder.setView(passLayout); + builder.setCancelable(true) + .setPositiveButton(mActivity.getString(R.string.title_sign_in), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + String user = name.getText().toString(); + String pass = password.getText().toString(); + handler.proceed(user.trim(), pass.trim()); + Log.d(Constants.TAG, "Request Login"); + + } + }) + .setNegativeButton(mActivity.getString(R.string.action_cancel), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + handler.cancel(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + + } + + private boolean mIsRunning = false; + private float mZoomScale = 0.0f; + + @TargetApi(Build.VERSION_CODES.KITKAT) + @Override + public void onScaleChanged(final WebView view, final float oldScale, final float newScale) { + if (view.isShown() && mLightningView.mPreferences.getTextReflowEnabled() && + Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + if (mIsRunning) + return; + if (Math.abs(mZoomScale - newScale) > 0.01f) { + mIsRunning = view.postDelayed(new Runnable() { + + @Override + public void run() { + mZoomScale = newScale; + view.evaluateJavascript(Constants.JAVASCRIPT_TEXT_REFLOW, null); + mIsRunning = false; + } + + }, 100); + } + + } + } + + private static List getAllSslErrorMessageCodes(SslError error) { + List errorCodeMessageCodes = new ArrayList<>(); + + if (error.hasError(SslError.SSL_DATE_INVALID)) { + errorCodeMessageCodes.add(R.string.message_certificate_date_invalid); + } + if (error.hasError(SslError.SSL_EXPIRED)) { + errorCodeMessageCodes.add(R.string.message_certificate_expired); + } + if (error.hasError(SslError.SSL_IDMISMATCH)) { + errorCodeMessageCodes.add(R.string.message_certificate_domain_mismatch); + } + if (error.hasError(SslError.SSL_NOTYETVALID)) { + errorCodeMessageCodes.add(R.string.message_certificate_not_yet_valid); + } + if (error.hasError(SslError.SSL_UNTRUSTED)) { + errorCodeMessageCodes.add(R.string.message_certificate_untrusted); + } + if (error.hasError(SslError.SSL_INVALID)) { + errorCodeMessageCodes.add(R.string.message_certificate_invalid); + } + + return errorCodeMessageCodes; + } + + @Override + public void onReceivedSslError(WebView view, @NonNull final SslErrorHandler handler, SslError error) { + List errorCodeMessageCodes = getAllSslErrorMessageCodes(error); + + StringBuilder stringBuilder = new StringBuilder(); + for (Integer messageCode : errorCodeMessageCodes) { + stringBuilder.append(" - ").append(mActivity.getString(messageCode)).append('\n'); + } + String alertMessage = + mActivity.getString(R.string.message_insecure_connection, stringBuilder.toString()); + + AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); + builder.setTitle(mActivity.getString(R.string.title_warning)); + builder.setMessage(alertMessage) + .setCancelable(true) + .setPositiveButton(mActivity.getString(R.string.action_yes), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + handler.proceed(); + } + }) + .setNegativeButton(mActivity.getString(R.string.action_no), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + handler.cancel(); + } + }); + builder.create().show(); + } + + @Override + public void onFormResubmission(WebView view, @NonNull final Message dontResend, final Message resend) { + AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); + builder.setTitle(mActivity.getString(R.string.title_form_resubmission)); + builder.setMessage(mActivity.getString(R.string.message_form_resubmission)) + .setCancelable(true) + .setPositiveButton(mActivity.getString(R.string.action_yes), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + resend.sendToTarget(); + } + }) + .setNegativeButton(mActivity.getString(R.string.action_no), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + dontResend.sendToTarget(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + // Check if configured proxy is available + if (!ProxyUtils.getInstance().isProxyReady()) { + // User has been notified + return true; + } + + Map headers = mLightningView.getRequestHeaders(); + + if (mLightningView.mIsIncognitoTab) { + view.loadUrl(url, headers); + return true; + } + if (url.startsWith("about:")) { + view.loadUrl(url, headers); + return true; + } + if (url.startsWith("mailto:")) { + MailTo mailTo = MailTo.parse(url); + Intent i = Utils.newEmailIntent(mailTo.getTo(), mailTo.getSubject(), + mailTo.getBody(), mailTo.getCc()); + mActivity.startActivity(i); + view.reload(); + return true; + } else if (url.startsWith("intent://")) { + Intent intent; + try { + intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); + } catch (URISyntaxException ignored) { + intent = null; + } + if (intent != null) { + intent.addCategory(Intent.CATEGORY_BROWSABLE); + intent.setComponent(null); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { + intent.setSelector(null); + } + try { + mActivity.startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.e(Constants.TAG, "ActivityNotFoundException"); + } + return true; + } + } + + if (!mIntentUtils.startActivityForUrl(view, url)) { + view.loadUrl(url, headers); + } + return true; + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 31777df..eae84d4 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -25,7 +25,14 @@ - + - - - + android:layout_height="match_parent" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/tab_drawer.xml b/app/src/main/res/layout/tab_drawer.xml index 3bbacc7..bbf334e 100644 --- a/app/src/main/res/layout/tab_drawer.xml +++ b/app/src/main/res/layout/tab_drawer.xml @@ -1,12 +1,9 @@ + android:dividerHeight="0dp" + android:overScrollMode="ifContentScrolls" /> + android:weightSum="4"> + + + + + + + diff --git a/app/src/main/res/layout/toolbar.xml b/app/src/main/res/layout/toolbar.xml index 9b144f3..cc0fbcb 100644 --- a/app/src/main/res/layout/toolbar.xml +++ b/app/src/main/res/layout/toolbar.xml @@ -8,13 +8,10 @@ android:elevation="2dp" android:orientation="vertical"> - + android:id="@+id/tabs_toolbar_container"/> Open Source-Lizenzen Suche nach Werbung blockieren - Das Zertifikat dieser Webseite ist nicht vertrauenswürdig. Trotzdem fortsetzen? Formularneuzustellung Daten erneut senden? \nMeine Position verwenden? @@ -184,4 +183,36 @@ URL Titel + Ordner + Umbenennen + Schwarzes Design (AMOLED) + Webinhalte leeren + Webinhalte beim Beenden leeren + Dunkles Design + Was möchten Sie mit diesem Ordner tun? + Was möchten Sie mit diesem Verlaufs-Eintrag tun? + Ordnername + Farben invertieren + Helles Design + Manueller Proxy + Die Verbindung zu dieser Seite ist nicht sicher:\n%1$s\nTrotzdem fortfahren? + Webinhalte geleert + Port: + Tabs + App-Design + Ordner umbenennen + HTTP-Proxy + I2P läuft nicht. + I2P Tunnel sind noch nicht bereit. + Zertifikats-Datum ungültig + Domain in Zertifikat stimmt nicht mit aktueller Domain überein + Zertifikat abgelaufen + Zertifikat ungültig + Zertifikat noch nicht gültig + Zertifikat wird nicht vertraut + Ungültige Adresse gefunden. Kann nicht herunterladen + Kann nicht an den eingestellten Ort herunterladen + Es sieht aus, als ob I2P installiert wäre. Möchten Sie es verwenden? + Textcodierung + Tabs in Drawer anzeigen diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b6c61a0..1503551 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -106,7 +106,6 @@ Licencias de código abierto (open source) Buscar Bloquear anuncios - El certificado del sitio no es de fiar. ¿Continuar de todas maneras? Resubir el formulario ¿Quieres mandar los datos de nuevo? \n¿Quieres usar tu geolocalización? diff --git a/app/src/main/res/values-gr/strings.xml b/app/src/main/res/values-gr/strings.xml index 55ab54d..fc5a410 100644 --- a/app/src/main/res/values-gr/strings.xml +++ b/app/src/main/res/values-gr/strings.xml @@ -107,7 +107,6 @@ Άδειες ανοιχτού κώδικα Αναζήτηση για Μπλοκάρισμα διαφημίσεων - Το πιστοποιητικό δεν είναι αξιόπιστο. Συνέχεια παρ\'όλα αυτά; Επαναυποβολή φόρμας Θα θέλατε να ξαναστείλετε τα στοιχεία; \nΘα θέλατε να χρησιμοποιήσετε την τοποθεσία σας; diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 5c03fb8..39d13db 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -123,7 +123,6 @@ Nyílt forráskódú licencek Keresés Reklámok blokkolása - Az oldal tanusítványa nem megfelelő.Folytatja mindenképpen? Űrlap újraküldése Szeretnél újból elküldi az adatokat? \nSzeretné használni a saját helyét? diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index a547cea..545b736 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1,10 +1,10 @@ - + Муња Нови језичак @@ -56,6 +59,7 @@ Отвори Шта желите да урадите са овом везом? Подели ову страницу + Шта желите да урадите са овом ставком историјата? Шта желите да урадите са овим обележивачем? Обриши Празна страница @@ -96,7 +100,9 @@ Назад Нађи на страници Покрећем преузимање\u2026 - Преузимање је могуће само са „http“ или „https“ адреса. + Могу да преузмем само са „http“ или „https“ адреса. + Неисправан УРЛ, не могу да преузмем + Не могу да преузмем у наведену локацију Нема СД картице УСБ складиште је потребно за преузимање фајла. УСБ складиште није доступно @@ -120,7 +126,13 @@ Лиценце отвореног кôда Тражи Блокирај рекламе - Сертификат овог сајта није поуздан. Да наставим свеједно? + Веза са овим сајтом није безбедна:\n%1$s\nДа наставим свеједно? + датум сертификата је неважећи + сертификат је истекао + домен на сертификату се не поклапа са доменом сајта + сертификат је неважећи + сертификат још није важећи + сертификат није поуздан Поновно слање формулара Желите ли да поново пошаљете податке? \nЖелите ли да користите вашу локацију @@ -131,8 +143,21 @@ Лозинка Предлози претраге Погоњено Гуглом + ХТТП прокси + + Ништа + Орбот + I2P + Ручно + + Ручне поставке проксија + Домаћин: + Порт: Изгледа да имате Орбот инсталиран. Желите ли да користите Тор? + Изгледа да имате I2P инсталиран. Желите ли да користите I2P? Инсталирајте Орбот да бисте користили Тор. + I2P није покренут. + I2P тунели још нису спремни. Да Не Очисти колачиће по затварању @@ -169,13 +194,14 @@ Затвори све језичке Блокирај колачиће треће стране Режим боје - Режим исцртавања + Режим за читање Учитавам… Нисам могао ништа да учитам са странице. Snacktory jsoup: Јава ХТМЛ рашчлањивач МИТ лиценца Садржај УРЛ бокса + Кодирање текста Домен (подраз.) УРЛ @@ -183,5 +209,19 @@ Обрнута боја Језичци - Тамна тема + Тема + Светла + Тамна + Црна (АМОЛЕД) + Назив фасцикле + Фасцикла + Преименуј + Преименуј фасциклу + Шта желите да урадите са овом фасциклом? + Очисти веб складиште + Очисти веб складиште по затварању + Веб складиште је очишћено + Извор hosts фајла за блокирање реклама + Поставке Адблока + Језичци у фиоци навигације diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 311cd92..c39a470 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -107,7 +107,6 @@ Open Source Licenses Ara Reklamları Engelle - Sitenin sertifikası güvenilir değil. Yine de devam edilsin mi? Formu yeniden gönder Veriyi yeniden göndermek istiyor musun? \nKonum bilgisi isteniyor diff --git a/app/src/main/res/values-v16/styles.xml b/app/src/main/res/values-v16/styles.xml new file mode 100644 index 0000000..e5a495e --- /dev/null +++ b/app/src/main/res/values-v16/styles.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 9947540..e35777b 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -107,7 +107,6 @@ 开源许可 搜索目标 拦截广告 - 该网站的证书不被信任。是否仍要继续? 重新提交表单 你想重新发送数据吗? \n你想使用你的位置吗? diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9183d6c..a8ec00f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -126,7 +126,13 @@ Open Source Licenses Search for Block Ads - The certificate of the site is not trusted. Proceed anyway? + Connection to this site is not secure:\n%1$s\nProceed anyway? + date of certificate is invalid + certificate is expired + domain in certificate does not match the site domain + certificate is invalid + certificate is not yet valid + certificate is not trusted Form Resubmission Would you like to resend the data? \nWould like to use your location @@ -218,4 +224,6 @@ Hosts File Ad Blocking Source Ad Block Settings Show tabs in Navigation Drawer + Request \'Do Not Track\' + Remove Identifying Headers diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 53bd99c..a4f614a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -123,7 +123,6 @@