Stefano Pacifici
9 years ago
81 changed files with 3389 additions and 2221 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,289 @@
@@ -0,0 +1,289 @@
|
||||
package acr.browser.lightning.activity; |
||||
|
||||
import android.app.Activity; |
||||
import android.content.Context; |
||||
import android.content.DialogInterface; |
||||
import android.content.Intent; |
||||
import android.support.annotation.Nullable; |
||||
import android.support.v7.app.AlertDialog; |
||||
import android.util.Log; |
||||
import android.webkit.WebView; |
||||
|
||||
import com.squareup.otto.Bus; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import javax.inject.Inject; |
||||
import javax.inject.Singleton; |
||||
|
||||
import acr.browser.lightning.R; |
||||
import acr.browser.lightning.bus.BrowserEvents; |
||||
import acr.browser.lightning.constant.Constants; |
||||
import acr.browser.lightning.preference.PreferenceManager; |
||||
import acr.browser.lightning.utils.UrlUtils; |
||||
import acr.browser.lightning.utils.Utils; |
||||
import acr.browser.lightning.view.LightningView; |
||||
|
||||
/** |
||||
* @author Stefano Pacifici |
||||
* @date 2015/09/14 |
||||
*/ |
||||
@Singleton |
||||
public class TabsManager { |
||||
|
||||
private static final String TAG = TabsManager.class.getSimpleName(); |
||||
private final List<LightningView> mWebViewList = new ArrayList<>(); |
||||
private LightningView mCurrentTab; |
||||
|
||||
@Inject |
||||
PreferenceManager mPreferenceManager; |
||||
|
||||
@Inject |
||||
Bus mEventBus; |
||||
|
||||
@Inject |
||||
public TabsManager() {} |
||||
|
||||
public synchronized void restoreTabsAndHandleIntent(final Activity activity, |
||||
final Intent intent, |
||||
final boolean incognito) { |
||||
String url = null; |
||||
if (intent != null) { |
||||
url = intent.getDataString(); |
||||
} |
||||
mWebViewList.clear(); |
||||
mCurrentTab = null; |
||||
if (!incognito && mPreferenceManager.getRestoreLostTabsEnabled()) { |
||||
final String mem = mPreferenceManager.getMemoryUrl(); |
||||
mPreferenceManager.setMemoryUrl(""); |
||||
String[] array = Utils.getArray(mem); |
||||
for (String urlString : array) { |
||||
if (!urlString.isEmpty()) { |
||||
newTab(activity, urlString, incognito); |
||||
} |
||||
} |
||||
} |
||||
if (url != null) { |
||||
if (url.startsWith(Constants.FILE)) { |
||||
final String urlToLoad = url; |
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity); |
||||
builder.setCancelable(true) |
||||
.setTitle(R.string.title_warning) |
||||
.setMessage(R.string.message_blocked_local) |
||||
.setNegativeButton(android.R.string.cancel, null) |
||||
.setPositiveButton(R.string.action_open, new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int which) { |
||||
newTab(activity, urlToLoad, incognito); |
||||
} |
||||
}) |
||||
.show(); |
||||
} else { |
||||
newTab(activity, url, incognito); |
||||
} |
||||
} |
||||
if (mWebViewList.size() == 0) { |
||||
newTab(activity, null, incognito); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Return a clone of the current tabs list. The list will not be updated, the user has to fetch |
||||
* a new copy when notified. |
||||
* |
||||
* @return a copy of the current tabs list |
||||
*/ |
||||
public List<LightningView> getTabsList() { |
||||
return new ArrayList<>(mWebViewList); |
||||
} |
||||
|
||||
/** |
||||
* Return the tab at the given position in tabs list, or null if position is not in tabs list |
||||
* range. |
||||
* |
||||
* @param position the index in tabs list |
||||
* @return the corespondent {@link LightningView}, or null if the index is invalid |
||||
*/ |
||||
@Nullable |
||||
public synchronized LightningView getTabAtPosition(final int position) { |
||||
if (position < 0 || position >= mWebViewList.size()) { |
||||
return null; |
||||
} |
||||
|
||||
return mWebViewList.get(position); |
||||
} |
||||
|
||||
/** |
||||
* Try to low memory pressure |
||||
*/ |
||||
public synchronized void freeMemory() { |
||||
for (LightningView tab : mWebViewList) { |
||||
tab.freeMemory(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Shutdown the manager |
||||
*/ |
||||
public synchronized void shutdown() { |
||||
for (LightningView tab : mWebViewList) { |
||||
tab.onDestroy(); |
||||
} |
||||
mWebViewList.clear(); |
||||
mCurrentTab = null; |
||||
} |
||||
|
||||
/** |
||||
* Resume the tabs |
||||
* |
||||
* @param context |
||||
*/ |
||||
public synchronized void resume(final Context context) { |
||||
for (LightningView tab : mWebViewList) { |
||||
tab.initializePreferences(null, context); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Forward network connection status to the webviews. |
||||
* |
||||
* @param isConnected |
||||
*/ |
||||
public synchronized void notifyConnectionStatus(final boolean isConnected) { |
||||
for (LightningView tab : mWebViewList) { |
||||
final WebView webView = tab.getWebView(); |
||||
if (webView != null) { |
||||
webView.setNetworkAvailable(isConnected); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @return The number of currently opened tabs |
||||
*/ |
||||
public synchronized int size() { |
||||
return mWebViewList.size(); |
||||
} |
||||
|
||||
/** |
||||
* Create and return a new tab. The tab is automatically added to the tabs list. |
||||
* |
||||
* @param activity |
||||
* @param url |
||||
* @param isIncognito |
||||
* @return |
||||
*/ |
||||
public synchronized LightningView newTab(final Activity activity, |
||||
final String url, |
||||
final boolean isIncognito) { |
||||
final LightningView tab = new LightningView(activity, url, isIncognito); |
||||
mWebViewList.add(tab); |
||||
return tab; |
||||
} |
||||
|
||||
/** |
||||
* Remove a tab and return its reference or null if the position is not in tabs range |
||||
* |
||||
* @param position The position of the tab to remove |
||||
* @return The removed tab reference or null |
||||
*/ |
||||
@Nullable |
||||
public synchronized LightningView removeTab(final int position) { |
||||
if (position >= mWebViewList.size()) { |
||||
return null; |
||||
} |
||||
final LightningView tab = mWebViewList.remove(position); |
||||
if (mCurrentTab == tab) { |
||||
mCurrentTab = null; |
||||
} |
||||
tab.onDestroy(); |
||||
Log.d(Constants.TAG, tab.toString()); |
||||
return tab; |
||||
} |
||||
|
||||
public synchronized void deleteTab(int position) { |
||||
final LightningView currentTab = getCurrentTab(); |
||||
int current = positionOf(currentTab); |
||||
|
||||
if (current == position) { |
||||
if (size() == 1) { |
||||
mCurrentTab = null; |
||||
} else if (current < size() - 1 ) { |
||||
// There is another tab after this one
|
||||
mCurrentTab = getTabAtPosition(current + 1); |
||||
} else { |
||||
mCurrentTab = getTabAtPosition(current - 1); |
||||
} |
||||
removeTab(current); |
||||
} else { |
||||
removeTab(position); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Return the position of the given tab. |
||||
* |
||||
* @param tab the tab to look for |
||||
* @return the position of the tab or -1 if the tab is not in the list |
||||
*/ |
||||
public synchronized int positionOf(final LightningView tab) { |
||||
return mWebViewList.indexOf(tab); |
||||
} |
||||
|
||||
/** |
||||
* @return A string representation of the currently opened tabs |
||||
*/ |
||||
public String tabsString() { |
||||
final StringBuilder builder = new StringBuilder(); |
||||
for (LightningView tab : mWebViewList) { |
||||
final String url = tab.getUrl(); |
||||
if (!url.isEmpty()) { |
||||
builder.append(url).append("|$|SEPARATOR|$|"); |
||||
} |
||||
} |
||||
return builder.toString(); |
||||
} |
||||
|
||||
/** |
||||
* Return the {@link WebView} associated to the current tab, or null if there is no current tab |
||||
* |
||||
* @return a {@link WebView} or null |
||||
*/ |
||||
@Nullable |
||||
public synchronized WebView getCurrentWebView() { |
||||
return mCurrentTab != null ? mCurrentTab.getWebView() : null; |
||||
} |
||||
|
||||
/** |
||||
* TODO We should remove also this, but probably not |
||||
* |
||||
* @return |
||||
*/ |
||||
@Nullable |
||||
public synchronized LightningView getCurrentTab() { |
||||
return mCurrentTab; |
||||
} |
||||
|
||||
/** |
||||
* Switch the current tab to the one at the given position. It returns the selected. After this |
||||
* call {@link TabsManager#getCurrentTab()} return the same reference returned by this method if |
||||
* position is valid. |
||||
* |
||||
* @return the selected tab or null if position is out of tabs range |
||||
*/ |
||||
@Nullable |
||||
public synchronized LightningView switchToTab(final int position) { |
||||
if (position < 0 || position >= mWebViewList.size()) { |
||||
Log.e(TAG, "Returning a null LightningView requested for position: " + position); |
||||
return null; |
||||
} else { |
||||
final LightningView tab = mWebViewList.get(position); |
||||
if (tab != null) { |
||||
mCurrentTab = tab; |
||||
} |
||||
return tab; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
package acr.browser.lightning.bus; |
||||
|
||||
/** |
||||
* Collections of navigation events, like go back or go forward |
||||
* |
||||
* @author Stefano Pacifici |
||||
* @date 2015/09/15 |
||||
*/ |
||||
public class NavigationEvents { |
||||
private NavigationEvents() { |
||||
// No instances please
|
||||
} |
||||
|
||||
/** |
||||
* Fired by {@link acr.browser.lightning.fragment.TabsFragment} when the user presses back |
||||
* button. |
||||
*/ |
||||
public static class GoBack { |
||||
} |
||||
|
||||
/** |
||||
* Fired by {@link acr.browser.lightning.fragment.TabsFragment} when the user presses forward |
||||
* button. |
||||
*/ |
||||
public static class GoForward { |
||||
} |
||||
|
||||
/** |
||||
* Fired by {@link acr.browser.lightning.fragment.TabsFragment} when the user presses the home |
||||
* button. |
||||
*/ |
||||
public static class GoHome { |
||||
} |
||||
} |
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
package acr.browser.lightning.bus; |
||||
|
||||
/** |
||||
* A collection of events been sent by {@link acr.browser.lightning.fragment.TabsFragment} |
||||
* |
||||
* @author Stefano Pacifici |
||||
* @date 2015/09/14 |
||||
*/ |
||||
public final class TabEvents { |
||||
|
||||
private TabEvents() { |
||||
// No instances
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user click on the |
||||
* tab exit button |
||||
*/ |
||||
public static class CloseTab { |
||||
public final int position; |
||||
|
||||
public CloseTab(int position) { |
||||
this.position = position; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user click on the |
||||
* tab itself. |
||||
*/ |
||||
public static class ShowTab { |
||||
public final int position; |
||||
|
||||
public ShowTab(int position) { |
||||
this.position = position; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user long press on the |
||||
* tab itself. |
||||
*/ |
||||
public static class ShowCloseDialog { |
||||
public final int position; |
||||
|
||||
public ShowCloseDialog(int position) { |
||||
this.position = position; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user want to create a |
||||
* new tab. |
||||
*/ |
||||
public static class NewTab { |
||||
} |
||||
|
||||
/** |
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user long presses on |
||||
* new tab button. |
||||
*/ |
||||
public static class NewTabLongPress { |
||||
} |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
package acr.browser.lightning.fragment; |
||||
|
||||
import android.os.Bundle; |
||||
import android.preference.PreferenceFragment; |
||||
|
||||
import javax.inject.Inject; |
||||
|
||||
import acr.browser.lightning.app.BrowserApp; |
||||
import acr.browser.lightning.preference.PreferenceManager; |
||||
|
||||
/** |
||||
* Simplify {@link PreferenceManager} inject in all the PreferenceFragments |
||||
* |
||||
* @author Stefano Pacifici |
||||
* @date 2015/09/16 |
||||
*/ |
||||
public class LightningPreferenceFragment extends PreferenceFragment { |
||||
|
||||
@Inject |
||||
PreferenceManager mPreferenceManager; |
||||
|
||||
@Override |
||||
public void onCreate(Bundle savedInstanceState) { |
||||
super.onCreate(savedInstanceState); |
||||
BrowserApp.getAppComponent().inject(this); |
||||
} |
||||
} |
@ -0,0 +1,359 @@
@@ -0,0 +1,359 @@
|
||||
package acr.browser.lightning.fragment; |
||||
|
||||
import android.content.Context; |
||||
import android.graphics.Bitmap; |
||||
import android.graphics.Canvas; |
||||
import android.graphics.Color; |
||||
import android.graphics.ColorFilter; |
||||
import android.graphics.ColorMatrix; |
||||
import android.graphics.ColorMatrixColorFilter; |
||||
import android.graphics.Paint; |
||||
import android.graphics.PorterDuff; |
||||
import android.graphics.drawable.BitmapDrawable; |
||||
import android.graphics.drawable.Drawable; |
||||
import android.os.Build; |
||||
import android.os.Bundle; |
||||
import android.support.annotation.IdRes; |
||||
import android.support.annotation.NonNull; |
||||
import android.support.annotation.Nullable; |
||||
import android.support.v4.app.Fragment; |
||||
import android.support.v4.view.ViewCompat; |
||||
import android.support.v7.widget.LinearLayoutManager; |
||||
import android.support.v7.widget.RecyclerView; |
||||
import android.support.v7.widget.RecyclerView.LayoutManager; |
||||
import android.view.LayoutInflater; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
import android.widget.FrameLayout; |
||||
import android.widget.ImageView; |
||||
import android.widget.LinearLayout; |
||||
import android.widget.TextView; |
||||
|
||||
import com.squareup.otto.Bus; |
||||
import com.squareup.otto.Subscribe; |
||||
|
||||
import javax.inject.Inject; |
||||
|
||||
import acr.browser.lightning.R; |
||||
import acr.browser.lightning.activity.TabsManager; |
||||
import acr.browser.lightning.app.BrowserApp; |
||||
import acr.browser.lightning.bus.BrowserEvents; |
||||
import acr.browser.lightning.bus.NavigationEvents; |
||||
import acr.browser.lightning.bus.TabEvents; |
||||
import acr.browser.lightning.controller.UIController; |
||||
import acr.browser.lightning.preference.PreferenceManager; |
||||
import acr.browser.lightning.utils.ThemeUtils; |
||||
import acr.browser.lightning.utils.Utils; |
||||
import acr.browser.lightning.view.LightningView; |
||||
|
||||
/** |
||||
* A fragment that holds and manages the tabs and interaction with the tabs. |
||||
* It is reliant on the BrowserController in order to get the current UI state |
||||
* of the browser. It also uses the BrowserController to signal that the UI needs |
||||
* to change. This class contains the adapter used by both the drawer tabs and |
||||
* the desktop tabs. It delegates touch events for the tab UI appropriately. |
||||
*/ |
||||
public class TabsFragment extends Fragment implements View.OnClickListener, View.OnLongClickListener { |
||||
|
||||
private static final String TAG = TabsFragment.class.getSimpleName(); |
||||
|
||||
/** |
||||
* Arguments boolean to tell the fragment it is displayed in the drawner or on the tab strip |
||||
* If true, the fragment is in the left drawner in the strip otherwise. |
||||
*/ |
||||
public static final String VERTICAL_MODE = TAG + ".VERTICAL_MODE"; |
||||
public static final String IS_INCOGNITO = TAG + ".IS_INCOGNITO"; |
||||
|
||||
private boolean mIsIncognito, mDarkTheme; |
||||
private int mIconColor; |
||||
private boolean mColorMode = true; |
||||
private boolean mShowInNavigationDrawer; |
||||
|
||||
private RecyclerView mRecyclerView; |
||||
private LightningViewAdapter mTabsAdapter; |
||||
private UIController mUiController; |
||||
|
||||
@Inject |
||||
TabsManager tabsManager; |
||||
|
||||
@Inject |
||||
Bus mBus; |
||||
|
||||
@Inject |
||||
PreferenceManager mPreferences; |
||||
|
||||
public TabsFragment() { |
||||
BrowserApp.getAppComponent().inject(this); |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(@Nullable Bundle savedInstanceState) { |
||||
super.onCreate(savedInstanceState); |
||||
final Bundle arguments = getArguments(); |
||||
final Context context = getContext(); |
||||
mUiController = (UIController) getActivity(); |
||||
mIsIncognito = arguments.getBoolean(IS_INCOGNITO, false); |
||||
mShowInNavigationDrawer = arguments.getBoolean(VERTICAL_MODE, true); |
||||
mDarkTheme = mPreferences.getUseTheme() != 0 || mIsIncognito; |
||||
mColorMode = mPreferences.getColorModeEnabled(); |
||||
mColorMode &= !mDarkTheme; |
||||
mIconColor = mDarkTheme ? |
||||
ThemeUtils.getIconDarkThemeColor(context) : |
||||
ThemeUtils.getIconLightThemeColor(context); |
||||
} |
||||
|
||||
@Nullable |
||||
@Override |
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
||||
final View view; |
||||
final LayoutManager layoutManager; |
||||
if (mShowInNavigationDrawer) { |
||||
view = inflater.inflate(R.layout.tab_drawer, container, false); |
||||
layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false); |
||||
setupFrameLayoutButton(view, R.id.tab_header_button, R.id.plusIcon); |
||||
setupFrameLayoutButton(view, R.id.new_tab_button, R.id.icon_plus); |
||||
setupFrameLayoutButton(view, R.id.action_back, R.id.icon_back); |
||||
setupFrameLayoutButton(view, R.id.action_forward, R.id.icon_forward); |
||||
setupFrameLayoutButton(view, R.id.action_home, R.id.icon_home); |
||||
} else { |
||||
view = inflater.inflate(R.layout.tab_strip, container, false); |
||||
layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false); |
||||
} |
||||
mRecyclerView = (RecyclerView) view.findViewById(R.id.tabs_list); |
||||
mRecyclerView.setLayoutManager(layoutManager); |
||||
mTabsAdapter = new LightningViewAdapter(mShowInNavigationDrawer); |
||||
mRecyclerView.setAdapter(mTabsAdapter); |
||||
mRecyclerView.setHasFixedSize(true); |
||||
return view; |
||||
} |
||||
|
||||
private void setupFrameLayoutButton(@NonNull final View root, @IdRes final int buttonId, |
||||
@IdRes final int imageId) { |
||||
final View frameButton = root.findViewById(buttonId); |
||||
final ImageView buttonImage = (ImageView) root.findViewById(imageId); |
||||
frameButton.setOnClickListener(this); |
||||
frameButton.setOnLongClickListener(this); |
||||
buttonImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroyView() { |
||||
super.onDestroyView(); |
||||
mRecyclerView = null; |
||||
mTabsAdapter = null; |
||||
} |
||||
|
||||
@Override |
||||
public void onStart() { |
||||
super.onStart(); |
||||
mBus.register(this); |
||||
} |
||||
|
||||
@Override |
||||
public void onResume() { |
||||
super.onResume(); |
||||
// Force adapter refresh
|
||||
mTabsAdapter.notifyDataSetChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public void onStop() { |
||||
super.onStop(); |
||||
mBus.unregister(this); |
||||
} |
||||
|
||||
@Subscribe |
||||
public void tabsChanged(final BrowserEvents.TabsChanged event) { |
||||
if (mTabsAdapter != null) { |
||||
mTabsAdapter.notifyDataSetChanged(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onClick(View v) { |
||||
switch (v.getId()) { |
||||
case R.id.new_tab_button: |
||||
mBus.post(new TabEvents.NewTab()); |
||||
break; |
||||
case R.id.action_back: |
||||
mBus.post(new NavigationEvents.GoBack()); |
||||
break; |
||||
case R.id.action_forward: |
||||
mBus.post(new NavigationEvents.GoForward()); |
||||
break; |
||||
case R.id.action_home: |
||||
mBus.post(new NavigationEvents.GoHome()); |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean onLongClick(View v) { |
||||
switch (v.getId()) { |
||||
case R.id.action_new_tab: |
||||
mBus.post(new TabEvents.NewTabLongPress()); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
public class LightningViewAdapter extends RecyclerView.Adapter<LightningViewAdapter.LightningViewHolder> { |
||||
|
||||
private final int mLayoutResourceId; |
||||
private final Drawable mBackgroundTabDrawable; |
||||
private final Drawable mForegroundTabDrawable; |
||||
private final Bitmap mForegroundTabBitmap; |
||||
private ColorMatrix mColorMatrix; |
||||
private Paint mPaint; |
||||
private ColorFilter mFilter; |
||||
private static final float DESATURATED = 0.5f; |
||||
|
||||
private final boolean mDrawerTabs; |
||||
|
||||
public LightningViewAdapter(final boolean vertical) { |
||||
this.mLayoutResourceId = vertical ? R.layout.tab_list_item : R.layout.tab_list_item_horizontal; |
||||
this.mDrawerTabs = vertical; |
||||
|
||||
if (vertical) { |
||||
mBackgroundTabDrawable = null; |
||||
mForegroundTabBitmap = null; |
||||
mForegroundTabDrawable = ThemeUtils.getSelectedBackground(getContext(), mDarkTheme); |
||||
} else { |
||||
int backgroundColor = Utils.mixTwoColors(ThemeUtils.getPrimaryColor(getContext()), Color.BLACK, 0.75f); |
||||
Bitmap backgroundTabBitmap = Bitmap.createBitmap(Utils.dpToPx(175), Utils.dpToPx(30), Bitmap.Config.ARGB_8888); |
||||
Utils.drawTrapezoid(new Canvas(backgroundTabBitmap), backgroundColor, true); |
||||
mBackgroundTabDrawable = new BitmapDrawable(getResources(), backgroundTabBitmap); |
||||
|
||||
int foregroundColor = ThemeUtils.getPrimaryColor(getContext()); |
||||
mForegroundTabBitmap = Bitmap.createBitmap(Utils.dpToPx(175), Utils.dpToPx(30), Bitmap.Config.ARGB_8888); |
||||
Utils.drawTrapezoid(new Canvas(mForegroundTabBitmap), foregroundColor, false); |
||||
mForegroundTabDrawable = null; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public LightningViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { |
||||
LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext()); |
||||
View view = inflater.inflate(mLayoutResourceId, viewGroup, false); |
||||
return new LightningViewHolder(view); |
||||
} |
||||
|
||||
@Override |
||||
public void onBindViewHolder(final LightningViewHolder holder, int position) { |
||||
holder.exitButton.setTag(position); |
||||
|
||||
ViewCompat.jumpDrawablesToCurrentState(holder.exitButton); |
||||
|
||||
LightningView web = tabsManager.getTabAtPosition(position); |
||||
if (web == null) { |
||||
return; |
||||
} |
||||
holder.txtTitle.setText(web.getTitle()); |
||||
|
||||
final Bitmap favicon = web.getFavicon(); |
||||
if (web.isForegroundTab()) { |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||
holder.txtTitle.setTextAppearance(R.style.boldText); |
||||
} else { |
||||
holder.txtTitle.setTextAppearance(getContext(), R.style.boldText); |
||||
} |
||||
Drawable foregroundDrawable; |
||||
if (!mDrawerTabs) { |
||||
foregroundDrawable = new BitmapDrawable(getResources(), mForegroundTabBitmap); |
||||
if (!mIsIncognito && mColorMode) { |
||||
foregroundDrawable.setColorFilter(mUiController.getUiColor(), PorterDuff.Mode.SRC_IN); |
||||
} |
||||
} else { |
||||
foregroundDrawable = mForegroundTabDrawable; |
||||
} |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { |
||||
holder.layout.setBackground(foregroundDrawable); |
||||
} else { |
||||
holder.layout.setBackgroundDrawable(foregroundDrawable); |
||||
} |
||||
if (!mIsIncognito && mColorMode) { |
||||
mUiController.changeToolbarBackground(favicon, foregroundDrawable); |
||||
} |
||||
holder.favicon.setImageBitmap(favicon); |
||||
} else { |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||
holder.txtTitle.setTextAppearance(R.style.normalText); |
||||
} else { |
||||
holder.txtTitle.setTextAppearance(getContext(), R.style.normalText); |
||||
} |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { |
||||
holder.layout.setBackground(mBackgroundTabDrawable); |
||||
} else { |
||||
holder.layout.setBackgroundDrawable(mBackgroundTabDrawable); |
||||
} |
||||
holder.favicon.setImageBitmap(getDesaturatedBitmap(favicon)); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public int getItemCount() { |
||||
return tabsManager.size(); |
||||
} |
||||
|
||||
public Bitmap getDesaturatedBitmap(Bitmap favicon) { |
||||
Bitmap grayscaleBitmap = Bitmap.createBitmap(favicon.getWidth(), |
||||
favicon.getHeight(), Bitmap.Config.ARGB_8888); |
||||
|
||||
Canvas c = new Canvas(grayscaleBitmap); |
||||
if (mColorMatrix == null || mFilter == null || mPaint == null) { |
||||
mPaint = new Paint(); |
||||
mColorMatrix = new ColorMatrix(); |
||||
mColorMatrix.setSaturation(DESATURATED); |
||||
mFilter = new ColorMatrixColorFilter(mColorMatrix); |
||||
mPaint.setColorFilter(mFilter); |
||||
} |
||||
|
||||
c.drawBitmap(favicon, 0, 0, mPaint); |
||||
return grayscaleBitmap; |
||||
} |
||||
|
||||
public class LightningViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { |
||||
|
||||
public LightningViewHolder(View view) { |
||||
super(view); |
||||
txtTitle = (TextView) view.findViewById(R.id.textTab); |
||||
favicon = (ImageView) view.findViewById(R.id.faviconTab); |
||||
exit = (ImageView) view.findViewById(R.id.deleteButton); |
||||
layout = (LinearLayout) view.findViewById(R.id.tab_item_background); |
||||
exitButton = (FrameLayout) view.findViewById(R.id.deleteAction); |
||||
exit.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); |
||||
|
||||
exitButton.setOnClickListener(this); |
||||
layout.setOnClickListener(this); |
||||
layout.setOnLongClickListener(this); |
||||
} |
||||
|
||||
final TextView txtTitle; |
||||
final ImageView favicon; |
||||
final ImageView exit; |
||||
final FrameLayout exitButton; |
||||
final LinearLayout layout; |
||||
|
||||
@Override |
||||
public void onClick(View v) { |
||||
if (v == exitButton) { |
||||
// Close tab
|
||||
mBus.post(new TabEvents.CloseTab(getAdapterPosition())); |
||||
} |
||||
if (v == layout) { |
||||
mBus.post(new TabEvents.ShowTab(getAdapterPosition())); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean onLongClick(View v) { |
||||
// Show close dialog
|
||||
mBus.post(new TabEvents.ShowCloseDialog(getAdapterPosition())); |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,30 +0,0 @@
@@ -1,30 +0,0 @@
|
||||
/* |
||||
* Copyright 2014 A.C.R. Development |
||||
*/ |
||||
package acr.browser.lightning.object; |
||||
|
||||
import android.content.Context; |
||||
import android.os.Handler; |
||||
import android.os.Message; |
||||
|
||||
import acr.browser.lightning.controller.BrowserController; |
||||
|
||||
public class ClickHandler extends Handler { |
||||
|
||||
private BrowserController mBrowserController; |
||||
|
||||
public ClickHandler(Context context) { |
||||
try { |
||||
mBrowserController = (BrowserController) context; |
||||
} catch (ClassCastException e) { |
||||
throw new ClassCastException(context + " must implement BrowserController"); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void handleMessage(Message msg) { |
||||
super.handleMessage(msg); |
||||
String url = msg.getData().getString("url"); |
||||
mBrowserController.longClickPage(url); |
||||
} |
||||
} |
@ -1,75 +0,0 @@
@@ -1,75 +0,0 @@
|
||||
package acr.browser.lightning.utils; |
||||
|
||||
import android.app.Activity; |
||||
import android.content.pm.PackageManager; |
||||
import android.os.Build; |
||||
import android.support.annotation.NonNull; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
|
||||
/** |
||||
* Copyright 8/22/2015 Anthony Restaino |
||||
*/ |
||||
public class PermissionsManager { |
||||
|
||||
private static PermissionsManager mInstance; |
||||
private final Set<String> mPendingRequests = new HashSet<>(); |
||||
|
||||
public static PermissionsManager getInstance() { |
||||
if (mInstance == null) { |
||||
mInstance = new PermissionsManager(); |
||||
} |
||||
return mInstance; |
||||
} |
||||
|
||||
public void requestPermissionsIfNecessary(Activity activity, @NonNull String[] permissions) { |
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || activity == null) { |
||||
return; |
||||
} |
||||
List<String> permList = new ArrayList<>(); |
||||
for (String perm : permissions) { |
||||
if (activity.checkSelfPermission(perm) != PackageManager.PERMISSION_GRANTED |
||||
&& !mPendingRequests.contains(perm)) { |
||||
permList.add(perm); |
||||
} |
||||
} |
||||
if (!permList.isEmpty()) { |
||||
String[] permsToRequest = permList.toArray(new String[permList.size()]); |
||||
mPendingRequests.addAll(permList); |
||||
activity.requestPermissions(permsToRequest, 1); |
||||
} |
||||
|
||||
} |
||||
|
||||
public static boolean checkPermission(Activity activity, @NonNull String permission) { |
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { |
||||
return true; |
||||
} else if (activity == null) { |
||||
return false; |
||||
} |
||||
return activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; |
||||
} |
||||
|
||||
public static boolean checkPermissions(Activity activity, @NonNull String[] permissions) { |
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { |
||||
return true; |
||||
} else if (activity == null) { |
||||
return false; |
||||
} |
||||
boolean permissionsNecessary = true; |
||||
for (String perm : permissions) { |
||||
permissionsNecessary &= activity.checkSelfPermission(perm) == PackageManager.PERMISSION_GRANTED; |
||||
} |
||||
return permissionsNecessary; |
||||
} |
||||
|
||||
public void notifyPermissionsChange(String[] permissions) { |
||||
for (String perm : permissions) { |
||||
mPendingRequests.remove(perm); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
package acr.browser.lightning.view; |
||||
|
||||
import android.graphics.Bitmap; |
||||
import android.net.Uri; |
||||
import android.util.Log; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
|
||||
import acr.browser.lightning.app.BrowserApp; |
||||
import acr.browser.lightning.constant.Constants; |
||||
import acr.browser.lightning.utils.Utils; |
||||
|
||||
/** |
||||
* @author Anthony C. Restaino |
||||
* @date 2015/09/29 |
||||
*/ |
||||
class IconCacheTask implements Runnable{ |
||||
private final Uri uri; |
||||
private final Bitmap icon; |
||||
|
||||
public IconCacheTask(Uri uri, Bitmap icon) { |
||||
this.uri = uri; |
||||
this.icon = icon; |
||||
} |
||||
|
||||
@Override |
||||
public void run() { |
||||
String hash = String.valueOf(uri.getHost().hashCode()); |
||||
Log.d(Constants.TAG, "Caching icon for " + uri.getHost()); |
||||
FileOutputStream fos = null; |
||||
try { |
||||
File image = new File(BrowserApp.getAppContext().getCacheDir(), hash + ".png"); |
||||
fos = new FileOutputStream(image); |
||||
icon.compress(Bitmap.CompressFormat.PNG, 100, fos); |
||||
fos.flush(); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} finally { |
||||
Utils.close(fos); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,233 @@
@@ -0,0 +1,233 @@
|
||||
package acr.browser.lightning.view; |
||||
|
||||
import android.Manifest; |
||||
import android.app.Activity; |
||||
import android.content.DialogInterface; |
||||
import android.content.res.Resources; |
||||
import android.graphics.Bitmap; |
||||
import android.graphics.BitmapFactory; |
||||
import android.net.Uri; |
||||
import android.os.Message; |
||||
import android.support.v7.app.AlertDialog; |
||||
import android.util.Log; |
||||
import android.view.LayoutInflater; |
||||
import android.view.View; |
||||
import android.webkit.GeolocationPermissions; |
||||
import android.webkit.ValueCallback; |
||||
import android.webkit.WebChromeClient; |
||||
import android.webkit.WebView; |
||||
|
||||
import com.squareup.otto.Bus; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
|
||||
import acr.browser.lightning.R; |
||||
import acr.browser.lightning.app.BrowserApp; |
||||
import acr.browser.lightning.bus.BrowserEvents; |
||||
import acr.browser.lightning.constant.Constants; |
||||
import acr.browser.lightning.controller.UIController; |
||||
import com.anthonycr.grant.PermissionsManager; |
||||
import com.anthonycr.grant.PermissionsResultAction; |
||||
import acr.browser.lightning.utils.Utils; |
||||
|
||||
/** |
||||
* @author Stefano Pacifici based on Anthony C. Restaino code |
||||
* @date 2015/09/21 |
||||
*/ |
||||
class LightningChromeClient extends WebChromeClient { |
||||
|
||||
private static final String[] PERMISSIONS = new String[]{Manifest.permission.ACCESS_FINE_LOCATION}; |
||||
|
||||
private final Activity mActivity; |
||||
private final LightningView mLightningView; |
||||
private final UIController mUIController; |
||||
private final Bus eventBus; |
||||
|
||||
LightningChromeClient(Activity activity, LightningView lightningView) { |
||||
mActivity = activity; |
||||
mUIController = (UIController) activity; |
||||
mLightningView = lightningView; |
||||
eventBus = BrowserApp.getAppComponent().getBus(); |
||||
} |
||||
|
||||
@Override |
||||
public void onProgressChanged(WebView view, int newProgress) { |
||||
if (mLightningView.isShown()) { |
||||
mUIController.updateProgress(newProgress); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onReceivedIcon(WebView view, Bitmap icon) { |
||||
mLightningView.mTitle.setFavicon(icon); |
||||
eventBus.post(new BrowserEvents.TabsChanged()); |
||||
cacheFavicon(view.getUrl(), icon); |
||||
} |
||||
|
||||
/** |
||||
* Naive caching of the favicon according to the domain name of the URL |
||||
* |
||||
* @param icon the icon to cache |
||||
*/ |
||||
private static void cacheFavicon(final String url, final Bitmap icon) { |
||||
if (icon == null) return; |
||||
final Uri uri = Uri.parse(url); |
||||
if (uri.getHost() == null) { |
||||
return; |
||||
} |
||||
new Thread(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
String hash = String.valueOf(uri.getHost().hashCode()); |
||||
Log.d(Constants.TAG, "Caching icon for " + uri.getHost()); |
||||
FileOutputStream fos = null; |
||||
try { |
||||
File image = new File(BrowserApp.getAppContext().getCacheDir(), hash + ".png"); |
||||
fos = new FileOutputStream(image); |
||||
icon.compress(Bitmap.CompressFormat.PNG, 100, fos); |
||||
fos.flush(); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} finally { |
||||
Utils.close(fos); |
||||
} |
||||
} |
||||
}).start(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void onReceivedTitle(WebView view, String title) { |
||||
if (title != null && !title.isEmpty()) { |
||||
mLightningView.mTitle.setTitle(title); |
||||
} else { |
||||
mLightningView.mTitle.setTitle(mActivity.getString(R.string.untitled)); |
||||
} |
||||
eventBus.post(new BrowserEvents.TabsChanged()); |
||||
if (view != null) { |
||||
mUIController.updateHistory(title, view.getUrl()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onGeolocationPermissionsShowPrompt(final String origin, |
||||
final GeolocationPermissions.Callback callback) { |
||||
PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(mActivity, PERMISSIONS, new PermissionsResultAction() { |
||||
@Override |
||||
public void onGranted() { |
||||
final boolean remember = true; |
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); |
||||
builder.setTitle(mActivity.getString(R.string.location)); |
||||
String org; |
||||
if (origin.length() > 50) { |
||||
org = origin.subSequence(0, 50) + "..."; |
||||
} else { |
||||
org = origin; |
||||
} |
||||
builder.setMessage(org + mActivity.getString(R.string.message_location)) |
||||
.setCancelable(true) |
||||
.setPositiveButton(mActivity.getString(R.string.action_allow), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
callback.invoke(origin, true, remember); |
||||
} |
||||
}) |
||||
.setNegativeButton(mActivity.getString(R.string.action_dont_allow), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
callback.invoke(origin, false, remember); |
||||
} |
||||
}); |
||||
AlertDialog alert = builder.create(); |
||||
alert.show(); |
||||
} |
||||
|
||||
@Override |
||||
public void onDenied(String permission) { |
||||
//TODO show message and/or turn off setting
|
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, |
||||
Message resultMsg) { |
||||
mUIController.onCreateWindow(resultMsg); |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public void onCloseWindow(WebView window) { |
||||
mUIController.onCloseWindow(mLightningView); |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg) { |
||||
mUIController.openFileChooser(uploadMsg); |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { |
||||
mUIController.openFileChooser(uploadMsg); |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { |
||||
mUIController.openFileChooser(uploadMsg); |
||||
} |
||||
|
||||
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, |
||||
WebChromeClient.FileChooserParams fileChooserParams) { |
||||
mUIController.showFileChooser(filePathCallback); |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Obtain an image that is displayed as a placeholder on a video until the video has initialized |
||||
* and can begin loading. |
||||
* |
||||
* @return a Bitmap that can be used as a place holder for videos. |
||||
*/ |
||||
@Override |
||||
public Bitmap getDefaultVideoPoster() { |
||||
if (mActivity == null) { |
||||
return null; |
||||
} |
||||
final Resources resources = mActivity.getResources(); |
||||
return BitmapFactory.decodeResource(resources, android.R.drawable.spinner_background); |
||||
} |
||||
|
||||
/** |
||||
* Inflate a view to send to a LightningView when it needs to display a video and has to |
||||
* show a loading dialog. Inflates a progress view and returns it. |
||||
* |
||||
* @return A view that should be used to display the state |
||||
* of a video's loading progress. |
||||
*/ |
||||
@Override |
||||
public View getVideoLoadingProgressView() { |
||||
LayoutInflater inflater = LayoutInflater.from(mActivity); |
||||
return inflater.inflate(R.layout.video_loading_progress, null); |
||||
} |
||||
|
||||
@Override |
||||
public void onHideCustomView() { |
||||
mUIController.onHideCustomView(); |
||||
} |
||||
|
||||
@Override |
||||
public void onShowCustomView(View view, CustomViewCallback callback) { |
||||
mUIController.onShowCustomView(view, callback); |
||||
} |
||||
|
||||
@SuppressWarnings("deprecation") |
||||
@Override |
||||
public void onShowCustomView(View view, int requestedOrientation, |
||||
CustomViewCallback callback) { |
||||
mUIController.onShowCustomView(view, callback, requestedOrientation); |
||||
} |
||||
} |
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
package acr.browser.lightning.view; |
||||
|
||||
import android.content.Context; |
||||
import android.graphics.Bitmap; |
||||
|
||||
import acr.browser.lightning.R; |
||||
import acr.browser.lightning.utils.ThemeUtils; |
||||
import acr.browser.lightning.utils.Utils; |
||||
|
||||
/** |
||||
* @author Stefano Pacifici base on Anthony C. Restaino's code |
||||
* @date 2015/09/21 |
||||
*/ |
||||
class LightningViewTitle { |
||||
|
||||
private static Bitmap DEFAULT_ICON = null; |
||||
|
||||
private Bitmap mFavicon; |
||||
private String mTitle; |
||||
|
||||
public LightningViewTitle(Context context, boolean darkTheme) { |
||||
if (DEFAULT_ICON == null) { |
||||
DEFAULT_ICON = ThemeUtils.getThemedBitmap(context, R.drawable.ic_webpage, darkTheme); |
||||
} |
||||
mFavicon = DEFAULT_ICON; |
||||
mTitle = context.getString(R.string.action_new_tab); |
||||
} |
||||
|
||||
public void setFavicon(Bitmap favicon) { |
||||
if (favicon == null) { |
||||
mFavicon = DEFAULT_ICON; |
||||
} else { |
||||
mFavicon = Utils.padFavicon(favicon); |
||||
} |
||||
} |
||||
|
||||
public void setTitle(String title) { |
||||
if (title == null) { |
||||
mTitle = ""; |
||||
} else { |
||||
mTitle = title; |
||||
} |
||||
} |
||||
|
||||
public void setTitleAndFavicon(String title, Bitmap favicon) { |
||||
mTitle = title; |
||||
|
||||
if (favicon == null) { |
||||
mFavicon = DEFAULT_ICON; |
||||
} else { |
||||
mFavicon = Utils.padFavicon(favicon); |
||||
} |
||||
} |
||||
|
||||
public String getTitle() { |
||||
return mTitle; |
||||
} |
||||
|
||||
public Bitmap getFavicon() { |
||||
return mFavicon; |
||||
} |
||||
|
||||
public static Bitmap getDefaultIcon() { |
||||
return DEFAULT_ICON; |
||||
} |
||||
} |
@ -0,0 +1,319 @@
@@ -0,0 +1,319 @@
|
||||
package acr.browser.lightning.view; |
||||
|
||||
import android.annotation.TargetApi; |
||||
import android.app.Activity; |
||||
import android.content.ActivityNotFoundException; |
||||
import android.content.DialogInterface; |
||||
import android.content.Intent; |
||||
import android.graphics.Bitmap; |
||||
import android.net.MailTo; |
||||
import android.net.http.SslError; |
||||
import android.os.Build; |
||||
import android.os.Message; |
||||
import android.support.annotation.NonNull; |
||||
import android.support.v7.app.AlertDialog; |
||||
import android.text.InputType; |
||||
import android.text.method.PasswordTransformationMethod; |
||||
import android.util.Log; |
||||
import android.webkit.HttpAuthHandler; |
||||
import android.webkit.SslErrorHandler; |
||||
import android.webkit.WebResourceRequest; |
||||
import android.webkit.WebResourceResponse; |
||||
import android.webkit.WebView; |
||||
import android.webkit.WebViewClient; |
||||
import android.widget.EditText; |
||||
import android.widget.LinearLayout; |
||||
|
||||
import com.squareup.otto.Bus; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.net.URISyntaxException; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import acr.browser.lightning.R; |
||||
import acr.browser.lightning.app.BrowserApp; |
||||
import acr.browser.lightning.bus.BrowserEvents; |
||||
import acr.browser.lightning.constant.Constants; |
||||
import acr.browser.lightning.controller.UIController; |
||||
import acr.browser.lightning.utils.AdBlock; |
||||
import acr.browser.lightning.utils.IntentUtils; |
||||
import acr.browser.lightning.utils.ProxyUtils; |
||||
import acr.browser.lightning.utils.Utils; |
||||
|
||||
class LightningWebClient extends WebViewClient { |
||||
|
||||
|
||||
private final Activity mActivity; |
||||
private final LightningView mLightningView; |
||||
private final UIController mUIController; |
||||
private final AdBlock mAdBlock; |
||||
private final Bus mEventBus; |
||||
private final IntentUtils mIntentUtils; |
||||
|
||||
LightningWebClient(Activity activity, LightningView lightningView) { |
||||
mActivity = activity; |
||||
mUIController = (UIController) activity; |
||||
mLightningView = lightningView; |
||||
mAdBlock = AdBlock.getInstance(activity); |
||||
mAdBlock.updatePreference(); |
||||
mEventBus = BrowserApp.getAppComponent().getBus(); |
||||
mIntentUtils = new IntentUtils(activity); |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
@Override |
||||
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { |
||||
if (mAdBlock.isAd(request.getUrl().toString())) { |
||||
ByteArrayInputStream EMPTY = new ByteArrayInputStream("".getBytes()); |
||||
return new WebResourceResponse("text/plain", "utf-8", EMPTY); |
||||
} |
||||
return super.shouldInterceptRequest(view, request); |
||||
} |
||||
|
||||
@SuppressWarnings("deprecation") |
||||
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH) |
||||
@Override |
||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) { |
||||
if (mAdBlock.isAd(url)) { |
||||
ByteArrayInputStream EMPTY = new ByteArrayInputStream("".getBytes()); |
||||
return new WebResourceResponse("text/plain", "utf-8", EMPTY); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT) |
||||
@Override |
||||
public void onPageFinished(WebView view, String url) { |
||||
if (view.isShown()) { |
||||
mUIController.updateUrl(url, true); |
||||
view.postInvalidate(); |
||||
} |
||||
if (view.getTitle() == null || view.getTitle().isEmpty()) { |
||||
mLightningView.mTitle.setTitle(mActivity.getString(R.string.untitled)); |
||||
} else { |
||||
mLightningView.mTitle.setTitle(view.getTitle()); |
||||
} |
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && |
||||
mLightningView.getInvertePage()) { |
||||
view.evaluateJavascript(Constants.JAVASCRIPT_INVERT_PAGE, null); |
||||
} |
||||
mEventBus.post(new BrowserEvents.TabsChanged()); |
||||
} |
||||
|
||||
@Override |
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) { |
||||
mLightningView.mTitle.setFavicon(null); |
||||
if (mLightningView.isShown()) { |
||||
mUIController.updateUrl(url, false); |
||||
mUIController.showActionBar(); |
||||
} |
||||
mEventBus.post(new BrowserEvents.TabsChanged()); |
||||
} |
||||
|
||||
@Override |
||||
public void onReceivedHttpAuthRequest(final WebView view, @NonNull final HttpAuthHandler handler, |
||||
final String host, final String realm) { |
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); |
||||
final EditText name = new EditText(mActivity); |
||||
final EditText password = new EditText(mActivity); |
||||
LinearLayout passLayout = new LinearLayout(mActivity); |
||||
passLayout.setOrientation(LinearLayout.VERTICAL); |
||||
|
||||
passLayout.addView(name); |
||||
passLayout.addView(password); |
||||
|
||||
name.setHint(mActivity.getString(R.string.hint_username)); |
||||
name.setSingleLine(); |
||||
password.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD); |
||||
password.setSingleLine(); |
||||
password.setTransformationMethod(new PasswordTransformationMethod()); |
||||
password.setHint(mActivity.getString(R.string.hint_password)); |
||||
builder.setTitle(mActivity.getString(R.string.title_sign_in)); |
||||
builder.setView(passLayout); |
||||
builder.setCancelable(true) |
||||
.setPositiveButton(mActivity.getString(R.string.title_sign_in), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
String user = name.getText().toString(); |
||||
String pass = password.getText().toString(); |
||||
handler.proceed(user.trim(), pass.trim()); |
||||
Log.d(Constants.TAG, "Request Login"); |
||||
|
||||
} |
||||
}) |
||||
.setNegativeButton(mActivity.getString(R.string.action_cancel), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
handler.cancel(); |
||||
} |
||||
}); |
||||
AlertDialog alert = builder.create(); |
||||
alert.show(); |
||||
|
||||
} |
||||
|
||||
private boolean mIsRunning = false; |
||||
private float mZoomScale = 0.0f; |
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT) |
||||
@Override |
||||
public void onScaleChanged(final WebView view, final float oldScale, final float newScale) { |
||||
if (view.isShown() && mLightningView.mPreferences.getTextReflowEnabled() && |
||||
Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { |
||||
if (mIsRunning) |
||||
return; |
||||
if (Math.abs(mZoomScale - newScale) > 0.01f) { |
||||
mIsRunning = view.postDelayed(new Runnable() { |
||||
|
||||
@Override |
||||
public void run() { |
||||
mZoomScale = newScale; |
||||
view.evaluateJavascript(Constants.JAVASCRIPT_TEXT_REFLOW, null); |
||||
mIsRunning = false; |
||||
} |
||||
|
||||
}, 100); |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
private static List<Integer> getAllSslErrorMessageCodes(SslError error) { |
||||
List<Integer> errorCodeMessageCodes = new ArrayList<>(); |
||||
|
||||
if (error.hasError(SslError.SSL_DATE_INVALID)) { |
||||
errorCodeMessageCodes.add(R.string.message_certificate_date_invalid); |
||||
} |
||||
if (error.hasError(SslError.SSL_EXPIRED)) { |
||||
errorCodeMessageCodes.add(R.string.message_certificate_expired); |
||||
} |
||||
if (error.hasError(SslError.SSL_IDMISMATCH)) { |
||||
errorCodeMessageCodes.add(R.string.message_certificate_domain_mismatch); |
||||
} |
||||
if (error.hasError(SslError.SSL_NOTYETVALID)) { |
||||
errorCodeMessageCodes.add(R.string.message_certificate_not_yet_valid); |
||||
} |
||||
if (error.hasError(SslError.SSL_UNTRUSTED)) { |
||||
errorCodeMessageCodes.add(R.string.message_certificate_untrusted); |
||||
} |
||||
if (error.hasError(SslError.SSL_INVALID)) { |
||||
errorCodeMessageCodes.add(R.string.message_certificate_invalid); |
||||
} |
||||
|
||||
return errorCodeMessageCodes; |
||||
} |
||||
|
||||
@Override |
||||
public void onReceivedSslError(WebView view, @NonNull final SslErrorHandler handler, SslError error) { |
||||
List<Integer> errorCodeMessageCodes = getAllSslErrorMessageCodes(error); |
||||
|
||||
StringBuilder stringBuilder = new StringBuilder(); |
||||
for (Integer messageCode : errorCodeMessageCodes) { |
||||
stringBuilder.append(" - ").append(mActivity.getString(messageCode)).append('\n'); |
||||
} |
||||
String alertMessage = |
||||
mActivity.getString(R.string.message_insecure_connection, stringBuilder.toString()); |
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); |
||||
builder.setTitle(mActivity.getString(R.string.title_warning)); |
||||
builder.setMessage(alertMessage) |
||||
.setCancelable(true) |
||||
.setPositiveButton(mActivity.getString(R.string.action_yes), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
handler.proceed(); |
||||
} |
||||
}) |
||||
.setNegativeButton(mActivity.getString(R.string.action_no), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
handler.cancel(); |
||||
} |
||||
}); |
||||
builder.create().show(); |
||||
} |
||||
|
||||
@Override |
||||
public void onFormResubmission(WebView view, @NonNull final Message dontResend, final Message resend) { |
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); |
||||
builder.setTitle(mActivity.getString(R.string.title_form_resubmission)); |
||||
builder.setMessage(mActivity.getString(R.string.message_form_resubmission)) |
||||
.setCancelable(true) |
||||
.setPositiveButton(mActivity.getString(R.string.action_yes), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
resend.sendToTarget(); |
||||
} |
||||
}) |
||||
.setNegativeButton(mActivity.getString(R.string.action_no), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
dontResend.sendToTarget(); |
||||
} |
||||
}); |
||||
AlertDialog alert = builder.create(); |
||||
alert.show(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) { |
||||
// Check if configured proxy is available
|
||||
if (!ProxyUtils.getInstance().isProxyReady()) { |
||||
// User has been notified
|
||||
return true; |
||||
} |
||||
|
||||
Map<String, String> headers = mLightningView.getRequestHeaders(); |
||||
|
||||
if (mLightningView.mIsIncognitoTab) { |
||||
view.loadUrl(url, headers); |
||||
return true; |
||||
} |
||||
if (url.startsWith("about:")) { |
||||
view.loadUrl(url, headers); |
||||
return true; |
||||
} |
||||
if (url.startsWith("mailto:")) { |
||||
MailTo mailTo = MailTo.parse(url); |
||||
Intent i = Utils.newEmailIntent(mailTo.getTo(), mailTo.getSubject(), |
||||
mailTo.getBody(), mailTo.getCc()); |
||||
mActivity.startActivity(i); |
||||
view.reload(); |
||||
return true; |
||||
} else if (url.startsWith("intent://")) { |
||||
Intent intent; |
||||
try { |
||||
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); |
||||
} catch (URISyntaxException ignored) { |
||||
intent = null; |
||||
} |
||||
if (intent != null) { |
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE); |
||||
intent.setComponent(null); |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { |
||||
intent.setSelector(null); |
||||
} |
||||
try { |
||||
mActivity.startActivity(intent); |
||||
} catch (ActivityNotFoundException e) { |
||||
Log.e(Constants.TAG, "ActivityNotFoundException"); |
||||
} |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
if (!mIntentUtils.startActivityForUrl(view, url)) { |
||||
view.loadUrl(url, headers); |
||||
} |
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="30dp" |
||||
android:background="@color/black" |
||||
android:overScrollMode="never" |
||||
android:scrollbars="none" |
||||
android:id="@+id/tabs_list" /> |
||||
|
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
|
||||
<style name="normalText"> |
||||
<item name="android:textStyle">normal</item> |
||||
<item name="android:fontFamily">sans-serif-light</item> |
||||
</style> |
||||
</resources> |
Loading…
Reference in new issue