Lightning browser with I2P configuration
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

2155 lines
80 KiB

/*
* 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.os.StrictMode;
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.annotation.StringRes;
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.BuildConfig;
import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.browser.BrowserPresenter;
import acr.browser.lightning.browser.BrowserView;
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.UIController;
import acr.browser.lightning.database.BookmarkManager;
import acr.browser.lightning.database.HistoryDatabase;
import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.dialog.LightningDialogBuilder;
import acr.browser.lightning.fragment.BookmarksFragment;
import acr.browser.lightning.fragment.TabsFragment;
import acr.browser.lightning.object.SearchAdapter;
import acr.browser.lightning.react.Schedulers;
import acr.browser.lightning.react.Subscription;
import acr.browser.lightning.receiver.NetworkReceiver;
import com.anthonycr.grant.PermissionsManager;
import acr.browser.lightning.react.Observable;
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 BrowserView, UIController, 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<Uri> mUploadMessage;
private ValueCallback<Uri[]> mFilePathCallback;
// Primatives
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;
private String mUntitledTitle;
private String mCameraPhotoPath;
// The singleton BookmarkManager
@Inject BookmarkManager mBookmarkManager;
// Event bus
@Inject Bus mEventBus;
@Inject LightningDialogBuilder mBookmarksDialogBuilder;
@Inject TabsManager mTabsManager;
@Inject HistoryDatabase mHistoryDatabase;
// Image
private Bitmap mWebpageBitmap;
private final ColorDrawable mBackground = new ColorDrawable();
private Drawable mDeleteIcon, mRefreshIcon, mClearIcon, mIcon;
private DrawerArrowDrawable mArrowDrawable;
private BrowserPresenter mPresenter;
// Proxy
@Inject ProxyUtils mProxyUtils;
// Constant
private static final int API = android.os.Build.VERSION.SDK_INT;
private static final String NETWORK_BROADCAST_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
private static final LayoutParams MATCH_PARENT = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
private static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS = new FrameLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
protected abstract boolean isIncognito();
public abstract void closeActivity();
public abstract void updateHistory(@Nullable final String title, @NonNull final String url);
abstract Observable<Void> updateCookiePreference();
@Override
protected void onCreate(Bundle savedInstanceState) {
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedClosableObjects()
.detectLeakedSqlLiteObjects()
.penaltyLog()
.build());
}
super.onCreate(savedInstanceState);
BrowserApp.getAppComponent().inject(this);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mPresenter = new BrowserPresenter(this, isIncognito());
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);
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);
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 {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, mDrawerLeft);
mArrowImage.setImageResource(R.drawable.ic_action_home);
mArrowImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
}
arrowButton.setOnClickListener(this);
// 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);
initializeSearchSuggestions(mSearch);
mDrawerLayout.setDrawerShadow(R.drawable.drawer_right_shadow, GravityCompat.END);
mDrawerLayout.setDrawerShadow(R.drawable.drawer_left_shadow, GravityCompat.START);
if (API <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
//noinspection deprecation
WebIconDatabase.getInstance().open(getDir("icons", MODE_PRIVATE).getPath());
}
mTabsManager.restoreTabsAndHandleIntent(this, getIntent(), isIncognito())
.subscribe(new Subscription<Void>() {
@Override
public void onComplete() {
// At this point we always have at least a tab in the tab manager
showTab(0);
mProxyUtils.checkForProxy(BrowserActivity.this);
}
@Override
public void onNext(Void item) {
}
});
}
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 = mTabsManager.getCurrentTab();
if (currentView != null) {
currentView.requestFocus();
}
return true;
default:
break;
}
return false;
}
@Override
public boolean onEditorAction(TextView arg0, int actionId, KeyEvent arg2) {
// hide the keyboard and search the web when the enter key
// button is pressed
if (actionId == EditorInfo.IME_ACTION_GO || actionId == EditorInfo.IME_ACTION_DONE
|| actionId == EditorInfo.IME_ACTION_NEXT
|| actionId == EditorInfo.IME_ACTION_SEND
|| actionId == EditorInfo.IME_ACTION_SEARCH
|| (arg2.getAction() == KeyEvent.KEYCODE_ENTER)) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mSearch.getWindowToken(), 0);
searchTheWeb(mSearch.getText().toString());
final LightningView currentView = mTabsManager.getCurrentTab();
if (currentView != null) {
currentView.requestFocus();
}
return true;
}
return false;
}
@Override
public void onFocusChange(View v, final boolean hasFocus) {
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);
}
// Hack to make sure the text gets selected
((AutoCompleteTextView) v).selectAll();
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 = mTabsManager.getCurrentTab();
final WebView currentWebView = mTabsManager.getCurrentWebView();
mFullScreen = mPreferences.getFullScreenEnabled();
boolean colorMode = mPreferences.getColorModeEnabled();
colorMode &= !mDarkTheme;
if (!isIncognito() && !colorMode && !mDarkTheme && mWebpageBitmap != null) {
changeToolbarBackground(mWebpageBitmap, null);
} else if (!isIncognito() && currentView != null && !mDarkTheme) {
changeToolbarBackground(currentView.getFavicon(), null);
}
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().subscribeOn(Schedulers.worker()).subscribe();
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 = mTabsManager.getCurrentTab();
final String currentUrl = currentView != null ? currentView.getUrl() : null;
// 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_add_to_homescreen:
if (currentView != null) {
HistoryItem shortcut = new HistoryItem(currentView.getUrl(), currentView.getTitle());
shortcut.setBitmap(currentView.getFavicon());
Utils.createShortcut(this, shortcut);
}
return true;
case R.id.action_new_tab:
newTab(null, true);
return true;
case R.id.action_incognito:
startActivity(new Intent(this, IncognitoActivity.class));
overridePendingTransition(R.anim.slide_up_in, R.anim.fade_out_scale);
return true;
case R.id.action_share:
if (currentUrl != null && !UrlUtils.isSpecialUrl(currentUrl)) {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT, currentView.getTitle());
shareIntent.putExtra(Intent.EXTRA_TEXT, currentUrl);
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 (currentUrl != null && !UrlUtils.isSpecialUrl(currentUrl)) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("label", currentUrl);
clipboard.setPrimaryClip(clip);
Utils.showSnackbar(this, R.string.message_link_copied);
}
return true;
case R.id.action_settings:
startActivity(new Intent(this, SettingsActivity.class));
return true;
case R.id.action_history:
openHistory();
return true;
case R.id.action_add_bookmark:
if (currentUrl != null && !UrlUtils.isSpecialUrl(currentUrl)) {
addBookmark(currentView.getTitle(), currentUrl);
}
return true;
case R.id.action_find:
findInPage();
return true;
case R.id.action_reading_mode:
if (currentUrl != null) {
Intent read = new Intent(this, ReadingActivity.class);
read.putExtra(Constants.LOAD_READING_URL, currentUrl);
startActivity(read);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
// By using a manager, adds a bookmark and notifies third parties about that
private void addBookmark(final String title, final String url) {
final HistoryItem item = !mBookmarkManager.isBookmark(url)
? new HistoryItem(url, title)
: null;
if (item != null && mBookmarkManager.addBookmark(item)) {
mSearchAdapter.refreshBookmarks();
mEventBus.post(new BrowserEvents.BookmarkAdded(title, url));
}
}
private void deleteBookmark(final String title, final String url) {
final HistoryItem item = mBookmarkManager.isBookmark(url)
? new HistoryItem(url, title)
: null;
if (item != null && mBookmarkManager.deleteBookmark(item)) {
mSearchAdapter.refreshBookmarks();
mEventBus.post(new BrowserEvents.CurrentPageUrl(url));
}
}
/**
* 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 = mTabsManager.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<String> 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();
}
private View mCurrentView;
@Override
public void removeTabView() {
// Set the background color so the color mode color doesn't show through
mBrowserFrame.setBackgroundColor(mBackgroundColor);
mIsNewIntent = false;
mBrowserFrame.removeAllViews();
removeViewFromParent(mCurrentView);
if (mFullScreen) {
// mToolbarLayout has already been removed
mBrowserFrame.addView(mToolbarLayout);
mToolbarLayout.bringToFront();
mToolbarLayout.setTranslationY(0);
}
mCurrentView = null;
// Use a delayed handler to make the transition smooth
// otherwise it will get caught up with the showTab code
// and cause a janky motion
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mDrawerLayout.closeDrawers();
}
}, 200);
}
@Override
public void setTabView(@NonNull View view) {
if (mCurrentView == view) {
return;
}
// Set the background color so the color mode color doesn't show through
mBrowserFrame.setBackgroundColor(mBackgroundColor);
mIsNewIntent = false;
final float translation = mToolbarLayout.getTranslationY();
mBrowserFrame.removeAllViews();
removeViewFromParent(view);
removeViewFromParent(mCurrentView);
mBrowserFrame.addView(view, MATCH_PARENT);
view.requestFocus();
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();
}
view.setTranslationY(translation + height);
mToolbarLayout.setTranslationY(translation);
} else {
view.setTranslationY(0);
}
mCurrentView = view;
showActionBar();
// Use a delayed handler to make the transition smooth
// otherwise it will get caught up with the showTab code
// and cause a janky motion
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
mDrawerLayout.closeDrawers();
}
}, 200);
// new Handler().postDelayed(new Runnable() {
// @Override
// public void run() {
// Remove browser frame background to reduce overdraw
//TODO evaluate performance
// mBrowserFrame.setBackgroundColor(Color.TRANSPARENT);
// }
// }, 300);
}
@Override
public void showBlockedLocalFileDialog(DialogInterface.OnClickListener listener) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setCancelable(true)
.setTitle(R.string.title_warning)
.setMessage(R.string.message_blocked_local)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_open, listener)
.show();
}
@Override
public void showSnackbar(@StringRes int resource) {
Utils.showSnackbar(this, resource);
}
/**
* displays the WebView contained in the LightningView Also handles the
* removal of previous views
*
* @param position the poition of the tab to display
*/
// TODO move to presenter
private synchronized void showTab(final int position) {
mPresenter.tabChanged(position);
}
private static void removeViewFromParent(@Nullable View view) {
if (view == null) {
return;
}
ViewGroup parent = ((ViewGroup) view.getParent());
if (parent != null) {
parent.removeView(view);
}
}
void handleNewIntent(Intent intent) {
mPresenter.onNewIntent(intent);
}
@Override
public void closeEmptyTab() {
final WebView currentWebView = mTabsManager.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");
mTabsManager.freeMemory();
}
}
// TODO move to presenter
private synchronized boolean newTab(String url, boolean show) {
return mPresenter.newTab(url, show);
}
// TODO move this to presenter
private synchronized void deleteTab(int position) {
mPresenter.deleteTab(position);
}
void performExitCleanUp() {
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()) {
WebUtils.clearHistory(this, mHistoryDatabase);
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 = mTabsManager.getCurrentTab();
if (keyCode == KeyEvent.KEYCODE_BACK) {
showCloseDialog(mTabsManager.positionOf(currentTab));
}
return true;
}
public void closeBrowser() {
mBrowserFrame.setBackgroundColor(mBackgroundColor);
performExitCleanUp();
LightningView currentTab = mTabsManager.getCurrentTab();
if (currentTab != null && currentTab.getWebView() != null) {
mBrowserFrame.removeView(currentTab.getWebView());
}
mTabsManager.shutdown();
mEventBus.post(new BrowserEvents.TabsChanged());
finish();
}
@Override
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());
} 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 {
if (mCustomView != null || mCustomViewCallback != null) {
onHideCustomView();
} else {
deleteTab(mTabsManager.positionOf(currentTab));
}
}
} else {
Log.e(Constants.TAG, "This shouldn't happen ever");
super.onBackPressed();
}
}
}
@Override
protected void onPause() {
super.onPause();
final LightningView currentTab = mTabsManager.getCurrentTab();
Log.d(Constants.TAG, "onPause");
if (currentTab != null) {
currentTab.pauseTimers();
currentTab.onPause();
}
try {
BrowserApp.get(this).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()) {
mTabsManager.saveState();
}
}
@Override
protected void onStop() {
super.onStop();
mProxyUtils.onStop();
}
@Override
protected void onDestroy() {
Log.d(Constants.TAG, "onDestroy");
mPresenter.shutdown();
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 = mTabsManager.getCurrentTab();
Log.d(Constants.TAG, "onResume");
if (mSearchAdapter != null) {
mSearchAdapter.refreshPreferences();
mSearchAdapter.refreshBookmarks();
}
if (currentTab != null) {
currentTab.resumeTimers();
currentTab.onResume();
}
initializePreferences();
mTabsManager.resume(this);
supportInvalidateOptionsMenu();
IntentFilter filter = new IntentFilter();
filter.addAction(NETWORK_BROADCAST_ACTION);
BrowserApp.get(this).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 = mTabsManager.getCurrentTab();
if (query.isEmpty()) {
return;
}
String searchUrl = mSearchText + UrlUtils.QUERY_PLACE_HOLDER;
query = query.trim();
if (currentTab != null) {
currentTab.stopLoading();
mPresenter.loadUrlInCurrentView(UrlUtils.smartUrlFilter(query, true, searchUrl));
}
}
/**
* Animates the color of the toolbar from one color to another. Optionally animates
* the color of the tab background, for use when the tabs are displayed on the top
* of the screen.
*
* @param favicon the Bitmap to extract the color from
* @param tabBackground the optional LinearLayout to color
*/
@Override
public void changeToolbarBackground(@NonNull Bitmap favicon, @Nullable final Drawable tabBackground) {
final int defaultColor = ContextCompat.getColor(this, R.color.primary_color);
if (mCurrentUiColor == Color.BLACK) {
mCurrentUiColor = defaultColor;
}
Palette.from(favicon).generate(new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
// OR with opaque black to remove transparency glitches
int color = 0xff000000 | palette.getVibrantColor(defaultColor);
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 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 && !UrlUtils.isSpecialUrl(url)) {
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 (UrlUtils.isSpecialUrl(url)) {
url = "";
}
mSearch.setText(url);
}
}
@Override
public void updateProgress(int n) {
setIsLoading(n < 100);
mProgressBar.setProgress(n);
}
void addItemToHistory(@Nullable final String title, @NonNull final String url) {
if (UrlUtils.isSpecialUrl(url)) {
return;
}
BrowserApp.getIOThread().execute(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);
}
}
});
}
/**
* method to generate search suggestions for the AutoCompleteTextView from
* previously searched URLs
*/
private void initializeSearchSuggestions(final AutoCompleteTextView getUrl) {
mSearchAdapter = new SearchAdapter(this, mDarkTheme, isIncognito());
getUrl.setThreshold(1);
getUrl.setDropDownWidth(-1);
getUrl.setDropDownAnchor(R.id.toolbar_layout);
getUrl.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int pos, long l) {
String url = null;
CharSequence urlString = ((TextView) view.findViewById(R.id.url)).getText();
if (urlString != null) {
url = urlString.toString();
}
if (url == null || url.startsWith(BrowserActivity.this.getString(R.string.suggestion))) {
CharSequence searchString = ((TextView) view.findViewById(R.id.title)).getText();
if (searchString != null) {
url = searchString.toString();
}
}
if (url == null) {
return;
}
getUrl.setText(url);
searchTheWeb(url);
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getUrl.getWindowToken(), 0);
final LightningView currentTab = mTabsManager.getCurrentTab();
if (currentTab != null) {
currentTab.requestFocus();
}
}
});
getUrl.setSelectAllOnFocus(true);
getUrl.setAdapter(mSearchAdapter);
}
/**
* function that opens the HTML history page in the browser
*/
private void openHistory() {
new HistoryPage(mTabsManager.getCurrentTab(), getApplication(), mHistoryDatabase).load();
}
/**
* 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<Uri> 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<Uri[]> 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);
startActivityForResult(chooserIntent, 1);
}
@Override
public synchronized void onShowCustomView(View view, CustomViewCallback callback) {
int requestedOrientation = mOriginalOrientation = getRequestedOrientation();
onShowCustomView(view, callback, requestedOrientation);
}
@Override
public synchronized void onShowCustomView(final View view, CustomViewCallback callback, int requestedOrientation) {
final LightningView currentTab = mTabsManager.getCurrentTab();
if (view == null || mCustomView != null) {
if (callback != null) {
try {
callback.onCustomViewHidden();
} catch (Exception e) {
Log.e(Constants.TAG, "Error hiding custom view", e);
}
}
return;
}
try {
view.setKeepScreenOn(true);
} catch (SecurityException e) {
Log.e(Constants.TAG, "WebView is not allowed to keep the screen on");
}
mOriginalOrientation = getRequestedOrientation();
mCustomViewCallback = callback;
mCustomView = view;
setRequestedOrientation(requestedOrientation);
final FrameLayout decorView = (FrameLayout) getWindow().getDecorView();
mFullscreenContainer = new FrameLayout(this);
mFullscreenContainer.setBackgroundColor(ContextCompat.getColor(this, android.R.color.black));
if (view instanceof FrameLayout) {
if (((FrameLayout) view).getFocusedChild() instanceof VideoView) {
mVideoView = (VideoView) ((FrameLayout) view).getFocusedChild();
mVideoView.setOnErrorListener(new VideoCompletionListener());
mVideoView.setOnCompletionListener(new VideoCompletionListener());
}
} else if (view instanceof VideoView) {
mVideoView = (VideoView) view;
mVideoView.setOnErrorListener(new VideoCompletionListener());
mVideoView.setOnCompletionListener(new VideoCompletionListener());
}
decorView.addView(mFullscreenContainer, COVER_SCREEN_PARAMS);
mFullscreenContainer.addView(mCustomView, COVER_SCREEN_PARAMS);
decorView.requestLayout();
setFullscreen(true, true);
if (currentTab != null) {
currentTab.setVisibility(View.INVISIBLE);
}
}
@Override
public void onHideCustomView() {
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");
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);
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);
}
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 synchronized void onCreateWindow(Message resultMsg) {
if (resultMsg == null) {
return;
}
if (newTab("", true)) {
LightningView newTab = mTabsManager.getTabAtPosition(mTabsManager.size() - 1);
if (newTab != null) {
final WebView webView = newTab.getWebView();
if (webView != null) {
WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
transport.setWebView(webView);
resultMsg.sendToTarget();
}
}
}
}
/**
* Closes the specified {@link LightningView}. This implements
* the JavaScript callback that asks the tab to close itself and
* is especially helpful when a page creates a redirect and does
* not need the tab to stay open any longer.
*
* @param view the LightningView to close, delete it.
*/
@Override
public void onCloseWindow(LightningView view) {
deleteTab(mTabsManager.positionOf(view));
}
/**
* Hide the ActionBar using an animation if we are in full-screen
* mode. This method also re-parents the ActionBar if its parent is
* incorrect so that the animation can happen correctly.
*/
@Override
public void hideActionBar() {
final WebView currentWebView = mTabsManager.getCurrentWebView();
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);
if (currentWebView != null) {
currentWebView.setTranslationY(mToolbarLayout.getHeight());
}
}
if (mToolbarLayout == null || currentWebView == null)
return;
final int height = mToolbarLayout.getHeight();
if (mToolbarLayout.getTranslationY() > -0.01f) {
Animation show = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float trans = (1.0f - interpolatedTime) * height;
mToolbarLayout.setTranslationY(trans - height);
currentWebView.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 = mTabsManager.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 = mTabsManager.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 = mTabsManager.getCurrentTab();
if (currentTab != null) {
if (currentTab.getProgress() < 100) {
currentTab.stopLoading();
} else {
currentTab.reload();
}
}
}
/**
* Handle the click event for the views that are using
* this class as a click listener. This method should
* distinguish between the various views using their IDs.
*
* @param v the view that the user has clicked
*/
@Override
public void onClick(View v) {
final LightningView currentTab = mTabsManager.getCurrentTab();
if (currentTab == null) {
return;
}
switch (v.getId()) {
case R.id.arrow_button:
if (mSearch != null && mSearch.hasFocus()) {
currentTab.requestFocus();
} else if (mShowTabsInDrawer) {
mDrawerLayout.openDrawer(mDrawerLeft);
} else {
currentTab.loadHomepage();
}
break;
case R.id.button_next:
currentTab.findNext();
break;
case R.id.button_back:
currentTab.findPrevious();
break;
case R.id.button_quit:
currentTab.clearFindMatches();
mSearchBar.setVisibility(View.GONE);
break;
case R.id.action_reading:
Intent read = new Intent(this, ReadingActivity.class);
read.putExtra(Constants.LOAD_READING_URL, currentTab.getUrl());
startActivity(read);
break;
case R.id.action_toggle_desktop:
currentTab.toggleDesktopUA(this);
currentTab.reload();
closeDrawers();
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));
mTabsManager.notifyConnectionStatus(isConnected);
}
};
/**
* Handle the callback that permissions requested have been granted or not.
* This method should act upon the results of the permissions request.
*
* @param requestCode the request code sent when initially making the request
* @param permissions the array of the permissions that was requested
* @param grantResults the results of the permissions requests that provides
* information on whether the request was granted or not
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
PermissionsManager.getInstance().notifyPermissionsChange(permissions, grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
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) {
mPresenter.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);
}
@Subscribe
public void loadHistory(final BrowserEvents.OpenHistoryInCurrentTab event) {
new HistoryPage(mTabsManager.getCurrentTab(), getApplication(), mHistoryDatabase).load();
}
/**
* 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 BookmarkEvents.ToggleBookmarkForCurrentPage}
* message this receiver answer firing the
* {@link BrowserEvents.BookmarkAdded} message
*
* @param event an event that the user wishes to bookmark the current page
*/
@Subscribe
public void bookmarkCurrentPage(final BookmarkEvents.ToggleBookmarkForCurrentPage event) {
final LightningView currentTab = mTabsManager.getCurrentTab();
final String url = currentTab != null ? currentTab.getUrl() : null;
final String title = currentTab != null ? currentTab.getTitle() : null;
if (url == null) {
return;
}
if (!mBookmarkManager.isBookmark(url)) {
addBookmark(title, url);
} else {
deleteBookmark(title, url);
}
}
/**
* 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 = mTabsManager.getCurrentTab();
if (currentTab != null && currentTab.getUrl().startsWith(Constants.FILE)
&& currentTab.getUrl().endsWith(BookmarkPage.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 = mTabsManager.getCurrentTab();
if (currentTab != null && currentTab.getUrl().startsWith(Constants.FILE)
&& currentTab.getUrl().endsWith(BookmarkPage.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 = 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);
}
}
};
}