package org.indywidualni.fblite.webview; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Dialog; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.database.SQLException; import android.net.Uri; import android.preference.PreferenceManager; import android.support.v4.content.ContextCompat; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.webkit.URLUtil; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.AdapterView; import android.widget.ListView; import com.devspark.appmsg.AppMsg; import org.indywidualni.fblite.MyApplication; import org.indywidualni.fblite.R; import org.indywidualni.fblite.activity.MainActivity; import org.indywidualni.fblite.util.Connectivity; import org.indywidualni.fblite.util.Dimension; import org.indywidualni.fblite.util.FileOperation; import org.indywidualni.fblite.util.FloatingActionButton; import org.indywidualni.fblite.util.Miscellany; import org.indywidualni.fblite.util.Offline; import org.indywidualni.fblite.util.OfflinePagesAdapter; import java.util.ArrayList; public class MyWebViewClient extends WebViewClient { // a currently loaded page public static String currentlyLoadedPage; private static long lastSavingTime = System.currentTimeMillis(); public static boolean wasOffline; // variable for onReceivedError private boolean refreshed; // get application context @SuppressLint("StaticFieldLeak") private static final Context context = MyApplication.getContextOfApplication(); // get shared preferences private final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); // convert css file to string only one time private static String cssFile; // object for offline mode management private Offline offline; private FloatingActionButton fab; @SuppressLint("StaticFieldLeak") private static WebView webView; // in order to load pages we need a reference to a WebView object from MainActivity public static void setWebviewReference(WebView wv) { webView = wv; } @SuppressLint("NewApi") @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { return shouldOverrideUrlLoading(view, request.getUrl().toString()); } @SuppressWarnings("deprecation") @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // clean an url from facebook redirection before processing (no more blank pages on back) if (url != null) url = Miscellany.cleanAndDecodeUrl(url); // really ugly but let's do it just to avoid rare crashes try { if (Uri.parse(url).getHost().endsWith("messenger.com")) { view.getSettings().setUseWideViewPort(false); return false; } else view.getSettings().setUseWideViewPort(true); if (Uri.parse(url).getHost().endsWith("facebook.com") || Uri.parse(url).getHost().endsWith("mobile.facebook.com") || Uri.parse(url).getHost().endsWith("touch.facebook.com") || Uri.parse(url).getHost().endsWith("m.facebook.com") || Uri.parse(url).getHost().endsWith("h.facebook.com") || Uri.parse(url).getHost().endsWith("l.facebook.com") || Uri.parse(url).getHost().endsWith("0.facebook.com") || Uri.parse(url).getHost().endsWith("zero.facebook.com") || Uri.parse(url).getHost().endsWith("fbcdn.net") || Uri.parse(url).getHost().endsWith("akamaihd.net") || Uri.parse(url).getHost().endsWith("ad.doubleclick.net") || Uri.parse(url).getHost().endsWith("sync.liverail.com") || Uri.parse(url).getHost().endsWith("fb.me")) { return false; } else if (preferences.getBoolean("load_extra", false) && (Uri.parse(url).getHost().endsWith("googleusercontent.com") || Uri.parse(url).getHost().endsWith("tumblr.com") || Uri.parse(url).getHost().endsWith("pinimg.com") || Uri.parse(url).getHost().endsWith("media.giphy.com"))) { return false; } Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); try { view.getContext().startActivity(intent); } catch (ActivityNotFoundException e) { Log.e("shouldOverrideUrlLoad", "No Activity to handle action", e); e.printStackTrace(); } return true; } catch (NullPointerException npe) { npe.printStackTrace(); return true; } } @SuppressWarnings("deprecation") @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { // refresh on connection error (sometimes there is an error even when there is a network connection) if (Connectivity.isConnected(context) && !failingUrl.contains("edge-chat") && !failingUrl.contains("akamaihd") && !failingUrl.contains("atdmt") && !refreshed) { view.loadUrl(failingUrl); // when network error is real do not reload url again refreshed = true; } } @TargetApi(android.os.Build.VERSION_CODES.M) @Override public void onReceivedError(WebView view, WebResourceRequest req, WebResourceError err) { // redirect to deprecated method, so we can use it in all SDK versions onReceivedError(view, err.getErrorCode(), err.getDescription().toString(), req.getUrl().toString()); } @Override public void onPageFinished(WebView view, String url) { if (url.contains("messenger.com")) ((MainActivity) MainActivity.getMainActivity()).getSwipeRefreshLayout().setEnabled(false); else ((MainActivity) MainActivity.getMainActivity()).getSwipeRefreshLayout().setEnabled(true); // when Zero is activated and there is a mobile network connection ignore extra customizations if (!preferences.getBoolean("facebook_zero", false) || !Connectivity.isConnectedMobile(context)) { // hide install messenger notice by default view.loadUrl("javascript:function addStyleString(str) { var node = document.createElement('style'); " + "node.innerHTML = str; document.body.appendChild(node); } " + "addStyleString('[data-sigil*=m-promo-jewel-header]{ display: none; }');"); // turn facebook black (experimental) if (preferences.getBoolean("dark_theme", false)) { // fill it with data only one time if (cssFile == null) cssFile = FileOperation.readRawTextFile(context, R.raw.black); view.loadUrl("javascript:function addStyleString(str) { var node = document.createElement('style'); " + "node.innerHTML = str; document.body.appendChild(node); } addStyleString('" + cssFile + "');"); } } // blue navigation bar always on top if (preferences.getBoolean("fixed_nav", false)) { String cssFixed = "#header{ position: fixed; z-index: 11; top: 0px; } #root{ padding-top: 44px; } " + ".flyout{ max-height: " + Dimension.heightForFixedFacebookNavbar(context) + "px; overflow-y: scroll; }"; // there is no host in offline mode if (Uri.parse(url).getHost() != null) { if (Uri.parse(url).getHost().endsWith("mbasic.facebook.com")) cssFixed = "#header{ position: fixed; z-index: 11; top: 0px; } #objects_container{ padding-top: 74px; }"; else if (Uri.parse(url).getHost().endsWith("0.facebook.com")) cssFixed = "#toggleHeaderContent{position: fixed; z-index: 11; top: 0px;} " + "#header{ position: fixed; z-index: 11; top: 28px; } #objects_container{ padding-top: 102px; }"; } view.loadUrl("javascript:function addStyleString(str) { var node = document.createElement('style'); " + "node.innerHTML = str; document.body.appendChild(node); } addStyleString('" + cssFixed + "');"); } // apply extra bottom padding for transparent navigation if (preferences.getBoolean("transparent_nav", false)) view.loadUrl("javascript:function addStyleString(str) { var node = document.createElement('style'); " + "node.innerHTML = str; document.body.appendChild(node); } addStyleString('body{ padding-bottom: 48px; }');"); // hide sponsored posts and ads (works only for an originally loaded section, not for a dynamically loaded content) if (preferences.getBoolean("hide_sponsored", false)) { final String cssHideSponsored = "#div[role=\"article\"]:has(iframe[src^=\"/xti.php?\"])" + "#m_newsfeed_stream section article:has(iframe[src^=\"/xti.php?\"])" + "#m_newsfeed_stream section article:has(span[data-sigil=\"pixelContainer\"])" + "#m_newsfeed_stream article[data-ft*=\"\\\"ei\\\":\\\"\"], " + ".aymlCoverFlow, .aymlNewCoverFlow[data-ft*=\"\\\"is_sponsored\\\":\\\"1\\\"\"], .pyml, " + ".storyStream > ._6t2[data-sigil=\"marea\"], .storyStream > .fullwidth._539p, .storyStream > " + "article[id^=\"u_\"]._676, .storyStream > article[id^=\"u_\"].storyAggregation { display: none; }"; view.loadUrl("javascript:function addStyleString(str) { var node = document.createElement('style'); " + "node.innerHTML = str; document.body.appendChild(node); } addStyleString('" + cssHideSponsored + "');"); } // hide people you may know if (preferences.getBoolean("hide_people", false)) view.loadUrl("javascript:function addStyleString(str) { var node = document.createElement('style'); " + "node.innerHTML = str; document.body.appendChild(node); } " + "addStyleString('article._55wo._5rgr._5gh8._35au{ display: none; }');"); // hide news feed (a feature requested by drjedd) if (preferences.getBoolean("hide_news_feed", false)) view.loadUrl("javascript:function addStyleString(str) { var node = document.createElement('style'); " + "node.innerHTML = str; document.body.appendChild(node); } " + "addStyleString('#m_newsfeed_stream{ display: none; }');"); // don't display images when they are disabled, we don't need empty placeholders if (preferences.getBoolean("no_images", false)) view.loadUrl("javascript:function addStyleString(str) { var node = document.createElement('style'); " + "node.innerHTML = str; document.body.appendChild(node); } " + "addStyleString('.img, ._5sgg, ._-_a, .widePic, .profile-icon{ display: none; }');"); // hide install messenger notice at messenger page if (url.contains("messenger.com")) { view.loadUrl("javascript:function addStyleString(str) { var node = document.createElement('style'); " + "node.innerHTML = str; document.body.appendChild(node); } " + "addStyleString('._s15{ display: none; }');"); } // offline mode if (preferences.getBoolean("offline_mode", false)) { if (offline == null) offline = new Offline(); // hide floating action button if (fab != null && Connectivity.isConnected(context) && !fab.isHidden()) fab.hideFloatingActionButton(); // reset offline status, it's gonna be set later if needed if (Connectivity.isConnected(context)) wasOffline = false; // rare case when we suddenly got an internet connection but an offline page is being refreshed if (Connectivity.isConnected(context) && Uri.parse(url).getHost() == null) { wasOffline = false; // again, just to be 100% sure webView.loadUrl(currentlyLoadedPage); return; } // is url valid and is it a facebook page if (!URLUtil.isValidUrl(url) || !url.contains("facebook.com")) return; try { if (Connectivity.isConnected(context)) { // save the current page when online if (!currentlyLoadedPage.equals(url)) { offline.savePage(url); lastSavingTime = System.currentTimeMillis(); } else if (currentlyLoadedPage.equals(url) && lastSavingTime < System.currentTimeMillis() - 5500) { // don't save it when it's the same page which was saved less than 5.5s ago offline.savePage(url); lastSavingTime = System.currentTimeMillis(); } } else { // try to load page from the database when offline view.loadData(offline.getPage(url), "text/html; charset=utf-8", "UTF-8"); wasOffline = true; // show the message at the bottom of the screen AppMsg appMsg = AppMsg.makeText(MainActivity.getMainActivity(), context.getString(R.string.loading_offline_database), new AppMsg.Style(AppMsg.LENGTH_SHORT, R.color.colorAccent)); appMsg.setLayoutGravity(Gravity.TOP); if (preferences.getBoolean("transparent_nav", false) && context.getResources() .getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) appMsg.setLayoutParams(Dimension.getParamsAppMsg(context)); appMsg.show(); // floating action button if (fab == null) { fab = new FloatingActionButton.Builder(MainActivity.getMainActivity()) .withDrawable(ContextCompat.getDrawable(context, R.mipmap.ic_device_storage)) .withButtonColor(ContextCompat.getColor(context, R.color.colorAccent)) .withGravity(Gravity.BOTTOM | Gravity.END) .withMargins(0, 0, 16, 16) .create(); if (preferences.getBoolean("transparent_nav", false)) fab.setY(-Dimension.getNavigationBarHeight(context, 0)); // on click listener for fab fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (offline == null) { fab.hideFloatingActionButton(); return; } LayoutInflater inflater = MainActivity.getMainActivity().getLayoutInflater(); @SuppressLint("InflateParams") View customView = inflater .inflate(R.layout.dialog_offline_list, null); final Dialog dialog = new Dialog(MainActivity.getMainActivity(), R.style.CustomDialogTheme); dialog.setContentView(customView); dialog.show(); final ArrayList<String> page = offline.getDataSource().getAllPages(); ListView lv = (ListView) dialog.findViewById(R.id.list_offline); OfflinePagesAdapter opa = new OfflinePagesAdapter(MainActivity.getMainActivity(), page); lv.setEmptyView(dialog.findViewById(R.id.empty_element)); lv.setAdapter(opa); lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { webView.loadUrl(page.get(position)); dialog.dismiss(); } }); } }); } else if (fab.isHidden()) fab.showFloatingActionButton(); } } catch (SQLException e) { e.printStackTrace(); } } else offline = null; // save the currently loaded page (needed for a reliable refreshing) currentlyLoadedPage = url; } }