package org.edx.mobile.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.google.inject.Inject;
import com.joanzapata.iconify.Icon;
import com.joanzapata.iconify.IconDrawable;
import com.joanzapata.iconify.fonts.FontAwesomeIcons;
import org.edx.mobile.R;
import org.edx.mobile.event.NetworkConnectivityChangeEvent;
import org.edx.mobile.event.SessionIdRefreshEvent;
import org.edx.mobile.http.HttpStatus;
import org.edx.mobile.logger.Logger;
import org.edx.mobile.model.course.CourseComponent;
import org.edx.mobile.model.course.HtmlBlockModel;
import org.edx.mobile.module.prefs.LoginPrefs;
import org.edx.mobile.services.EdxCookieManager;
import org.edx.mobile.services.ViewPagerDownloadManager;
import org.edx.mobile.util.NetworkUtil;
import org.edx.mobile.view.custom.URLInterceptorWebViewClient;
import java.util.HashMap;
import java.util.Map;
import de.greenrobot.event.EventBus;
import roboguice.inject.InjectView;
public class CourseUnitWebViewFragment extends CourseUnitFragment {
protected final Logger logger = new Logger(getClass().getName());
private final static String EMPTY_HTML = "<html><body></body></html>";
private boolean pageIsLoaded;
@InjectView(R.id.loading_indicator)
private ProgressBar progressWheel;
@InjectView(R.id.course_unit_webView)
private WebView webView;
@InjectView(R.id.content_unavailable_error_text)
private TextView errorTextView;
@Inject
private LoginPrefs loginPrefs;
public static CourseUnitWebViewFragment newInstance(HtmlBlockModel unit) {
CourseUnitWebViewFragment f = new CourseUnitWebViewFragment();
// Supply num input as an argument.
Bundle args = new Bundle();
args.putSerializable(Router.EXTRA_COURSE_UNIT, unit);
f.setArguments(args);
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_course_unit_webview, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//should we recover here?
webView.clearCache(true);
webView.getSettings().setJavaScriptEnabled(true);
URLInterceptorWebViewClient client =
new URLInterceptorWebViewClient(getActivity(), webView) {
private boolean didReceiveError = false;
@Override
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
didReceiveError = true;
hideLoadingProgress();
pageIsLoaded = false;
ViewPagerDownloadManager.instance.done(CourseUnitWebViewFragment.this, false);
showErrorMessage(R.string.network_error_message,
FontAwesomeIcons.fa_exclamation_circle);
}
@Override
@TargetApi(Build.VERSION_CODES.M)
public void onReceivedHttpError(WebView view, WebResourceRequest request,
WebResourceResponse errorResponse) {
didReceiveError = true;
switch (errorResponse.getStatusCode()) {
case HttpStatus.FORBIDDEN:
case HttpStatus.UNAUTHORIZED:
case HttpStatus.NOT_FOUND:
EdxCookieManager.getSharedInstance().tryToRefreshSessionCookie();
break;
}
showErrorMessage(R.string.network_error_message,
FontAwesomeIcons.fa_exclamation_circle);
}
public void onPageFinished(WebView view, String url) {
if (didReceiveError) {
didReceiveError = false;
return;
}
if (url != null && url.equals("data:text/html," + EMPTY_HTML)) {
//we load a local empty html page to release the memory
} else {
pageIsLoaded = true;
}
//TODO -disable it for now. as it causes some issues for assessment
//webview to fit in the screen. But we still need it to show additional
//compenent below the webview in the future?
// view.loadUrl("javascript:EdxAssessmentView.resize(document.body.getBoundingClientRect().height)");
ViewPagerDownloadManager.instance.done(CourseUnitWebViewFragment.this, true);
hideLoadingProgress();
}
};
client.setAllLinksAsExternal(true);
if (ViewPagerDownloadManager.USING_UI_PRELOADING) {
if (ViewPagerDownloadManager.instance.inInitialPhase(unit)) {
ViewPagerDownloadManager.instance.addTask(this);
} else {
tryToLoadWebView(true);
}
}
}
@Override
public void onResume() {
super.onResume();
webView.onResume();
if (hasComponentCallback != null) {
CourseComponent component = hasComponentCallback.getComponent();
if (component != null && component.equals(unit)) {
try {
tryToLoadWebView(false);
} catch (Exception ex) {
logger.error(ex);
}
}
}
}
@Override
public void onPause() {
super.onPause();
webView.onPause();
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().unregister(this);
}
}
@Override
public void onDestroy() {
super.onDestroy();
webView.destroy();
}
@SuppressWarnings("unused")
public void onEventMainThread(NetworkConnectivityChangeEvent event) {
if (NetworkUtil.isConnected(getContext())) {
hideErrorMessage();
} else {
showErrorMessage(R.string.reset_no_network_message, FontAwesomeIcons.fa_wifi);
}
}
@SuppressWarnings("unused")
public void onEventMainThread(SessionIdRefreshEvent event) {
if (event.success) {
tryToLoadWebView(false);
} else {
hideLoadingProgress();
}
}
/**
* Shows the error message with the given icon, if the web page failed to load
*
* @param errorMsg The error message to show
* @param errorIcon The error icon to show with the error message
*/
private void showErrorMessage(@StringRes int errorMsg, @NonNull Icon errorIcon) {
if (!pageIsLoaded) {
tryToClearWebView();
Context context = getContext();
errorTextView.setVisibility(View.VISIBLE);
errorTextView.setText(errorMsg);
errorTextView.setCompoundDrawablesWithIntrinsicBounds(null,
new IconDrawable(context, errorIcon)
.sizeRes(context, R.dimen.content_unavailable_error_icon_size)
.colorRes(context, R.color.edx_brand_gray_back),
null, null
);
}
}
/**
* Hides the error message view and reloads the web page if it wasn't already loaded
*/
private void hideErrorMessage() {
errorTextView.setVisibility(View.GONE);
if (!pageIsLoaded) {
tryToLoadWebView(true);
}
}
private void tryToLoadWebView(boolean forceLoad) {
System.gc(); //there is a well known Webview Memory Issue With Galaxy S3 With 4.3 Update
if ((!forceLoad && pageIsLoaded) || progressWheel == null) {
return;
}
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().register(this);
}
if (!NetworkUtil.isConnected(getContext())) {
showErrorMessage(R.string.reset_no_network_message, FontAwesomeIcons.fa_wifi);
return;
}
showLoadingProgress();
if (unit != null) {
Map<String, String> map = new HashMap<>();
final String token = loginPrefs.getAuthorizationHeader();
if (token != null) {
map.put("Authorization", token);
}
// Requery the session cookie if unavailable or expired if we are on
// an API level lesser than Marshmallow (which provides HTTP error
// codes in the error callback for WebViewClient).
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M &&
EdxCookieManager.getSharedInstance().isSessionCookieMissingOrExpired()) {
EdxCookieManager.getSharedInstance().tryToRefreshSessionCookie();
} else {
webView.loadUrl(unit.getBlockUrl(), map);
}
}
}
@Override
public void run() {
if (this.isRemoving() || this.isDetached()) {
ViewPagerDownloadManager.instance.done(this, false);
} else {
tryToLoadWebView(true);
}
}
private void tryToClearWebView() {
pageIsLoaded = false;
if (webView != null) {
webView.loadData(EMPTY_HTML, "text/html", "UTF-8");
}
}
//the problem with viewpager is that it loads this fragment
//and calls onResume even it is not visible.
//which breaks the normal behavior of activity/fragment
//lifecycle.
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (ViewPagerDownloadManager.USING_UI_PRELOADING)
return;
if (ViewPagerDownloadManager.instance.inInitialPhase(unit))
return;
if (isVisibleToUser) {
tryToLoadWebView(false);
} else {
tryToClearWebView();
}
}
private void showLoadingProgress() {
progressWheel.setVisibility(View.VISIBLE);
}
private void hideLoadingProgress() {
progressWheel.setVisibility(View.GONE);
}
}