package org.edx.mobile.services; import android.os.Handler; import android.os.Looper; import org.edx.mobile.logger.Logger; import org.edx.mobile.model.course.CourseComponent; import org.edx.mobile.util.UiUtil; import org.edx.mobile.util.WeakList; import org.edx.mobile.view.common.RunnableCourseComponent; import org.edx.mobile.view.common.TaskCallback; import java.util.List; /** * by default, ViewPager will populate UI for both current one-in-view and cached one-in-the-future. * in order to improve performance, we wont download it in parallel. * but it matters only for the initial load when user navigate from course outline page to detail page. * */ public class ViewPagerDownloadManager implements TaskCallback { //TODO - current we just disable the pre-loading behavior //for potential memory usage issue for certain type devices //we will enable it when we can reduce javascript file size //in assesssment webview public static boolean USING_UI_PRELOADING = false; protected final Logger logger = new Logger(getClass().getName()); public static ViewPagerDownloadManager instance = new ViewPagerDownloadManager(); private WeakList<RunnableCourseComponent> runnableCourseComponentWeakList; //it is the first component to show private CourseComponent mainComponent; private CourseComponent nextComponent; private CourseComponent prevComponent; private boolean taskIsRunning; private ViewPagerDownloadManager(){ runnableCourseComponentWeakList = new WeakList<>(); } public void clear(){ runnableCourseComponentWeakList.clear(); mainComponent = null; nextComponent = null; prevComponent = null; taskIsRunning = false; } /** * specify the cache range for viewpager. we only care about the initial visualization of the viewpagers. * for the [prev, main, next] fragment UI/pages. * */ public void setMainComponent(CourseComponent component,List<CourseComponent> unitList){ if ( !USING_UI_PRELOADING ) return; clear(); this.mainComponent = component; int index = unitList.indexOf(component); if (index > 0 ){ prevComponent = unitList.get(index -1); } if ( index < unitList.size() - 1 ){ nextComponent = unitList.get(index +1); } } /** * specify the cache range for viewpager. we only care about the initial visualization of the viewpagers. * for the [prev, main, next] fragment UI/pages. * for the viewpagers in this range, also main page is not finish downloading, it will be initial phase. * we will apply our loading policy only in initial phase */ public synchronized boolean inInitialPhase(CourseComponent component){ if ( component != mainComponent && component != nextComponent && component != prevComponent ) return false; return runnableCourseComponentWeakList.size() > 0; } public synchronized void removeTask(RunnableCourseComponent callback){ runnableCourseComponentWeakList.remove(callback); } /** * if the UI is mainUI, we will kick off the downloading process for this mainUI. * if mainUI is downloaded, but no task is running, we kick off the downloading process for this UI */ public synchronized void addTask(RunnableCourseComponent callback){ runnableCourseComponentWeakList.add(callback); if ( mainComponent != null && mainComponent.equals(callback.getCourseComponent())) { taskIsRunning = true; callback.run(); return; } if ( !taskIsRunning ){ taskIsRunning = true; callback.run(); } } private void tryToRunTask(){ if ( runnableCourseComponentWeakList.size() == 0 ) return; new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { //we use only default caching policy here for ViewPager, which is caching size = 1 Runnable callback = UiUtil.isLeftToRightOrientation() ? runnableCourseComponentWeakList.getLastValid() : runnableCourseComponentWeakList.getFirstValid(); if ( callback == null ) { tryToRunTask(); //if we are in some kind of race condition for GC, let's retry. //it wont run into infinite loop as we check the list size at the very beginning } else { taskIsRunning = true; callback.run(); } } }); } @Override public synchronized void done(Runnable task, boolean success) { RunnableCourseComponent runnableCourseComponent = (RunnableCourseComponent)task; if( runnableCourseComponentWeakList.remove(runnableCourseComponent) ){ taskIsRunning = false; tryToRunTask(); } } public boolean isTaskIsRunning(){ return taskIsRunning; } public int numTaskInStack(){ return runnableCourseComponentWeakList.size(); } }