/* * Copyright 2015 Anthony Restaino */ package acr.browser.lightning.activity; import android.animation.ArgbEvaluator; import android.animation.LayoutTransition; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.app.Activity; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.database.sqlite.SQLiteException; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.media.MediaPlayer; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.provider.MediaStore; 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.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.Toolbar; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.View.OnKeyListener; import android.view.View.OnLongClickListener; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.Window; import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.DecelerateInterpolator; import android.view.animation.Transformation; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.webkit.ValueCallback; import android.webkit.WebChromeClient.CustomViewCallback; import android.webkit.WebIconDatabase; import android.webkit.WebView; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; import android.widget.VideoView; import com.squareup.otto.Bus; import com.squareup.otto.Subscribe; import java.io.File; import java.io.IOException; 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.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.database.BookmarkManager; import acr.browser.lightning.database.HistoryDatabase; 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.receiver.NetworkReceiver; 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; import acr.browser.lightning.utils.WebUtils; import acr.browser.lightning.view.AnimatedProgressBar; import acr.browser.lightning.view.LightningView; import butterknife.Bind; import butterknife.ButterKnife; public abstract class BrowserActivity extends ThemableBrowserActivity implements BrowserController, OnClickListener, OnLongClickListener { // Static Layout @Bind(R.id.drawer_layout) DrawerLayout mDrawerLayout; @Bind(R.id.content_frame) FrameLayout mBrowserFrame; @Bind(R.id.left_drawer) ViewGroup mDrawerLeft; @Bind(R.id.right_drawer) ViewGroup mDrawerRight; @Bind(R.id.ui_layout) ViewGroup mUiLayout; @Bind(R.id.toolbar_layout) ViewGroup mToolbarLayout; @Bind(R.id.progress_view) AnimatedProgressBar mProgressBar; @Bind(R.id.search_bar) RelativeLayout mSearchBar; // Toolbar Views private AutoCompleteTextView mSearch; private ImageView mArrowImage; // Full Screen Video Views private FrameLayout mFullscreenContainer; private VideoView mVideoView; private View mCustomView; // Adapter private SearchAdapter mSearchAdapter; // Callback private CustomViewCallback mCustomViewCallback; private ValueCallback mUploadMessage; private ValueCallback mFilePathCallback; // Primatives private boolean mFullScreen, mColorMode, mDarkTheme, mIsNewIntent = false, mIsFullScreen = false, mIsImmersive = false, mShowTabsInDrawer; private int mOriginalOrientation, mBackgroundColor, mIdGenerator, mIconColor, mCurrentUiColor = Color.BLACK; private String mSearchText, mUntitledTitle, mHomepage, mCameraPhotoPath; // The singleton BookmarkManager @Inject BookmarkManager mBookmarkManager; // Event bus @Inject Bus mEventBus; @Inject BookmarkPage mBookmarkPage; @Inject LightningDialogBuilder bookmarksDialogBuilder; @Inject TabsManager tabsManager; // Preference manager was moved on ThemeableBrowserActivity @Inject HistoryDatabase mHistoryDatabase; // Image private Bitmap mWebpageBitmap; private final ColorDrawable mBackground = new ColorDrawable(); private Drawable mDeleteIcon, mRefreshIcon, mClearIcon, mIcon; private DrawerArrowDrawable mArrowDrawable; // Proxy private ProxyUtils mProxyUtils; // Constant private static final int API = android.os.Build.VERSION.SDK_INT; private static final String NETWORK_BROADCAST_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; private static final LayoutParams MATCH_PARENT = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); private static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); public abstract boolean isIncognito(); // abstract void initializeTabs(); abstract void closeActivity(); public abstract void updateHistory(final String title, final String url); abstract void updateCookiePreference(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); BrowserApp.getAppComponent().inject(this); setContentView(R.layout.activity_main); ButterKnife.bind(this); initialize(); } private synchronized void initialize() { Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); ActionBar actionBar = getSupportActionBar(); //TODO make sure dark theme flag gets set correctly mDarkTheme = mPreferences.getUseTheme() != 0 || isIncognito(); mIconColor = mDarkTheme ? ThemeUtils.getIconDarkThemeColor(this) : ThemeUtils.getIconLightThemeColor(this); mShowTabsInDrawer = mPreferences.getShowTabsInDrawer(!isTablet()); // initialize background ColorDrawable mBackground.setColor(((ColorDrawable) mToolbarLayout.getBackground()).getColor()); // Drawer stutters otherwise mDrawerLeft.setLayerType(View.LAYER_TYPE_HARDWARE, null); mDrawerRight.setLayerType(View.LAYER_TYPE_HARDWARE, null); // TODO Please review this // 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); } setNavigationDrawerWidth(); mDrawerLayout.setDrawerListener(new DrawerLocker()); mWebpageBitmap = ThemeUtils.getThemedBitmap(this, R.drawable.ic_webpage, mDarkTheme); mHomepage = mPreferences.getHomepage(); 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) { mToolbarLayout.removeView(findViewById(R.id.tabs_toolbar_container)); } if (actionBar == null) return; // set display options of the ActionBar actionBar.setDisplayShowTitleEnabled(false); actionBar.setDisplayShowHomeEnabled(false); actionBar.setDisplayShowCustomEnabled(true); actionBar.setCustomView(R.layout.toolbar_content); View customView = actionBar.getCustomView(); LayoutParams lp = customView.getLayoutParams(); lp.width = LayoutParams.MATCH_PARENT; lp.height = LayoutParams.MATCH_PARENT; customView.setLayoutParams(lp); mArrowImage = (ImageView) customView.findViewById(R.id.arrow); FrameLayout arrowButton = (FrameLayout) customView.findViewById(R.id.arrow_button); if (mShowTabsInDrawer) { // Use hardware acceleration for the animation mArrowDrawable = new DrawerArrowDrawable(this); mArrowImage.setLayerType(View.LAYER_TYPE_HARDWARE, null); mArrowImage.setImageDrawable(mArrowDrawable); } else { mArrowImage.setImageResource(R.drawable.ic_action_home); mArrowImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); } arrowButton.setOnClickListener(this); mProxyUtils = ProxyUtils.getInstance(); // create the search EditText in the ToolBar mSearch = (AutoCompleteTextView) customView.findViewById(R.id.search); mUntitledTitle = getString(R.string.untitled); mBackgroundColor = ContextCompat.getColor(this, R.color.primary_color); mDeleteIcon = ThemeUtils.getLightThemedDrawable(this, R.drawable.ic_action_delete); mRefreshIcon = ThemeUtils.getLightThemedDrawable(this, R.drawable.ic_action_refresh); mClearIcon = ThemeUtils.getLightThemedDrawable(this, R.drawable.ic_action_delete); int iconBounds = Utils.dpToPx(30); mDeleteIcon.setBounds(0, 0, iconBounds, iconBounds); mRefreshIcon.setBounds(0, 0, iconBounds, iconBounds); mClearIcon.setBounds(0, 0, iconBounds, iconBounds); mIcon = mRefreshIcon; SearchListenerClass search = new SearchListenerClass(); mSearch.setCompoundDrawables(null, null, mRefreshIcon, null); mSearch.setOnKeyListener(search); mSearch.setOnFocusChangeListener(search); mSearch.setOnEditorActionListener(search); mSearch.setOnTouchListener(search); new Thread(new Runnable() { @Override public void run() { initializeSearchSuggestions(mSearch); } }).run(); mDrawerLayout.setDrawerShadow(R.drawable.drawer_right_shadow, GravityCompat.END); mDrawerLayout.setDrawerShadow(R.drawable.drawer_left_shadow, GravityCompat.START); if (API <= Build.VERSION_CODES.JELLY_BEAN_MR2) { //noinspection deprecation WebIconDatabase.getInstance().open(getDir("icons", MODE_PRIVATE).getPath()); } tabsManager.restoreTabs(this, mDarkTheme, isIncognito()); // At this point we always have at least a tab in the tab manager showTab(0); mProxyUtils.checkForProxy(this); } private class SearchListenerClass implements OnKeyListener, OnEditorActionListener, OnFocusChangeListener, OnTouchListener { @Override public boolean onKey(View arg0, int arg1, KeyEvent arg2) { switch (arg1) { case KeyEvent.KEYCODE_ENTER: InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mSearch.getWindowToken(), 0); searchTheWeb(mSearch.getText().toString()); final LightningView currentView = tabsManager.getCurrentTab(); if (currentView != null) { currentView.requestFocus(); } return true; default: break; } return false; } @Override public boolean onEditorAction(TextView arg0, int actionId, KeyEvent arg2) { // hide the keyboard and search the web when the enter key // button is pressed if (actionId == EditorInfo.IME_ACTION_GO || actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_NEXT || actionId == EditorInfo.IME_ACTION_SEND || actionId == EditorInfo.IME_ACTION_SEARCH || (arg2.getAction() == KeyEvent.KEYCODE_ENTER)) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mSearch.getWindowToken(), 0); searchTheWeb(mSearch.getText().toString()); final LightningView currentView = tabsManager.getCurrentTab(); if (currentView != null) { currentView.requestFocus(); } return true; } return false; } @Override public void onFocusChange(View v, final boolean hasFocus) { final LightningView currentView = tabsManager.getCurrentTab(); if (!hasFocus && currentView != null) { setIsLoading(currentView.getProgress() < 100); updateUrl(currentView.getUrl(), true); } else if (hasFocus) { String url = currentView.getUrl(); if (url.startsWith(Constants.FILE)) { mSearch.setText(""); } else { mSearch.setText(url); } ((AutoCompleteTextView) v).selectAll(); // Hack to make sure // the text gets // selected mIcon = mClearIcon; mSearch.setCompoundDrawables(null, null, mClearIcon, null); } final Animation anim = new Animation() { @Override protected void applyTransformation(float interpolatedTime, Transformation t) { if (!hasFocus) { mArrowDrawable.setProgress(1.0f - interpolatedTime); } else { mArrowDrawable.setProgress(interpolatedTime); } } @Override public boolean willChangeBounds() { return true; } }; anim.setDuration(300); anim.setInterpolator(new DecelerateInterpolator()); anim.setAnimationListener(new AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { if (!hasFocus) { mArrowDrawable.setProgress(0.0f); } else { mArrowDrawable.setProgress(1.0f); } } @Override public void onAnimationRepeat(Animation animation) { } }); new Handler().postDelayed(new Runnable() { @Override public void run() { if (mArrowDrawable != null) { mArrowImage.startAnimation(anim); } } }, 100); if (!hasFocus) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mSearch.getWindowToken(), 0); } } @Override public boolean onTouch(View v, MotionEvent event) { if (mSearch.getCompoundDrawables()[2] != null) { boolean tappedX = event.getX() > (mSearch.getWidth() - mSearch.getPaddingRight() - mIcon.getIntrinsicWidth()); if (tappedX) { if (event.getAction() == MotionEvent.ACTION_UP) { if (mSearch.hasFocus()) { mSearch.setText(""); } else { refreshOrStop(); } } return true; } } return false; } } private class DrawerLocker implements DrawerListener { @Override public void onDrawerClosed(View v) { if (v == mDrawerRight && mShowTabsInDrawer) { mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLeft); } else { mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerRight); } } @Override public void onDrawerOpened(View v) { if (v == mDrawerRight) { mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, mDrawerLeft); } else { mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, mDrawerRight); } } @Override public void onDrawerSlide(View v, float arg) { } @Override public void onDrawerStateChanged(int arg) { } } private void setNavigationDrawerWidth() { int width = getResources().getDisplayMetrics().widthPixels - Utils.dpToPx(56); int maxWidth; if (isTablet()) { maxWidth = Utils.dpToPx(320); } else { maxWidth = Utils.dpToPx(300); } if (width > maxWidth) { DrawerLayout.LayoutParams params = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerLeft .getLayoutParams(); params.width = maxWidth; mDrawerLeft.setLayoutParams(params); mDrawerLeft.requestLayout(); DrawerLayout.LayoutParams paramsRight = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerRight .getLayoutParams(); paramsRight.width = maxWidth; mDrawerRight.setLayoutParams(paramsRight); mDrawerRight.requestLayout(); } else { DrawerLayout.LayoutParams params = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerLeft .getLayoutParams(); params.width = width; mDrawerLeft.setLayoutParams(params); mDrawerLeft.requestLayout(); DrawerLayout.LayoutParams paramsRight = (android.support.v4.widget.DrawerLayout.LayoutParams) mDrawerRight .getLayoutParams(); paramsRight.width = width; mDrawerRight.setLayoutParams(paramsRight); mDrawerRight.requestLayout(); } } private void initializePreferences() { final LightningView currentView = tabsManager.getCurrentTab(); final WebView currentWebView = currentView.getWebView(); mFullScreen = mPreferences.getFullScreenEnabled(); mColorMode = mPreferences.getColorModeEnabled(); mColorMode &= !mDarkTheme; if (!isIncognito() && !mColorMode && !mDarkTheme && mWebpageBitmap != null) { changeToolbarBackground(mWebpageBitmap, null); } else if (!isIncognito() && currentView != null && !mDarkTheme && currentView.getFavicon() != null) { changeToolbarBackground(currentView.getFavicon(), null); } if (mFullScreen) { mToolbarLayout.setTranslationY(0); int height = mToolbarLayout.getHeight(); if (height == 0) { mToolbarLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); height = mToolbarLayout.getMeasuredHeight(); } if (currentWebView != null) currentWebView.setTranslationY(height); mBrowserFrame.setLayoutTransition(null); if (mBrowserFrame.findViewById(R.id.toolbar_layout) == null) { mUiLayout.removeView(mToolbarLayout); mBrowserFrame.addView(mToolbarLayout); mToolbarLayout.bringToFront(); } } else { mToolbarLayout.setTranslationY(0); if (mBrowserFrame.findViewById(R.id.toolbar_layout) != null) { mBrowserFrame.removeView(mToolbarLayout); mUiLayout.addView(mToolbarLayout, 0); } mBrowserFrame.setLayoutTransition(new LayoutTransition()); if (currentWebView != null) currentWebView.setTranslationY(0); } setFullscreen(mPreferences.getHideStatusBarEnabled(), false); switch (mPreferences.getSearchChoice()) { case 0: mSearchText = mPreferences.getSearchUrl(); if (!mSearchText.startsWith(Constants.HTTP) && !mSearchText.startsWith(Constants.HTTPS)) { mSearchText = Constants.GOOGLE_SEARCH; } break; case 1: mSearchText = Constants.GOOGLE_SEARCH; break; case 2: mSearchText = Constants.ASK_SEARCH; break; case 3: mSearchText = Constants.BING_SEARCH; break; case 4: mSearchText = Constants.YAHOO_SEARCH; break; case 5: mSearchText = Constants.STARTPAGE_SEARCH; break; case 6: mSearchText = Constants.STARTPAGE_MOBILE_SEARCH; break; case 7: mSearchText = Constants.DUCK_SEARCH; break; case 8: mSearchText = Constants.DUCK_LITE_SEARCH; break; case 9: mSearchText = Constants.BAIDU_SEARCH; break; case 10: mSearchText = Constants.YANDEX_SEARCH; break; } updateCookiePreference(); mProxyUtils.updateProxySettings(this); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_ENTER) { if (mSearch.hasFocus()) { searchTheWeb(mSearch.getText().toString()); } } else if ((keyCode == KeyEvent.KEYCODE_MENU) && (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) && (Build.MANUFACTURER.compareTo("LGE") == 0)) { // Workaround for stupid LG devices that crash return true; } return super.onKeyDown(keyCode, event); } @Override public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) { if ((keyCode == KeyEvent.KEYCODE_MENU) && (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) && (Build.MANUFACTURER.compareTo("LGE") == 0)) { // Workaround for stupid LG devices that crash openOptionsMenu(); return true; } return super.onKeyUp(keyCode, event); } @Override public boolean onOptionsItemSelected(MenuItem item) { final LightningView currentView = tabsManager.getCurrentTab(); // Handle action buttons switch (item.getItemId()) { case android.R.id.home: if (mDrawerLayout.isDrawerOpen(mDrawerRight)) { mDrawerLayout.closeDrawer(mDrawerRight); } return true; case R.id.action_back: if (currentView != null && currentView.canGoBack()) { currentView.goBack(); } return true; case R.id.action_forward: if (currentView != null && currentView.canGoForward()) { currentView.goForward(); } return true; case R.id.action_new_tab: newTab(null, true); return true; case R.id.action_incognito: startActivity(new Intent(this, IncognitoActivity.class)); overridePendingTransition(R.anim.slide_up_in, R.anim.fade_out_scale); return true; case R.id.action_share: if (currentView != null && !currentView.getUrl().startsWith(Constants.FILE)) { Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("text/plain"); 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; case R.id.action_bookmarks: openBookmarks(); return true; case R.id.action_copy: if (currentView != null && !currentView.getUrl().startsWith(Constants.FILE)) { ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText("label", currentView.getUrl()); clipboard.setPrimaryClip(clip); Utils.showSnackbar(this, R.string.message_link_copied); } return true; case R.id.action_settings: startActivity(new Intent(this, SettingsActivity.class)); return true; case R.id.action_history: openHistory(); return true; case R.id.action_add_bookmark: if (currentView != null && !currentView.getUrl().startsWith(Constants.FILE)) { 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, currentView.getUrl()); startActivity(read); return true; default: return super.onOptionsItemSelected(item); } } /** * method that shows a dialog asking what string the user wishes to search * for. It highlights the text entered. */ private void findInPage() { final AlertDialog.Builder finder = new AlertDialog.Builder(this); finder.setTitle(getResources().getString(R.string.action_find)); final EditText getHome = new EditText(this); getHome.setHint(getResources().getString(R.string.search_hint)); finder.setView(getHome); finder.setPositiveButton(getResources().getString(R.string.search_hint), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String query = getHome.getText().toString(); if (!query.isEmpty()) showSearchInterfaceBar(query); } }); finder.show(); } private void showSearchInterfaceBar(String text) { final LightningView currentView = tabsManager.getCurrentTab(); if (currentView != null) { currentView.find(text); } mSearchBar.setVisibility(View.VISIBLE); TextView tw = (TextView) findViewById(R.id.search_query); tw.setText('\'' + text + '\''); ImageButton up = (ImageButton) findViewById(R.id.button_next); up.setOnClickListener(this); ImageButton down = (ImageButton) findViewById(R.id.button_back); down.setOnClickListener(this); ImageButton quit = (ImageButton) findViewById(R.id.button_quit); quit.setOnClickListener(this); } private void showCloseDialog(final int position) { if (position < 0) { return; } AlertDialog.Builder builder = new AlertDialog.Builder(this); ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line); adapter.add(this.getString(R.string.close_tab)); adapter.add(this.getString(R.string.close_all_tabs)); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case 0: deleteTab(position); break; case 1: closeBrowser(); break; default: break; } } }); builder.show(); } /** * displays the WebView contained in the LightningView Also handles the * removal of previous views * * @param position the poition of the tab to display */ private synchronized void showTab(final int position) { final LightningView currentView = tabsManager.getCurrentTab(); final WebView currentWebView = currentView != null ? currentView.getWebView() : null; final LightningView newView = tabsManager.switchToTab(position); final WebView newWebView = newView != null ? newView.getWebView() : null; // Set the background color so the color mode color doesn't show through mBrowserFrame.setBackgroundColor(mBackgroundColor); if (newView == currentView && !currentView.isShown()) { return; } mIsNewIntent = false; final float translation = mToolbarLayout.getTranslationY(); mBrowserFrame.removeAllViews(); 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(newWebView, MATCH_PARENT); newView.requestFocus(); newView.onResume(); if (mFullScreen) { // mToolbarLayout has already been removed mBrowserFrame.addView(mToolbarLayout); mToolbarLayout.bringToFront(); Log.d(Constants.TAG, "Move view to browser frame"); int height = mToolbarLayout.getHeight(); if (height == 0) { mToolbarLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); height = mToolbarLayout.getMeasuredHeight(); } newWebView.setTranslationY(translation + height); mToolbarLayout.setTranslationY(translation); } else { newWebView.setTranslationY(0); } showActionBar(); // Use a delayed handler to make the transition smooth // otherwise it will get caught up with the showTab code // and cause a janky motion new Handler().postDelayed(new Runnable() { @Override public void run() { mDrawerLayout.closeDrawers(); } }, 200); // Should update the bookmark status in BookmarksFragment mEventBus.post(new BrowserEvents.CurrentPageUrl(newView.getUrl())); // new Handler().postDelayed(new Runnable() { // @Override // public void run() { // Remove browser frame background to reduce overdraw //TODO evaluate performance // mBrowserFrame.setBackgroundColor(Color.TRANSPARENT); // } // }, 300); } void handleNewIntent(Intent intent) { String url = null; if (intent != null) { url = intent.getDataString(); } int num = 0; String source = null; if (intent != null && intent.getExtras() != null) { num = intent.getExtras().getInt(getPackageName() + ".Origin"); source = intent.getExtras().getString("SOURCE"); } if (num == 1) { loadUrlInCurrentView(url); } else if (url != null) { if (url.startsWith(Constants.FILE)) { Utils.showSnackbar(this, R.string.message_blocked_local); url = null; } newTab(url, true); mIsNewIntent = (source == null); } } private void loadUrlInCurrentView(final String url) { final LightningView currentTab = tabsManager.getCurrentTab(); if (currentTab == null) { // This is a problem, probably an assert will be better than a return return; } currentTab.loadUrl(url); mEventBus.post(new BrowserEvents.CurrentPageUrl(url)); } @Override public void closeEmptyTab() { final WebView currentWebView = tabsManager.getCurrentWebView(); if (currentWebView != null && currentWebView.copyBackForwardList().getSize() == 0) { closeCurrentTab(); } } private void closeCurrentTab() { // don't delete the tab because the browser will close and mess stuff up } @Override public void onTrimMemory(int level) { if (level > TRIM_MEMORY_MODERATE && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { Log.d(Constants.TAG, "Low Memory, Free Memory"); tabsManager.freeMemory(); } } synchronized boolean newTab(String url, boolean show) { // Limit number of tabs for limited version of app if (!Constants.FULL_VERSION && tabsManager.size() >= 10) { Utils.showSnackbar(this, R.string.max_tabs); return false; } mIsNewIntent = false; LightningView startingTab = tabsManager.newTab(this, url, mDarkTheme, isIncognito()); if (mIdGenerator == 0) { startingTab.resumeTimers(); } mIdGenerator++; if (show) { showTab(tabsManager.size() - 1); } // 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(tabsManager.size() - 1); // } // }, 300); return true; } private synchronized void deleteTab(int position) { final LightningView tabToDelete = tabsManager.getTabAtPosition(position); final LightningView currentTab = tabsManager.getCurrentTab(); if (tabToDelete == null) { return; } // What? int current = tabsManager.positionOf(currentTab); if (current < 0) { return; } if (!tabToDelete.getUrl().startsWith(Constants.FILE) && !isIncognito()) { mPreferences.setSavedUrl(tabToDelete.getUrl()); } final boolean isShown = tabToDelete.isShown(); if (isShown) { mBrowserFrame.setBackgroundColor(mBackgroundColor); } if (current > position) { tabsManager.deleteTab(position); showTab(current - 1); mEventBus.post(new BrowserEvents.TabsChanged()); } else if (tabsManager.size() > position + 1) { if (current == position) { showTab(position + 1); tabsManager.deleteTab(position); showTab(position); mEventBus.post(new BrowserEvents.TabsChanged()); } else { tabsManager.deleteTab(position); } } else if (tabsManager.size() > 1) { if (current == position) { showTab(position - 1); tabsManager.deleteTab(position); showTab(position - 1); mEventBus.post(new BrowserEvents.TabsChanged()); } else { } } else { if (currentTab.getUrl().startsWith(Constants.FILE) || currentTab.getUrl().equals(mHomepage)) { closeActivity(); } else { tabsManager.deleteTab(position); performExitCleanUp(); tabToDelete.pauseTimers(); mEventBus.post(new BrowserEvents.TabsChanged()); finish(); } } mEventBus.post(new BrowserEvents.TabsChanged()); if (mIsNewIntent && isShown) { mIsNewIntent = false; closeActivity(); } Log.d(Constants.TAG, "deleted tab"); } private void performExitCleanUp() { final LightningView currentTab = tabsManager.getCurrentTab(); if (mPreferences.getClearCacheExit() && currentTab != null && !isIncognito()) { WebUtils.clearCache(currentTab.getWebView()); Log.d(Constants.TAG, "Cache Cleared"); } if (mPreferences.getClearHistoryExitEnabled() && !isIncognito()) { WebUtils.clearHistory(this); Log.d(Constants.TAG, "History Cleared"); } if (mPreferences.getClearCookiesExitEnabled() && !isIncognito()) { WebUtils.clearCookies(this); Log.d(Constants.TAG, "Cookies Cleared"); } if (mPreferences.getClearWebStorageExitEnabled() && !isIncognito()) { WebUtils.clearWebStorage(); Log.d(Constants.TAG, "WebStorage Cleared"); } else if (isIncognito()) { WebUtils.clearWebStorage(); // We want to make sure incognito mode is secure } } @Override public boolean onKeyLongPress(int keyCode, KeyEvent event) { final LightningView currentTab = tabsManager.getCurrentTab(); if (keyCode == KeyEvent.KEYCODE_BACK) { showCloseDialog(tabsManager.positionOf(currentTab)); } return true; } private void closeBrowser() { mBrowserFrame.setBackgroundColor(mBackgroundColor); performExitCleanUp(); tabsManager.shutdown(); mEventBus.post(new BrowserEvents.TabsChanged()); finish(); } @Override public void onBackPressed() { final LightningView currentTab = tabsManager.getCurrentTab(); if (mDrawerLayout.isDrawerOpen(mDrawerLeft)) { mDrawerLayout.closeDrawer(mDrawerLeft); } else if (mDrawerLayout.isDrawerOpen(mDrawerRight)) { mEventBus .post(new BrowserEvents.UserPressedBack()); } else { if (currentTab != null) { Log.d(Constants.TAG, "onBackPressed"); if (mSearch.hasFocus()) { currentTab.requestFocus(); } else if (currentTab.canGoBack()) { if (!currentTab.isShown()) { onHideCustomView(); } else { currentTab.goBack(); } } else { deleteTab(tabsManager.positionOf(currentTab)); } } else { Log.e(Constants.TAG, "This shouldn't happen ever"); super.onBackPressed(); } } } @Override protected void onPause() { super.onPause(); final LightningView currentTab = tabsManager.getCurrentTab(); Log.d(Constants.TAG, "onPause"); if (currentTab != null) { currentTab.pauseTimers(); currentTab.onPause(); } try { unregisterReceiver(mNetworkReceiver); } catch (IllegalArgumentException e) { e.printStackTrace(); } if (isIncognito() && isFinishing()) { overridePendingTransition(R.anim.fade_in_scale, R.anim.slide_down_out); } mEventBus.unregister(mBusEventListener); } void saveOpenTabs() { if (mPreferences.getRestoreLostTabsEnabled()) { final String s = tabsManager.tabsString(); mPreferences.setMemoryUrl(s.toString()); } } @Override protected void onStop() { super.onStop(); mProxyUtils.onStop(); } @Override protected void onDestroy() { Log.d(Constants.TAG, "onDestroy"); if (mHistoryDatabase != null) { mHistoryDatabase.close(); mHistoryDatabase = null; } super.onDestroy(); } @Override protected void onStart() { super.onStart(); mProxyUtils.onStart(this); } @Override protected void onResume() { super.onResume(); final LightningView currentTab = tabsManager.getCurrentTab(); Log.d(Constants.TAG, "onResume"); if (mSearchAdapter != null) { mSearchAdapter.refreshPreferences(); mSearchAdapter.refreshBookmarks(); } if (currentTab != null) { currentTab.resumeTimers(); currentTab.onResume(); } initializePreferences(); tabsManager.resume(this); supportInvalidateOptionsMenu(); IntentFilter filter = new IntentFilter(); filter.addAction(NETWORK_BROADCAST_ACTION); registerReceiver(mNetworkReceiver, filter); mEventBus.register(mBusEventListener); } /** * searches the web for the query fixing any and all problems with the input * checks if it is a search, url, etc. */ private void searchTheWeb(@NonNull String query) { final LightningView currentTab = tabsManager.getCurrentTab(); if (query.isEmpty()) { return; } String searchUrl = mSearchText + UrlUtils.QUERY_PLACE_HOLDER; query = query.trim(); currentTab.stopLoading(); if (currentTab != null) { loadUrlInCurrentView(UrlUtils.smartUrlFilter(query, true, searchUrl)); } } /** * Animates the color of the toolbar from one color to another. Optionally animates * the color of the tab background, for use when the tabs are displayed on the top * of the screen. * * @param favicon the Bitmap to extract the color from * @param tabBackground the optional LinearLayout to color */ private void changeToolbarBackground(@NonNull Bitmap favicon, @Nullable final Drawable tabBackground) { final int defaultColor = ContextCompat.getColor(this, R.color.primary_color); if (mCurrentUiColor == Color.BLACK) { mCurrentUiColor = defaultColor; } Palette.from(favicon).generate(new Palette.PaletteAsyncListener() { @Override public void onGenerated(Palette palette) { // OR with opaque black to remove transparency glitches int color = 0xff000000 | palette.getVibrantColor(defaultColor); int finalColor; // Lighten up the dark color if it is // too dark if (!mShowTabsInDrawer || Utils.isColorTooDark(color)) { finalColor = Utils.mixTwoColors(defaultColor, color, 0.25f); } else { finalColor = color; } ValueAnimator anim = ValueAnimator.ofInt(mCurrentUiColor, finalColor); anim.setEvaluator(new ArgbEvaluator()); final Window window = getWindow(); if (!mShowTabsInDrawer) { window.setBackgroundDrawable(new ColorDrawable(Color.BLACK)); } anim.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { final int color = (Integer) animation.getAnimatedValue(); if (mShowTabsInDrawer) { mBackground.setColor(color); window.setBackgroundDrawable(mBackground); } else if (tabBackground != null) { tabBackground.setColorFilter(color, PorterDuff.Mode.SRC_IN); } mCurrentUiColor = color; mToolbarLayout.setBackgroundColor(color); } }); anim.setDuration(300); anim.start(); } }); } @Override public void updateUrl(String url, boolean shortUrl) { final LightningView currentTab = tabsManager.getCurrentTab(); if (url == null || mSearch == null || mSearch.hasFocus()) { return; } mEventBus.post(new BrowserEvents.CurrentPageUrl(url)); if (shortUrl && !url.startsWith(Constants.FILE)) { switch (mPreferences.getUrlBoxContentChoice()) { case 0: // Default, show only the domain url = url.replaceFirst(Constants.HTTP, ""); url = Utils.getDomainName(url); mSearch.setText(url); break; case 1: // URL, show the entire URL mSearch.setText(url); break; case 2: // Title, show the page's title if (currentTab != null && !currentTab.getTitle().isEmpty()) { mSearch.setText(currentTab.getTitle()); } else { mSearch.setText(mUntitledTitle); } break; } } else { if (url.startsWith(Constants.FILE)) { url = ""; } mSearch.setText(url); } } @Override public void updateProgress(int n) { setIsLoading(n < 100); mProgressBar.setProgress(n); } void addItemToHistory(final String title, final String url) { Runnable update = new Runnable() { @Override public void run() { try { mHistoryDatabase.visitHistoryItem(url, title); } catch (IllegalStateException e) { Log.e(Constants.TAG, "IllegalStateException in updateHistory", e); } catch (NullPointerException e) { Log.e(Constants.TAG, "NullPointerException in updateHistory", e); } catch (SQLiteException e) { Log.e(Constants.TAG, "SQLiteException in updateHistory", e); } } }; if (url != null && !url.startsWith(Constants.FILE)) { new Thread(update).start(); } } /** * method to generate search suggestions for the AutoCompleteTextView from * previously searched URLs */ private void initializeSearchSuggestions(final AutoCompleteTextView getUrl) { final LightningView currentTab = tabsManager.getCurrentTab(); 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 (currentTab != null) { currentTab.requestFocus(); } } catch (NullPointerException e) { Log.e("Browser Error: ", "NullPointerException on item click"); } } }); getUrl.setSelectAllOnFocus(true); mSearchAdapter = new SearchAdapter(this, mDarkTheme, isIncognito()); getUrl.setAdapter(mSearchAdapter); } /** * function that opens the HTML history page in the browser */ private void openHistory() { // use a thread so that history retrieval doesn't block the UI Thread history = new Thread(new Runnable() { @Override public void run() { loadUrlInCurrentView(HistoryPage.getHistoryPage(BrowserActivity.this)); mSearch.setText(""); } }); history.run(); } /** * helper function that opens the bookmark drawer */ private void openBookmarks() { if (mDrawerLayout.isDrawerOpen(mDrawerLeft)) { mDrawerLayout.closeDrawers(); } mDrawerLayout.openDrawer(mDrawerRight); } void closeDrawers() { mDrawerLayout.closeDrawers(); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuItem back = menu.findItem(R.id.action_back); MenuItem forward = menu.findItem(R.id.action_forward); if (back != null && back.getIcon() != null) back.getIcon().setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); if (forward != null && forward.getIcon() != null) forward.getIcon().setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); return super.onCreateOptionsMenu(menu); } /** * opens a file chooser * param ValueCallback is the message from the WebView indicating a file chooser * should be opened */ @Override public void openFileChooser(ValueCallback uploadMsg) { mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("*/*"); startActivityForResult(Intent.createChooser(i, getString(R.string.title_file_chooser)), 1); } /** * used to allow uploading into the browser */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { if (API < Build.VERSION_CODES.LOLLIPOP) { if (requestCode == 1) { if (null == mUploadMessage) { return; } Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); mUploadMessage.onReceiveValue(result); mUploadMessage = null; } } if (requestCode != 1 || mFilePathCallback == null) { super.onActivityResult(requestCode, resultCode, intent); return; } Uri[] results = null; // Check that the response is a good one if (resultCode == Activity.RESULT_OK) { if (intent == null) { // If there is not data, then we may have taken a photo if (mCameraPhotoPath != null) { results = new Uri[]{Uri.parse(mCameraPhotoPath)}; } } else { String dataString = intent.getDataString(); if (dataString != null) { results = new Uri[]{Uri.parse(dataString)}; } } } mFilePathCallback.onReceiveValue(results); mFilePathCallback = null; } @Override public void showFileChooser(ValueCallback filePathCallback) { if (mFilePathCallback != null) { mFilePathCallback.onReceiveValue(null); } mFilePathCallback = filePathCallback; Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (takePictureIntent.resolveActivity(this.getPackageManager()) != null) { // Create the File where the photo should go File photoFile = null; try { photoFile = Utils.createImageFile(); takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath); } catch (IOException ex) { // Error occurred while creating the File Log.e(Constants.TAG, "Unable to create Image File", ex); } // Continue only if the File was successfully created if (photoFile != null) { mCameraPhotoPath = "file:" + photoFile.getAbsolutePath(); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); } else { takePictureIntent = null; } } Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT); contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); contentSelectionIntent.setType("*/*"); Intent[] intentArray; if (takePictureIntent != null) { intentArray = new Intent[]{takePictureIntent}; } else { intentArray = new Intent[0]; } Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent); chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser"); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray); this.startActivityForResult(chooserIntent, 1); } @Override public void onShowCustomView(View view, CustomViewCallback callback) { final LightningView currentTab = tabsManager.getCurrentTab(); if (view == null) { return; } if (mCustomView != null && callback != null) { callback.onCustomViewHidden(); return; } try { view.setKeepScreenOn(true); } catch (SecurityException e) { Log.e(Constants.TAG, "WebView is not allowed to keep the screen on"); } mOriginalOrientation = getRequestedOrientation(); FrameLayout decor = (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); currentTab.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()); } } mCustomViewCallback = callback; } @Override public void onHideCustomView() { final LightningView currentTab = tabsManager.getCurrentTab(); if (mCustomView == null || mCustomViewCallback == null || currentTab == null) { return; } Log.d(Constants.TAG, "onHideCustomView"); 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) { } } mFullscreenContainer = null; mCustomView = null; if (mVideoView != null) { mVideoView.setOnErrorListener(null); mVideoView.setOnCompletionListener(null); mVideoView = null; } setRequestedOrientation(mOriginalOrientation); } private class VideoCompletionListener implements MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener { @Override public boolean onError(MediaPlayer mp, int what, int extra) { return false; } @Override public void onCompletion(MediaPlayer mp) { onHideCustomView(); } } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { setFullscreen(mIsFullScreen, mIsImmersive); } } /** * This method sets whether or not the activity will display * in full-screen mode (i.e. the ActionBar will be hidden) and * whether or not immersive mode should be set. This is used to * set both parameters correctly as during a full-screen video, * both need to be set, but other-wise we leave it up to user * preference. * * @param enabled true to enable full-screen, false otherwise * @param immersive true to enable immersive mode, false otherwise */ private void setFullscreen(boolean enabled, boolean immersive) { mIsFullScreen = enabled; mIsImmersive = immersive; Window window = getWindow(); View decor = window.getDecorView(); if (enabled) { window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); if (immersive) { decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); } } else { window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); } } /** * This method handles the JavaScript callback to create a new tab. * Basically this handles the event that JavaScript needs to create * a popup. * * @param resultMsg the transport message used to send the URL to * the newly created WebView. */ @Override public void onCreateWindow(Message resultMsg) { if (resultMsg == null) { return; } if (newTab("", true)) { // TODO Review this final WebView webView = tabsManager.getTabAtPosition(tabsManager.size() - 1).getWebView(); WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; transport.setWebView(webView); resultMsg.sendToTarget(); } } /** * Closes the specified {@link LightningView}. This implements * the JavaScript callback that asks the tab to close itself and * is especially helpful when a page creates a redirect and does * not need the tab to stay open any longer. * * @param view the LightningView to close, delete it. */ @Override public void onCloseWindow(LightningView view) { deleteTab(tabsManager.positionOf(view)); } /** * Hide the ActionBar using an animation if we are in full-screen * mode. This method also re-parents the ActionBar if its parent is * incorrect so that the animation can happen correctly. */ @Override public void hideActionBar() { final LightningView currentTab = tabsManager.getCurrentTab(); final WebView currentWebView = currentTab.getWebView(); if (mFullScreen) { if (mBrowserFrame.findViewById(R.id.toolbar_layout) == null) { mUiLayout.removeView(mToolbarLayout); mBrowserFrame.addView(mToolbarLayout); mToolbarLayout.bringToFront(); Log.d(Constants.TAG, "Move view to browser frame"); mToolbarLayout.setTranslationY(0); currentWebView.setTranslationY(mToolbarLayout.getHeight()); } if (mToolbarLayout == null || currentTab == null) return; final int height = mToolbarLayout.getHeight(); final WebView view = currentWebView; 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); } }; show.setDuration(250); show.setInterpolator(new DecelerateInterpolator()); currentWebView.startAnimation(show); } } } /** * Display the ActionBar using an animation if we are in full-screen * mode. This method also re-parents the ActionBar if its parent is * incorrect so that the animation can happen correctly. */ @Override public void showActionBar() { if (mFullScreen) { final WebView view = tabsManager.getCurrentWebView(); if (mToolbarLayout == null) return; int height = mToolbarLayout.getHeight(); if (height == 0) { mToolbarLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); height = mToolbarLayout.getMeasuredHeight(); } if (mBrowserFrame.findViewById(R.id.toolbar_layout) == null) { mUiLayout.removeView(mToolbarLayout); mBrowserFrame.addView(mToolbarLayout); mToolbarLayout.bringToFront(); Log.d(Constants.TAG, "Move view to browser frame"); mToolbarLayout.setTranslationY(0); if (view != null) { view.setTranslationY(height); } } final LightningView currentTab = tabsManager.getCurrentTab(); if (currentTab == null) return; final int totalHeight = height; if (mToolbarLayout.getTranslationY() < -(height - 0.01f)) { Animation show = new Animation() { @Override protected void applyTransformation(float interpolatedTime, Transformation t) { float trans = interpolatedTime * totalHeight; mToolbarLayout.setTranslationY(trans - totalHeight); // null pointer here on close if (view != null) view.setTranslationY(trans); } }; show.setDuration(250); show.setInterpolator(new DecelerateInterpolator()); if (view != null) { view.startAnimation(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 * pressing it stops the page from loading */ private void setIsLoading(boolean isLoading) { if (!mSearch.hasFocus()) { mIcon = isLoading ? mDeleteIcon : mRefreshIcon; mSearch.setCompoundDrawables(null, null, mIcon, null); } } /** * handle presses on the refresh icon in the search bar, if the page is * loading, stop the page, if it is done loading refresh the page. * See setIsFinishedLoading and setIsLoading for displaying the correct icon */ private void refreshOrStop() { final LightningView currentTab = tabsManager.getCurrentTab(); if (currentTab != null) { if (currentTab.getProgress() < 100) { currentTab.stopLoading(); } else { currentTab.reload(); } } } /** * Handle the click event for the views that are using * this class as a click listener. This method should * distinguish between the various views using their IDs. * * @param v the view that the user has clicked */ @Override public void onClick(View v) { final LightningView currentTab = tabsManager.getCurrentTab(); final WebView currentWebView = currentTab.getWebView(); switch (v.getId()) { case R.id.arrow_button: if (mSearch != null && mSearch.hasFocus()) { currentTab.requestFocus(); } else if (mShowTabsInDrawer) { mDrawerLayout.openDrawer(mDrawerLeft); } else if (currentTab != null) { currentTab.loadHomepage(); } break; case R.id.button_next: currentWebView.findNext(false); break; case R.id.button_back: currentWebView.findNext(true); break; case R.id.button_quit: currentWebView.clearMatches(); mSearchBar.setVisibility(View.GONE); break; case R.id.action_reading: Intent read = new Intent(this, ReadingActivity.class); read.putExtra(Constants.LOAD_READING_URL, currentTab.getUrl()); startActivity(read); break; case R.id.action_toggle_desktop: currentTab.toggleDesktopUA(this); currentTab.reload(); closeDrawers(); break; } } /** * Handle long presses on views that use this class * as their OnLongClickListener. This method should * distinguish between the IDs of the views that are * getting clicked. * * @param view the view that has been long pressed * @return returns true since the method handles the long press * event */ @Override public boolean onLongClick(View view) { 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 * listeners are set to this class, so BrowserActivity should handle those events * there. Additionally, it tints the images according to the current theme. * This method only is a convenience so that this code does not have to be repeated * for the several "Buttons" that use this. * * @param buttonId the view id of the button * @param imageId the image to set as the button image */ private void setupFrameLayoutButton(@IdRes int buttonId, @IdRes int imageId) { final View frameButton = findViewById(buttonId); final ImageView buttonImage = (ImageView) findViewById(imageId); frameButton.setOnClickListener(this); frameButton.setOnLongClickListener(this); buttonImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); } /** * This NetworkReceiver notifies each of the WebViews in the browser whether * the network is currently connected or not. This is important because some * JavaScript properties rely on the WebView knowing the current network state. * It is used to help the browser be compliant with the HTML5 spec, sec. 5.7.7 */ private final NetworkReceiver mNetworkReceiver = new NetworkReceiver() { @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); boolean isConnected = isConnected(context); Log.d(Constants.TAG, "Network Connected: " + String.valueOf(isConnected)); tabsManager.notifyConnectioneStatus(isConnected); } }; /** * Handle the callback that permissions requested have been granted or not. * This method should act upon the results of the permissions request. * * @param requestCode the request code sent when initially making the request * @param permissions the array of the permissions that was requested * @param grantResults the results of the permissions requests that provides * information on whether the request was granted or not */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { PermissionsManager.getInstance().notifyPermissionsChange(permissions, grantResults); super.onRequestPermissionsResult(requestCode, permissions, grantResults); } private final Object mBusEventListener = new Object() { /** * 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 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(); handler.postDelayed(new Runnable() { @Override public void run() { mDrawerLayout.closeDrawer(mDrawerRight); } }, 150); } /** * 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 loadUrlInNewTab(final BrowserEvents.OpenUrlInNewTab event) { BrowserActivity.this.newTab(event.url, true); mDrawerLayout.closeDrawers(); } /** * When receive a {@link acr.browser.lightning.bus.BookmarkEvents.WantToBookmarkCurrentPage} * message this receiver answer firing the * {@link acr.browser.lightning.bus.BrowserEvents.AddBookmark} message * * @param event an event that the user wishes to bookmark the current page */ @Subscribe public void bookmarkCurrentPage(final BookmarkEvents.WantToBookmarkCurrentPage event) { final LightningView currentTab = tabsManager.getCurrentTab(); if (currentTab != null) { mEventBus.post(new BrowserEvents.AddBookmark(currentTab.getTitle(), currentTab.getUrl())); } } /** * This message is received when a bookmark was added by the * {@link acr.browser.lightning.fragment.BookmarksFragment} * * @param event the event that a bookmark has been added */ @Subscribe public void bookmarkAdded(final BookmarkEvents.Added event) { mSearchAdapter.refreshBookmarks(); } /** * This method is called when the user edits a bookmark. * * @param event the event that the bookmark has changed. */ @Subscribe public void bookmarkChanged(final BookmarkEvents.BookmarkChanged event) { final LightningView currentTab = tabsManager.getCurrentTab(); final WebView currentWebView = currentTab.getWebView(); if (currentTab != null && currentTab.getUrl().startsWith(Constants.FILE) && currentTab.getUrl().endsWith(Constants.BOOKMARKS_FILENAME)) { currentTab.loadBookmarkpage(); } if (currentTab != null) { mEventBus.post(new BrowserEvents.CurrentPageUrl(currentTab.getUrl())); } } /** * Notify the browser that a bookmark was deleted. * * @param event the event that the bookmark has been deleted */ @Subscribe public void bookmarkDeleted(final BookmarkEvents.Deleted event) { final LightningView currentTab = tabsManager.getCurrentTab(); final WebView currentWebView = currentTab.getWebView(); if (currentTab != null && currentTab.getUrl().startsWith(Constants.FILE) && currentTab.getUrl().endsWith(Constants.BOOKMARKS_FILENAME)) { currentTab.loadBookmarkpage(); } if (currentTab != null) { mEventBus.post(new BrowserEvents.CurrentPageUrl(currentTab.getUrl())); } } /** * The {@link acr.browser.lightning.fragment.BookmarksFragment} send this message on reply * to {@link acr.browser.lightning.bus.BrowserEvents.UserPressedBack} message if the * fragement is showing the boomarks root folder. * * @param event an event notifying the browser that the bookmark drawer * should be closed. */ @Subscribe 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 = tabsManager.getCurrentTab(); if (currentTab != null) { if (currentTab.canGoBack()) { currentTab.goBack(); } else { deleteTab(tabsManager.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 = tabsManager.getCurrentTab(); if (currentTab != null) { if (currentTab.canGoForward()) { currentTab.goForward(); } } } @Subscribe public void goHome(final NavigationEvents.GoHome event) { final LightningView currentTab = tabsManager.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); } } }; }