blob: d6358f997d31280113449d8d6d685cf87f4f066a [file] [log] [blame]
package org.wordpress.android.ui.reader.views;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import org.wordpress.android.WordPress;
import org.wordpress.android.models.AccountHelper;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.UrlUtils;
import org.wordpress.android.util.WPRestClient;
import org.wordpress.android.util.WPUrlUtils;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/*
* WebView descendant used by ReaderPostDetailFragment - handles
* displaying fullscreen video and detecting url/image clicks
*/
public class ReaderWebView extends WebView {
public interface ReaderWebViewUrlClickListener {
@SuppressWarnings("SameReturnValue")
boolean onUrlClick(String url);
boolean onImageUrlClick(String imageUrl, View view, int x, int y);
}
public interface ReaderCustomViewListener {
void onCustomViewShown();
void onCustomViewHidden();
ViewGroup onRequestCustomView();
ViewGroup onRequestContentView();
}
public interface ReaderWebViewPageFinishedListener {
void onPageFinished(WebView view, String url);
}
private ReaderWebChromeClient mReaderChromeClient;
private ReaderCustomViewListener mCustomViewListener;
private ReaderWebViewUrlClickListener mUrlClickListener;
private ReaderWebViewPageFinishedListener mPageFinishedListener;
private static String mToken;
private static boolean mIsPrivatePost;
private static boolean mBlogSchemeIsHttps;
private boolean mIsDestroyed;
public ReaderWebView(Context context) {
super(context);
init();
}
@Override
public void destroy() {
mIsDestroyed = true;
super.destroy();
}
public boolean isDestroyed() {
return mIsDestroyed;
}
public ReaderWebView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ReaderWebView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
@SuppressLint("NewApi")
private void init() {
if (!isInEditMode()) {
mToken = AccountHelper.getDefaultAccount().getAccessToken();
mReaderChromeClient = new ReaderWebChromeClient(this);
this.setWebChromeClient(mReaderChromeClient);
this.setWebViewClient(new ReaderWebViewClient(this));
this.getSettings().setUserAgentString(WordPress.getUserAgent());
// Adjust content font size on APIs 19 and below as those do not do it automatically.
// If fontScale is close to 1, just let it be 1.
final float fontScale = getResources().getConfiguration().fontScale;
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT && ((int) (fontScale * 10000)) != 10000) {
this.getSettings().setDefaultFontSize((int) (this.getSettings().getDefaultFontSize() * fontScale));
this.getSettings().setDefaultFixedFontSize(
(int) (this.getSettings().getDefaultFixedFontSize() * fontScale));
}
// Lollipop disables third-party cookies by default, but we need them in order
// to support authenticated images
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().setAcceptThirdPartyCookies(this, true);
}
}
}
public void clearContent() {
loadUrl("about:blank");
}
private ReaderWebViewUrlClickListener getUrlClickListener() {
return mUrlClickListener;
}
public void setUrlClickListener(ReaderWebViewUrlClickListener listener) {
mUrlClickListener = listener;
}
private boolean hasUrlClickListener() {
return (mUrlClickListener != null);
}
private ReaderWebViewPageFinishedListener getPageFinishedListener() {
return mPageFinishedListener;
}
public void setPageFinishedListener(ReaderWebViewPageFinishedListener listener) {
mPageFinishedListener = listener;
}
private boolean hasPageFinishedListener() {
return (mPageFinishedListener != null);
}
public void setCustomViewListener(ReaderCustomViewListener listener) {
mCustomViewListener = listener;
}
private boolean hasCustomViewListener() {
return (mCustomViewListener != null);
}
private ReaderCustomViewListener getCustomViewListener() {
return mCustomViewListener;
}
public void setIsPrivatePost(boolean isPrivatePost) {
mIsPrivatePost = isPrivatePost;
}
public void setBlogSchemeIsHttps(boolean blogSchemeIsHttps) {
mBlogSchemeIsHttps = blogSchemeIsHttps;
}
private static boolean isValidClickedUrl(String url) {
// only return true for http(s) urls so we avoid file: and data: clicks
return (url != null && (url.startsWith("http") || url.startsWith("wordpress:")));
}
public boolean isCustomViewShowing() {
return mReaderChromeClient.isCustomViewShowing();
}
public void hideCustomView() {
if (isCustomViewShowing()) {
mReaderChromeClient.onHideCustomView();
}
}
/*
* detect when a link is tapped
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP && mUrlClickListener != null) {
HitTestResult hr = getHitTestResult();
if (hr != null && isValidClickedUrl(hr.getExtra())) {
if (UrlUtils.isImageUrl(hr.getExtra())) {
return mUrlClickListener.onImageUrlClick(
hr.getExtra(),
this,
(int) event.getX(),
(int) event.getY());
} else {
return mUrlClickListener.onUrlClick(hr.getExtra());
}
}
}
return super.onTouchEvent(event);
}
private static class ReaderWebViewClient extends WebViewClient {
private final ReaderWebView mReaderWebView;
ReaderWebViewClient(ReaderWebView readerWebView) {
if (readerWebView == null) {
throw new IllegalArgumentException("ReaderWebViewClient requires readerWebView");
}
mReaderWebView = readerWebView;
}
@Override
public void onPageFinished(WebView view, String url) {
if (mReaderWebView.hasPageFinishedListener()) {
mReaderWebView.getPageFinishedListener().onPageFinished(view, url);
}
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// fire the url click listener, but only do this when webView has
// loaded (is visible) - have seen some posts containing iframes
// automatically try to open urls (without being clicked)
// before the page has loaded
return view.getVisibility() == View.VISIBLE
&& mReaderWebView.hasUrlClickListener()
&& isValidClickedUrl(url)
&& mReaderWebView.getUrlClickListener().onUrlClick(url);
}
@SuppressWarnings("deprecation")
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
URL imageUrl = null;
if (mIsPrivatePost && mBlogSchemeIsHttps && UrlUtils.isImageUrl(url)) {
try {
imageUrl = new URL(UrlUtils.makeHttps(url));
} catch (MalformedURLException e) {
AppLog.e(AppLog.T.READER, e);
}
}
// Intercept requests for private images and add the WP.com authorization header
if (imageUrl != null && WPUrlUtils.safeToAddWordPressComAuthToken(imageUrl) &&
!TextUtils.isEmpty(mToken)) {
try {
HttpURLConnection conn = (HttpURLConnection) imageUrl.openConnection();
conn.setRequestProperty("Authorization", "Bearer " + mToken);
conn.setReadTimeout(WPRestClient.REST_TIMEOUT_MS);
conn.setConnectTimeout(WPRestClient.REST_TIMEOUT_MS);
conn.setRequestProperty("User-Agent", WordPress.getUserAgent());
conn.setRequestProperty("Connection", "Keep-Alive");
return new WebResourceResponse(conn.getContentType(),
conn.getContentEncoding(),
conn.getInputStream());
} catch (IOException e) {
AppLog.e(AppLog.T.READER, e);
}
}
return super.shouldInterceptRequest(view, url);
}
}
private static class ReaderWebChromeClient extends WebChromeClient {
private final ReaderWebView mReaderWebView;
private View mCustomView;
private CustomViewCallback mCustomViewCallback;
ReaderWebChromeClient(ReaderWebView readerWebView) {
if (readerWebView == null) {
throw new IllegalArgumentException("ReaderWebChromeClient requires readerWebView");
}
mReaderWebView = readerWebView;
}
/*
* request the view that will host the fullscreen video
*/
private ViewGroup getTargetView() {
if (mReaderWebView.hasCustomViewListener()) {
return mReaderWebView.getCustomViewListener().onRequestCustomView();
} else {
return null;
}
}
/*
* request the view that should be hidden when showing fullscreen video
*/
private ViewGroup getContentView() {
if (mReaderWebView.hasCustomViewListener()) {
return mReaderWebView.getCustomViewListener().onRequestContentView();
} else {
return null;
}
}
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
AppLog.i(AppLog.T.READER, "onShowCustomView");
if (mCustomView != null) {
AppLog.w(AppLog.T.READER, "customView already showing");
onHideCustomView();
return;
}
// hide the post detail content
ViewGroup contentView = getContentView();
if (contentView != null) {
contentView.setVisibility(View.INVISIBLE);
}
// show the full screen view
ViewGroup targetView = getTargetView();
if (targetView != null) {
targetView.addView(view);
targetView.setVisibility(View.VISIBLE);
}
if (mReaderWebView.hasCustomViewListener()) {
mReaderWebView.getCustomViewListener().onCustomViewShown();
}
mCustomView = view;
mCustomViewCallback = callback;
}
@Override
public void onHideCustomView() {
AppLog.i(AppLog.T.READER, "onHideCustomView");
if (mCustomView == null) {
AppLog.w(AppLog.T.READER, "customView does not exist");
return;
}
// hide the target view
ViewGroup targetView = getTargetView();
if (targetView != null) {
targetView.removeView(mCustomView);
targetView.setVisibility(View.GONE);
}
// redisplay the post detail content
ViewGroup contentView = getContentView();
if (contentView != null) {
contentView.setVisibility(View.VISIBLE);
}
if (mCustomViewCallback != null) {
mCustomViewCallback.onCustomViewHidden();
}
if (mReaderWebView.hasCustomViewListener()) {
mReaderWebView.getCustomViewListener().onCustomViewHidden();
}
mCustomView = null;
mCustomViewCallback = null;
}
boolean isCustomViewShowing() {
return (mCustomView != null);
}
}
}