diff --git a/.gitignore b/.gitignore index 89f8fd8a18..aa02d7f7c9 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,6 @@ Thumbs.db build/ #NDK -obj/ \ No newline at end of file +obj/ + +maka-vlc/ \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 6d85aa18df..62cc34b1db 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "org.stepic.droid" minSdkVersion 14 targetSdkVersion 23 - versionCode 32 - versionName "1.4" + versionCode 41 + versionName "1.5.3" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" // Enabling multidex support. multiDexEnabled true @@ -25,6 +25,7 @@ android { 'proguard-rules.pro' } debug { + versionNameSuffix "DEV" } } @@ -40,6 +41,26 @@ android { main.java.srcDirs += 'src/main/kotlin' } + splits { + abi { + enable true + reset() + include 'armeabi', 'armeabi-v7a', 'mips', 'x86' //select ABIs to build APKs for + universalApk true //generate an additional APK that contains all the ABIs + } + } + + // map for the version code + project.ext.versionCodes = ['armeabi': 1, 'armeabi-v7a': 2, 'mips': 5, 'x86': 8] + + android.applicationVariants.all { variant -> + // assign different version code for each output + variant.outputs.each { output -> + output.versionCodeOverride = + project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) + android.defaultConfig.versionCode*10 + } + } + packagingOptions { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' @@ -60,9 +81,6 @@ dependencies { compile 'com.android.support:multidex:1.0.1' compile 'com.google.code.gson:gson:2.4' - - compile 'com.squareup.okhttp3:okhttp:3.1.2' - compile 'com.squareup.okhttp:okhttp-ws:2.7.4' compile 'com.squareup.okio:okio:1.6.0' compile 'de.hdodenhof:circleimageview:1.3.0' @@ -108,8 +126,9 @@ dependencies { debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta1' // or 1.3.1 releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1' // or 1.3.1 - testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1' - compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" // or 1.3.1 + testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1' // or 1.3.1 + + compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile files('libs/commons-logging-api-1.1.1.jar') @@ -117,6 +136,7 @@ dependencies { compile files('libs/log4j-api-2.4.1.jar') compile files('libs/slf4j-api-1.7.12.jar') + compile 'de.mrmaffen:vlc-android-sdk:3.0.0' } repositories { @@ -130,6 +150,4 @@ buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } -} - - +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b2b64e640b..65c9073e77 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,9 +11,11 @@ + - + @@ -129,6 +131,13 @@ android:value=".view.activities.MainFeedActivity"/> + + diff --git a/app/src/main/java/org/stepic/droid/base/CoursesDatabaseFragmentBase.java b/app/src/main/java/org/stepic/droid/base/CoursesDatabaseFragmentBase.java index 43ed880b9e..3927fb3283 100644 --- a/app/src/main/java/org/stepic/droid/base/CoursesDatabaseFragmentBase.java +++ b/app/src/main/java/org/stepic/droid/base/CoursesDatabaseFragmentBase.java @@ -15,8 +15,8 @@ import com.yandex.metrica.YandexMetrica; import org.stepic.droid.R; -import org.stepic.droid.concurrency.FromDbCoursesTask; -import org.stepic.droid.concurrency.ToDbCoursesTask; +import org.stepic.droid.concurrency.tasks.FromDbCoursesTask; +import org.stepic.droid.concurrency.tasks.ToDbCoursesTask; import org.stepic.droid.events.courses.FailCoursesDownloadEvent; import org.stepic.droid.events.courses.FailDropCourseEvent; import org.stepic.droid.events.courses.FinishingGetCoursesFromDbEvent; @@ -44,9 +44,8 @@ import retrofit.Retrofit; public abstract class CoursesDatabaseFragmentBase extends CourseListFragmentBase { - protected FromDbCoursesTask mDbGetCoursesTask; protected ToDbCoursesTask mDbSaveCoursesTask; - + protected FromDbCoursesTask mDbFromCoursesTask; public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); @@ -82,19 +81,19 @@ protected void showCourses(List cachedCourses) { private void saveDataToCache(List courses) { mDbSaveCoursesTask = new ToDbCoursesTask(courses, getCourseType(), mCurrentPage); - mDbSaveCoursesTask.execute(); + mDbSaveCoursesTask.executeOnExecutor(mThreadPoolExecutor); } public void getAndShowDataFromCache() { - mDbGetCoursesTask = new FromDbCoursesTask(getCourseType()) { + mDbFromCoursesTask = new FromDbCoursesTask(getCourseType()){ @Override protected void onSuccess(List courses) { super.onSuccess(courses); bus.post(new GettingCoursesFromDbSuccessEvent(getCourseType(), courses)); } }; - mDbGetCoursesTask.execute(); + mDbFromCoursesTask.executeOnExecutor(mThreadPoolExecutor); } @Subscribe diff --git a/app/src/main/java/org/stepic/droid/base/FragmentActivityBase.java b/app/src/main/java/org/stepic/droid/base/FragmentActivityBase.java index c8b1d20db6..7ee2769bf1 100644 --- a/app/src/main/java/org/stepic/droid/base/FragmentActivityBase.java +++ b/app/src/main/java/org/stepic/droid/base/FragmentActivityBase.java @@ -19,6 +19,8 @@ import org.stepic.droid.util.resolvers.CoursePropertyResolver; import org.stepic.droid.util.resolvers.IStepResolver; +import java.util.concurrent.ThreadPoolExecutor; + import javax.inject.Inject; import butterknife.ButterKnife; @@ -49,6 +51,9 @@ public abstract class FragmentActivityBase extends AppCompatActivity { @Inject protected ILoginManager mLoginManager; + @Inject + protected ThreadPoolExecutor mThreadPoolExecutor; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -99,7 +104,7 @@ protected void onDestroy() { protected void setFragment(@IdRes int res, Fragment fragment) { android.support.v4.app.FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); - fragmentTransaction.replace(res, fragment); + fragmentTransaction.replace(res, fragment, fragment.getClass().toString()); fragmentTransaction.commit(); } } diff --git a/app/src/main/java/org/stepic/droid/base/FragmentBase.java b/app/src/main/java/org/stepic/droid/base/FragmentBase.java index b8e3c07d05..47e1e585b6 100644 --- a/app/src/main/java/org/stepic/droid/base/FragmentBase.java +++ b/app/src/main/java/org/stepic/droid/base/FragmentBase.java @@ -1,5 +1,6 @@ package org.stepic.droid.base; +import android.app.DownloadManager; import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; @@ -10,8 +11,10 @@ import com.squareup.leakcanary.RefWatcher; import com.squareup.otto.Bus; +import org.stepic.droid.core.AudioFocusHelper; import org.stepic.droid.core.ILessonSessionManager; import org.stepic.droid.core.ILocalProgressManager; +import org.stepic.droid.concurrency.IMainHandler; import org.stepic.droid.core.IShell; import org.stepic.droid.preferences.SharedPreferenceHelper; import org.stepic.droid.preferences.UserPreferences; @@ -22,13 +25,18 @@ import org.stepic.droid.util.resolvers.IStepResolver; import org.stepic.droid.util.resolvers.IVideoResolver; +import java.util.concurrent.ThreadPoolExecutor; + import javax.inject.Inject; import butterknife.ButterKnife; public class FragmentBase extends Fragment { - protected String TAG = "StepicFragment"; +// protected String TAG = "StepicFragment"; + + @Inject + public ThreadPoolExecutor mThreadPoolExecutor; @Inject public ILessonSessionManager mLessonManager; @@ -69,6 +77,14 @@ public class FragmentBase extends Fragment { @Inject public IStepResolver mStepResolver; + @Inject + public IMainHandler mMainHandler; + + @Inject + public AudioFocusHelper mAudioFocusHelper; + + @Inject + public DownloadManager mSystemDownloadManager; public FragmentBase() { MainApplication.component(MainApplication.getAppContext()).inject(this); diff --git a/app/src/main/java/org/stepic/droid/base/SingleFragmentActivity.kt b/app/src/main/java/org/stepic/droid/base/SingleFragmentActivity.kt index f90ab17a8c..a9f4788b93 100644 --- a/app/src/main/java/org/stepic/droid/base/SingleFragmentActivity.kt +++ b/app/src/main/java/org/stepic/droid/base/SingleFragmentActivity.kt @@ -6,7 +6,7 @@ import android.support.v4.app.Fragment import org.stepic.droid.R abstract class SingleFragmentActivity : FragmentActivityBase() { - protected abstract fun createFragment(): Fragment + protected abstract fun createFragment(): Fragment? open fun getLayoutResId() = R.layout.activity_fragment override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/java/org/stepic/droid/concurrency/HandlerBaseDelegate.kt b/app/src/main/java/org/stepic/droid/concurrency/HandlerBaseDelegate.kt new file mode 100644 index 0000000000..593112373a --- /dev/null +++ b/app/src/main/java/org/stepic/droid/concurrency/HandlerBaseDelegate.kt @@ -0,0 +1,8 @@ +package org.stepic.droid.concurrency + +import android.os.Handler + +abstract class HandlerBaseDelegate : IHandler { + override fun post(body: () -> Unit) = getHandler().post { body.invoke() } + abstract fun getHandler() : Handler +} \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/concurrency/IHandler.kt b/app/src/main/java/org/stepic/droid/concurrency/IHandler.kt new file mode 100644 index 0000000000..1d6ab4b7cf --- /dev/null +++ b/app/src/main/java/org/stepic/droid/concurrency/IHandler.kt @@ -0,0 +1,5 @@ +package org.stepic.droid.concurrency + +interface IHandler { + fun post(body : ()->Unit): Boolean +} diff --git a/app/src/main/java/org/stepic/droid/concurrency/IMainHandler.kt b/app/src/main/java/org/stepic/droid/concurrency/IMainHandler.kt new file mode 100644 index 0000000000..420c0cbeb4 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/concurrency/IMainHandler.kt @@ -0,0 +1,3 @@ +package org.stepic.droid.concurrency + +interface IMainHandler : IHandler diff --git a/app/src/main/java/org/stepic/droid/concurrency/MainHandlerImpl.kt b/app/src/main/java/org/stepic/droid/concurrency/MainHandlerImpl.kt new file mode 100644 index 0000000000..b9b196f6d3 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/concurrency/MainHandlerImpl.kt @@ -0,0 +1,10 @@ +package org.stepic.droid.concurrency + +import android.os.Handler +import org.stepic.droid.base.MainApplication + +class MainHandlerImpl : HandlerBaseDelegate(), IMainHandler { + val mainHandler = Handler(MainApplication.getAppContext().mainLooper) + + override fun getHandler() = mainHandler +} diff --git a/app/src/main/java/org/stepic/droid/concurrency/ToDbCachedVideo.java b/app/src/main/java/org/stepic/droid/concurrency/ToDbCachedVideo.java deleted file mode 100644 index fe46256207..0000000000 --- a/app/src/main/java/org/stepic/droid/concurrency/ToDbCachedVideo.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.stepic.droid.concurrency; - -import android.content.Context; - -import org.stepic.droid.base.MainApplication; -import org.stepic.droid.model.CachedVideo; -import org.stepic.droid.store.operations.DatabaseFacade; - -import javax.inject.Inject; - -public class ToDbCachedVideo extends StepicTask { - - @Inject - DatabaseFacade mDatabaseFacade; - - private CachedVideo cachedVideo; - - public ToDbCachedVideo(CachedVideo cachedVideo) { - this(MainApplication.getAppContext(), cachedVideo); - } - - public ToDbCachedVideo(Context context, CachedVideo cachedVideo) { - super(context); - MainApplication.component(context).inject(this); - this.cachedVideo = cachedVideo; - } - - @Override - protected Void doInBackgroundBody(Void... params) throws Exception { - mDatabaseFacade.addVideo(cachedVideo); - return null; - } -} diff --git a/app/src/main/java/org/stepic/droid/concurrency/ToDbStepTask.java b/app/src/main/java/org/stepic/droid/concurrency/ToDbStepTask.java deleted file mode 100644 index ca4b417b24..0000000000 --- a/app/src/main/java/org/stepic/droid/concurrency/ToDbStepTask.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.stepic.droid.concurrency; - -import com.squareup.otto.Bus; - -import org.stepic.droid.base.MainApplication; -import org.stepic.droid.events.steps.SuccessToDbStepEvent; -import org.stepic.droid.model.Lesson; -import org.stepic.droid.model.Step; -import org.stepic.droid.store.operations.DatabaseFacade; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -public class ToDbStepTask extends StepicTask { - private final List mStepList; - private final Lesson mLesson; - @Inject - DatabaseFacade mDatabaseFacade; - - @Inject - Bus mBus; - - public ToDbStepTask(Lesson parentLesson, List steps) { - super(MainApplication.getAppContext()); - - MainApplication.component().inject(this); - mStepList = steps; - mLesson = parentLesson; - - } - - public ToDbStepTask(Step oneStep) { - super(MainApplication.getAppContext()); - MainApplication.component().inject(this); - mStepList = new ArrayList<>(); - mStepList.add(oneStep); - mLesson = null; - - } - - @Override - protected Void doInBackgroundBody(Void... params) throws Exception { - for (Step step : mStepList) { - mDatabaseFacade.addStep(step); - } - return null; - } - - @Override - protected void onPostExecute(AsyncResultWrapper voidAsyncResultWrapper) { - super.onPostExecute(voidAsyncResultWrapper); - mBus.post(new SuccessToDbStepEvent(mLesson)); - } -} diff --git a/app/src/main/java/org/stepic/droid/concurrency/AsyncResultWrapper.java b/app/src/main/java/org/stepic/droid/concurrency/tasks/AsyncResultWrapper.java similarity index 90% rename from app/src/main/java/org/stepic/droid/concurrency/AsyncResultWrapper.java rename to app/src/main/java/org/stepic/droid/concurrency/tasks/AsyncResultWrapper.java index 3505eed2d5..f083172b65 100644 --- a/app/src/main/java/org/stepic/droid/concurrency/AsyncResultWrapper.java +++ b/app/src/main/java/org/stepic/droid/concurrency/tasks/AsyncResultWrapper.java @@ -1,4 +1,4 @@ -package org.stepic.droid.concurrency; +package org.stepic.droid.concurrency.tasks; public class AsyncResultWrapper { private T result; diff --git a/app/src/main/java/org/stepic/droid/concurrency/FromDbCoursesTask.java b/app/src/main/java/org/stepic/droid/concurrency/tasks/FromDbCoursesTask.java similarity index 92% rename from app/src/main/java/org/stepic/droid/concurrency/FromDbCoursesTask.java rename to app/src/main/java/org/stepic/droid/concurrency/tasks/FromDbCoursesTask.java index ef0e3b2e3e..0fe244ddfe 100644 --- a/app/src/main/java/org/stepic/droid/concurrency/FromDbCoursesTask.java +++ b/app/src/main/java/org/stepic/droid/concurrency/tasks/FromDbCoursesTask.java @@ -1,10 +1,9 @@ -package org.stepic.droid.concurrency; +package org.stepic.droid.concurrency.tasks; import com.squareup.otto.Bus; import org.jetbrains.annotations.NotNull; import org.stepic.droid.base.MainApplication; -import org.stepic.droid.core.IShell; import org.stepic.droid.events.courses.FinishingGetCoursesFromDbEvent; import org.stepic.droid.events.courses.StartingGetCoursesFromDbEvent; import org.stepic.droid.model.Course; @@ -16,9 +15,6 @@ public class FromDbCoursesTask extends StepicTask> { - @Inject - IShell mShell; - @Inject Bus mBus; @@ -51,4 +47,4 @@ protected void onPostExecute(AsyncResultWrapper> listAsyncResultWra super.onPostExecute(listAsyncResultWrapper); mBus.post(new FinishingGetCoursesFromDbEvent(mCourseType, listAsyncResultWrapper.getResult())); } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/concurrency/FromDbSectionTask.java b/app/src/main/java/org/stepic/droid/concurrency/tasks/FromDbSectionTask.java similarity index 97% rename from app/src/main/java/org/stepic/droid/concurrency/FromDbSectionTask.java rename to app/src/main/java/org/stepic/droid/concurrency/tasks/FromDbSectionTask.java index 83eb9c1b59..d25b4f5f19 100644 --- a/app/src/main/java/org/stepic/droid/concurrency/FromDbSectionTask.java +++ b/app/src/main/java/org/stepic/droid/concurrency/tasks/FromDbSectionTask.java @@ -1,4 +1,4 @@ -package org.stepic.droid.concurrency; +package org.stepic.droid.concurrency.tasks; import com.squareup.otto.Bus; diff --git a/app/src/main/java/org/stepic/droid/concurrency/FromDbStepTask.java b/app/src/main/java/org/stepic/droid/concurrency/tasks/FromDbStepTask.java similarity index 97% rename from app/src/main/java/org/stepic/droid/concurrency/FromDbStepTask.java rename to app/src/main/java/org/stepic/droid/concurrency/tasks/FromDbStepTask.java index 917df143db..13cd9e9817 100644 --- a/app/src/main/java/org/stepic/droid/concurrency/FromDbStepTask.java +++ b/app/src/main/java/org/stepic/droid/concurrency/tasks/FromDbStepTask.java @@ -1,4 +1,4 @@ -package org.stepic.droid.concurrency; +package org.stepic.droid.concurrency.tasks; import com.squareup.otto.Bus; diff --git a/app/src/main/java/org/stepic/droid/concurrency/FromDbUnitLessonTask.java b/app/src/main/java/org/stepic/droid/concurrency/tasks/FromDbUnitLessonTask.java similarity index 98% rename from app/src/main/java/org/stepic/droid/concurrency/FromDbUnitLessonTask.java rename to app/src/main/java/org/stepic/droid/concurrency/tasks/FromDbUnitLessonTask.java index c69865150a..a8e9c7fd2c 100644 --- a/app/src/main/java/org/stepic/droid/concurrency/FromDbUnitLessonTask.java +++ b/app/src/main/java/org/stepic/droid/concurrency/tasks/FromDbUnitLessonTask.java @@ -1,4 +1,4 @@ -package org.stepic.droid.concurrency; +package org.stepic.droid.concurrency.tasks; import com.squareup.otto.Bus; diff --git a/app/src/main/java/org/stepic/droid/concurrency/StepicTask.java b/app/src/main/java/org/stepic/droid/concurrency/tasks/StepicTask.java similarity index 98% rename from app/src/main/java/org/stepic/droid/concurrency/StepicTask.java rename to app/src/main/java/org/stepic/droid/concurrency/tasks/StepicTask.java index c880d5e1e2..be321cac4a 100644 --- a/app/src/main/java/org/stepic/droid/concurrency/StepicTask.java +++ b/app/src/main/java/org/stepic/droid/concurrency/tasks/StepicTask.java @@ -1,4 +1,4 @@ -package org.stepic.droid.concurrency; +package org.stepic.droid.concurrency.tasks; import android.content.Context; import android.os.AsyncTask; diff --git a/app/src/main/java/org/stepic/droid/concurrency/ToDbCoursesTask.java b/app/src/main/java/org/stepic/droid/concurrency/tasks/ToDbCoursesTask.java similarity index 81% rename from app/src/main/java/org/stepic/droid/concurrency/ToDbCoursesTask.java rename to app/src/main/java/org/stepic/droid/concurrency/tasks/ToDbCoursesTask.java index 7f2d941315..3eea9baa07 100644 --- a/app/src/main/java/org/stepic/droid/concurrency/ToDbCoursesTask.java +++ b/app/src/main/java/org/stepic/droid/concurrency/tasks/ToDbCoursesTask.java @@ -1,4 +1,4 @@ -package org.stepic.droid.concurrency; +package org.stepic.droid.concurrency.tasks; import com.squareup.otto.Bus; @@ -9,7 +9,6 @@ import org.stepic.droid.model.Course; import org.stepic.droid.store.operations.DatabaseFacade; -import java.util.ArrayList; import java.util.List; import javax.inject.Inject; @@ -37,17 +36,6 @@ public ToDbCoursesTask(List courses, DatabaseFacade.Table type, int page mCourses = courses; } - public ToDbCoursesTask(Course course, DatabaseFacade.Table type) { - super(MainApplication.getAppContext()); - MainApplication.component().inject(this); - - //courses now is not thread safe - mPage = Integer.MAX_VALUE; //neutral value - mCourseType = type; - mCourses = new ArrayList<>(); - mCourses.add(course); - } - @Override protected Void doInBackgroundBody(Void... params) throws Exception { diff --git a/app/src/main/java/org/stepic/droid/concurrency/ToDbSectionTask.java b/app/src/main/java/org/stepic/droid/concurrency/tasks/ToDbSectionTask.java similarity index 97% rename from app/src/main/java/org/stepic/droid/concurrency/ToDbSectionTask.java rename to app/src/main/java/org/stepic/droid/concurrency/tasks/ToDbSectionTask.java index 24ed75a287..cbecd3f47b 100644 --- a/app/src/main/java/org/stepic/droid/concurrency/ToDbSectionTask.java +++ b/app/src/main/java/org/stepic/droid/concurrency/tasks/ToDbSectionTask.java @@ -1,4 +1,4 @@ -package org.stepic.droid.concurrency; +package org.stepic.droid.concurrency.tasks; import com.squareup.otto.Bus; diff --git a/app/src/main/java/org/stepic/droid/concurrency/ToDbUnitLessonTask.java b/app/src/main/java/org/stepic/droid/concurrency/tasks/ToDbUnitLessonTask.java similarity index 97% rename from app/src/main/java/org/stepic/droid/concurrency/ToDbUnitLessonTask.java rename to app/src/main/java/org/stepic/droid/concurrency/tasks/ToDbUnitLessonTask.java index ce6fbc862a..83ab2f8a21 100644 --- a/app/src/main/java/org/stepic/droid/concurrency/ToDbUnitLessonTask.java +++ b/app/src/main/java/org/stepic/droid/concurrency/tasks/ToDbUnitLessonTask.java @@ -1,4 +1,4 @@ -package org.stepic.droid.concurrency; +package org.stepic.droid.concurrency.tasks; import com.squareup.otto.Bus; diff --git a/app/src/main/java/org/stepic/droid/concurrency/UpdateCourseTask.java b/app/src/main/java/org/stepic/droid/concurrency/tasks/UpdateCourseTask.java similarity index 96% rename from app/src/main/java/org/stepic/droid/concurrency/UpdateCourseTask.java rename to app/src/main/java/org/stepic/droid/concurrency/tasks/UpdateCourseTask.java index 3428a35b99..49e5f3dc7c 100644 --- a/app/src/main/java/org/stepic/droid/concurrency/UpdateCourseTask.java +++ b/app/src/main/java/org/stepic/droid/concurrency/tasks/UpdateCourseTask.java @@ -1,4 +1,4 @@ -package org.stepic.droid.concurrency; +package org.stepic.droid.concurrency.tasks; import org.stepic.droid.base.MainApplication; import org.stepic.droid.core.IShell; diff --git a/app/src/main/java/org/stepic/droid/core/ActivityFinisher.java b/app/src/main/java/org/stepic/droid/core/ActivityFinisher.java deleted file mode 100644 index aa64a37d0b..0000000000 --- a/app/src/main/java/org/stepic/droid/core/ActivityFinisher.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.stepic.droid.core; - -public interface ActivityFinisher { - void onFinish(); -} diff --git a/app/src/main/java/org/stepic/droid/core/ActivityFinisher.kt b/app/src/main/java/org/stepic/droid/core/ActivityFinisher.kt new file mode 100644 index 0000000000..b9bd1f8b3c --- /dev/null +++ b/app/src/main/java/org/stepic/droid/core/ActivityFinisher.kt @@ -0,0 +1,5 @@ +package org.stepic.droid.core + +interface ActivityFinisher { + fun onFinish() +} diff --git a/app/src/main/java/org/stepic/droid/core/AudioFocusHelper.kt b/app/src/main/java/org/stepic/droid/core/AudioFocusHelper.kt new file mode 100644 index 0000000000..ce39210fe8 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/core/AudioFocusHelper.kt @@ -0,0 +1,30 @@ +package org.stepic.droid.core + +import android.content.Context +import android.media.AudioManager +import com.squareup.otto.Bus +import org.stepic.droid.concurrency.IMainHandler +import org.stepic.droid.events.audio.AudioFocusGainEvent +import org.stepic.droid.events.audio.AudioFocusLossEvent + +class AudioFocusHelper(context: Context, bus: Bus, mainHandler: IMainHandler) : AudioManager.OnAudioFocusChangeListener { + val mAudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + val mContext = context + val mBus = bus + val mMainHandler = mainHandler + + fun requestAudioFocus() = AudioManager.AUDIOFOCUS_REQUEST_GRANTED == + mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) + + + fun releaseAudioFocus() = AudioManager.AUDIOFOCUS_REQUEST_GRANTED == + mAudioManager.abandonAudioFocus(this); + + + override fun onAudioFocusChange(focusChange: Int) { + when (focusChange) { + AudioManager.AUDIOFOCUS_GAIN -> mMainHandler.post { mBus.post(AudioFocusGainEvent()) } + AudioManager.AUDIOFOCUS_LOSS -> mMainHandler.post { mBus.post(AudioFocusLossEvent()) } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/core/ILessonSessionManager.java b/app/src/main/java/org/stepic/droid/core/ILessonSessionManager.java deleted file mode 100644 index e40a219475..0000000000 --- a/app/src/main/java/org/stepic/droid/core/ILessonSessionManager.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.stepic.droid.core; - -import org.jetbrains.annotations.Nullable; -import org.stepic.droid.model.Attempt; -import org.stepic.droid.model.Submission; - -public interface ILessonSessionManager { - @Nullable - Submission restoreSubmissionForStep(long stepId); - - @Nullable - Attempt restoreAttemptForStep(long stepId); - - void saveSession(long stepId, @Nullable Attempt attempt, @Nullable Submission submission); - - void reset(); -} diff --git a/app/src/main/java/org/stepic/droid/core/ILessonSessionManager.kt b/app/src/main/java/org/stepic/droid/core/ILessonSessionManager.kt new file mode 100644 index 0000000000..27cd741bf1 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/core/ILessonSessionManager.kt @@ -0,0 +1,14 @@ +package org.stepic.droid.core + +import org.stepic.droid.model.Attempt +import org.stepic.droid.model.Submission + +interface ILessonSessionManager { + fun restoreSubmissionForStep(stepId: Long): Submission? + + fun restoreAttemptForStep(stepId: Long): Attempt? + + fun saveSession(stepId: Long, attempt: Attempt?, submission: Submission?) + + fun reset() +} diff --git a/app/src/main/java/org/stepic/droid/core/IScreenManager.java b/app/src/main/java/org/stepic/droid/core/IScreenManager.java index 76915e8bbf..6d6b8181ed 100644 --- a/app/src/main/java/org/stepic/droid/core/IScreenManager.java +++ b/app/src/main/java/org/stepic/droid/core/IScreenManager.java @@ -32,15 +32,10 @@ public interface IScreenManager { void openStepInWeb(Context context, Step step); - void openSignUpInWeb(Context context); - void openRemindPassword(AppCompatActivity context); void pushToViewedQueue(ViewAssignment viewAssignmentWrapper); - @Deprecated - void showSocialLogin(Context context); - void showCourseDescription(Activity sourceActivity, @NotNull Course course); void showTextFeedback(Activity sourceActivity); @@ -48,4 +43,6 @@ public interface IScreenManager { void showStoreWithApp(Activity sourceActivity); void showDownload(); + + void showVideo(Activity sourceActivity, String source); } diff --git a/app/src/main/java/org/stepic/droid/core/MyStatePhoneListener.kt b/app/src/main/java/org/stepic/droid/core/MyStatePhoneListener.kt new file mode 100644 index 0000000000..3de1005523 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/core/MyStatePhoneListener.kt @@ -0,0 +1,27 @@ +package org.stepic.droid.core + +import android.telephony.PhoneStateListener +import com.squareup.otto.Bus +import org.stepic.droid.base.MainApplication +import org.stepic.droid.concurrency.IMainHandler +import org.stepic.droid.events.IncomingCallEvent +import javax.inject.Inject + +class MyStatePhoneListener: PhoneStateListener() { + + init { + MainApplication.component().inject(this) + } + + @Inject + lateinit var mBus: Bus + + @Inject + lateinit var mHandler: IMainHandler + + override fun onCallStateChanged(state: Int, incomingNumber: String?) { + if (state == 1) { + mHandler.post { mBus.post(IncomingCallEvent()) } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/core/ScreenManager.java b/app/src/main/java/org/stepic/droid/core/ScreenManager.java index 0bd58d6abe..9ead51d3a7 100644 --- a/app/src/main/java/org/stepic/droid/core/ScreenManager.java +++ b/app/src/main/java/org/stepic/droid/core/ScreenManager.java @@ -8,10 +8,12 @@ import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v7.app.AppCompatActivity; +import android.widget.Toast; import com.yandex.metrica.YandexMetrica; import org.jetbrains.annotations.NotNull; +import org.stepic.droid.R; import org.stepic.droid.base.MainApplication; import org.stepic.droid.configuration.IConfig; import org.stepic.droid.model.Course; @@ -19,6 +21,7 @@ import org.stepic.droid.model.Section; import org.stepic.droid.model.Step; import org.stepic.droid.model.Unit; +import org.stepic.droid.preferences.UserPreferences; import org.stepic.droid.services.ViewPusher; import org.stepic.droid.util.AppConstants; import org.stepic.droid.util.JsonHelper; @@ -32,9 +35,11 @@ import org.stepic.droid.view.activities.StepsActivity; import org.stepic.droid.view.activities.TextFeedbackActivity; import org.stepic.droid.view.activities.UnitsActivity; +import org.stepic.droid.view.activities.VideoActivity; import org.stepic.droid.view.dialogs.RemindPasswordDialogFragment; import org.stepic.droid.view.fragments.DownloadsFragment; import org.stepic.droid.web.ViewAssignment; +import org.videolan.libvlc.util.VLCUtil; import javax.inject.Inject; import javax.inject.Singleton; @@ -43,12 +48,13 @@ public class ScreenManager implements IScreenManager { private IConfig mConfig; private IMainMenuResolver mMainMenuResolver; - + private UserPreferences mUserPreferences; @Inject - public ScreenManager(IConfig config, IMainMenuResolver mainMenuResolver) { + public ScreenManager(IConfig config, IMainMenuResolver mainMenuResolver, UserPreferences userPreferences) { this.mConfig = config; mMainMenuResolver = mainMenuResolver; + mUserPreferences = userPreferences; } @Override @@ -134,8 +140,8 @@ public void showStoreWithApp(@NotNull Activity sourceActivity) { @Override public void showDownload() { - Intent intent = new Intent (MainApplication.getAppContext(), MainFeedActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Intent intent = new Intent(MainApplication.getAppContext(), MainFeedActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); Bundle bundle = new Bundle(); int index = mMainMenuResolver.getIndexOfFragment(DownloadsFragment.class); bundle.putInt(MainFeedActivity.KEY_CURRENT_INDEX, index); @@ -143,6 +149,41 @@ public void showDownload() { MainApplication.getAppContext().startActivity(intent); } + @Override + public void showVideo(Activity sourceActivity, String videoPath) { + YandexMetrica.reportEvent("video is tried to show"); + boolean isOpenExternal = mUserPreferences.isOpenInExternal(); + if (isOpenExternal){ + YandexMetrica.reportEvent("video open external"); + } + else{ + YandexMetrica.reportEvent("video open native"); + } + + boolean isCompatible = VLCUtil.hasCompatibleCPU(MainApplication.getAppContext()); + if (!isCompatible){ + YandexMetrica.reportEvent("video is not compatible"); + } + + + + if (isCompatible && !isOpenExternal) { + Intent intent = new Intent(MainApplication.getAppContext(), VideoActivity.class); + intent.putExtra(VideoActivity.Companion.getVideoPathKey(), videoPath); + sourceActivity.startActivity(intent); + } else { + Uri videoUri = Uri.parse(videoPath); + Intent intent = new Intent(Intent.ACTION_VIEW, videoUri); + intent.setDataAndType(videoUri, "video/*"); + try { + sourceActivity.startActivity(intent); + } catch (Exception ex) { + YandexMetrica.reportError("NotPlayer", ex); + Toast.makeText(sourceActivity, R.string.not_video_player_error, Toast.LENGTH_LONG).show(); + } + } + } + @Override public void showSections(Context sourceActivity, @NotNull Course course) { YandexMetrica.reportEvent("Screen manager: show section", JsonHelper.toJson(course)); @@ -183,14 +224,6 @@ public void openStepInWeb(Context context, Step step) { context.startActivity(intent); } - @Override - public void openSignUpInWeb(Context context) { - YandexMetrica.reportEvent("Screen manager: open signup in Web"); - String url = mConfig.getBaseUrl() + "/accounts/signup/"; - final Intent intent = new Intent(Intent.ACTION_VIEW).setData(Uri.parse(url)); - context.startActivity(intent); - } - @Override public void openRemindPassword(AppCompatActivity context) { YandexMetrica.reportEvent("Screen manager: remind password"); @@ -208,11 +241,4 @@ public void pushToViewedQueue(ViewAssignment viewAssignmentWrapper) { MainApplication.getAppContext().startService(loadIntent); } - @Override - public void showSocialLogin(Context context) { - String url = mConfig.getBaseUrl() + "/oauth2/authorize/?client_id=P3svssuGYOJ8g8rrJSJtVbqnyE0QinTfncbfFr9p&response_type=token"; - final Intent intent = new Intent(Intent.ACTION_VIEW).setData(Uri.parse(url)); - context.startActivity(intent); - } - } diff --git a/app/src/main/java/org/stepic/droid/core/StepicCoreComponent.java b/app/src/main/java/org/stepic/droid/core/StepicCoreComponent.java index ee7828d9d6..e1804756e6 100644 --- a/app/src/main/java/org/stepic/droid/core/StepicCoreComponent.java +++ b/app/src/main/java/org/stepic/droid/core/StepicCoreComponent.java @@ -2,16 +2,14 @@ import org.stepic.droid.base.FragmentActivityBase; import org.stepic.droid.base.FragmentBase; -import org.stepic.droid.concurrency.FromDbCoursesTask; -import org.stepic.droid.concurrency.FromDbSectionTask; -import org.stepic.droid.concurrency.FromDbStepTask; -import org.stepic.droid.concurrency.FromDbUnitLessonTask; -import org.stepic.droid.concurrency.ToDbCachedVideo; -import org.stepic.droid.concurrency.ToDbCoursesTask; -import org.stepic.droid.concurrency.ToDbSectionTask; -import org.stepic.droid.concurrency.ToDbStepTask; -import org.stepic.droid.concurrency.ToDbUnitLessonTask; -import org.stepic.droid.concurrency.UpdateCourseTask; +import org.stepic.droid.concurrency.tasks.FromDbCoursesTask; +import org.stepic.droid.concurrency.tasks.FromDbSectionTask; +import org.stepic.droid.concurrency.tasks.FromDbStepTask; +import org.stepic.droid.concurrency.tasks.FromDbUnitLessonTask; +import org.stepic.droid.concurrency.tasks.ToDbCoursesTask; +import org.stepic.droid.concurrency.tasks.ToDbSectionTask; +import org.stepic.droid.concurrency.tasks.ToDbUnitLessonTask; +import org.stepic.droid.concurrency.tasks.UpdateCourseTask; import org.stepic.droid.model.Course; import org.stepic.droid.model.Section; import org.stepic.droid.receivers.DownloadClickReceiver; @@ -36,7 +34,6 @@ import org.stepic.droid.view.dialogs.RemindPasswordDialogFragment; import org.stepic.droid.view.dialogs.VideoQualityDialog; import org.stepic.droid.view.fragments.DownloadsFragment; -import org.stepic.droid.web.HttpManager; import org.stepic.droid.web.RetrofitRESTApi; import javax.inject.Singleton; @@ -50,8 +47,6 @@ public interface StepicCoreComponent { void inject(Shell injectAllToShell); - void inject(HttpManager httpManager); - void inject(MyCoursesAdapter adapter); void inject(Course adapter); @@ -77,8 +72,6 @@ public interface StepicCoreComponent { //All Tasks: - void inject(FromDbCoursesTask stepicTask); - void inject(ToDbCoursesTask stepicTask); void inject(UpdateCourseTask stepicTask); @@ -91,12 +84,8 @@ public interface StepicCoreComponent { void inject(ToDbUnitLessonTask stepicTask); - void inject(ToDbStepTask stepicTask); - void inject(FromDbStepTask stepicTask); - void inject(ToDbCachedVideo stepicTask); - void inject(AllowMobileDataDialogFragment allowMobileDataDialogFragment); void inject(LoadService loadService); @@ -126,4 +115,8 @@ public interface StepicCoreComponent { void inject(CancelLoadingService service); void inject(DownloadClickReceiver downloadClickReceiver); + + void inject(FromDbCoursesTask fromDbCoursesTask); + + void inject(MyStatePhoneListener listener); } diff --git a/app/src/main/java/org/stepic/droid/core/StepicDefaultModule.java b/app/src/main/java/org/stepic/droid/core/StepicDefaultModule.java index e9efaddcef..dac2bd8c42 100644 --- a/app/src/main/java/org/stepic/droid/core/StepicDefaultModule.java +++ b/app/src/main/java/org/stepic/droid/core/StepicDefaultModule.java @@ -6,6 +6,8 @@ import com.squareup.otto.Bus; +import org.stepic.droid.concurrency.IMainHandler; +import org.stepic.droid.concurrency.MainHandlerImpl; import org.stepic.droid.configuration.ConfigRelease; import org.stepic.droid.configuration.IConfig; import org.stepic.droid.model.Assignment; @@ -51,12 +53,14 @@ import org.stepic.droid.util.resolvers.SearchResolver; import org.stepic.droid.util.resolvers.StepTypeResolver; import org.stepic.droid.util.resolvers.VideoResolver; -import org.stepic.droid.web.HttpManager; import org.stepic.droid.web.IApi; -import org.stepic.droid.web.IHttpManager; import org.stepic.droid.web.RetrofitRESTApi; import org.stepic.droid.web.ViewAssignment; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; + import javax.inject.Singleton; import dagger.Module; @@ -74,8 +78,8 @@ public StepicDefaultModule(Context context) { @Provides @Singleton - public IScreenManager provideIScreenManager(IConfig config, IMainMenuResolver mainMenuResolver) { - return new ScreenManager(config, mainMenuResolver); + public IScreenManager provideIScreenManager(IConfig config, IMainMenuResolver mainMenuResolver, UserPreferences userPreferences) { + return new ScreenManager(config, mainMenuResolver, userPreferences); } @Provides @@ -97,12 +101,6 @@ public IApi provideIApi() { return new RetrofitRESTApi(); } - @Provides - @Singleton - public IHttpManager provideIHttpManager(Context context) { - return new HttpManager(context); - } - @Provides @Singleton public SharedPreferenceHelper provideSharedPreferencesHelper() { @@ -279,8 +277,33 @@ public ICancelSniffer provideCancelSniffer() { @Provides @Singleton - public IMainMenuResolver provideResolver(){ + public IMainMenuResolver provideResolver() { return new MainMenuResolverImpl(); } + @Provides + @Singleton + public ExecutorService provideSingle() { + return Executors.newSingleThreadExecutor(); + } + + + //it is good for many short lived, which should do async + @Provides + @Singleton + public ThreadPoolExecutor provideThreadPool() { + return (ThreadPoolExecutor) Executors.newCachedThreadPool(); + } + + @Singleton + @Provides + public IMainHandler provideHandlerForUIThread() { + return new MainHandlerImpl(); + } + + @Singleton + @Provides + public AudioFocusHelper provideAudioFocusHelper(Context context, IMainHandler mainHandler, Bus bus) { + return new AudioFocusHelper(context, bus, mainHandler); + } } diff --git a/app/src/main/java/org/stepic/droid/events/IncomingCallEvent.kt b/app/src/main/java/org/stepic/droid/events/IncomingCallEvent.kt new file mode 100644 index 0000000000..422b8ab80c --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/IncomingCallEvent.kt @@ -0,0 +1,4 @@ +package org.stepic.droid.events + +class IncomingCallEvent { +} \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/InternetIsEnabledEvent.java b/app/src/main/java/org/stepic/droid/events/InternetIsEnabledEvent.java deleted file mode 100644 index 360ce91516..0000000000 --- a/app/src/main/java/org/stepic/droid/events/InternetIsEnabledEvent.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.stepic.droid.events; - -public class InternetIsEnabledEvent { -} diff --git a/app/src/main/java/org/stepic/droid/events/InternetIsEnabledEvent.kt b/app/src/main/java/org/stepic/droid/events/InternetIsEnabledEvent.kt new file mode 100644 index 0000000000..f8a97f3eea --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/InternetIsEnabledEvent.kt @@ -0,0 +1,3 @@ +package org.stepic.droid.events + +class InternetIsEnabledEvent \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/attempts/AttemptBaseEvent.java b/app/src/main/java/org/stepic/droid/events/attempts/AttemptBaseEvent.java deleted file mode 100644 index 17c12001ac..0000000000 --- a/app/src/main/java/org/stepic/droid/events/attempts/AttemptBaseEvent.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.stepic.droid.events.attempts; - -import org.stepic.droid.model.Attempt; - -public class AttemptBaseEvent { - - private long stepId; - private Attempt mAttempt; - - public AttemptBaseEvent(long stepId, Attempt attempt) { - - this.stepId = stepId; - mAttempt = attempt; - } - - public Attempt getAttempt() { - return mAttempt; - } - - - public long getStepId() { - return stepId; - } -} diff --git a/app/src/main/java/org/stepic/droid/events/attempts/AttemptBaseEvent.kt b/app/src/main/java/org/stepic/droid/events/attempts/AttemptBaseEvent.kt new file mode 100644 index 0000000000..0e6c5f63e4 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/attempts/AttemptBaseEvent.kt @@ -0,0 +1,5 @@ +package org.stepic.droid.events.attempts + +import org.stepic.droid.model.Attempt + +open class AttemptBaseEvent(val stepId: Long, val attempt: Attempt?) \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/attempts/FailAttemptEvent.java b/app/src/main/java/org/stepic/droid/events/attempts/FailAttemptEvent.java deleted file mode 100644 index a5f4e929eb..0000000000 --- a/app/src/main/java/org/stepic/droid/events/attempts/FailAttemptEvent.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.stepic.droid.events.attempts; - -public class FailAttemptEvent extends AttemptBaseEvent { - public FailAttemptEvent(long stepId) { - super(stepId, null); - } -} - diff --git a/app/src/main/java/org/stepic/droid/events/attempts/FailAttemptEvent.kt b/app/src/main/java/org/stepic/droid/events/attempts/FailAttemptEvent.kt new file mode 100644 index 0000000000..8190b8f620 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/attempts/FailAttemptEvent.kt @@ -0,0 +1,3 @@ +package org.stepic.droid.events.attempts + +class FailAttemptEvent(stepId: Long) : AttemptBaseEvent(stepId, null) \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/attempts/SuccessAttemptEvent.java b/app/src/main/java/org/stepic/droid/events/attempts/SuccessAttemptEvent.java deleted file mode 100644 index f2af8ba35d..0000000000 --- a/app/src/main/java/org/stepic/droid/events/attempts/SuccessAttemptEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.stepic.droid.events.attempts; - -import org.stepic.droid.model.Attempt; - -public class SuccessAttemptEvent extends AttemptBaseEvent { - private final boolean justCreated; - - public SuccessAttemptEvent(long stepId, Attempt attempt, boolean b) { - super(stepId, attempt); - justCreated = b; - } - - public boolean isJustCreated() { - return justCreated; - } -} diff --git a/app/src/main/java/org/stepic/droid/events/attempts/SuccessAttemptEvent.kt b/app/src/main/java/org/stepic/droid/events/attempts/SuccessAttemptEvent.kt new file mode 100644 index 0000000000..37137d654c --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/attempts/SuccessAttemptEvent.kt @@ -0,0 +1,5 @@ +package org.stepic.droid.events.attempts + +import org.stepic.droid.model.Attempt + +class SuccessAttemptEvent(stepId: Long, attempt: Attempt?, val isJustCreated: Boolean) : AttemptBaseEvent(stepId, attempt) \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/audio/AudioFocusGainEvent.kt b/app/src/main/java/org/stepic/droid/events/audio/AudioFocusGainEvent.kt new file mode 100644 index 0000000000..cd0aeb66af --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/audio/AudioFocusGainEvent.kt @@ -0,0 +1,3 @@ +package org.stepic.droid.events.audio + +class AudioFocusGainEvent \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/audio/AudioFocusLossEvent.kt b/app/src/main/java/org/stepic/droid/events/audio/AudioFocusLossEvent.kt new file mode 100644 index 0000000000..7028362fdb --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/audio/AudioFocusLossEvent.kt @@ -0,0 +1,3 @@ +package org.stepic.droid.events.audio + +class AudioFocusLossEvent \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/audio/AudioFocusLostDownVolume.kt b/app/src/main/java/org/stepic/droid/events/audio/AudioFocusLostDownVolume.kt new file mode 100644 index 0000000000..783491e04b --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/audio/AudioFocusLostDownVolume.kt @@ -0,0 +1,3 @@ +package org.stepic.droid.events.audio + +class AudioFocusLostDownVolume \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/audio/AudioFocusLostShortTimeEvent.kt b/app/src/main/java/org/stepic/droid/events/audio/AudioFocusLostShortTimeEvent.kt new file mode 100644 index 0000000000..5a486eeea5 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/audio/AudioFocusLostShortTimeEvent.kt @@ -0,0 +1,3 @@ +package org.stepic.droid.events.audio + +class AudioFocusLostShortTimeEvent \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/feedback/FeedbackFailedEvent.java b/app/src/main/java/org/stepic/droid/events/feedback/FeedbackFailedEvent.java deleted file mode 100644 index dd3f671614..0000000000 --- a/app/src/main/java/org/stepic/droid/events/feedback/FeedbackFailedEvent.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.stepic.droid.events.feedback; - -public class FeedbackFailedEvent { -} diff --git a/app/src/main/java/org/stepic/droid/events/feedback/FeedbackFailedEvent.kt b/app/src/main/java/org/stepic/droid/events/feedback/FeedbackFailedEvent.kt new file mode 100644 index 0000000000..06d0125ca4 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/feedback/FeedbackFailedEvent.kt @@ -0,0 +1,3 @@ +package org.stepic.droid.events.feedback + +class FeedbackFailedEvent \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/feedback/FeedbackInternetProblemsEvent.java b/app/src/main/java/org/stepic/droid/events/feedback/FeedbackInternetProblemsEvent.java deleted file mode 100644 index 2bf4184920..0000000000 --- a/app/src/main/java/org/stepic/droid/events/feedback/FeedbackInternetProblemsEvent.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.stepic.droid.events.feedback; - -public class FeedbackInternetProblemsEvent { -} diff --git a/app/src/main/java/org/stepic/droid/events/feedback/FeedbackInternetProblemsEvent.kt b/app/src/main/java/org/stepic/droid/events/feedback/FeedbackInternetProblemsEvent.kt new file mode 100644 index 0000000000..6af029dded --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/feedback/FeedbackInternetProblemsEvent.kt @@ -0,0 +1,3 @@ +package org.stepic.droid.events.feedback + +class FeedbackInternetProblemsEvent \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/feedback/FeedbackSentEvent.java b/app/src/main/java/org/stepic/droid/events/feedback/FeedbackSentEvent.java deleted file mode 100644 index 92a6692dc3..0000000000 --- a/app/src/main/java/org/stepic/droid/events/feedback/FeedbackSentEvent.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.stepic.droid.events.feedback; - -public class FeedbackSentEvent { -} diff --git a/app/src/main/java/org/stepic/droid/events/feedback/FeedbackSentEvent.kt b/app/src/main/java/org/stepic/droid/events/feedback/FeedbackSentEvent.kt new file mode 100644 index 0000000000..48a529bf93 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/feedback/FeedbackSentEvent.kt @@ -0,0 +1,3 @@ +package org.stepic.droid.events.feedback + +class FeedbackSentEvent \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/joining_course/FailJoinEvent.java b/app/src/main/java/org/stepic/droid/events/joining_course/FailJoinEvent.java deleted file mode 100644 index 64d35689ff..0000000000 --- a/app/src/main/java/org/stepic/droid/events/joining_course/FailJoinEvent.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.stepic.droid.events.joining_course; - -import retrofit.Response; - -public class FailJoinEvent { - private final Response response; - - public Response getResponse() { - return response; - } - - public FailJoinEvent() { - response = null; - - } - - public FailJoinEvent(Response response) { - this.response = response; - } -} diff --git a/app/src/main/java/org/stepic/droid/events/joining_course/FailJoinEvent.kt b/app/src/main/java/org/stepic/droid/events/joining_course/FailJoinEvent.kt new file mode 100644 index 0000000000..df092970d8 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/joining_course/FailJoinEvent.kt @@ -0,0 +1,5 @@ +package org.stepic.droid.events.joining_course + +import retrofit.Response + +class FailJoinEvent(val response: Response? = null) \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/joining_course/SuccessJoinEvent.java b/app/src/main/java/org/stepic/droid/events/joining_course/SuccessJoinEvent.java deleted file mode 100644 index 5bbc3c93d6..0000000000 --- a/app/src/main/java/org/stepic/droid/events/joining_course/SuccessJoinEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.stepic.droid.events.joining_course; - -import org.stepic.droid.model.Course; - -public class SuccessJoinEvent { - private Course mCourse; - - public SuccessJoinEvent(Course mCourse) { - - this.mCourse = mCourse; - } - - public Course getCourse() { - return mCourse; - } -} diff --git a/app/src/main/java/org/stepic/droid/events/joining_course/SuccessJoinEvent.kt b/app/src/main/java/org/stepic/droid/events/joining_course/SuccessJoinEvent.kt new file mode 100644 index 0000000000..7e62dc832c --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/joining_course/SuccessJoinEvent.kt @@ -0,0 +1,5 @@ +package org.stepic.droid.events.joining_course + +import org.stepic.droid.model.Course + +class SuccessJoinEvent(val course: Course?) \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/profile/ProfileCanBeShownEvent.java b/app/src/main/java/org/stepic/droid/events/profile/ProfileCanBeShownEvent.java deleted file mode 100644 index 2a5dff767d..0000000000 --- a/app/src/main/java/org/stepic/droid/events/profile/ProfileCanBeShownEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.stepic.droid.events.profile; - -import org.stepic.droid.model.Profile; - -public class ProfileCanBeShownEvent { - Profile profile; - - public ProfileCanBeShownEvent(Profile profile) { - this.profile = profile; - } - - public Profile getProfile() { - return profile; - } -} - diff --git a/app/src/main/java/org/stepic/droid/events/profile/ProfileCanBeShownEvent.kt b/app/src/main/java/org/stepic/droid/events/profile/ProfileCanBeShownEvent.kt new file mode 100644 index 0000000000..2838a8559c --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/profile/ProfileCanBeShownEvent.kt @@ -0,0 +1,5 @@ +package org.stepic.droid.events.profile + +import org.stepic.droid.model.Profile + +class ProfileCanBeShownEvent(var profile: Profile?) \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/video/DownloadReportEvent.kt b/app/src/main/java/org/stepic/droid/events/video/DownloadReportEvent.kt new file mode 100644 index 0000000000..b0727b44ac --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/video/DownloadReportEvent.kt @@ -0,0 +1,5 @@ +package org.stepic.droid.events.video + +import org.stepic.droid.model.DownloadReportItem + +data class DownloadReportEvent(val downloadReportItem: DownloadReportItem) \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/events/video/VideoCachedOnDiskEvent.kt b/app/src/main/java/org/stepic/droid/events/video/VideoCachedOnDiskEvent.kt new file mode 100644 index 0000000000..025a5daa93 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/video/VideoCachedOnDiskEvent.kt @@ -0,0 +1,6 @@ +package org.stepic.droid.events.video + +import org.stepic.droid.model.CachedVideo +import org.stepic.droid.model.Lesson + +data class VideoCachedOnDiskEvent(val stepId : Long, val lesson: Lesson, val video : CachedVideo) diff --git a/app/src/main/java/org/stepic/droid/events/wifi_settings/WifiLoadIsChangedEvent.java b/app/src/main/java/org/stepic/droid/events/wifi_settings/WifiLoadIsChangedEvent.java deleted file mode 100644 index ce7fe5f36d..0000000000 --- a/app/src/main/java/org/stepic/droid/events/wifi_settings/WifiLoadIsChangedEvent.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.stepic.droid.events.wifi_settings; - -public class WifiLoadIsChangedEvent { - final boolean newStateMobileAllowed; - - public WifiLoadIsChangedEvent(boolean newStateMobileAllowed) { - this.newStateMobileAllowed = newStateMobileAllowed; - } - - public boolean isNewStateMobileAllowed() { - return newStateMobileAllowed; - } -} diff --git a/app/src/main/java/org/stepic/droid/events/wifi_settings/WifiLoadIsChangedEvent.kt b/app/src/main/java/org/stepic/droid/events/wifi_settings/WifiLoadIsChangedEvent.kt new file mode 100644 index 0000000000..1268e0e99b --- /dev/null +++ b/app/src/main/java/org/stepic/droid/events/wifi_settings/WifiLoadIsChangedEvent.kt @@ -0,0 +1,3 @@ +package org.stepic.droid.events.wifi_settings + +class WifiLoadIsChangedEvent(val isNewStateMobileAllowed: Boolean) \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/exceptions/AuthException.java b/app/src/main/java/org/stepic/droid/exceptions/AuthException.java deleted file mode 100644 index 3fea041dcd..0000000000 --- a/app/src/main/java/org/stepic/droid/exceptions/AuthException.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.stepic.droid.exceptions; - -import java.io.IOException; - -public class AuthException extends IOException { -} diff --git a/app/src/main/java/org/stepic/droid/exceptions/NullCourseListException.java b/app/src/main/java/org/stepic/droid/exceptions/NullCourseListException.java deleted file mode 100644 index 6d416e0fb9..0000000000 --- a/app/src/main/java/org/stepic/droid/exceptions/NullCourseListException.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.stepic.droid.exceptions; - -public class NullCourseListException extends NullPointerException { -} diff --git a/app/src/main/java/org/stepic/droid/exceptions/NullProfileException.java b/app/src/main/java/org/stepic/droid/exceptions/NullProfileException.java deleted file mode 100644 index 619bb94620..0000000000 --- a/app/src/main/java/org/stepic/droid/exceptions/NullProfileException.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.stepic.droid.exceptions; - -public class NullProfileException extends NullPointerException { -} diff --git a/app/src/main/java/org/stepic/droid/exceptions/UnitStoredButLessonNotException.java b/app/src/main/java/org/stepic/droid/exceptions/UnitStoredButLessonNotException.java deleted file mode 100644 index 9291afdaeb..0000000000 --- a/app/src/main/java/org/stepic/droid/exceptions/UnitStoredButLessonNotException.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.stepic.droid.exceptions; - -public class UnitStoredButLessonNotException extends Exception { -} diff --git a/app/src/main/java/org/stepic/droid/exceptions/UnitStoredButLessonNotException.kt b/app/src/main/java/org/stepic/droid/exceptions/UnitStoredButLessonNotException.kt new file mode 100644 index 0000000000..ec56b58441 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/exceptions/UnitStoredButLessonNotException.kt @@ -0,0 +1,3 @@ +package org.stepic.droid.exceptions + +class UnitStoredButLessonNotException : Exception() \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/model/DownloadReportItem.kt b/app/src/main/java/org/stepic/droid/model/DownloadReportItem.kt new file mode 100644 index 0000000000..78371f34de --- /dev/null +++ b/app/src/main/java/org/stepic/droid/model/DownloadReportItem.kt @@ -0,0 +1,9 @@ +package org.stepic.droid.model + +data class DownloadReportItem( + val mBytesDownloaded: Int, + val mBytesTotal: Int, + val mColumnStatus: Int, + val mDownloadId: Int, + val mColumnReason: Int +) \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/preferences/SharedPreferenceHelper.java b/app/src/main/java/org/stepic/droid/preferences/SharedPreferenceHelper.java index d4c1338d05..1ea5fea5bc 100644 --- a/app/src/main/java/org/stepic/droid/preferences/SharedPreferenceHelper.java +++ b/app/src/main/java/org/stepic/droid/preferences/SharedPreferenceHelper.java @@ -32,11 +32,13 @@ public SharedPreferenceHelper() { mContext = MainApplication.getAppContext(); } + public enum PreferenceType { LOGIN("login preference"), WIFI("wifi_preference"), VIDEO_QUALITY("video_quality_preference"), - TEMP("temporary"); + TEMP("temporary"), + VIDEO_SETTINGS("video_settings"); private String description; @@ -49,6 +51,30 @@ private String getStoreName() { } } + public void storeVideoPlaybackRate(@NotNull VideoPlaybackRate videoPlaybackRate) { + int videoIndex = videoPlaybackRate.getIndex(); + put(PreferenceType.VIDEO_SETTINGS, VIDEO_RATE_PREF_KEY, videoIndex); + } + + @NotNull + public VideoPlaybackRate getVideoPlaybackRate() { + int index = getInt(PreferenceType.VIDEO_SETTINGS, VIDEO_RATE_PREF_KEY); + + for (VideoPlaybackRate item : VideoPlaybackRate.values()) { + if (index == item.getIndex()) return item; + } + + return VideoPlaybackRate.x1_0;//default + } + + public boolean isOpenInExternal() { + return getBoolean(PreferenceType.VIDEO_SETTINGS, VIDEO_EXTERNAL_PREF_KEY); + } + + public void setOpenInExternal(boolean isOpenInExternal) { + put(PreferenceType.VIDEO_SETTINGS, VIDEO_EXTERNAL_PREF_KEY, isOpenInExternal); + } + public void storeProfile(Profile profile) { //todo save picture of user profile //todo validate profile from the server with cached profile and make restore to cache. make @@ -222,5 +248,7 @@ private boolean getBoolean(PreferenceType preferenceType, String key) { private final String IS_SOCIAL = "is_social_key"; private final String VIDEO_QUALITY_KEY = "video_quality_key"; private final String TEMP_POSITION_KEY = "temp_position_key"; + private final String VIDEO_RATE_PREF_KEY = "video_rate_pref_key"; + private final String VIDEO_EXTERNAL_PREF_KEY = "video_external_pref_key"; } diff --git a/app/src/main/java/org/stepic/droid/preferences/UserPreferences.java b/app/src/main/java/org/stepic/droid/preferences/UserPreferences.java index 761e58af73..7299be4daf 100644 --- a/app/src/main/java/org/stepic/droid/preferences/UserPreferences.java +++ b/app/src/main/java/org/stepic/droid/preferences/UserPreferences.java @@ -100,4 +100,20 @@ public void storeQualityVideo(String videoQuality) { mSharedPreferenceHelper.storeVideoQuality(videoQuality); } + public VideoPlaybackRate getVideoPlaybackRate() { + return mSharedPreferenceHelper.getVideoPlaybackRate(); + } + + public void setVideoPlaybackRate(VideoPlaybackRate rate) { + mSharedPreferenceHelper.storeVideoPlaybackRate(rate); + } + + public boolean isOpenInExternal() { + return mSharedPreferenceHelper.isOpenInExternal(); + } + + public void setOpenInExternal(boolean isOpenInExternal) { + mSharedPreferenceHelper.setOpenInExternal(isOpenInExternal); + } + } diff --git a/app/src/main/java/org/stepic/droid/preferences/VideoPlaybackRate.kt b/app/src/main/java/org/stepic/droid/preferences/VideoPlaybackRate.kt new file mode 100644 index 0000000000..3cd1faa27d --- /dev/null +++ b/app/src/main/java/org/stepic/droid/preferences/VideoPlaybackRate.kt @@ -0,0 +1,20 @@ +package org.stepic.droid.preferences + +import android.graphics.drawable.Drawable +import android.support.v4.content.ContextCompat +import org.stepic.droid.R +import org.stepic.droid.base.MainApplication +import java.util.* + +enum class VideoPlaybackRate internal constructor(val index: Int, val rateFloat: Float, val icon: Drawable) { + x0_5(0, 0.5f, ContextCompat.getDrawable(MainApplication.getAppContext(), R.drawable.ic_playbackrate_0_5_light)), + x0_75(1, 0.75f, ContextCompat.getDrawable(MainApplication.getAppContext(), R.drawable.ic_playbackrate_0_75_light)), + x1_0(2, 1f, ContextCompat.getDrawable(MainApplication.getAppContext(), R.drawable.ic_playbackrate_1_light)), + x1_25(3, 1.25f, ContextCompat.getDrawable(MainApplication.getAppContext(), R.drawable.ic_playbackrate_1_25_light)), + x1_5(4, 1.5f, ContextCompat.getDrawable(MainApplication.getAppContext(), R.drawable.ic_playbackrate_1_5_light)), + x2(5, 2f, ContextCompat.getDrawable(MainApplication.getAppContext(), R.drawable.ic_playbackrate_2_0_light)); + + fun getAllOptions(): List { + return Arrays.asList(*VideoPlaybackRate.values()) + } +} diff --git a/app/src/main/java/org/stepic/droid/receivers/DownloadCompleteReceiver.java b/app/src/main/java/org/stepic/droid/receivers/DownloadCompleteReceiver.java index 1056331455..766354b163 100644 --- a/app/src/main/java/org/stepic/droid/receivers/DownloadCompleteReceiver.java +++ b/app/src/main/java/org/stepic/droid/receivers/DownloadCompleteReceiver.java @@ -5,11 +5,16 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.AsyncTask; +import android.os.Handler; +import android.util.Log; + +import com.squareup.otto.Bus; import org.stepic.droid.base.MainApplication; +import org.stepic.droid.events.video.VideoCachedOnDiskEvent; import org.stepic.droid.model.CachedVideo; import org.stepic.droid.model.DownloadEntity; +import org.stepic.droid.model.Lesson; import org.stepic.droid.model.Step; import org.stepic.droid.preferences.UserPreferences; import org.stepic.droid.store.ICancelSniffer; @@ -18,6 +23,7 @@ import org.stepic.droid.util.RWLocks; import java.io.File; +import java.util.concurrent.ExecutorService; import javax.inject.Inject; @@ -29,10 +35,15 @@ public class DownloadCompleteReceiver extends BroadcastReceiver { DatabaseFacade mDatabaseFacade; @Inject IStoreStateManager mStoreStateManager; + @Inject + Bus bus; @Inject ICancelSniffer mCancelSniffer; + @Inject + ExecutorService mThreadSingleThreadExecutor; + public DownloadCompleteReceiver() { MainApplication.component().inject(this); } @@ -40,50 +51,64 @@ public DownloadCompleteReceiver() { @Override public void onReceive(Context context, Intent intent) { final long referenceId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); - AsyncTask task = new AsyncTask() { + + mThreadSingleThreadExecutor.execute(new Runnable() { @Override - protected Void doInBackground(Void[] params) { - try { - RWLocks.DownloadLock.writeLock().lock(); - - DownloadEntity downloadEntity = mDatabaseFacade.getDownloadEntityIfExist(referenceId); - if (downloadEntity != null) { - long video_id = downloadEntity.getVideoId(); - long step_id = downloadEntity.getStepId(); - mDatabaseFacade.deleteDownloadEntityByDownloadId(referenceId); - - - File downloadFolderAndFile = new File(mUserPrefs.getUserDownloadFolder(), video_id + ""); - String path = Uri.fromFile(downloadFolderAndFile).getPath(); - - if (mCancelSniffer.isStepIdCanceled(step_id)) { - File file = new File(path); - if (file.exists()) { - file.delete(); - } - mCancelSniffer.removeStepIdCancel(step_id); - } - { - //is not canceled - CachedVideo cachedVideo = new CachedVideo(step_id, video_id, path, downloadEntity.getThumbnail()); - cachedVideo.setQuality(downloadEntity.getQuality()); - mDatabaseFacade.addVideo(cachedVideo); - - Step step = mDatabaseFacade.getStepById(step_id); - step.set_cached(true); - step.set_loading(false); - mDatabaseFacade.updateOnlyCachedLoadingStep(step); - mStoreStateManager.updateUnitLessonState(step.getLesson()); - } + public void run() { + blockForInBackground(referenceId); + Log.d("thread", Thread.currentThread().getName()+ " "); + } + }); + } + + private void blockForInBackground(final long referenceId) { + try { + RWLocks.DownloadLock.writeLock().lock(); + + DownloadEntity downloadEntity = mDatabaseFacade.getDownloadEntityIfExist(referenceId); + if (downloadEntity != null) { + long video_id = downloadEntity.getVideoId(); + final long step_id = downloadEntity.getStepId(); + mDatabaseFacade.deleteDownloadEntityByDownloadId(referenceId); + + + File downloadFolderAndFile = new File(mUserPrefs.getUserDownloadFolder(), video_id + ""); + String path = Uri.fromFile(downloadFolderAndFile).getPath(); + + if (mCancelSniffer.isStepIdCanceled(step_id)) { + File file = new File(path); + if (file.exists()) { + file.delete(); } - } finally { - RWLocks.DownloadLock.writeLock().unlock(); + mCancelSniffer.removeStepIdCancel(step_id); + } + { + //is not canceled + final CachedVideo cachedVideo = new CachedVideo(step_id, video_id, path, downloadEntity.getThumbnail()); + cachedVideo.setQuality(downloadEntity.getQuality()); + mDatabaseFacade.addVideo(cachedVideo); + + final Step step = mDatabaseFacade.getStepById(step_id); + step.set_cached(true); + step.set_loading(false); + mDatabaseFacade.updateOnlyCachedLoadingStep(step); + mStoreStateManager.updateUnitLessonState(step.getLesson()); + final Lesson lesson = mDatabaseFacade.getLessonById(step.getLesson()); + Handler mainHandler = new Handler(MainApplication.getAppContext().getMainLooper()); + //Say to ui that ui is cached now + Runnable myRunnable = new Runnable() { + @Override + public void run() { + if (lesson != null) + bus.post(new VideoCachedOnDiskEvent(step_id, lesson, cachedVideo)); + } + }; + mainHandler.post(myRunnable); } - return null; - //end critical section } - }; - task.execute(); + } finally { + RWLocks.DownloadLock.writeLock().unlock(); + } } } \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/services/LoadService.java b/app/src/main/java/org/stepic/droid/services/LoadService.java index 54b15a63f2..a266775a7e 100644 --- a/app/src/main/java/org/stepic/droid/services/LoadService.java +++ b/app/src/main/java/org/stepic/droid/services/LoadService.java @@ -20,6 +20,7 @@ import org.stepic.droid.model.Step; import org.stepic.droid.model.Unit; import org.stepic.droid.model.Video; +import org.stepic.droid.model.VideoUrl; import org.stepic.droid.preferences.UserPreferences; import org.stepic.droid.store.ICancelSniffer; import org.stepic.droid.store.IStoreStateManager; @@ -159,10 +160,24 @@ private void addDownload(String url, long fileId, String title, Step step) { } if (!mDb.isExistDownloadEntityByVideoId(fileId) && !downloadFolderAndFile.exists()) { - long downloadId = mSystemDownloadManager.enqueue(request); + + String videoQuality = null; + try { + for (VideoUrl urlItem : step.getBlock().getVideo().getUrls()) { + if (urlItem.getUrl().trim().equals(url)) { + videoQuality = urlItem.getQuality(); + break; + } + } + } + catch (NullPointerException npe){ + videoQuality = mUserPrefs.getQualityVideo(); + } + + long downloadId = mSystemDownloadManager.enqueue(request); String local_thumbnail = fileId + AppConstants.THUMBNAIL_POSTFIX_EXTENSION; String thumbnailsPath = FileUtil.saveImageToDisk(local_thumbnail, step.getBlock().getVideo().getThumbnail(), mUserPrefs.getUserDownloadFolder()); - final DownloadEntity newEntity = new DownloadEntity(downloadId, step.getId(), fileId, thumbnailsPath, mUserPrefs.getQualityVideo()); + final DownloadEntity newEntity = new DownloadEntity(downloadId, step.getId(), fileId, thumbnailsPath, videoQuality); mDb.addDownloadEntity(newEntity); } } catch (SecurityException ex) { diff --git a/app/src/main/java/org/stepic/droid/services/PlaybackService.java b/app/src/main/java/org/stepic/droid/services/PlaybackService.java new file mode 100644 index 0000000000..5e13c4aff5 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/services/PlaybackService.java @@ -0,0 +1,2111 @@ +///***************************************************************************** +// * PlaybackService.java +// ***************************************************************************** +// * Copyright © 2011-2015 VLC authors and VideoLAN +// * +// * This program is free software; you can redistribute it and/or modify +// * it under the terms of the GNU General Public License as published by +// * the Free Software Foundation; either version 2 of the License, or +// * (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU General Public License for more details. +// * +// * You should have received a copy of the GNU General Public License +// * along with this program; if not, write to the Free Software +// * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. +// *****************************************************************************/ +// +//package org.stepic.droid.services; +// +//import android.annotation.TargetApi; +//import android.app.Notification; +//import android.app.PendingIntent; +//import android.app.Service; +//import android.content.BroadcastReceiver; +//import android.content.ComponentName; +//import android.content.Context; +//import android.content.Intent; +//import android.content.IntentFilter; +//import android.content.ServiceConnection; +//import android.content.SharedPreferences; +//import android.content.pm.PackageManager; +//import android.graphics.Bitmap; +//import android.graphics.BitmapFactory; +//import android.media.AudioManager; +//import android.media.AudioManager.OnAudioFocusChangeListener; +//import android.net.Uri; +//import android.os.Binder; +//import android.os.Build; +//import android.os.Bundle; +//import android.os.Handler; +//import android.os.IBinder; +//import android.os.Message; +//import android.os.PowerManager; +//import android.preference.PreferenceManager; +//import android.support.annotation.MainThread; +//import android.support.annotation.Nullable; +//import android.support.v4.app.NotificationManagerCompat; +//import android.support.v4.content.LocalBroadcastManager; +//import android.support.v4.media.MediaMetadataCompat; +//import android.support.v4.media.session.MediaSessionCompat; +//import android.support.v4.media.session.PlaybackStateCompat; +//import android.support.v7.app.NotificationCompat; +//import android.telephony.PhoneStateListener; +//import android.telephony.TelephonyManager; +//import android.text.TextUtils; +//import android.util.Log; +//import android.widget.Toast; +// +//import org.videolan.libvlc.IVLCVout; +//import org.videolan.libvlc.LibVLC; +//import org.videolan.libvlc.Media; +//import org.videolan.libvlc.MediaList; +//import org.videolan.libvlc.MediaPlayer; +//import org.videolan.libvlc.util.AndroidUtil; +//import org.videolan.vlc.gui.AudioPlayerContainerActivity; +//import org.videolan.vlc.gui.helpers.AudioUtil; +//import org.videolan.vlc.gui.preferences.PreferencesActivity; +//import org.videolan.vlc.gui.preferences.PreferencesFragment; +//import org.videolan.vlc.gui.video.VideoPlayerActivity; +//import org.videolan.vlc.media.MediaDatabase; +//import org.videolan.vlc.media.MediaUtils; +//import org.videolan.vlc.media.MediaWrapper; +//import org.videolan.vlc.media.MediaWrapperList; +//import org.videolan.vlc.util.AndroidDevices; +//import org.videolan.vlc.util.FileUtils; +//import org.videolan.vlc.util.Strings; +//import org.videolan.vlc.util.Util; +//import org.videolan.vlc.util.VLCInstance; +//import org.videolan.vlc.util.VLCOptions; +//import org.videolan.vlc.util.WeakHandler; +//import org.videolan.vlc.widget.VLCAppWidgetProvider; +// +//import java.io.File; +//import java.net.URI; +//import java.net.URISyntaxException; +//import java.util.ArrayList; +//import java.util.Calendar; +//import java.util.Collections; +//import java.util.List; +//import java.util.Locale; +//import java.util.Random; +//import java.util.Stack; +//import java.util.concurrent.atomic.AtomicBoolean; +// +//public class PlaybackService extends Service implements IVLCVout.Callback { +// +// private static final String TAG = "VLC/PlaybackService"; +// +// private static final int SHOW_PROGRESS = 0; +// private static final int SHOW_TOAST = 1; +// public static final String ACTION_REMOTE_GENERIC = Strings.buildPkgString("remote."); +// public static final String ACTION_REMOTE_BACKWARD = ACTION_REMOTE_GENERIC+"Backward"; +// public static final String ACTION_REMOTE_PLAY = ACTION_REMOTE_GENERIC+"Play"; +// public static final String ACTION_REMOTE_PLAYPAUSE = ACTION_REMOTE_GENERIC+"PlayPause"; +// public static final String ACTION_REMOTE_PAUSE = ACTION_REMOTE_GENERIC+"Pause"; +// public static final String ACTION_REMOTE_STOP = ACTION_REMOTE_GENERIC+"Stop"; +// public static final String ACTION_REMOTE_FORWARD = ACTION_REMOTE_GENERIC+"Forward"; +// public static final String ACTION_REMOTE_LAST_PLAYLIST = ACTION_REMOTE_GENERIC+"LastPlaylist"; +// public static final String ACTION_REMOTE_LAST_VIDEO_PLAYLIST = ACTION_REMOTE_GENERIC+"LastVideoPlaylist"; +// public static final String ACTION_REMOTE_SWITCH_VIDEO = ACTION_REMOTE_GENERIC+"SwitchToVideo"; +// +// public interface Callback { +// void update(); +// void updateProgress(); +// void onMediaEvent(Media.Event event); +// void onMediaPlayerEvent(MediaPlayer.Event event); +// } +// +// private class LocalBinder extends Binder { +// PlaybackService getService() { +// return PlaybackService.this; +// } +// } +// public static PlaybackService getService(IBinder iBinder) { +// LocalBinder binder = (LocalBinder) iBinder; +// return binder.getService(); +// } +// +// private SharedPreferences mSettings; +// private final IBinder mBinder = new LocalBinder(); +// private MediaWrapperList mMediaList = new MediaWrapperList(); +// private MediaPlayer mMediaPlayer; +// private boolean mParsed = false; +// private boolean mSeekable = false; +// private boolean mPausable = false; +// private boolean mIsAudioTrack = false; +// private boolean mHasHdmiAudio = false; +// private boolean mSwitchingToVideo = false; +// +// final private ArrayList mCallbacks = new ArrayList(); +// private boolean mDetectHeadset = true; +// private boolean mPebbleEnabled; +// private PowerManager.WakeLock mWakeLock; +// private final AtomicBoolean mExpanding = new AtomicBoolean(false); +// +// private static boolean mWasPlayingAudio = false; // used only if readPhoneState returns true +// +// // Index management +// /** +// * Stack of previously played indexes, used in shuffle mode +// */ +// private Stack mPrevious; +// private int mCurrentIndex; // Set to -1 if no media is currently loaded +// private int mPrevIndex; // Set to -1 if no previous media +// private int mNextIndex; // Set to -1 if no next media +// +// // Playback management +// +// MediaSessionCompat mMediaSession; +// protected MediaSessionCallback mSessionCallback; +// private static final long PLAYBACK_ACTIONS = PlaybackStateCompat.ACTION_PAUSE +// | PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_STOP +// | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; +// +// public static final int TYPE_AUDIO = 0; +// public static final int TYPE_VIDEO = 1; +// +// public static final int REPEAT_NONE = 0; +// public static final int REPEAT_ONE = 1; +// public static final int REPEAT_ALL = 2; +// private boolean mShuffling = false; +// private int mRepeating = REPEAT_NONE; +// private Random mRandom = null; // Used in shuffling process +// private long mSavedTime = 0l; +// private boolean mHasAudioFocus = false; +// // RemoteControlClient-related +// /** +// * RemoteControlClient is for lock screen playback control. +// */ +// private RemoteControlClientReceiver mRemoteControlClientReceiver = null; +// /** +// * Last widget position update timestamp +// */ +// private long mWidgetPositionTimestamp = Calendar.getInstance().getTimeInMillis(); +// private ComponentName mRemoteControlClientReceiverComponent; +// +// private static LibVLC LibVLC() { +// return VLCInstance.get(); +// } +// +// private MediaPlayer newMediaPlayer() { +// final MediaPlayer mp = new MediaPlayer(LibVLC()); +// final String aout = VLCOptions.getAout(mSettings); +// if (mp.setAudioOutput(aout) && aout.equals("android_audiotrack")) { +// mIsAudioTrack = true; +// if (mHasHdmiAudio) +// mp.setAudioOutputDevice("hdmi"); +// } else +// mIsAudioTrack = false; +// mp.getVLCVout().addCallback(this); +// +// return mp; +// } +// +// private static boolean readPhoneState() { +// return !AndroidUtil.isFroyoOrLater(); +// } +// +// @Override +// public void onCreate() { +// super.onCreate(); +// +// mSettings = PreferenceManager.getDefaultSharedPreferences(this); +// mMediaPlayer = newMediaPlayer(); +// mMediaPlayer.setEqualizer(VLCOptions.getEqualizer(this)); +// +// if (!VLCInstance.testCompatibleCPU(this)) { +// stopSelf(); +// return; +// } +// +// if (!AndroidDevices.hasTsp() && !AndroidDevices.hasPlayServices()) +// AndroidDevices.setRemoteControlReceiverEnabled(true); +// +// mDetectHeadset = mSettings.getBoolean("enable_headset_detection", true); +// +// mCurrentIndex = -1; +// mPrevIndex = -1; +// mNextIndex = -1; +// mPrevious = new Stack(); +// mRemoteControlClientReceiverComponent = new ComponentName(BuildConfig.APPLICATION_ID, +// RemoteControlClientReceiver.class.getName()); +// +// // Make sure the audio player will acquire a wake-lock while playing. If we don't do +// // that, the CPU might go to sleep while the song is playing, causing playback to stop. +// PowerManager pm = (PowerManager) VLCApplication.getAppContext().getSystemService(Context.POWER_SERVICE); +// mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); +// +// IntentFilter filter = new IntentFilter(); +// filter.setPriority(Integer.MAX_VALUE); +// filter.addAction(ACTION_REMOTE_BACKWARD); +// filter.addAction(ACTION_REMOTE_PLAYPAUSE); +// filter.addAction(ACTION_REMOTE_PLAY); +// filter.addAction(ACTION_REMOTE_PAUSE); +// filter.addAction(ACTION_REMOTE_STOP); +// filter.addAction(ACTION_REMOTE_FORWARD); +// filter.addAction(ACTION_REMOTE_LAST_PLAYLIST); +// filter.addAction(ACTION_REMOTE_LAST_VIDEO_PLAYLIST); +// filter.addAction(ACTION_REMOTE_SWITCH_VIDEO); +// filter.addAction(VLCAppWidgetProvider.ACTION_WIDGET_INIT); +// filter.addAction(Intent.ACTION_HEADSET_PLUG); +// filter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY); +// filter.addAction(VLCApplication.SLEEP_INTENT); +// registerReceiver(mReceiver, filter); +// registerV21(); +// +// boolean stealRemoteControl = mSettings.getBoolean("enable_steal_remote_control", false); +// +// if (!AndroidUtil.isFroyoOrLater() || stealRemoteControl) { +// /* Backward compatibility for API 7 */ +// filter = new IntentFilter(); +// if (stealRemoteControl) +// filter.setPriority(Integer.MAX_VALUE); +// filter.addAction(Intent.ACTION_MEDIA_BUTTON); +// mRemoteControlClientReceiver = new RemoteControlClientReceiver(); +// registerReceiver(mRemoteControlClientReceiver, filter); +// } +// try { +// getPackageManager().getPackageInfo("com.getpebble.android", PackageManager.GET_ACTIVITIES); +// mPebbleEnabled = true; +// } catch (PackageManager.NameNotFoundException e) { +// mPebbleEnabled = false; +// } +// +// if (readPhoneState()) { +// initPhoneListener(); +// TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); +// tm.listen(mPhoneStateListener, mPhoneEvents); +// } +// } +// +// @Override +// public int onStartCommand(Intent intent, int flags, int startId) { +// if (intent == null) +// return START_STICKY; +// if(ACTION_REMOTE_PLAYPAUSE.equals(intent.getAction())){ +// if (hasCurrentMedia()) +// return START_STICKY; +// else +// loadLastPlaylist(TYPE_AUDIO); +// } else if (ACTION_REMOTE_PLAY.equals(intent.getAction())) { +// if (hasCurrentMedia()) +// play(); +// else +// loadLastPlaylist(TYPE_AUDIO); +// } +// updateWidget(); +// return super.onStartCommand(intent, flags, startId); +// } +// +// @Override +// public void onDestroy() { +// super.onDestroy(); +// stop(); +// +// if (!AndroidDevices.hasTsp() && !AndroidDevices.hasPlayServices()) +// AndroidDevices.setRemoteControlReceiverEnabled(false); +// +// if (mWakeLock.isHeld()) +// mWakeLock.release(); +// unregisterReceiver(mReceiver); +// if (mReceiverV21 != null) +// unregisterReceiver(mReceiverV21); +// if (mRemoteControlClientReceiver != null) { +// unregisterReceiver(mRemoteControlClientReceiver); +// mRemoteControlClientReceiver = null; +// } +// mMediaPlayer.release(); +// +// if (readPhoneState()) { +// TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); +// tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); +// } +// } +// +// @Override +// public IBinder onBind(Intent intent) { +// return mBinder; +// } +// +// @Override +// public boolean onUnbind(Intent intent) { +// if (!hasCurrentMedia()) +// stopSelf(); +// return true; +// } +// +// public IVLCVout getVLCVout() { +// return mMediaPlayer.getVLCVout(); +// } +// +// private final OnAudioFocusChangeListener mAudioFocusListener = AndroidUtil.isFroyoOrLater() ? +// createOnAudioFocusChangeListener() : null; +// +// @TargetApi(Build.VERSION_CODES.FROYO) +// private OnAudioFocusChangeListener createOnAudioFocusChangeListener() { +// return new OnAudioFocusChangeListener() { +// private boolean mLossTransient = false; +// private boolean mLossTransientCanDuck = false; +// +// @Override +// public void onAudioFocusChange(int focusChange) { +// /* +// * Pause playback during alerts and notifications +// */ +// switch (focusChange) { +// case AudioManager.AUDIOFOCUS_LOSS: +// Log.i(TAG, "AUDIOFOCUS_LOSS"); +// // Pause playback +// changeAudioFocus(false); +// pause(); +// break; +// case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: +// Log.i(TAG, "AUDIOFOCUS_LOSS_TRANSIENT"); +// // Pause playback +// if (mMediaPlayer.isPlaying()) { +// mLossTransient = true; +// mMediaPlayer.pause(); +// } +// break; +// case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: +// Log.i(TAG, "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK"); +// // Lower the volume +// if (mMediaPlayer.isPlaying()) { +// mMediaPlayer.setVolume(36); +// mLossTransientCanDuck = true; +// } +// break; +// case AudioManager.AUDIOFOCUS_GAIN: +// Log.i(TAG, "AUDIOFOCUS_GAIN: " + mLossTransientCanDuck + ", " + mLossTransient); +// // Resume playback +// if (mLossTransientCanDuck) { +// mMediaPlayer.setVolume(100); +// mLossTransientCanDuck = false; +// } +// if (mLossTransient) { +// mMediaPlayer.play(); +// mLossTransient = false; +// } +// break; +// } +// } +// }; +// } +// +// @TargetApi(Build.VERSION_CODES.FROYO) +// private void changeAudioFocus(boolean acquire) { +// final AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE); +// if (am == null) +// return; +// +// if (acquire) { +// if (!mHasAudioFocus) { +// final int result = am.requestAudioFocus(mAudioFocusListener, +// AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); +// if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { +// am.setParameters("bgm_state=true"); +// mHasAudioFocus = true; +// } +// } +// } else { +// if (mHasAudioFocus) { +// final int result = am.abandonAudioFocus(mAudioFocusListener); +// am.setParameters("bgm_state=false"); +// mHasAudioFocus = false; +// } +// } +// } +// +// +// @TargetApi(Build.VERSION_CODES.LOLLIPOP) +// private void registerV21() { +// final IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG); +// registerReceiver(mReceiverV21, intentFilter); +// } +// +// private final BroadcastReceiver mReceiverV21 = AndroidUtil.isLolliPopOrLater() ? new BroadcastReceiver() +// { +// @TargetApi(Build.VERSION_CODES.LOLLIPOP) +// @Override +// public void onReceive(Context context, Intent intent) { +// final String action = intent.getAction(); +// if (action == null) +// return; +// if (action.equalsIgnoreCase(AudioManager.ACTION_HDMI_AUDIO_PLUG)) { +// mHasHdmiAudio = intent.getIntExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, 0) == 1; +// if (mMediaPlayer != null && mIsAudioTrack) +// mMediaPlayer.setAudioOutputDevice(mHasHdmiAudio ? "hdmi" : "stereo"); +// } +// } +// } : null; +// +// private final BroadcastReceiver mReceiver = new BroadcastReceiver() { +// @Override +// public void onReceive(Context context, Intent intent) { +// String action = intent.getAction(); +// int state = intent.getIntExtra("state", 0); +// if( mMediaPlayer == null ) { +// Log.w(TAG, "Intent received, but VLC is not loaded, skipping."); +// return; +// } +// +// // skip all headsets events if there is a call +// TelephonyManager telManager = (TelephonyManager) VLCApplication.getAppContext().getSystemService(Context.TELEPHONY_SERVICE); +// if (telManager != null && telManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) +// return; +// +// /* +// * Launch the activity if needed +// */ +// if (action.startsWith(ACTION_REMOTE_GENERIC) && !mMediaPlayer.isPlaying() && !hasCurrentMedia()) { +// context.startActivity(getPackageManager().getLaunchIntentForPackage(getPackageName())); +// } +// +// /* +// * Remote / headset control events +// */ +// if (action.equalsIgnoreCase(ACTION_REMOTE_PLAYPAUSE)) { +// if (mMediaPlayer.isPlaying() && hasCurrentMedia()) +// pause(); +// else if (!mMediaPlayer.isPlaying() && hasCurrentMedia()) +// play(); +// } else if (action.equalsIgnoreCase(ACTION_REMOTE_PLAY)) { +// if (!mMediaPlayer.isPlaying() && hasCurrentMedia()) +// play(); +// } else if (action.equalsIgnoreCase(ACTION_REMOTE_PAUSE)) { +// if (mMediaPlayer.isPlaying() && hasCurrentMedia()) +// pause(); +// } else if (action.equalsIgnoreCase(ACTION_REMOTE_BACKWARD)) { +// previous(); +// } else if (action.equalsIgnoreCase(ACTION_REMOTE_STOP)) { +// stop(); +// } else if (action.equalsIgnoreCase(ACTION_REMOTE_FORWARD)) { +// next(); +// } else if (action.equalsIgnoreCase(ACTION_REMOTE_LAST_PLAYLIST)) { +// loadLastPlaylist(TYPE_AUDIO); +// } else if (action.equalsIgnoreCase(ACTION_REMOTE_LAST_VIDEO_PLAYLIST)) { +// loadLastPlaylist(TYPE_VIDEO); +// } else if (action.equalsIgnoreCase(ACTION_REMOTE_SWITCH_VIDEO)) { +// getCurrentMediaWrapper().removeFlags(MediaWrapper.MEDIA_FORCE_AUDIO); +// switchToVideo(); +// } else if (action.equalsIgnoreCase(VLCAppWidgetProvider.ACTION_WIDGET_INIT)) { +// updateWidget(); +// } +// +// /* +// * headset plug events +// */ +// if (mDetectHeadset && !mHasHdmiAudio) { +// if (action.equalsIgnoreCase(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { +// Log.i(TAG, "Headset Removed."); +// if (mMediaPlayer.isPlaying() && hasCurrentMedia()) +// pause(); +// } +// else if (action.equalsIgnoreCase(Intent.ACTION_HEADSET_PLUG) && state != 0) { +// Log.i(TAG, "Headset Inserted."); +// if (!mMediaPlayer.isPlaying() && hasCurrentMedia()) +// play(); +// } +// } +// +// /* +// * Sleep +// */ +// if (action.equalsIgnoreCase(VLCApplication.SLEEP_INTENT)) { +// stop(); +// } +// } +// }; +// +// +// @Override +// public void onNewLayout(IVLCVout vlcVout, int width, int height, int visibleWidth, int visibleHeight, int sarNum, int sarDen) { +// } +// +// @Override +// public void onSurfacesCreated(IVLCVout vlcVout) { +// hideNotification(false); +// } +// +// @Override +// public void onSurfacesDestroyed(IVLCVout vlcVout) { +// mSwitchingToVideo = false; +// } +// +// @Override +// public void onHardwareAccelerationError(IVLCVout vlcVout) { +// } +// +// private final Media.EventListener mMediaListener = new Media.EventListener() { +// @Override +// public void onEvent(Media.Event event) { +// switch (event.type) { +// case Media.Event.MetaChanged: +// /* Update Meta if file is already parsed */ +// if (mParsed && updateCurrentMeta(event.getMetaId())) +// executeUpdate(); +// Log.i(TAG, "Media.Event.MetaChanged: " + event.getMetaId()); +// break; +// case Media.Event.ParsedChanged: +// Log.i(TAG, "Media.Event.ParsedChanged"); +// updateCurrentMeta(-1); +// mParsed = true; +// break; +// +// } +// for (Callback callback : mCallbacks) +// callback.onMediaEvent(event); +// } +// }; +// +// /** +// * Update current media meta and return true if player needs to be updated +// * +// * @param id of the Meta event received, -1 for none +// * @return true if UI needs to be updated +// */ +// private boolean updateCurrentMeta(int id) { +// if (id == Media.Meta.Publisher) +// return false; +// final MediaWrapper mw = getCurrentMedia(); +// if (mw != null) +// mw.updateMeta(mMediaPlayer); +// return id != Media.Meta.NowPlaying || getCurrentMedia().getNowPlaying() != null; +// } +// +// private final MediaPlayer.EventListener mMediaPlayerListener = new MediaPlayer.EventListener() { +// +// @Override +// public void onEvent(MediaPlayer.Event event) { +// switch (event.type) { +// case MediaPlayer.Event.Playing: +// +// Log.i(TAG, "MediaPlayer.Event.Playing"); +// executeUpdate(); +// publishState(event.type); +// executeUpdateProgress(); +// +// final MediaWrapper mw = mMediaList.getMedia(mCurrentIndex); +// if (mw != null) { +// long length = mMediaPlayer.getLength(); +// MediaDatabase dbManager = MediaDatabase.getInstance(); +// MediaWrapper m = dbManager.getMedia(mw.getUri()); +// /** +// * 1) There is a media to update +// * 2) It has a length of 0 +// * (dynamic track loading - most notably the OGG container) +// * 3) We were able to get a length even after parsing +// * (don't want to replace a 0 with a 0) +// */ +// if (m != null && m.getLength() == 0 && length > 0) { +// dbManager.updateMedia(mw.getUri(), +// MediaDatabase.INDEX_MEDIA_LENGTH, length); +// } +// } +// +// changeAudioFocus(true); +// if (!mWakeLock.isHeld()) +// mWakeLock.acquire(); +// if (switchToVideo()) +// hideNotification(); +// else +// showNotification(); +// break; +// case MediaPlayer.Event.Paused: +// Log.i(TAG, "MediaPlayer.Event.Paused"); +// executeUpdate(); +// publishState(event.type); +// executeUpdateProgress(); +// if (mWakeLock.isHeld()) +// mWakeLock.release(); +// break; +// case MediaPlayer.Event.Stopped: +// Log.i(TAG, "MediaPlayer.Event.Stopped"); +// executeUpdate(); +// publishState(event.type); +// executeUpdateProgress(); +// if (mWakeLock.isHeld()) +// mWakeLock.release(); +// changeAudioFocus(false); +// break; +// case MediaPlayer.Event.EndReached: +// Log.i(TAG, "MediaPlayer.Event.EndReached"); +// executeUpdateProgress(); +// determinePrevAndNextIndices(true); +// next(); +// if (mWakeLock.isHeld()) +// mWakeLock.release(); +// changeAudioFocus(false); +// break; +// case MediaPlayer.Event.EncounteredError: +// showToast(getString( +// R.string.invalid_location, +// mMediaList.getMRL(mCurrentIndex)), Toast.LENGTH_SHORT); +// executeUpdate(); +// executeUpdateProgress(); +// next(); +// if (mWakeLock.isHeld()) +// mWakeLock.release(); +// break; +// case MediaPlayer.Event.TimeChanged: +// break; +// case MediaPlayer.Event.PositionChanged: +// updateWidgetPosition(event.getPositionChanged()); +// break; +// case MediaPlayer.Event.Vout: +// break; +// case MediaPlayer.Event.ESAdded: +// if (event.getEsChangedType() == Media.Track.Type.Video && !switchToVideo()) { +// /* Update notification content intent: resume video or resume audio activity */ +// updateMetadata(); +// } +// break; +// case MediaPlayer.Event.ESDeleted: +// break; +// case MediaPlayer.Event.PausableChanged: +// mPausable = event.getPausable(); +// break; +// case MediaPlayer.Event.SeekableChanged: +// mSeekable = event.getSeekable(); +// break; +// } +// for (Callback callback : mCallbacks) +// callback.onMediaPlayerEvent(event); +// } +// }; +// +// private final MediaWrapperList.EventListener mListEventListener = new MediaWrapperList.EventListener() { +// +// @Override +// public void onItemAdded(int index, String mrl) { +// Log.i(TAG, "CustomMediaListItemAdded"); +// if(mCurrentIndex >= index && !mExpanding.get()) +// mCurrentIndex++; +// +// determinePrevAndNextIndices(); +// executeUpdate(); +// } +// +// @Override +// public void onItemRemoved(int index, String mrl) { +// Log.i(TAG, "CustomMediaListItemDeleted"); +// if (mCurrentIndex == index && !mExpanding.get()) { +// // The current item has been deleted +// mCurrentIndex--; +// determinePrevAndNextIndices(); +// if (mNextIndex != -1) +// next(); +// else if (mCurrentIndex != -1) { +// playIndex(mCurrentIndex, 0); +// } else +// stop(); +// } +// +// if(mCurrentIndex > index && !mExpanding.get()) +// mCurrentIndex--; +// determinePrevAndNextIndices(); +// executeUpdate(); +// } +// +// @Override +// public void onItemMoved(int indexBefore, int indexAfter, String mrl) { +// Log.i(TAG, "CustomMediaListItemMoved"); +// if (mCurrentIndex == indexBefore) { +// mCurrentIndex = indexAfter; +// if (indexAfter > indexBefore) +// mCurrentIndex--; +// } else if (indexBefore > mCurrentIndex +// && indexAfter <= mCurrentIndex) +// mCurrentIndex++; +// else if (indexBefore < mCurrentIndex +// && indexAfter > mCurrentIndex) +// mCurrentIndex--; +// +// // If we are in random mode, we completely reset the stored previous track +// // as their indices changed. +// mPrevious.clear(); +// +// determinePrevAndNextIndices(); +// executeUpdate(); +// } +// }; +// +// public boolean canSwitchToVideo() { +// return hasCurrentMedia() && mMediaPlayer.getVideoTracksCount() > 0; +// } +// +// @MainThread +// public boolean switchToVideo() { +// if (mMediaList.getMedia(mCurrentIndex).hasFlag(MediaWrapper.MEDIA_FORCE_AUDIO) || !canSwitchToVideo()) +// return false; +// if (isVideoPlaying()) {//Player is already running, just send it an intent +// mMediaPlayer.setVideoTrackEnabled(true); +// LocalBroadcastManager.getInstance(this).sendBroadcast( +// VideoPlayerActivity.getIntent(VideoPlayerActivity.PLAY_FROM_SERVICE, +// getCurrentMediaWrapper(), false, mCurrentIndex)); +// } else if (!mSwitchingToVideo) {//Start the video player +// Log.e(TAG, "startOpened", new Exception()); +// VideoPlayerActivity.startOpened(VLCApplication.getAppContext(), +// getCurrentMediaWrapper().getUri(), mCurrentIndex); +// mSwitchingToVideo = true; +// } +// return true; +// } +// +// private void executeUpdate() { +// executeUpdate(true); +// } +// +// private void executeUpdate(Boolean updateWidget) { +// for (Callback callback : mCallbacks) { +// callback.update(); +// } +// if (updateWidget) +// updateWidget(); +// updateMetadata(); +// } +// +// private void executeUpdateProgress() { +// for (Callback callback : mCallbacks) { +// callback.updateProgress(); +// } +// } +// +// /** +// * Return the current media. +// * +// * @return The current media or null if there is not any. +// */ +// @Nullable +// private MediaWrapper getCurrentMedia() { +// return mMediaList.getMedia(mCurrentIndex); +// } +// +// /** +// * Alias for mCurrentIndex >= 0 +// * +// * @return True if a media is currently loaded, false otherwise +// */ +// private boolean hasCurrentMedia() { +// return mCurrentIndex >= 0 && mCurrentIndex < mMediaList.size(); +// } +// +// private final Handler mHandler = new AudioServiceHandler(this); +// +// private static class AudioServiceHandler extends WeakHandler { +// public AudioServiceHandler(PlaybackService fragment) { +// super(fragment); +// } +// +// @Override +// public void handleMessage(Message msg) { +// PlaybackService service = getOwner(); +// if(service == null) return; +// +// switch (msg.what) { +// case SHOW_PROGRESS: +// if (service.mCallbacks.size() > 0) { +// removeMessages(SHOW_PROGRESS); +// service.executeUpdateProgress(); +// sendEmptyMessageDelayed(SHOW_PROGRESS, 1000); +// } +// break; +// case SHOW_TOAST: +// final Bundle bundle = msg.getData(); +// final String text = bundle.getString("text"); +// final int duration = bundle.getInt("duration"); +// Toast.makeText(VLCApplication.getAppContext(), text, duration).show(); +// break; +// } +// } +// } +// +// @TargetApi(Build.VERSION_CODES.LOLLIPOP) +// private void showNotification() { +// if (mMediaPlayer.getVLCVout().areViewsAttached()) +// return; +// try { +// boolean coverOnLockscreen = mSettings.getBoolean("lockscreen_cover", true); +// MediaMetadataCompat metaData = mMediaSession.getController().getMetadata(); +// String title = metaData.getString(MediaMetadataCompat.METADATA_KEY_TITLE); +// String artist = metaData.getString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST); +// String album = metaData.getString(MediaMetadataCompat.METADATA_KEY_ALBUM); +// Bitmap cover = coverOnLockscreen ? +// metaData.getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART) : +// AudioUtil.getCover(this, getCurrentMedia(), 512); +// if (cover == null) +// cover = BitmapFactory.decodeResource(VLCApplication.getAppContext().getResources(), R.drawable.icon); +// Notification notification; +// +// //Watch notification dismissed +// PendingIntent piStop = PendingIntent.getBroadcast(this, 0, +// new Intent(ACTION_REMOTE_STOP), PendingIntent.FLAG_UPDATE_CURRENT); +// +// // add notification to status bar +// NotificationCompat.Builder builder = new NotificationCompat.Builder(this); +// builder.setSmallIcon(R.drawable.ic_stat_vlc) +// .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) +// .setContentTitle(title) +// .setContentText(artist + " - " + album) +// .setLargeIcon(cover) +// .setTicker(title + " - " + artist) +// .setAutoCancel(!mMediaPlayer.isPlaying()) +// .setOngoing(mMediaPlayer.isPlaying()) +// .setDeleteIntent(piStop); +// +// +// PendingIntent pendingIntent; +// if (canSwitchToVideo() && !mMediaList.getMedia(mCurrentIndex).hasFlag(MediaWrapper.MEDIA_FORCE_AUDIO) ) { +// /* Resume VideoPlayerActivity from ACTION_REMOTE_SWITCH_VIDEO intent */ +// final Intent notificationIntent = new Intent(ACTION_REMOTE_SWITCH_VIDEO); +// pendingIntent = PendingIntent.getBroadcast(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); +// } else { +// /* Resume AudioPlayerActivity */ +// +// final Intent notificationIntent = getPackageManager().getLaunchIntentForPackage(getPackageName()); +// notificationIntent.setAction(AudioPlayerContainerActivity.ACTION_SHOW_PLAYER); +// notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER); +// pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); +// } +// +// builder.setContentIntent(pendingIntent); +// +// PendingIntent piBackward = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_REMOTE_BACKWARD), PendingIntent.FLAG_UPDATE_CURRENT); +// PendingIntent piPlay = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_REMOTE_PLAYPAUSE), PendingIntent.FLAG_UPDATE_CURRENT); +// PendingIntent piForward = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_REMOTE_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT); +// +// builder.addAction(R.drawable.ic_previous_w, getString(R.string.previous), piBackward); +// if (mMediaPlayer.isPlaying()) +// builder.addAction(R.drawable.ic_pause_w, getString(R.string.pause), piPlay); +// else +// builder.addAction(R.drawable.ic_play_w, getString(R.string.play), piPlay); +// builder.addAction(R.drawable.ic_next_w, getString(R.string.next), piForward); +// +// builder.setStyle(new NotificationCompat.MediaStyle() +// .setMediaSession(mMediaSession.getSessionToken()) +// .setShowActionsInCompactView(new int[] {0,1,2}) +// .setShowCancelButton(true) +// .setCancelButtonIntent(piStop) +// ); +// +// notification = builder.build(); +// +// startService(new Intent(this, PlaybackService.class)); +// if (!AndroidUtil.isLolliPopOrLater() || mMediaPlayer.isPlaying()) +// startForeground(3, notification); +// else { +// stopForeground(false); +// NotificationManagerCompat.from(this).notify(3, notification); +// } +// } +// catch (NoSuchMethodError e){ +// // Compat library is wrong on 3.2 +// // http://code.google.com/p/android/issues/detail?id=36359 +// // http://code.google.com/p/android/issues/detail?id=36502 +// } +// } +// +// private void hideNotification() { +// hideNotification(true); +// } +// +// /** +// * Hides the VLC notification and stops the service. +// * +// * @param stopPlayback True to also stop playback at the same time. Set to false to preserve playback (e.g. for vout events) +// */ +// private void hideNotification(boolean stopPlayback) { +// stopForeground(true); +// if(stopPlayback) +// stopSelf(); +// } +// +// @MainThread +// public void pause() { +// if (mPausable) { +// savePosition(); +// mHandler.removeMessages(SHOW_PROGRESS); +// // hideNotification(); <-- see event handler +// mMediaPlayer.pause(); +// broadcastMetadata(); +// } +// } +// +// @MainThread +// public void play() { +// if(hasCurrentMedia()) { +// mMediaPlayer.play(); +// mHandler.sendEmptyMessage(SHOW_PROGRESS); +// updateMetadata(); +// updateWidget(); +// broadcastMetadata(); +// } +// } +// +// @MainThread +// public void stop() { +// if (mMediaSession != null) { +// mMediaSession.setActive(false); +// mMediaSession.release(); +// mMediaSession = null; +// } +// if (mMediaPlayer == null) +// return; +// savePosition(); +// final Media media = mMediaPlayer.getMedia(); +// if (media != null) { +// media.setEventListener(null); +// mMediaPlayer.setEventListener(null); +// mMediaPlayer.stop(); +// mMediaPlayer.setMedia(null); +// media.release(); +// } +// mMediaList.removeEventListener(mListEventListener); +// mCurrentIndex = -1; +// mPrevious.clear(); +// mHandler.removeMessages(SHOW_PROGRESS); +// hideNotification(); +// broadcastMetadata(); +// executeUpdate(); +// executeUpdateProgress(); +// changeAudioFocus(false); +// +// stopSelf(); +// } +// +// private void determinePrevAndNextIndices() { +// determinePrevAndNextIndices(false); +// } +// +// private void determinePrevAndNextIndices(boolean expand) { +// if (expand) { +// mExpanding.set(true); +// mNextIndex = expand(); +// mExpanding.set(false); +// } else { +// mNextIndex = -1; +// } +// mPrevIndex = -1; +// +// if (mNextIndex == -1) { +// // No subitems; play the next item. +// int size = mMediaList.size(); +// mShuffling &= size > 2; +// +// // Repeating once doesn't change the index +// if (mRepeating == REPEAT_ONE) { +// mPrevIndex = mNextIndex = mCurrentIndex; +// } else { +// +// if(mShuffling) { +// if(mPrevious.size() > 0) +// mPrevIndex = mPrevious.peek(); +// // If we've played all songs already in shuffle, then either +// // reshuffle or stop (depending on RepeatType). +// if(mPrevious.size() + 1 == size) { +// if(mRepeating == REPEAT_NONE) { +// mNextIndex = -1; +// return; +// } else { +// mPrevious.clear(); +// } +// } +// if(mRandom == null) mRandom = new Random(); +// // Find a new index not in mPrevious. +// do +// { +// mNextIndex = mRandom.nextInt(size); +// } +// while(mNextIndex == mCurrentIndex || mPrevious.contains(mNextIndex)); +// +// } else { +// // normal playback +// if(mCurrentIndex > 0) +// mPrevIndex = mCurrentIndex - 1; +// if(mCurrentIndex + 1 < size) +// mNextIndex = mCurrentIndex + 1; +// else { +// if(mRepeating == REPEAT_NONE) { +// mNextIndex = -1; +// } else { +// mNextIndex = 0; +// } +// } +// } +// } +// } +// } +// +// private void initMediaSession() { +// ComponentName mediaButtonEventReceiver = new ComponentName(this, +// RemoteControlClientReceiver.class); +// mSessionCallback = new MediaSessionCallback(); +// mMediaSession = new MediaSessionCompat(this, "VLC", mediaButtonEventReceiver, null); +// mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS +// | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); +// mMediaSession.setCallback(mSessionCallback); +// try { +// mMediaSession.setActive(true); +// } catch (NullPointerException e) { +// // Some versions of KitKat do not support AudioManager.registerMediaButtonIntent +// // with a PendingIntent. They will throw a NullPointerException, in which case +// // they should be able to activate a MediaSessionCompat with only transport +// // controls. +// mMediaSession.setActive(false); +// mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); +// mMediaSession.setActive(true); +// } +// } +// +// private final class MediaSessionCallback extends MediaSessionCompat.Callback { +// @Override +// public void onPlay() { +// play(); +// } +// @Override +// public void onPause() { +// pause(); +// } +// +// @Override +// public void onStop() { +// stop(); +// } +// +// @Override +// public void onSkipToNext() { +// next(); +// } +// +// @Override +// public void onSkipToPrevious() { +// previous(); +// } +// +// @Override +// public void onSeekTo(long pos) { +// setTime(pos); +// } +// +// @Override +// public void onFastForward() { +// next(); +// } +// +// @Override +// public void onRewind() { +// previous(); +// } +// } +// +// protected void updateMetadata() { +// MediaWrapper media = getCurrentMedia(); +// if (media == null) +// return; +// if (mMediaSession == null) +// initMediaSession(); +// String title = media.getNowPlaying(); +// if (title == null) +// title = media.getTitle(); +// boolean coverOnLockscreen = mSettings.getBoolean("lockscreen_cover", true); +// MediaMetadataCompat.Builder bob = new MediaMetadataCompat.Builder(); +// bob.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) +// .putString(MediaMetadataCompat.METADATA_KEY_GENRE, MediaUtils.getMediaGenre(this, media)) +// .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, media.getTrackNumber()) +// .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, MediaUtils.getMediaReferenceArtist(this, media)) +// .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, MediaUtils.getMediaAlbum(this, media)) +// .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, media.getLength()); +// if (coverOnLockscreen) { +// Bitmap cover = AudioUtil.getCover(this, media, 512); +// if (cover != null && cover.getConfig() != null) //In case of format not supported +// bob.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, cover.copy(cover.getConfig(), false)); +// } +// mMediaSession.setMetadata(bob.build()); +// +// //Send metadata to Pebble watch +// if (mPebbleEnabled) { +// final Intent i = new Intent("com.getpebble.action.NOW_PLAYING"); +// i.putExtra("artist", MediaUtils.getMediaArtist(this, media)); +// i.putExtra("album", MediaUtils.getMediaAlbum(this, media)); +// i.putExtra("track", media.getTitle()); +// sendBroadcast(i); +// } +// showNotification(); +// } +// +// protected void publishState(int state) { +// if (mMediaSession == null) +// return; +// PlaybackStateCompat.Builder bob = new PlaybackStateCompat.Builder(); +// bob.setActions(PLAYBACK_ACTIONS); +// switch (state) { +// case MediaPlayer.Event.Playing: +// bob.setState(PlaybackStateCompat.STATE_PLAYING, -1, 1); +// break; +// case MediaPlayer.Event.Stopped: +// bob.setState(PlaybackStateCompat.STATE_STOPPED, -1, 0); +// break; +// default: +// bob.setState(PlaybackStateCompat.STATE_PAUSED, -1, 0); +// } +// PlaybackStateCompat pbState = bob.build(); +// mMediaSession.setPlaybackState(pbState); +// mMediaSession.setActive(state != PlaybackStateCompat.STATE_STOPPED); +// } +// +// private void notifyTrackChanged() { +// mHandler.sendEmptyMessage(SHOW_PROGRESS); +// updateMetadata(); +// updateWidget(); +// broadcastMetadata(); +// } +// +// private void onMediaChanged() { +// notifyTrackChanged(); +// +// saveCurrentMedia(); +// determinePrevAndNextIndices(); +// } +// +// private void onMediaListChanged() { +// saveMediaList(); +// determinePrevAndNextIndices(); +// executeUpdate(); +// } +// +// @MainThread +// public void next() { +// int size = mMediaList.size(); +// +// mPrevious.push(mCurrentIndex); +// mCurrentIndex = mNextIndex; +// Log.d(TAG, "setting current to " + mCurrentIndex); +// if (size == 0 || mCurrentIndex < 0 || mCurrentIndex >= size) { +// if (mCurrentIndex < 0) +// saveCurrentMedia(); +// Log.w(TAG, "Warning: invalid next index, aborted !"); +// //Close video player if started +// LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(VideoPlayerActivity.EXIT_PLAYER)); +// stop(); +// return; +// } +// playIndex(mCurrentIndex, 0); +// saveCurrentMedia(); +// } +// +// @MainThread +// public void previous() { +// int size = mMediaList.size(); +// if (hasPrevious() && mCurrentIndex > 0 && mMediaPlayer.getTime() < 2000l) { +// mCurrentIndex = mPrevIndex; +// if (mPrevious.size() > 0) +// mPrevious.pop(); +// if (size == 0 || mPrevIndex < 0 || mCurrentIndex >= size) { +// Log.w(TAG, "Warning: invalid previous index, aborted !"); +// stop(); +// return; +// } +// } else +// setPosition(0f); +// +// playIndex(mCurrentIndex, 0); +// saveCurrentMedia(); +// } +// +// @MainThread +// public void shuffle() { +// if (mShuffling) +// mPrevious.clear(); +// mShuffling = !mShuffling; +// savePosition(); +// determinePrevAndNextIndices(); +// } +// +// @MainThread +// public void setRepeatType(int repeatType) { +// mRepeating = repeatType; +// savePosition(); +// determinePrevAndNextIndices(); +// } +// +// private void updateWidget() { +// updateWidgetState(); +// updateWidgetCover(); +// } +// +// private void updateWidgetState() { +// Intent i = new Intent(VLCAppWidgetProvider.ACTION_WIDGET_UPDATE); +// +// if (hasCurrentMedia()) { +// final MediaWrapper media = getCurrentMedia(); +// i.putExtra("title", media.getTitle()); +// i.putExtra("artist", media.isArtistUnknown() && media.getNowPlaying() != null ? +// media.getNowPlaying() +// : MediaUtils.getMediaArtist(this, media)); +// } +// else { +// i.putExtra("title", getString(R.string.widget_default_text)); +// i.putExtra("artist", ""); +// } +// i.putExtra("isplaying", mMediaPlayer.isPlaying()); +// +// sendBroadcast(i); +// } +// +// private void updateWidgetCover() { +// Intent i = new Intent(VLCAppWidgetProvider.ACTION_WIDGET_UPDATE_COVER); +// +// Bitmap cover = hasCurrentMedia() ? AudioUtil.getCover(this, getCurrentMedia(), 64) : null; +// i.putExtra("cover", cover); +// +// sendBroadcast(i); +// } +// +// private void updateWidgetPosition(float pos) { +// // no more than one widget update for each 1/50 of the song +// long timestamp = Calendar.getInstance().getTimeInMillis(); +// if (!hasCurrentMedia() +// || timestamp - mWidgetPositionTimestamp < getCurrentMedia().getLength() / 50) +// return; +// +// updateWidgetState(); +// +// mWidgetPositionTimestamp = timestamp; +// Intent i = new Intent(VLCAppWidgetProvider.ACTION_WIDGET_UPDATE_POSITION); +// i.putExtra("position", pos); +// sendBroadcast(i); +// } +// +// private void broadcastMetadata() { +// MediaWrapper media = getCurrentMedia(); +// if (media == null || media.getType() != MediaWrapper.TYPE_AUDIO) +// return; +// +// boolean playing = mMediaPlayer.isPlaying(); +// +// Intent broadcast = new Intent("com.android.music.metachanged"); +// broadcast.putExtra("track", media.getTitle()); +// broadcast.putExtra("artist", media.getArtist()); +// broadcast.putExtra("album", media.getAlbum()); +// broadcast.putExtra("duration", media.getLength()); +// broadcast.putExtra("playing", playing); +// +// sendBroadcast(broadcast); +// } +// +// public synchronized void loadLastPlaylist(int type) { +// boolean audio = type == TYPE_AUDIO; +// String currentMedia = mSettings.getString(audio ? "current_song" : "current_media", ""); +// if (currentMedia.equals("")) +// return; +// String[] locations = mSettings.getString(audio ? "audio_list" : "media_list", "").split(" "); +// if (locations.length == 0) +// return; +// +// List mediaPathList = new ArrayList(locations.length); +// for (int i = 0 ; i < locations.length ; ++i) +// mediaPathList.add(Uri.decode(locations[i])); +// +// mShuffling = mSettings.getBoolean(audio ? "audio_shuffling" : "media_shuffling", false); +// mRepeating = mSettings.getInt(audio ? "audio_repeating" : "media_repeating", REPEAT_NONE); +// int position = mSettings.getInt(audio ? "position_in_audio_list" : "position_in_media_list", +// Math.max(0, mediaPathList.indexOf(currentMedia))); +// long time = mSettings.getLong(audio ? "position_in_song" : "position_in_media", -1); +// mSavedTime = time; +// // load playlist +// loadLocations(mediaPathList, position); +// if (time > 0) +// setTime(time); +// if(!audio) { +// boolean paused = mSettings.getBoolean(PreferencesActivity.VIDEO_PAUSED, !isPlaying()); +// float rate = mSettings.getFloat(PreferencesActivity.VIDEO_SPEED, getRate()); +// if (paused) +// pause(); +// if (rate != 1.0f) +// setRate(rate); +// } +// SharedPreferences.Editor editor = mSettings.edit(); +// editor.putInt(audio ? "position_in_audio_list" : "position_in_media_list", 0); +// editor.putLong(audio ? "position_in_song" : "position_in_media", 0); +// Util.commitPreferences(editor); +// } +// +// private synchronized void saveCurrentMedia() { +// boolean audio = true; +// for (int i = 0; i < mMediaList.size(); i++) { +// if (mMediaList.getMedia(i).getType() == MediaWrapper.TYPE_VIDEO) +// audio = false; +// } +// SharedPreferences.Editor editor = mSettings.edit(); +// editor.putString(audio ? "current_song" : "current_media", mMediaList.getMRL(Math.max(mCurrentIndex, 0))); +// Util.commitPreferences(editor); +// } +// +// private synchronized void saveMediaList() { +// if (getCurrentMedia() == null) +// return; +// StringBuilder locations = new StringBuilder(); +// boolean audio = true; +// for (int i = 0; i < mMediaList.size(); i++) { +// if (mMediaList.getMedia(i).getType() == MediaWrapper.TYPE_VIDEO) +// audio = false; +// locations.append(" ").append(Uri.encode(mMediaList.getMRL(i))); +// } +// //We save a concatenated String because putStringSet is APIv11. +// SharedPreferences.Editor editor = mSettings.edit(); +// editor.putString(audio ? "audio_list" : "media_list", locations.toString().trim()); +// Util.commitPreferences(editor); +// } +// +// private synchronized void savePosition(){ +// if (getCurrentMedia() == null) +// return; +// SharedPreferences.Editor editor = mSettings.edit(); +// boolean audio = true; +// for (int i = 0; i < mMediaList.size(); i++) { +// if (mMediaList.getMedia(i).getType() == MediaWrapper.TYPE_VIDEO) +// audio = false; +// } +// editor.putBoolean(audio ? "audio_shuffling" : "media_shuffling", mShuffling); +// editor.putInt(audio ? "audio_repeating" : "media_repeating", mRepeating); +// editor.putInt(audio ? "position_in_audio_list" : "position_in_media_list", mCurrentIndex); +// editor.putLong(audio ? "position_in_song" : "position_in_media", mMediaPlayer.getTime()); +// if(!audio) { +// editor.putBoolean(PreferencesActivity.VIDEO_PAUSED, !isPlaying()); +// editor.putFloat(PreferencesActivity.VIDEO_SPEED, getRate()); +// } +// Util.commitPreferences(editor); +// } +// +// private boolean validateLocation(String location) +// { +// /* Check if the MRL contains a scheme */ +// if (!location.matches("\\w+://.+")) +// location = "file://".concat(location); +// if (location.toLowerCase(Locale.ENGLISH).startsWith("file://")) { +// /* Ensure the file exists */ +// File f; +// try { +// f = new File(new URI(location)); +// } catch (URISyntaxException e) { +// return false; +// } catch (IllegalArgumentException e) { +// return false; +// } +// if (!f.isFile()) +// return false; +// } +// return true; +// } +// +// private void showToast(String text, int duration) { +// Message msg = new Message(); +// Bundle bundle = new Bundle(); +// bundle.putString("text", text); +// bundle.putInt("duration", duration); +// msg.setData(bundle); +// msg.what = SHOW_TOAST; +// mHandler.sendMessage(msg); +// } +// +// @MainThread +// public boolean isPlaying() { +// return mMediaPlayer.isPlaying(); +// } +// +// @MainThread +// public boolean isSeekable() { +// return mSeekable; +// } +// +// @MainThread +// public boolean isPausable() { +// return mPausable; +// } +// +// @MainThread +// public boolean isShuffling() { +// return mShuffling; +// } +// +// @MainThread +// public int getRepeatType() { +// return mRepeating; +// } +// +// @MainThread +// public boolean hasMedia() { +// return hasCurrentMedia(); +// } +// +// @MainThread +// public boolean isVideoPlaying() { +// return mMediaPlayer.getVLCVout().areViewsAttached(); +// } +// +// @MainThread +// public String getAlbum() { +// if (hasCurrentMedia()) +// return MediaUtils.getMediaAlbum(PlaybackService.this, getCurrentMedia()); +// else +// return null; +// } +// +// @MainThread +// public String getArtist() { +// if (hasCurrentMedia()) { +// final MediaWrapper media = getCurrentMedia(); +// return media.getNowPlaying() != null ? +// media.getTitle() +// : MediaUtils.getMediaArtist(PlaybackService.this, media); +// } else +// return null; +// } +// +// @MainThread +// public String getArtistPrev() { +// if (mPrevIndex != -1) +// return MediaUtils.getMediaArtist(PlaybackService.this, mMediaList.getMedia(mPrevIndex)); +// else +// return null; +// } +// +// @MainThread +// public String getArtistNext() { +// if (mNextIndex != -1) +// return MediaUtils.getMediaArtist(PlaybackService.this, mMediaList.getMedia(mNextIndex)); +// else +// return null; +// } +// +// @MainThread +// public String getTitle() { +// if (hasCurrentMedia()) +// return getCurrentMedia().getNowPlaying() != null ? getCurrentMedia().getNowPlaying() : getCurrentMedia().getTitle(); +// else +// return null; +// } +// +// @MainThread +// public String getTitlePrev() { +// if (mPrevIndex != -1) +// return mMediaList.getMedia(mPrevIndex).getTitle(); +// else +// return null; +// } +// +// @MainThread +// public String getTitleNext() { +// if (mNextIndex != -1) +// return mMediaList.getMedia(mNextIndex).getTitle(); +// else +// return null; +// } +// +// @MainThread +// public Bitmap getCover() { +// if (hasCurrentMedia()) { +// return AudioUtil.getCover(PlaybackService.this, getCurrentMedia(), 512); +// } +// return null; +// } +// +// @MainThread +// public Bitmap getCoverPrev() { +// if (mPrevIndex != -1) +// return AudioUtil.getCover(PlaybackService.this, mMediaList.getMedia(mPrevIndex), 64); +// else +// return null; +// } +// +// @MainThread +// public Bitmap getCoverNext() { +// if (mNextIndex != -1) +// return AudioUtil.getCover(PlaybackService.this, mMediaList.getMedia(mNextIndex), 64); +// else +// return null; +// } +// +// @MainThread +// public synchronized void addCallback(Callback cb) { +// if (!mCallbacks.contains(cb)) { +// mCallbacks.add(cb); +// if (hasCurrentMedia()) +// mHandler.sendEmptyMessage(SHOW_PROGRESS); +// } +// } +// +// @MainThread +// public synchronized void removeCallback(Callback cb) { +// mCallbacks.remove(cb); +// } +// +// @MainThread +// public long getTime() { +// return mMediaPlayer.getTime(); +// } +// +// @MainThread +// public long getLength() { +// return mMediaPlayer.getLength(); +// } +// +// /** +// * Loads a selection of files (a non-user-supplied collection of media) +// * into the primary or "currently playing" playlist. +// * +// * @param mediaPathList A list of locations to load +// * @param position The position to start playing at +// */ +// @MainThread +// public void loadLocations(List mediaPathList, int position) { +// ArrayList mediaList = new ArrayList(); +// MediaDatabase db = MediaDatabase.getInstance(); +// +// for (int i = 0; i < mediaPathList.size(); i++) { +// String location = mediaPathList.get(i); +// MediaWrapper mediaWrapper = db.getMedia(Uri.parse(location)); +// if (mediaWrapper == null) { +// if (!validateLocation(location)) { +// Log.w(TAG, "Invalid location " + location); +// showToast(getResources().getString(R.string.invalid_location, location), Toast.LENGTH_SHORT); +// continue; +// } +// Log.v(TAG, "Creating on-the-fly Media object for " + location); +// mediaWrapper = new MediaWrapper(Uri.parse(location)); +// } +// mediaList.add(mediaWrapper); +// } +// load(mediaList, position); +// } +// +// @MainThread +// public void loadUri(Uri uri) { +// String path = uri.toString(); +// if (TextUtils.equals(uri.getScheme(), "content")) { +// path = "file://"+ FileUtils.getPathFromURI(uri); +// } +// loadLocation(path); +// } +// +// @MainThread +// public void loadLocation(String mediaPath) { +// loadLocations(Collections.singletonList(mediaPath), 0); +// } +// +// @MainThread +// public void load(List mediaList, int position) { +// Log.v(TAG, "Loading position " + ((Integer) position).toString() + " in " + mediaList.toString()); +// +// if (hasCurrentMedia()) +// savePosition(); +// +// mMediaList.removeEventListener(mListEventListener); +// mMediaList.clear(); +// MediaWrapperList currentMediaList = mMediaList; +// +// mPrevious.clear(); +// +// for (int i = 0; i < mediaList.size(); i++) { +// currentMediaList.add(mediaList.get(i)); +// } +// +// if (mMediaList.size() == 0) { +// Log.w(TAG, "Warning: empty media list, nothing to play !"); +// return; +// } +// if (mMediaList.size() > position && position >= 0) { +// mCurrentIndex = position; +// } else { +// Log.w(TAG, "Warning: positon " + position + " out of bounds"); +// mCurrentIndex = 0; +// } +// +// // Add handler after loading the list +// mMediaList.addEventListener(mListEventListener); +// +// playIndex(mCurrentIndex, 0); +// saveMediaList(); +// onMediaChanged(); +// } +// +// @MainThread +// public void load(MediaWrapper media) { +// ArrayList arrayList = new ArrayList(); +// arrayList.add(media); +// load(arrayList, 0); +// } +// +// /** +// * Play a media from the media list (playlist) +// * +// * @param index The index of the media +// * @param flags LibVLC.MEDIA_* flags +// */ +// public void playIndex(int index, int flags) { +// if (mMediaList.size() == 0) { +// Log.w(TAG, "Warning: empty media list, nothing to play !"); +// return; +// } +// if (index >= 0 && index < mMediaList.size()) { +// mCurrentIndex = index; +// } else { +// Log.w(TAG, "Warning: index " + index + " out of bounds"); +// mCurrentIndex = 0; +// } +// +// String mrl = mMediaList.getMRL(index); +// if (mrl == null) +// return; +// final MediaWrapper mw = mMediaList.getMedia(index); +// if (mw == null) +// return; +// if (mw.getType() == MediaWrapper.TYPE_VIDEO && isVideoPlaying()) +// mw.addFlags(MediaWrapper.MEDIA_VIDEO); +// +// /* Pausable and seekable are true by default */ +// mParsed = false; +// mSwitchingToVideo = false; +// mPausable = mSeekable = true; +// final Media media = new Media(VLCInstance.get(), mw.getUri()); +// VLCOptions.setMediaOptions(media, this, flags | mw.getFlags()); +// media.setEventListener(mMediaListener); +// mMediaPlayer.setMedia(media); +// media.release(); +// if (mw .getType() != MediaWrapper.TYPE_VIDEO || mw.hasFlag(MediaWrapper.MEDIA_FORCE_AUDIO) || isVideoPlaying()) { +// mMediaPlayer.setEqualizer(VLCOptions.getEqualizer(this)); +// mMediaPlayer.setVideoTitleDisplay(MediaPlayer.Position.Disable, 0); +// changeAudioFocus(true); +// mMediaPlayer.setEventListener(mMediaPlayerListener); +// mMediaPlayer.play(); +// if(mSavedTime != 0l) +// mMediaPlayer.setTime(mSavedTime); +// mSavedTime = 0l; +// +// notifyTrackChanged(); +// determinePrevAndNextIndices(); +// if (mSettings.getBoolean(PreferencesFragment.PLAYBACK_HISTORY, true)) +// VLCApplication.runBackground(new Runnable() { +// @Override +// public void run() { +// MediaDatabase.getInstance().addHistoryItem(mw); +// } +// }); +// } else {//Start VideoPlayer for first video, it will trigger playIndex when ready. +// VideoPlayerActivity.startOpened(VLCApplication.getAppContext(), +// getCurrentMediaWrapper().getUri(), mCurrentIndex); +// } +// } +// +// /** +// * Use this function to play a media inside whatever MediaList LibVLC is following. +// * +// * Unlike load(), it does not import anything into the primary list. +// */ +// @MainThread +// public void playIndex(int index) { +// playIndex(index, 0); +// } +// +// /** +// * Use this function to show an URI in the audio interface WITHOUT +// * interrupting the stream. +// * +// * Mainly used by VideoPlayerActivity in response to loss of video track. +// */ +// @MainThread +// public void showWithoutParse(int index) { +// String URI = mMediaList.getMRL(index); +// Log.v(TAG, "Showing index " + index + " with playing URI " + URI); +// // Show an URI without interrupting/losing the current stream +// +// if(URI == null || !mMediaPlayer.isPlaying()) +// return; +// mCurrentIndex = index; +// +// notifyTrackChanged(); +// showNotification(); +// } +// +// /** +// * Append to the current existing playlist +// */ +// @MainThread +// public void append(List mediaList) { +// if (!hasCurrentMedia()) +// { +// load(mediaList, 0); +// return; +// } +// +// for (int i = 0; i < mediaList.size(); i++) { +// MediaWrapper mediaWrapper = mediaList.get(i); +// mMediaList.add(mediaWrapper); +// } +// onMediaListChanged(); +// } +// +// @MainThread +// public void append(MediaWrapper media) { +// ArrayList arrayList = new ArrayList(); +// arrayList.add(media); +// append(arrayList); +// } +// +// /** +// * Move an item inside the playlist. +// */ +// @MainThread +// public void moveItem(int positionStart, int positionEnd) { +// mMediaList.move(positionStart, positionEnd); +// PlaybackService.this.saveMediaList(); +// Log.d(TAG, "moveItem " + positionStart + " -> " + positionEnd); +// } +// +// @MainThread +// public void insertItem(int position, MediaWrapper mw) { +// mMediaList.insert(position, mw); +// saveMediaList(); +// determinePrevAndNextIndices(); +// } +// +// +// @MainThread +// public void remove(int position) { +// mMediaList.remove(position); +// saveMediaList(); +// determinePrevAndNextIndices(); +// Log.d(TAG, "remove "+position); +// } +// +// @MainThread +// public void removeLocation(String location) { +// mMediaList.remove(location); +// saveMediaList(); +// determinePrevAndNextIndices(); +// } +// +// public int getMediaListSize() { +// return mMediaList.size(); +// } +// +// @MainThread +// public List getMedias() { +// final ArrayList ml = new ArrayList(); +// for (int i = 0; i < mMediaList.size(); i++) { +// ml.add(mMediaList.getMedia(i)); +// } +// return ml; +// } +// +// @MainThread +// public List getMediaLocations() { +// ArrayList medias = new ArrayList(); +// for (int i = 0; i < mMediaList.size(); i++) { +// medias.add(mMediaList.getMRL(i)); +// } +// return medias; +// } +// +// @MainThread +// public String getCurrentMediaLocation() { +// return mMediaList.getMRL(mCurrentIndex); +// } +// +// @MainThread +// public int getCurrentMediaPosition() { +// return mCurrentIndex; +// } +// +// @MainThread +// public MediaWrapper getCurrentMediaWrapper() { +// return PlaybackService.this.getCurrentMedia(); +// } +// +// @MainThread +// public void setTime(long time) { +// if (mSeekable) +// mMediaPlayer.setTime(time); +// } +// +// @MainThread +// public boolean hasNext() { +// return mNextIndex != -1; +// } +// +// @MainThread +// public boolean hasPrevious() { +// return mPrevIndex != -1; +// } +// +// @MainThread +// public void detectHeadset(boolean enable) { +// mDetectHeadset = enable; +// } +// +// @MainThread +// public float getRate() { +// return mMediaPlayer.getRate(); +// } +// +// @MainThread +// public void setRate(float rate) { +// mMediaPlayer.setRate(rate); +// } +// +// @MainThread +// public void navigate(int where) { +// mMediaPlayer.navigate(where); +// } +// +// @MainThread +// public MediaPlayer.Chapter[] getChapters(int title) { +// return mMediaPlayer.getChapters(title); +// } +// +// @MainThread +// public MediaPlayer.Title[] getTitles() { +// return mMediaPlayer.getTitles(); +// } +// +// @MainThread +// public int getChapterIdx() { +// return mMediaPlayer.getChapter(); +// } +// +// @MainThread +// public void setChapterIdx(int chapter) { +// mMediaPlayer.setChapter(chapter); +// } +// +// @MainThread +// public int getTitleIdx() { +// return mMediaPlayer.getTitle(); +// } +// +// @MainThread +// public void setTitleIdx(int title) { +// mMediaPlayer.setTitle(title); +// } +// +// @MainThread +// public int getVolume() { +// return mMediaPlayer.getVolume(); +// } +// +// @MainThread +// public int setVolume(int volume) { +// return mMediaPlayer.setVolume(volume); +// } +// +// @MainThread +// public void setPosition(float pos) { +// if (mSeekable) +// mMediaPlayer.setPosition(pos); +// } +// +// @MainThread +// public int getAudioTracksCount() { +// return mMediaPlayer.getAudioTracksCount(); +// } +// +// @MainThread +// public MediaPlayer.TrackDescription[] getAudioTracks() { +// return mMediaPlayer.getAudioTracks(); +// } +// +// @MainThread +// public int getAudioTrack() { +// return mMediaPlayer.getAudioTrack(); +// } +// +// @MainThread +// public boolean setAudioTrack(int index) { +// return mMediaPlayer.setAudioTrack(index); +// } +// +// @MainThread +// public int getVideoTracksCount() { +// return mMediaPlayer.getVideoTracksCount(); +// } +// +// @MainThread +// public boolean addSubtitleTrack(String path) { +// return mMediaPlayer.setSubtitleFile(path); +// } +// +// @MainThread +// public MediaPlayer.TrackDescription[] getSpuTracks() { +// return mMediaPlayer.getSpuTracks(); +// } +// +// @MainThread +// public int getSpuTrack() { +// return mMediaPlayer.getSpuTrack(); +// } +// +// @MainThread +// public boolean setSpuTrack(int index) { +// return mMediaPlayer.setSpuTrack(index); +// } +// +// @MainThread +// public int getSpuTracksCount() { +// return mMediaPlayer.getSpuTracksCount(); +// } +// +// @MainThread +// public boolean setAudioDelay(long delay) { +// return mMediaPlayer.setAudioDelay(delay); +// } +// +// @MainThread +// public long getAudioDelay() { +// return mMediaPlayer.getAudioDelay(); +// } +// +// @MainThread +// public boolean setSpuDelay(long delay) { +// return mMediaPlayer.setSpuDelay(delay); +// } +// +// @MainThread +// public long getSpuDelay() { +// return mMediaPlayer.getSpuDelay(); +// } +// +// @MainThread +// public void setEqualizer(MediaPlayer.Equalizer equalizer) { +// mMediaPlayer.setEqualizer(equalizer); +// } +// +// /** +// * Expand the current media. +// * @return the index of the media was expanded, and -1 if no media was expanded +// */ +// @MainThread +// public int expand() { +// final Media media = mMediaPlayer.getMedia(); +// if (media == null) +// return -1; +// final MediaList ml = media.subItems(); +// media.release(); +// int ret; +// +// if (ml.getCount() > 0) { +// mMediaList.remove(mCurrentIndex); +// for (int i = ml.getCount() - 1; i >= 0; --i) { +// final Media child = ml.getMediaAt(i); +// child.parse(); +// mMediaList.insert(mCurrentIndex, new MediaWrapper(child)); +// child.release(); +// } +// ret = 0; +// } else { +// ret = -1; +// } +// ml.release(); +// return ret; +// } +// +// public void restartMediaPlayer() { +// stop(); +// mMediaPlayer.release(); +// mMediaPlayer = newMediaPlayer(); +// /* TODO RESUME */ +// } +// +// public static class Client { +// public static final String TAG = "PlaybackService.Client"; +// +// @MainThread +// public interface Callback { +// void onConnected(PlaybackService service); +// void onDisconnected(); +// } +// +// private boolean mBound = false; +// private final Callback mCallback; +// private final Context mContext; +// +// private final ServiceConnection mServiceConnection = new ServiceConnection() { +// @Override +// public void onServiceConnected(ComponentName name, IBinder iBinder) { +// Log.d(TAG, "Service Connected"); +// if (!mBound) +// return; +// +// final PlaybackService service = PlaybackService.getService(iBinder); +// if (service != null) +// mCallback.onConnected(service); +// } +// +// @Override +// public void onServiceDisconnected(ComponentName name) { +// Log.d(TAG, "Service Disconnected"); +// mCallback.onDisconnected(); +// } +// }; +// +// private static Intent getServiceIntent(Context context) { +// return new Intent(context, PlaybackService.class); +// } +// +// private static void startService(Context context) { +// context.startService(getServiceIntent(context)); +// } +// +// private static void stopService(Context context) { +// context.stopService(getServiceIntent(context)); +// } +// +// public Client(Context context, Callback callback) { +// if (context == null || callback == null) +// throw new IllegalArgumentException("Context and callback can't be null"); +// mContext = context; +// mCallback = callback; +// } +// +// @MainThread +// public void connect() { +// if (mBound) +// throw new IllegalStateException("already connected"); +// startService(mContext); +// mBound = mContext.bindService(getServiceIntent(mContext), mServiceConnection, BIND_AUTO_CREATE); +// } +// +// @MainThread +// public void disconnect() { +// if (mBound) { +// mBound = false; +// mContext.unbindService(mServiceConnection); +// } +// } +// +// public static void restartService(Context context) { +// stopService(context); +// startService(context); +// } +// } +// +// int mPhoneEvents = PhoneStateListener.LISTEN_CALL_STATE; +// PhoneStateListener mPhoneStateListener; +// +// private void initPhoneListener() { +// mPhoneStateListener = new PhoneStateListener(){ +// @Override +// public void onCallStateChanged(int state, String incomingNumber) { +// if (!mMediaPlayer.isPlaying() || !hasCurrentMedia()) +// return; +// if (state == TelephonyManager.CALL_STATE_RINGING || state == TelephonyManager.CALL_STATE_OFFHOOK) +// pause(); +// else if (state == TelephonyManager.CALL_STATE_IDLE) +// play(); +// } +// }; +// } +//} diff --git a/app/src/main/java/org/stepic/droid/util/AndroidDevices.java b/app/src/main/java/org/stepic/droid/util/AndroidDevices.java new file mode 100644 index 0000000000..7431a7a4b6 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/util/AndroidDevices.java @@ -0,0 +1,169 @@ +/***************************************************************************** + * AndroidDevices.java + ***************************************************************************** + * Copyright © 2011-2014 VLC authors and VideoLAN + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +package org.stepic.droid.util; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.os.Environment; +import android.telephony.TelephonyManager; +import android.view.InputDevice; +import android.view.MotionEvent; + +import org.stepic.droid.base.MainApplication; +import org.videolan.libvlc.util.AndroidUtil; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.StringTokenizer; + +public class AndroidDevices { + public final static String TAG = "VLC/UiTools/AndroidDevices"; + public final static String EXTERNAL_PUBLIC_DIRECTORY = Environment.getExternalStorageDirectory().getPath(); + + final static boolean hasNavBar; + final static boolean hasTsp, isTv; + + static { + HashSet devicesWithoutNavBar = new HashSet(); + devicesWithoutNavBar.add("HTC One V"); + devicesWithoutNavBar.add("HTC One S"); + devicesWithoutNavBar.add("HTC One X"); + devicesWithoutNavBar.add("HTC One XL"); + hasNavBar = AndroidUtil.isICSOrLater() + && !devicesWithoutNavBar.contains(android.os.Build.MODEL); + hasTsp = MainApplication.getAppContext().getPackageManager().hasSystemFeature("android.hardware.touchscreen"); + isTv = MainApplication.getAppContext().getPackageManager().hasSystemFeature("android.software.leanback"); + } + + public static boolean hasExternalStorage() { + return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); + } + + public static boolean hasNavBar() { + return hasNavBar; + } + + /** + * hasCombBar test if device has Combined Bar : only for tablet with Honeycomb or ICS + */ + public static boolean hasCombBar() { + return (!AndroidDevices.isPhone() + && ((VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) && + (VERSION.SDK_INT <= VERSION_CODES.JELLY_BEAN))); + } + + public static boolean isPhone() { + TelephonyManager manager = (TelephonyManager) MainApplication.getAppContext().getSystemService(Context.TELEPHONY_SERVICE); + return manager.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE; + } + + public static boolean hasTsp() { + return hasTsp; + } + + public static boolean isAndroidTv() { + return isTv; + } + + public static ArrayList getStorageDirectories() { + BufferedReader bufReader = null; + ArrayList list = new ArrayList(); + list.add(EXTERNAL_PUBLIC_DIRECTORY); + + List typeWL = Arrays.asList("vfat", "exfat", "sdcardfs", "fuse", "ntfs", "fat32", "ext3", "ext4", "esdfs"); + List typeBL = Arrays.asList("tmpfs"); + String[] mountWL = {"/mnt", "/Removable", "/storage"}; + String[] mountBL = { + "/mnt/secure", + "/mnt/shell", + "/mnt/asec", + "/mnt/obb", + "/mnt/media_rw/extSdCard", + "/mnt/media_rw/sdcard", + "/storage/emulated"}; + String[] deviceWL = { + "/dev/block/vold", + "/dev/fuse", + "/mnt/media_rw"}; + + try { + bufReader = new BufferedReader(new FileReader("/proc/mounts")); + String line; + while ((line = bufReader.readLine()) != null) { + + StringTokenizer tokens = new StringTokenizer(line, " "); + String device = tokens.nextToken(); + String mountpoint = tokens.nextToken(); + String type = tokens.nextToken(); + + // skip if already in list or if type/mountpoint is blacklisted + if (list.contains(mountpoint) || typeBL.contains(type) || Strings.startsWith(mountBL, mountpoint)) + continue; + + // check that device is in whitelist, and either type or mountpoint is in a whitelist + if (Strings.startsWith(deviceWL, device) && (typeWL.contains(type) || Strings.startsWith(mountWL, mountpoint))) { + int position = Strings.containsName(list, FileUtils.getFileNameFromPath(mountpoint)); + if (position > -1) + list.remove(position); + list.add(mountpoint); + } + } + } catch (FileNotFoundException e) { + } catch (IOException e) { + } finally { + Util.close(bufReader); + } + return list; + } + + + + @TargetApi(VERSION_CODES.HONEYCOMB_MR1) + public static float getCenteredAxis(MotionEvent event, + InputDevice device, int axis) { + final InputDevice.MotionRange range = + device.getMotionRange(axis, event.getSource()); + + // A joystick at rest does not always report an absolute position of + // (0,0). Use the getFlat() method to determine the range of values + // bounding the joystick axis center. + if (range != null) { + final float flat = range.getFlat(); + final float value = event.getAxisValue(axis); + + // Ignore axis values that are within the 'flat' region of the + // joystick axis center. + if (Math.abs(value) > flat) { + return value; + } + } + return 0; + } + +} diff --git a/app/src/main/java/org/stepic/droid/util/AppConstants.java b/app/src/main/java/org/stepic/droid/util/AppConstants.java index 08705c2602..68d26fda7a 100644 --- a/app/src/main/java/org/stepic/droid/util/AppConstants.java +++ b/app/src/main/java/org/stepic/droid/util/AppConstants.java @@ -9,7 +9,7 @@ public class AppConstants { public static final String KEY_UNIT_BUNDLE = "unit"; public static final String KEY_LESSON_BUNDLE = "lesson"; public static final String KEY_STEP_BUNDLE = "step"; - public static final String DEFAULT_QUALITY = "270"; + public static final String DEFAULT_QUALITY = "360"; public static final String PRE_BODY = "\n" + "\n" + "Step. Stepic.org\n" + diff --git a/app/src/main/java/org/stepic/droid/util/FileUtils.java b/app/src/main/java/org/stepic/droid/util/FileUtils.java new file mode 100644 index 0000000000..9853baa1f4 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/util/FileUtils.java @@ -0,0 +1,192 @@ +/* + * ************************************************************************* + * FileUtils.java + * ************************************************************************** + * Copyright © 2015 VLC authors and VideoLAN + * Author: Geoffrey Métais + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + * *************************************************************************** + */ + +package org.stepic.droid.util; + +import android.annotation.TargetApi; +import android.content.ContentResolver; +import android.content.res.AssetManager; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; +import android.text.TextUtils; + +import org.stepic.droid.base.MainApplication; +import org.videolan.libvlc.util.AndroidUtil; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class FileUtils { + + public interface Callback { + void onResult(boolean success); + } + + public static String getFileNameFromPath(String path){ + if (path == null) + return ""; + int index = path.lastIndexOf('/'); + if (index> -1) + return path.substring(index+1); + else + return path; + } + + public static String getParent(String path){ + if (TextUtils.equals("/", path)) + return path; + String parentPath = path; + if (parentPath.endsWith("/")) + parentPath = parentPath.substring(0, parentPath.length()-1); + int index = parentPath.lastIndexOf('/'); + if (index > 0){ + parentPath = parentPath.substring(0, index); + } else if (index == 0) + parentPath = "/"; + return parentPath; + } + + public static boolean copyAssetFolder(AssetManager assetManager, String fromAssetPath, String toPath) { + try { + String[] files = assetManager.list(fromAssetPath); + if (files.length == 0) + return false; + new File(toPath).mkdirs(); + boolean res = true; + for (String file : files) + if (file.contains(".")) + res &= copyAsset(assetManager, + fromAssetPath + "/" + file, + toPath + "/" + file); + else + res &= copyAssetFolder(assetManager, + fromAssetPath + "/" + file, + toPath + "/" + file); + return res; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + private static boolean copyAsset(AssetManager assetManager, + String fromAssetPath, String toPath) { + InputStream in = null; + OutputStream out = null; + try { + in = assetManager.open(fromAssetPath); + new File(toPath).createNewFile(); + out = new FileOutputStream(toPath); + copyFile(in, out); + out.flush(); + return true; + } catch(Exception e) { + e.printStackTrace(); + return false; + } finally { + Util.close(in); + Util.close(out); + } + } + + public static void copyFile(InputStream in, OutputStream out) throws IOException { + byte[] buffer = new byte[1024]; + int read; + while((read = in.read(buffer)) != -1){ + out.write(buffer, 0, read); + } + } + + public static boolean copyFile(File src, File dst){ + boolean ret = true; + if (src.isDirectory()) { + File[] filesList = src.listFiles(); + dst.mkdirs(); + for (File file : filesList) + ret &= copyFile(file, new File(dst, file.getName())); + } else if (src.isFile()) { + InputStream in = null; + OutputStream out = null; + try { + in = new BufferedInputStream(new FileInputStream(src)); + out = new BufferedOutputStream(new FileOutputStream(dst)); + + // Transfer bytes from in to out + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + return true; + } catch (FileNotFoundException e) { + } catch (IOException e) { + } finally { + Util.close(in); + Util.close(out); + } + return false; + } + return ret; + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static boolean deleteFile (String path){ + boolean deleted = false; + path = Uri.decode(Strings.removeFileProtocol(path)); + //Delete from Android Medialib, for consistency with device MTP storing and other apps listing content:// media + if (AndroidUtil.isHoneycombOrLater()){ + ContentResolver cr = MainApplication.getAppContext().getContentResolver(); + String[] selectionArgs = { path }; + deleted = cr.delete(MediaStore.Files.getContentUri("external"), + MediaStore.Files.FileColumns.DATA + "=?", selectionArgs) > 0; + } + File file = new File(path); + if (file.exists()) + deleted |= file.delete(); + return deleted; + } + + + public static boolean canWrite(String path){ + if (path == null) + return false; + if (path.startsWith("file://")) + path = path.substring(7); + if (!path.startsWith("/")) + return false; + if (path.startsWith(AndroidDevices.EXTERNAL_PUBLIC_DIRECTORY)) + return true; + if (AndroidUtil.isKitKatOrLater()) + return false; + File file = new File(path); + return (file.exists() && file.canWrite()); + } +} diff --git a/app/src/main/java/org/stepic/droid/util/Strings.java b/app/src/main/java/org/stepic/droid/util/Strings.java new file mode 100644 index 0000000000..6ea03caa07 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/util/Strings.java @@ -0,0 +1,135 @@ +/***************************************************************************** + * Strings.java + * **************************************************************************** + * Copyright © 2011-2014 VLC authors and VideoLAN + *

+ * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + *

+ * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + *

+ * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +package org.stepic.droid.util; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.List; +import java.util.Locale; + +public class Strings { + public final static String TAG = "VLC/UiTools/Strings"; + + public static String stripTrailingSlash(String s) { + if (s.endsWith("/") && s.length() > 1) + return s.substring(0, s.length() - 1); + return s; + } + + static boolean startsWith(String[] array, String text) { + for (String item : array) + if (text.startsWith(item)) + return true; + return false; + } + + static int containsName(List array, String text) { + for (int i = array.size() - 1; i >= 0; --i) + if (array.get(i).endsWith(text)) + return i; + return -1; + } + + /** + * Convert time to a string + * @param millis e.g.time/length from file + * @return formated string (hh:)mm:ss + */ + public static String millisToString(long millis) { + return Strings.millisToString(millis, false); + } + + /** + * Convert time to a string + * @param millis e.g.time/length from file + * @return formated string "[hh]h[mm]min" / "[mm]min[s]s" + */ + public static String millisToText(long millis) { + return Strings.millisToString(millis, true); + } + + static String millisToString(long millis, boolean text) { + boolean negative = millis < 0; + millis = java.lang.Math.abs(millis); + + millis /= 1000; + int sec = (int) (millis % 60); + millis /= 60; + int min = (int) (millis % 60); + millis /= 60; + int hours = (int) millis; + + String time; + DecimalFormat format = (DecimalFormat) NumberFormat.getInstance(Locale.US); + format.applyPattern("00"); + if (text) { + if (millis > 0) + time = (negative ? "-" : "") + hours + "h" + format.format(min) + "min"; + else if (min > 0) + time = (negative ? "-" : "") + min + "min"; + else + time = (negative ? "-" : "") + sec + "s"; + } else { + if (millis > 0) + time = (negative ? "-" : "") + hours + ":" + format.format(min) + ":" + format.format(sec); + else + time = (negative ? "-" : "") + min + ":" + format.format(sec); + } + return time; + } + + /** + * equals() with two strings where either could be null + */ + public static boolean nullEquals(String s1, String s2) { + return (s1 == null ? s2 == null : s1.equals(s2)); + } + + /** + * Get the formatted current playback speed in the form of 1.00x + */ + public static String formatRateString(float rate) { + return String.format(java.util.Locale.US, "%.2fx", rate); + } + + public static String readableFileSize(long size) { + if (size <= 0) return "0"; + final String[] units = new String[]{"B", "KiB", "MiB", "GiB", "TiB"}; + int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); + return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; + } + + public static String readableSize(long size) { + if (size <= 0) return "0"; + final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"}; + int digitGroups = (int) (Math.log10(size) / Math.log10(1000)); + return new DecimalFormat("#,##0.#").format(size / Math.pow(1000, digitGroups)) + " " + units[digitGroups]; + } + + public static String removeFileProtocol(String path) { + if (path == null) + return null; + if (path.startsWith("file://")) + return path.substring(7); + else + return path; + } +} diff --git a/app/src/main/java/org/stepic/droid/util/TimeUtil.java b/app/src/main/java/org/stepic/droid/util/TimeUtil.java new file mode 100644 index 0000000000..a397e4880e --- /dev/null +++ b/app/src/main/java/org/stepic/droid/util/TimeUtil.java @@ -0,0 +1,24 @@ +package org.stepic.droid.util; + +public class TimeUtil { + public static String getFormattedVideoTime(long milliseconds) { + long seconds = milliseconds / 1000; + long minutes = seconds / 60; + seconds = seconds % 60; + + return getRepresentation(minutes) + " : " + getRepresentation(seconds); + } + + private static String getRepresentation(long number) { + + if (number == 0) { + return "00"; + } + + if (number / 10 == 0) { + return "0" + number; + } + + return String.valueOf(number); + } +} diff --git a/app/src/main/java/org/stepic/droid/util/Util.java b/app/src/main/java/org/stepic/droid/util/Util.java new file mode 100644 index 0000000000..f28a6a5594 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/util/Util.java @@ -0,0 +1,90 @@ +/***************************************************************************** + * UiTools.java + ***************************************************************************** + * Copyright © 2011-2014 VLC authors and VideoLAN + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +package org.stepic.droid.util; + +import android.annotation.TargetApi; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; + +import org.stepic.droid.base.MainApplication; +import org.videolan.libvlc.util.AndroidUtil; + +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; + +public class Util { + public final static String TAG = "VLC/Util"; + + public static String readAsset(String assetName, String defaultS) { + InputStream is = null; + BufferedReader r = null; + try { + is = MainApplication.getAppContext().getResources().getAssets().open(assetName); + r = new BufferedReader(new InputStreamReader(is, "UTF8")); + StringBuilder sb = new StringBuilder(); + String line = r.readLine(); + if(line != null) { + sb.append(line); + line = r.readLine(); + while(line != null) { + sb.append('\n'); + sb.append(line); + line = r.readLine(); + } + } + return sb.toString(); + } catch (IOException e) { + return defaultS; + } finally { + close(is); + close(r); + } + } + + @TargetApi(android.os.Build.VERSION_CODES.GINGERBREAD) + public static void commitPreferences(SharedPreferences.Editor editor){ + if (AndroidUtil.isGingerbreadOrLater()) + editor.apply(); + else + editor.commit(); + } + + public static boolean close(Closeable closeable) { + if (closeable != null) + try { + closeable.close(); + return true; + } catch (IOException e) {} + return false; + } + + public static boolean isCallable(Intent intent) { + List list = MainApplication.getAppContext().getPackageManager().queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY); + return list.size() > 0; + } +} diff --git a/app/src/main/java/org/stepic/droid/util/resolvers/VideoResolver.java b/app/src/main/java/org/stepic/droid/util/resolvers/VideoResolver.java index 3505a57c2e..e83bc0a2b5 100644 --- a/app/src/main/java/org/stepic/droid/util/resolvers/VideoResolver.java +++ b/app/src/main/java/org/stepic/droid/util/resolvers/VideoResolver.java @@ -55,13 +55,14 @@ public String resolveVideoUrl(@Nullable final Video video) { private String resolveFromWeb(List urlList) { String resolvedURL = null; - for (int i = 0; i < urlList.size(); i++) { + if (urlList == null || urlList.isEmpty()) return null; + int upperBound = urlList.size() - 1; + for (int i = upperBound; i >= 0; i--) { VideoUrl tempLink = urlList.get(i); if (tempLink != null) { String quality = tempLink.getQuality(); if (quality != null && - (quality.equals(mUserPreferences.getQualityVideo()) || i == urlList.size() - 1)) { - //// TODO: 15.10.15 determine video which is available for the phone. Not default + (quality.equals(mUserPreferences.getQualityVideo()) || i == 0)) { resolvedURL = tempLink.getUrl(); break; } diff --git a/app/src/main/java/org/stepic/droid/view/activities/MainFeedActivity.java b/app/src/main/java/org/stepic/droid/view/activities/MainFeedActivity.java index a9857e9df3..505bd6a55f 100644 --- a/app/src/main/java/org/stepic/droid/view/activities/MainFeedActivity.java +++ b/app/src/main/java/org/stepic/droid/view/activities/MainFeedActivity.java @@ -1,5 +1,6 @@ package org.stepic.droid.view.activities; +import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; @@ -71,14 +72,14 @@ public class MainFeedActivity extends FragmentActivityBase private int mCurrentIndex; -// @Override -// protected void onNewIntent(Intent intent) { -// super.onNewIntent(intent); -// Bundle extras = intent.getExtras(); -// if (extras != null){ -// initFragments(extras); -// } -// } + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + Bundle extras = intent.getExtras(); + if (extras != null) { + initFragments(extras); + } + } @Override protected void onCreate(Bundle savedInstanceState) { @@ -268,7 +269,16 @@ private void setFragment(MenuItem menuItem) { } mCurrentIndex--; // menu indices from 1 if (shortLifetimeRef != null) { - setFragment(R.id.frame, shortLifetimeRef); + Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.frame); + if (fragment != null) { + String before = fragment.getTag(); + String now = shortLifetimeRef.getClass().toString(); + if (!before.equals(now)) { + setFragment(R.id.frame, shortLifetimeRef); + } + } else { + setFragment(R.id.frame, shortLifetimeRef); + } } } diff --git a/app/src/main/java/org/stepic/droid/view/activities/SectionActivity.java b/app/src/main/java/org/stepic/droid/view/activities/SectionActivity.java index 5e31b17117..d7d28d41d4 100644 --- a/app/src/main/java/org/stepic/droid/view/activities/SectionActivity.java +++ b/app/src/main/java/org/stepic/droid/view/activities/SectionActivity.java @@ -18,8 +18,8 @@ import org.stepic.droid.R; import org.stepic.droid.base.FragmentActivityBase; -import org.stepic.droid.concurrency.FromDbSectionTask; -import org.stepic.droid.concurrency.ToDbSectionTask; +import org.stepic.droid.concurrency.tasks.FromDbSectionTask; +import org.stepic.droid.concurrency.tasks.ToDbSectionTask; import org.stepic.droid.events.notify_ui.NotifyUISectionsEvent; import org.stepic.droid.events.sections.FailureResponseSectionEvent; import org.stepic.droid.events.sections.FinishingGetSectionFromDbEvent; @@ -64,8 +64,6 @@ public class SectionActivity extends FragmentActivityBase implements SwipeRefres private Course mCourse; private SectionAdapter mAdapter; private List

mSectionList; - private FromDbSectionTask mFromDbSectionTask; - private ToDbSectionTask mToDbSectionTask; boolean isScreenEmpty; boolean firstLoad; @@ -143,8 +141,8 @@ public void onFailure(Throwable t) { } private void getAndShowSectionsFromCache() { - mFromDbSectionTask = new FromDbSectionTask(mCourse); - mFromDbSectionTask.execute(); + FromDbSectionTask fromDbSectionTask = new FromDbSectionTask(mCourse); + fromDbSectionTask.executeOnExecutor(mThreadPoolExecutor); } private void showSections(List
sections) { @@ -184,8 +182,8 @@ public void onRefresh() { private void saveDataToCache(List
sections) { - mToDbSectionTask = new ToDbSectionTask(sections); - mToDbSectionTask.execute(); + ToDbSectionTask toDbSectionTask = new ToDbSectionTask(sections); + toDbSectionTask.executeOnExecutor(mThreadPoolExecutor); } @Subscribe diff --git a/app/src/main/java/org/stepic/droid/view/activities/UnitsActivity.java b/app/src/main/java/org/stepic/droid/view/activities/UnitsActivity.java index 8ac1322bf0..cfa6e6290d 100644 --- a/app/src/main/java/org/stepic/droid/view/activities/UnitsActivity.java +++ b/app/src/main/java/org/stepic/droid/view/activities/UnitsActivity.java @@ -18,8 +18,8 @@ import org.jetbrains.annotations.Nullable; import org.stepic.droid.R; import org.stepic.droid.base.FragmentActivityBase; -import org.stepic.droid.concurrency.FromDbUnitLessonTask; -import org.stepic.droid.concurrency.ToDbUnitLessonTask; +import org.stepic.droid.concurrency.tasks.FromDbUnitLessonTask; +import org.stepic.droid.concurrency.tasks.ToDbUnitLessonTask; import org.stepic.droid.events.lessons.SuccessLoadLessonsEvent; import org.stepic.droid.events.notify_ui.NotifyUIUnitLessonEvent; import org.stepic.droid.events.units.FailureLoadEvent; @@ -118,7 +118,7 @@ protected void onCreate(Bundle savedInstanceState) { private void getAndShowUnitsFromCache() { mFromDbTask = new FromDbUnitLessonTask(mSection); - mFromDbTask.execute(); + mFromDbTask.executeOnExecutor(mThreadPoolExecutor); } @@ -197,7 +197,7 @@ public void onFailure(Throwable t) { private void saveToDb(List unitList, List lessonList, List progresses) { mToDbTask = new ToDbUnitLessonTask(mSection, unitList, lessonList, progresses); - mToDbTask.execute(); + mToDbTask.executeOnExecutor(mThreadPoolExecutor); } private void showUnitsLessons(List units, List lessons, Map longProgressMap) { diff --git a/app/src/main/java/org/stepic/droid/view/activities/VideoActivity.kt b/app/src/main/java/org/stepic/droid/view/activities/VideoActivity.kt new file mode 100644 index 0000000000..1017220b00 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/view/activities/VideoActivity.kt @@ -0,0 +1,20 @@ +package org.stepic.droid.view.activities + +import android.support.v4.app.Fragment +import org.stepic.droid.base.SingleFragmentActivity +import org.stepic.droid.view.fragments.VideoFragment + +class VideoActivity : SingleFragmentActivity() { + companion object { + val videoPathKey = "VIDEO_URI_KEY" + } + + override fun createFragment(): Fragment? { + val path: String? = intent.extras.getString(videoPathKey) + if (path != null) { + return VideoFragment.newInstance(path) + } else { + return null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/view/adapters/DownloadsAdapter.java b/app/src/main/java/org/stepic/droid/view/adapters/DownloadsAdapter.java index afb5ac59c8..b40015328c 100644 --- a/app/src/main/java/org/stepic/droid/view/adapters/DownloadsAdapter.java +++ b/app/src/main/java/org/stepic/droid/view/adapters/DownloadsAdapter.java @@ -1,7 +1,6 @@ package org.stepic.droid.view.adapters; -import android.content.Context; -import android.content.Intent; +import android.app.Activity; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; @@ -11,13 +10,12 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import android.widget.Toast; import com.squareup.picasso.Picasso; -import com.yandex.metrica.YandexMetrica; import org.stepic.droid.R; import org.stepic.droid.base.MainApplication; +import org.stepic.droid.core.IScreenManager; import org.stepic.droid.model.CachedVideo; import org.stepic.droid.model.Lesson; import org.stepic.droid.model.Step; @@ -43,7 +41,7 @@ public class DownloadsAdapter extends RecyclerView.Adapter implements StepicOnClickItemListener, OnClickLoadListener { private List mCachedVideoList; - private Context mContext; + private Activity sourceActivity; private Map mStepIdToLessonMap; @Inject @@ -51,19 +49,21 @@ public class DownloadsAdapter extends RecyclerView.Adapter cachedVideos, Map videoIdToStepMap, Context context, DownloadsFragment downloadsFragment) { + public DownloadsAdapter(List cachedVideos, Map videoIdToStepMap, Activity context, DownloadsFragment downloadsFragment) { this.downloadsFragment = downloadsFragment; MainApplication.component().inject(this); mCachedVideoList = cachedVideos; - mContext = context; + sourceActivity = context; mStepIdToLessonMap = videoIdToStepMap; } @Override public DownloadsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View v = LayoutInflater.from(mContext).inflate(R.layout.cached_video_item, null); + View v = LayoutInflater.from(sourceActivity).inflate(R.layout.cached_video_item, null); return new DownloadsViewHolder(v, this, this); } @@ -130,16 +130,7 @@ public int getItemCount() { public void onClick(int position) { if (position >= 0 && position < mCachedVideoList.size()) { CachedVideo video = mCachedVideoList.get(position); - Uri videoUri = Uri.parse(video.getUrl()); - - Intent intent = new Intent(Intent.ACTION_VIEW, videoUri); - intent.setDataAndType(videoUri, "video/*"); - try { - mContext.startActivity(intent); - } catch (Exception ex) { - YandexMetrica.reportError("NotPlayer", ex); - Toast.makeText(mContext, R.string.not_video_player_error, Toast.LENGTH_LONG).show(); - } + mScreenManager.showVideo(sourceActivity, video.getUrl()); } } diff --git a/app/src/main/java/org/stepic/droid/view/fragments/CourseDetailFragment.java b/app/src/main/java/org/stepic/droid/view/fragments/CourseDetailFragment.java index 47b1bc25c5..f38fba9c04 100644 --- a/app/src/main/java/org/stepic/droid/view/fragments/CourseDetailFragment.java +++ b/app/src/main/java/org/stepic/droid/view/fragments/CourseDetailFragment.java @@ -29,11 +29,10 @@ import com.squareup.otto.Subscribe; import com.squareup.picasso.Picasso; -import com.yandex.metrica.YandexMetrica; import org.stepic.droid.R; import org.stepic.droid.base.FragmentBase; -import org.stepic.droid.concurrency.UpdateCourseTask; +import org.stepic.droid.concurrency.tasks.UpdateCourseTask; import org.stepic.droid.events.instructors.FailureLoadInstructorsEvent; import org.stepic.droid.events.instructors.OnResponseLoadingInstructorsEvent; import org.stepic.droid.events.instructors.StartLoadingInstructorsEvent; @@ -244,17 +243,7 @@ private void showNewStyleVideo(final String urlToVideo, String pathThumbnail) { mPlayer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Uri videoUri = Uri.parse(urlToVideo); - - Intent intent = new Intent(Intent.ACTION_VIEW, videoUri); - intent.setDataAndType(videoUri, "video/*"); - //todo change icon to play - try { - startActivity(intent); - } catch (Exception ex) { - YandexMetrica.reportError("NotPlayer", ex); - Toast.makeText(getActivity(), R.string.not_video_player_error, Toast.LENGTH_LONG).show(); - } + mShell.getScreenProvider().showVideo(getActivity(), urlToVideo); } }); } @@ -356,10 +345,10 @@ public void onResponse(Response response, Retrofit retrofit) { localCopy.setEnrollment((int) localCopy.getCourseId()); UpdateCourseTask updateCourseTask = new UpdateCourseTask(DatabaseFacade.Table.enrolled, localCopy); - updateCourseTask.execute(); + updateCourseTask.executeOnExecutor(mThreadPoolExecutor); UpdateCourseTask updateCourseFeaturedTask = new UpdateCourseTask(DatabaseFacade.Table.featured, localCopy); - updateCourseFeaturedTask.execute(); + updateCourseFeaturedTask.executeOnExecutor(mThreadPoolExecutor); bus.post(new SuccessJoinEvent(localCopy)); diff --git a/app/src/main/java/org/stepic/droid/view/fragments/DownloadsFragment.java b/app/src/main/java/org/stepic/droid/view/fragments/DownloadsFragment.java index 168a13fdfb..9000431af9 100644 --- a/app/src/main/java/org/stepic/droid/view/fragments/DownloadsFragment.java +++ b/app/src/main/java/org/stepic/droid/view/fragments/DownloadsFragment.java @@ -1,33 +1,41 @@ package org.stepic.droid.view.fragments; import android.app.Dialog; +import android.app.DownloadManager; import android.content.DialogInterface; +import android.database.Cursor; import android.os.AsyncTask; import android.os.Bundle; -import android.support.annotation.Nullable; import android.support.v4.app.DialogFragment; import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.ProgressBar; import com.squareup.otto.Bus; import com.squareup.otto.Subscribe; import com.yandex.metrica.YandexMetrica; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.stepic.droid.R; import org.stepic.droid.base.FragmentBase; import org.stepic.droid.base.MainApplication; import org.stepic.droid.events.steps.ClearAllDownloadWithoutAnimationEvent; import org.stepic.droid.events.steps.StepRemovedEvent; +import org.stepic.droid.events.video.DownloadReportEvent; import org.stepic.droid.events.video.FinishDownloadCachedVideosEvent; +import org.stepic.droid.events.video.VideoCachedOnDiskEvent; import org.stepic.droid.model.CachedVideo; +import org.stepic.droid.model.DownloadEntity; +import org.stepic.droid.model.DownloadReportItem; import org.stepic.droid.model.Lesson; import org.stepic.droid.model.Step; import org.stepic.droid.model.VideosAndMapToLesson; @@ -35,6 +43,7 @@ import org.stepic.droid.store.operations.DatabaseFacade; import org.stepic.droid.util.AppConstants; import org.stepic.droid.util.DbParseHelper; +import org.stepic.droid.util.ProgressHelper; import org.stepic.droid.util.StepicLogicHelper; import org.stepic.droid.view.adapters.DownloadsAdapter; @@ -48,10 +57,12 @@ import butterknife.Bind; import butterknife.ButterKnife; import jp.wasabeef.recyclerview.animators.SlideInRightAnimator; +import kotlin.Unit; +import kotlin.jvm.functions.Function0; public class DownloadsFragment extends FragmentBase { - public static DownloadsFragment newInstance(){ + public static DownloadsFragment newInstance() { return new DownloadsFragment(); } @@ -63,10 +74,25 @@ public static DownloadsFragment newInstance(){ @Bind(R.id.list_of_downloads) RecyclerView mDownloadsView; + @Bind(R.id.progress_bar) + ProgressBar mProgressBar; + private DownloadsAdapter mDownloadAdapter; private List mCachedVideoList; private Map mStepIdToLesson; + private List mNowDownloadingList; + private Runnable mLoadingUpdater = null; + + private boolean isLoaded; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + mCachedVideoList = new ArrayList<>(); + mStepIdToLesson = new HashMap<>(); + } @Nullable @Override @@ -80,29 +106,125 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mCachedVideoList = new ArrayList<>(); - mStepIdToLesson = new HashMap<>(); - mDownloadAdapter = new DownloadsAdapter(mCachedVideoList, mStepIdToLesson, getContext(), this); + + mDownloadAdapter = new DownloadsAdapter(mCachedVideoList, mStepIdToLesson, getActivity(), this); mDownloadsView.setAdapter(mDownloadAdapter); mDownloadsView.setLayoutManager(new LinearLayoutManager(getContext())); mDownloadsView.setItemAnimator(new SlideInRightAnimator()); mDownloadsView.getItemAnimator().setRemoveDuration(10); + mDownloadsView.getItemAnimator().setAddDuration(10); + + if (isLoaded) { + checkForEmpty(); + } else { + mEmptyDownloadView.setVisibility(View.GONE); + ProgressHelper.activate(mProgressBar); + } + bus.register(this); +// startLoadingStatusUpdater(); + } + + private void startLoadingStatusUpdater() { + if (mLoadingUpdater != null) return; + mLoadingUpdater = new Runnable() { + @Override + public void run() { + while (!Thread.currentThread().isInterrupted()) { + Thread t = Thread.currentThread(); + Cursor cursor = getCursorForAllDownloads(); + if (cursor == null) continue; + try { + cursor.moveToFirst(); + + while (!cursor.isAfterLast()) { + int bytes_total = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)); + if (bytes_total > 0) { + int bytes_downloaded = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)); + int columnStatus = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)); + int downloadId = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_ID)); + int columnReason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON)); + final DownloadReportItem downloadReportItem = new DownloadReportItem(bytes_downloaded, bytes_total, columnStatus, downloadId, columnReason); + final Boolean isInterrupted = Thread.currentThread().isInterrupted(); + mMainHandler.post(new Function0() { + @Override + public Unit invoke() { + bus.post(new DownloadReportEvent(downloadReportItem)); + return Unit.INSTANCE; + } + }); + } + } + } finally { + cursor.close(); + } + + } + Log.d("ppp", "This thread is terminated"); + } + + + }; + mThread = new Thread(mLoadingUpdater); + mThread.start(); + } + + private Thread mThread; + + private void stopLoadingStatusUpdater() { + if (mLoadingUpdater != null) { + mThread.interrupt(); + mLoadingUpdater = null; + } + } + + @Subscribe + public void onLoadingUpdate(DownloadReportEvent event) { + DownloadReportItem item = event.getDownloadReportItem(); + Log.d("wakawaka", "receive"); + } + + private long[] getAllDownloadIds(@NotNull List list) { + final List copyOfList = new ArrayList<>(list); + long[] result = new long[copyOfList.size()]; + int i = 0; + for (DownloadEntity element : copyOfList) { + result[i++] = element.getDownloadId(); + } + return result; + } + + //Query the download manager about downloads that have been requested. + @Nullable + private Cursor getCursorForAllDownloads() { + mNowDownloadingList = mDatabaseFacade.getAllDownloadEntities(); + long[] ids = getAllDownloadIds(mNowDownloadingList); + if (ids == null || ids.length == 0) return null; + + + DownloadManager.Query query = new DownloadManager.Query(); + query.setFilterById(ids); + return mSystemDownloadManager.query(query); + } @Override public void onDestroyView() { +// stopLoadingStatusUpdater(); bus.unregister(this); + mDownloadsView.setAdapter(null); + mDownloadAdapter = null; super.onDestroyView(); } @Override public void onStart() { super.onStart(); -// bus.register(this); - updateCachedAsync(); + if (!isLoaded) { + updateCachedAsync(); + } } private void updateCachedAsync() { @@ -112,7 +234,7 @@ protected VideosAndMapToLesson doInBackground(Void... params) { List videos = mDatabaseFacade.getAllCachedVideos(); List filteredVideos = new ArrayList<>(); for (CachedVideo video : videos) { - if (video != null && video.getStepId() >=0){ + if (video != null && video.getStepId() >= 0) { filteredVideos.add(video); } } @@ -134,20 +256,13 @@ protected void onPostExecute(VideosAndMapToLesson videoAndMap) { @Subscribe public void onFinishLoadCachedVideos(FinishDownloadCachedVideosEvent event) { - List list = event.getCachedVideos(); - if (list == null) { - return; - } - - Map map = event.getMap(); - if (map == null) { - return; - } - - showCachedVideos(list, map); + showCachedVideos(event.getCachedVideos(), event.getMap()); } private void showCachedVideos(List videosForShowing, Map map) { + isLoaded = true; + ProgressHelper.dismiss(mProgressBar); + if (videosForShowing == null || map == null) return; mStepIdToLesson.clear(); mStepIdToLesson.putAll(map); mCachedVideoList.clear(); @@ -160,7 +275,7 @@ private void showCachedVideos(List videosForShowing, Map= 0 && pos < mCachedVideoList.size()) { + checkForEmpty(); + mDownloadAdapter.notifyItemInserted(pos); + } + } + + private int removeByStepId(long stepId) { if (!mStepIdToLesson.containsKey(stepId)) return -1; CachedVideo videoForDeleteFromList = null; @@ -266,15 +398,16 @@ private int removeByStepId(long stepId) { int position = mCachedVideoList.indexOf(videoForDeleteFromList); mCachedVideoList.remove(videoForDeleteFromList); mStepIdToLesson.remove(videoForDeleteFromList.getStepId()); - if (mCachedVideoList.size() == 0){ + if (mCachedVideoList.size() == 0) { } return position; } - public void checkForEmpty () { + public void checkForEmpty() { //// FIXME: 14.12.15 add to notify methods if (!mCachedVideoList.isEmpty()) { + ProgressHelper.dismiss(mProgressBar); mEmptyDownloadView.setVisibility(View.GONE); } else { mEmptyDownloadView.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/org/stepic/droid/view/fragments/SettingsFragment.java b/app/src/main/java/org/stepic/droid/view/fragments/SettingsFragment.java index cc9524ae4f..283edc6ddb 100644 --- a/app/src/main/java/org/stepic/droid/view/fragments/SettingsFragment.java +++ b/app/src/main/java/org/stepic/droid/view/fragments/SettingsFragment.java @@ -34,7 +34,7 @@ public class SettingsFragment extends FragmentBase { - public static SettingsFragment newInstance(){ + public static SettingsFragment newInstance() { return new SettingsFragment(); } @@ -46,6 +46,9 @@ public static SettingsFragment newInstance(){ @Bind(R.id.fragment_settings_wifi_enable_switch) BetterSwitch mWifiLoadSwitch; + @Bind(R.id.fragment_settings_external_player_switch) + BetterSwitch mExternalPlayerSwitch; + @Bind(R.id.video_quality_view) View mVideoQuality; @@ -84,6 +87,15 @@ public void onActivityCreated(@Nullable Bundle savedInstanceState) { mWifiLoadSwitch.setChecked(!mSharedPreferenceHelper.isMobileInternetAlsoAllowed());//if first time it is true + mExternalPlayerSwitch.setChecked(mUserPreferences.isOpenInExternal()); + + mExternalPlayerSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mUserPreferences.setOpenInExternal(isChecked); + } + }); + mWifiLoadSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override @@ -139,8 +151,7 @@ public void onClick(View v) { clearCacheButtonText.append(kb); } clearCacheButtonText.append(")"); - } - else{ + } else { mClearCacheButton.setEnabled(false); } mClearCacheButton.setText(clearCacheButtonText.toString()); @@ -161,6 +172,7 @@ public void onStop() { @Override public void onDestroyView() { mWifiLoadSwitch.setOnCheckedChangeListener(null); + mExternalPlayerSwitch.setOnCheckedChangeListener(null); super.onDestroyView(); } diff --git a/app/src/main/java/org/stepic/droid/view/fragments/StepWithAttemptsFragment.java b/app/src/main/java/org/stepic/droid/view/fragments/StepWithAttemptsFragment.java index 77caa9fa4a..713acee2b3 100644 --- a/app/src/main/java/org/stepic/droid/view/fragments/StepWithAttemptsFragment.java +++ b/app/src/main/java/org/stepic/droid/view/fragments/StepWithAttemptsFragment.java @@ -135,7 +135,6 @@ public void onClick(View v) { } }); - startWork(); } @@ -194,7 +193,7 @@ public void onResponse(Response response, Retrofit retrofit) { List attemptList = body.getAttempts(); if (attemptList == null || attemptList.isEmpty() || !attemptList.get(0).getStatus().equals("active")) { - createNewAttempt(); //fixme it is ok way, but 'active' can be not active + createNewAttempt(); } else { YandexMetrica.reportEvent(AppConstants.GET_OLD_ATTEMPT); Attempt attempt = attemptList.get(0); @@ -281,9 +280,7 @@ public void onResponse(Response response, Retrofit retrofit) if (response.isSuccess()) { List submissionList = response.body().getSubmissions(); if (submissionList == null || submissionList.isEmpty()) { -// bus.post(new SuccessGettingLastSubmissionEvent(localAttemptId, null));//fixme it is normal way, but old attempts are broken - //it should run when we not post our submission, just getting submission of last attempt - createNewAttempt();//fixme make new working attempt, it generates some number extra queries + bus.post(new SuccessGettingLastSubmissionEvent(localAttemptId, null)); return; } diff --git a/app/src/main/java/org/stepic/droid/view/fragments/StepsFragment.java b/app/src/main/java/org/stepic/droid/view/fragments/StepsFragment.java index d0f98f2452..b3e06cdff7 100644 --- a/app/src/main/java/org/stepic/droid/view/fragments/StepsFragment.java +++ b/app/src/main/java/org/stepic/droid/view/fragments/StepsFragment.java @@ -21,7 +21,7 @@ import org.stepic.droid.R; import org.stepic.droid.base.FragmentBase; -import org.stepic.droid.concurrency.FromDbStepTask; +import org.stepic.droid.concurrency.tasks.FromDbStepTask; import org.stepic.droid.events.steps.FailLoadStepEvent; import org.stepic.droid.events.steps.FromDbStepEvent; import org.stepic.droid.events.steps.SuccessLoadStepEvent; @@ -34,6 +34,7 @@ import org.stepic.droid.model.Progress; import org.stepic.droid.model.Step; import org.stepic.droid.model.Unit; +import org.stepic.droid.model.VideoUrl; import org.stepic.droid.util.AppConstants; import org.stepic.droid.util.ProgressHelper; import org.stepic.droid.util.ProgressUtil; @@ -182,7 +183,31 @@ protected String doInBackground(Void... params) { CachedVideo video = mDatabaseFacade.getCachedVideoById(step.getBlock().getVideo().getId()); String quality; if (video == null) { - quality = mUserPreferences.getQualityVideo(); + String resultQuality = mUserPreferences.getQualityVideo(); + try { + + int weWant = Integer.parseInt( mUserPreferences.getQualityVideo()); + final List urls = step.getBlock().getVideo().getUrls(); + int bestDelta = Integer.MAX_VALUE; + int bestIndex = 0; + for (int i = 0; i= 0 && mStepAdapter != null) { + TabLayout.Tab tab = mTabLayout.getTabAt(i); + if (tab != null) { + tab.setIcon(mStepAdapter.getTabDrawable(i)); + } + } } } diff --git a/app/src/main/java/org/stepic/droid/view/fragments/TextFeedbackFragment.kt b/app/src/main/java/org/stepic/droid/view/fragments/TextFeedbackFragment.kt index a6fb3b46f9..9291b3a211 100644 --- a/app/src/main/java/org/stepic/droid/view/fragments/TextFeedbackFragment.kt +++ b/app/src/main/java/org/stepic/droid/view/fragments/TextFeedbackFragment.kt @@ -19,7 +19,6 @@ import org.stepic.droid.events.feedback.FeedbackSentEvent import org.stepic.droid.util.ProgressHelper import org.stepic.droid.util.ValidatorUtil import org.stepic.droid.view.custom.LoadingProgressDialog -import retrofit.BaseUrl import retrofit.Callback import retrofit.Response import retrofit.Retrofit @@ -40,7 +39,7 @@ class TextFeedbackFragment : FragmentBase() { lateinit var mEmailEditText: EditText lateinit var mDescriptionEditTex: EditText lateinit var mSendButton: Button - var mProgressDialog : ProgressDialog? = null + var mProgressDialog: ProgressDialog? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -81,8 +80,9 @@ class TextFeedbackFragment : FragmentBase() { if (i == EditorInfo.IME_ACTION_SEND) { sendFeedback() true + } else { + false } - false } val primaryEmail = mUserPreferences.primaryEmail?.email primaryEmail?.let { mEmailEditText.setText(primaryEmail) } @@ -111,12 +111,11 @@ class TextFeedbackFragment : FragmentBase() { } fun sendFeedback() { - //todo implement hideSoftKeypad() val email = mEmailEditText.text.toString() val description = mDescriptionEditTex.text.toString() if (email.isEmpty() || description.isEmpty()) { - Toast.makeText(context, "Заполните поля", Toast.LENGTH_SHORT).show() + Toast.makeText(context, R.string.feedback_fill_fields, Toast.LENGTH_SHORT).show() return } if (!ValidatorUtil.isEmailValid(email)) { diff --git a/app/src/main/java/org/stepic/droid/view/fragments/VideoFragment.kt b/app/src/main/java/org/stepic/droid/view/fragments/VideoFragment.kt new file mode 100644 index 0000000000..233d120951 --- /dev/null +++ b/app/src/main/java/org/stepic/droid/view/fragments/VideoFragment.kt @@ -0,0 +1,924 @@ +package org.stepic.droid.view.fragments + +import android.app.KeyguardManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.ActivityInfo +import android.content.res.Configuration +import android.media.AudioManager +import android.net.Uri +import android.os.Bundle +import android.support.v7.widget.AppCompatSeekBar +import android.telephony.PhoneStateListener +import android.telephony.TelephonyManager +import android.view.* +import android.widget.* +import com.squareup.otto.Subscribe +import com.yandex.metrica.YandexMetrica +import org.stepic.droid.R +import org.stepic.droid.base.FragmentBase +import org.stepic.droid.base.MainApplication +import org.stepic.droid.core.MyStatePhoneListener +import org.stepic.droid.events.IncomingCallEvent +import org.stepic.droid.events.audio.AudioFocusLossEvent +import org.stepic.droid.preferences.VideoPlaybackRate +import org.stepic.droid.util.AndroidDevices +import org.stepic.droid.util.TimeUtil +import org.stepic.droid.view.custom.TouchDispatchableFrameLayout +import org.videolan.libvlc.IVLCVout +import org.videolan.libvlc.LibVLC +import org.videolan.libvlc.Media +import org.videolan.libvlc.MediaPlayer +import org.videolan.libvlc.util.AndroidUtil +import java.io.File +import java.util.* + +class VideoFragment : FragmentBase(), LibVLC.HardwareAccelerationError, IVLCVout.Callback { + companion object { + private val TIMEOUT_BEFORE_HIDE = 4500L + private val INDEX_PLAY_IMAGE = 0 + private val INDEX_PAUSE_IMAGE = 1 + private val JUMP_TIME_MILLIS = 10000L + private val JUMP_MAX_DELTA = 3000L + private val VIDEO_KEY = "video_key" + private val DELTA_TIME = 0L + private val TAG = "video player: " + fun newInstance(videoUri: String): VideoFragment { + val args = Bundle() + args.putString(VIDEO_KEY, videoUri) + val fragment = VideoFragment() + fragment.arguments = args + return fragment + } + } + + val myStatePhoneListener = MyStatePhoneListener() + val tmgr = MainApplication.getAppContext().getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager + var mSurfaceFrame: FrameLayout? = null + var mFragmentContainer: ViewGroup? = null + var mVideoView: SurfaceView? = null; + var mFilePath: String? = null; + var libvlc: LibVLC? = null + var mMediaPlayer: MediaPlayer? = null + var mVideoWidth: Int = 0 + var mVideoHeight: Int = 0 + private val mPlayerListener: MyPlayerListener = MyPlayerListener(this) + var mMaxTimeInMillis: Long? = null + var mCurrentTimeInMillis: Long = 0L + var mProgressBar: ProgressBar? = null + + var isSeekBarDragging: Boolean = false + + //Controller: + var mController: TouchDispatchableFrameLayout? = null + var mPlayerSeekBar: AppCompatSeekBar? = null + var mCurrentTime: TextView? = null + var mMaxTime: TextView? = null + var mPlayPauseSwitcher: ImageSwitcher? = null + var mPlayImageView: ImageView? = null + var mPauseImageView: ImageView? = null + var mJumpForwardImageView: ImageView? = null + var mJumpBackwardImageView: ImageView? = null + var mVideoRateChooser: ImageView? = null + private var mSlashTime: TextView? = null + var isControllerVisible = true + + var isEndReached = false + + var isOnStartAfterSurfaceDestroyed = false + + private var mVideoVisibleHeight: Int = 0 + private var mVideoVisibleWidth: Int = 0 + private var mSarNum: Int = 0 + private var mSarDen: Int = 0 + private var isOnResumeDirectlyAfterOnCreate = true + + private var isLoading: Boolean = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + retainInstance = true + mFilePath = arguments.getString(VIDEO_KEY) + initPhoneStateListener() + isOnResumeDirectlyAfterOnCreate = true + } + + + private val mReceiver: BroadcastReceiver = MyBroadcastReceiver(this) + + private class MyBroadcastReceiver(owner: VideoFragment) : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + when (intent?.action) { + AudioManager.ACTION_AUDIO_BECOMING_NOISY -> + mOwner?.pausePlayer() + } + + } + + private var mOwner: VideoFragment? + + init { + mOwner = owner + } + } + + + override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { + mFragmentContainer = inflater?.inflate(R.layout.fragment_video, container, false) as ViewGroup + + mProgressBar = mFragmentContainer?.findViewById(R.id.load_progressbar) as ProgressBar + mProgressBar?.visibility = View.VISIBLE + + mSurfaceFrame = mFragmentContainer?.findViewById(R.id.player_surface_frame) as FrameLayout + mVideoView = mFragmentContainer?.findViewById(R.id.texture_video_view) as SurfaceView + mFragmentContainer?.setOnTouchListener { view, motionEvent -> + if (!isLoading) { + showController(!isControllerVisible) + } + false + } + setupController(mFragmentContainer) + isOnStartAfterSurfaceDestroyed = false + + var filter = IntentFilter() + filter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY) + activity.registerReceiver(mReceiver, filter) + startLoading() + return mFragmentContainer + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + hideNavigationBar(false) + activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR + } + + private fun bindViewWithPlayer() { + val vout = mMediaPlayer?.vlcVout + vout?.setVideoView(mVideoView) + vout?.addCallback(this) + vout?.attachViews() + // + mMediaPlayer?.setEventListener(mPlayerListener) + + mPlayPauseSwitcher?.isClickable = true + + } + + private fun createPlayer() { + try { + val options = ArrayList() + + options.add("--audio-time-stretch") // time stretching + options.add("--no-drop-late-frames") //help when user accelerates video + options.add("--no-skip-frames") + // //Magic commands: HW_ACCELERATION_FULL + // val decoder = HWDecoderUtil.getDecoderFromDevice() + // if (decoder == HWDecoderUtil.Decoder.NONE) { + // options.add("--codec=all") + // } else { + // + // /* + // * Set higher caching values if using iomx decoding, since some omx + // * decoders have a very high latency, and if the preroll data isn't + // * enough to make the decoder output a frame, the playback timing gets + // * started too soon, and every decoded frame appears to be too late. + // * On Nexus One, the decoder latency seems to be 25 input packets + // * for 320x170 H.264, a few packets less on higher resolutions. + // * On Nexus S, the decoder latency seems to be about 7 packets. + // */ + // options.add("--file-caching=1500") + // options.add("--network-caching=1500") + // + // val sb = StringBuilder("--codec=") + // if (decoder == HWDecoderUtil.Decoder.MEDIACODEC) + // sb.append(if (AndroidUtil.isLolliPopOrLater()) "mediacodec_ndk" else "mediacodec_jni").append(",") + // else if (decoder == HWDecoderUtil.Decoder.OMX) + // sb.append("iomx,") + // else + // sb.append(if (AndroidUtil.isLolliPopOrLater()) "mediacodec_ndk" else "mediacodec_jni").append(",iomx,") + // sb.append("all") + // + // options.add(sb.toString()) + // } + // //end magic + + libvlc = LibVLC(options) + libvlc?.setOnHardwareAccelerationError(this) + + // Create media player + mMediaPlayer = MediaPlayer(libvlc) + // mPlayerListener = MyPlayerListener(this) + // mMediaPlayer?.setEventListener(mPlayerListener) + + // Set up video output + // val vout = mMediaPlayer?.getVLCVout() + // vout?.setVideoView(mVideoView) + // vout?.addCallback(this) + // vout?.attachViews() + + val file = File (mFilePath) + var uri: Uri? + if (file.exists()) { + uri = Uri.fromFile(file) + } else { + uri = Uri.parse(mFilePath) + } + + val media = Media(libvlc, uri) + // mMedia?.setHWDecoderEnabled(true, true) + mMediaPlayer?.media = media + media.release() + + mMediaPlayer?.rate = mUserPreferences.videoPlaybackRate.rateFloat + isEndReached = false + } catch (e: Exception) { + YandexMetrica.reportEvent(TAG + "Error creating player") + } + + } + + private fun releasePlayer() { + mMediaPlayer?.stop() + val vout = mMediaPlayer?.vlcVout + vout?.removeCallback(this) + vout?.detachViews() + // mVideoViewHolder = null + mMediaPlayer?.setEventListener(null) + libvlc?.release() + libvlc?.setOnHardwareAccelerationError (null) + libvlc = null + mMediaPlayer?.release() + mMediaPlayer = null + mVideoWidth = 0 + mVideoHeight = 0 + } + + override fun eventHardwareAccelerationError() { + //throw UnsupportedOperationException() + YandexMetrica.reportEvent(TAG + "vlc error hardware") + recreateAndPreloadPlayer() + } + + override fun onStart() { + super.onStart() + } + + + override fun onResume() { + super.onResume() + bus.register(this) + if (!isLoading) { + showController(true) + } + recreateAndPreloadPlayer(isNeedPlayAfterRecreating = false) + } + + fun recreateAndPreloadPlayer(isNeedPlayAfterRecreating: Boolean = true) { + needPlay = isNeedPlayAfterRecreating + val km = MainApplication.getAppContext().getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + createPlayer() + bindViewWithPlayer() + if (isOnResumeDirectlyAfterOnCreate) { + isOnResumeDirectlyAfterOnCreate = false + mMediaPlayer?.setEventListener(mPlayerListener) + + } else { + mMediaPlayer?.setEventListener(preRollListener) + + } + if (!km.inKeyguardRestrictedInputMode()) { + if (!needPlay && !isEndReached) { + startLoading() + } + mMediaPlayer?.play() + mMediaPlayer?.time = mCurrentTimeInMillis + mPlayerSeekBar?.let { + if (!isSeekBarDragging) { + val max = it.max + var positionByHand = 0f + if (mMaxTimeInMillis != null) { + val maxTime = mMaxTimeInMillis ?: 1L + positionByHand = (mCurrentTimeInMillis.toFloat() / maxTime.toFloat()).toFloat() + + } + if (positionByHand > max) { + positionByHand = 0f + } + + it.progress = (max.toFloat() * positionByHand).toInt() + } + } + } + + } + + override fun onPause() { + super.onPause() + + stopPlayingBeforeRecreating() + + clearAutoHideQueue() + mAudioFocusHelper.releaseAudioFocus() + bus.unregister(this) + } + + fun stopPlayingBeforeRecreating() { + showPlay() // because callback not working here + val player = mMediaPlayer + if (player == null || player.isReleased) { + mCurrentTimeInMillis = 0L + } else if (player.time >= 0) { + mCurrentTimeInMillis = (mMediaPlayer?.time ?: 0L) - DELTA_TIME + } + if (mCurrentTimeInMillis < 0L) mCurrentTimeInMillis = 0L + pausePlayer() + mMediaPlayer?.setEventListener(null) + releasePlayer() + } + + override fun onDestroyView() { + destroyVideoView() + destroyController() + activity?.unregisterReceiver(mReceiver) + super.onDestroyView() + } + + override fun onDestroy() { + removePhoneStateCallbacks() + super.onDestroy() + } + + override fun onSurfacesCreated(vlcOut: IVLCVout?) { + } + + override fun onConfigurationChanged(newConfig: Configuration?) { + if (isEndReached) { + recreateAndPreloadPlayer(isNeedPlayAfterRecreating = false) + } + + changeSurfaceLayout() + super.onConfigurationChanged(newConfig) + } + + override fun onSurfacesDestroyed(vlcOut: IVLCVout?) { + isOnStartAfterSurfaceDestroyed = true + } + + override fun onNewLayout(vout: IVLCVout, width: Int, height: Int, visibleWidth: Int, visibleHeight: Int, sarNum: Int, sarDen: Int) { + if (width * height == 0) + return + // store video size + mVideoWidth = width + mVideoHeight = height + mVideoVisibleWidth = visibleWidth + mVideoVisibleHeight = visibleHeight + mSarNum = sarNum + mSarDen = sarDen + changeSurfaceLayout() + } + + private fun changeSurfaceLayout() { + if (mVideoView == null) + return + + // get screen size + var w = activity.window.decorView.width.toDouble() + var h = activity.window.decorView.height.toDouble() + + mMediaPlayer?.vlcVout?.setWindowSize(w.toInt(), h.toInt()) + + // getWindow().getDecorView() doesn't always take orientation into + // account, we have to correct the values + val isPortrait = resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT + if (w > h && isPortrait || w < h && !isPortrait) { + val i = w + w = h + h = i + } + + // sanity check + if (w * h == 0.toDouble() || mVideoWidth * mVideoHeight == 0) { + YandexMetrica.reportEvent(TAG + "Invalid surface size") + return + } + + + // compute the aspect ratio + var ar: Double + // val vw: Double + if (mSarDen == mSarNum) { + /* No indication about the density, assuming 1:1 */ + // vw = mVideoVisibleWidth.toDouble() + // ar = mVideoVisibleWidth.toDouble() / mVideoVisibleHeight.toDouble() + } else { + /* Use the specified aspect ratio */ + // vw = mVideoVisibleWidth * mSarNum.toDouble() / mSarDen + // ar = vw / mVideoVisibleHeight + } + // compute the display aspect ratio + val dar = w / h + + // //optimize view (ORIGINAL) + // h = mVideoVisibleHeight.toDouble() + // w = vw + + ar = 16.0 / 9.0 + if (dar < ar) + h = w / ar + else + w = h * ar + + var lp: ViewGroup.LayoutParams = mVideoView!!.layoutParams + lp.width = Math.ceil(w.toDouble() * mVideoWidth / mVideoVisibleWidth).toInt() + lp.height = Math.ceil(h.toDouble() * mVideoHeight / mVideoVisibleHeight).toInt() + mVideoView!!.layoutParams = lp + + + + // set frame size (crop if necessary) + lp = mSurfaceFrame!!.layoutParams + lp.width = Math.floor(w.toDouble()).toInt() + lp.height = Math.floor(h.toDouble()).toInt() + mSurfaceFrame!!.layoutParams = lp + + mVideoView?.invalidate() + + } + + private fun setupController(inflatingView: View?) { + inflatingView?.let { + mController = it.findViewById(R.id.player_controller) as TouchDispatchableFrameLayout + mController?.setParentTouchEvent { + showController(true) + } + + if (mController == null) throw RuntimeException() + mController?.setOnTouchListener { view, motionEvent -> + true + } + + mPlayPauseSwitcher = mController?.findViewById(R.id.play_pause_switcher) as ImageSwitcher + mPauseImageView = mController?.findViewById(R.id.pause_image_view) as ImageView + mPlayImageView = mController?.findViewById(R.id.play_image_view) as ImageView + mPlayPauseSwitcher?.setOnClickListener { + onPlayPause() + } + + mMediaPlayer?.let { + if (!it.isReleased) { + if (it.isPlaying) { + showPause() + } else { + showPlay() + } + } + } + + mJumpForwardImageView = mController?.findViewById(R.id.jump_forward_button) as ImageView + mJumpBackwardImageView = mController?.findViewById(R.id.jump_back_button) as ImageView + + mJumpForwardImageView?.setOnClickListener { + onJumpForward() + } + + mJumpBackwardImageView?.setOnClickListener { + onJumpBackward() + } + + mVideoRateChooser = mController?.findViewById(R.id.rate_chooser) as ImageView + mVideoRateChooser?.setImageDrawable(mUserPreferences.videoPlaybackRate.icon) + mVideoRateChooser?.setOnClickListener { + showChooseRateMenu(it) + } + + initSeekBar(mController) + mSlashTime = mController?.findViewById(R.id.slash_video_time) as TextView + mCurrentTime = mController?.findViewById(R.id.current_video_time) as TextView + mMaxTime = mController?.findViewById(R.id.overall_video_time) as TextView + } + } + + private fun onPlayPause() { + val index = mPlayPauseSwitcher?.displayedChild + when (index) { + INDEX_PLAY_IMAGE -> { + if (!(mMediaPlayer?.isPlaying ?: true)) { + playPlayer() //double checking here =( + } else if (mMediaPlayer == null) { + mPlayPauseSwitcher?.isClickable = false + createPlayer() + bindViewWithPlayer() + playPlayer() + mPlayPauseSwitcher?.showNext() + } + } + INDEX_PAUSE_IMAGE -> { + pausePlayer() + } + } + } + + private var mHideRunnable: Runnable? = null + + private fun autoHideController(timeout: Long = TIMEOUT_BEFORE_HIDE) { + val view = mController + mHideRunnable?.let { + view?.removeCallbacks(it) + } + if (timeout >= 0) { + mHideRunnable = Runnable { + mController?.let { + showController(false) + } + } + view?.postDelayed(mHideRunnable, timeout) + } + } + + private fun clearAutoHideQueue() { + val view = mController + mHideRunnable?.let { + view?.removeCallbacks(it) + } + } + + private fun showChooseRateMenu(view: View) { + YandexMetrica.reportEvent(TAG + "showChooseRateMenu") + val popupMenu = PopupMenu(MainApplication.getAppContext(), view) + popupMenu.inflate(R.menu.video_rate_menu) + popupMenu.setOnMenuItemClickListener { + when (it.itemId) { + R.id.x0_5 -> { + handleRate(VideoPlaybackRate.x0_5) + true + } + + R.id.x0_75 -> { + handleRate(VideoPlaybackRate.x0_75) + true + } + + R.id.x1 -> { + handleRate(VideoPlaybackRate.x1_0) + true + } + + R.id.x1_25 -> { + handleRate(VideoPlaybackRate.x1_25) + true + } + + R.id.x1_5 -> { + handleRate(VideoPlaybackRate.x1_5) + true + } + R.id.x2 -> { + handleRate(VideoPlaybackRate.x2) + true + } + + else -> { + false + } + } + } + popupMenu.show() + } + + private fun handleRate(rate: VideoPlaybackRate) { + mVideoRateChooser?.setImageDrawable(rate.icon) + mMediaPlayer?.rate = rate.rateFloat + mUserPreferences.videoPlaybackRate = rate + } + + private fun onJumpForward() { + YandexMetrica.reportEvent(TAG + "onJumpForward") + var currentTime = mMediaPlayer?.time + if (currentTime == 0L && mCurrentTimeInMillis > 0L) { + currentTime = mCurrentTimeInMillis + } + val maxTime = mMaxTimeInMillis + if (currentTime != null && maxTime != null && maxTime != 0L) { + val newTime: Long = Math.min(currentTime + JUMP_TIME_MILLIS, maxTime - JUMP_MAX_DELTA) + + val positionByHand = (newTime.toFloat() / maxTime.toFloat()).toFloat() + mPlayerSeekBar?.let { + it.progress = (it.max.toFloat() * positionByHand).toInt() + } + mCurrentTimeInMillis = newTime + + pausePlayer() + mMediaPlayer?.setEventListener(null) + releasePlayer() + recreateAndPreloadPlayer() + } + } + + private fun onJumpBackward() { + YandexMetrica.reportEvent(TAG + "onJumpBackward") + if (mMediaPlayer == null) { + val length: Long = mMaxTimeInMillis ?: 0L + mCurrentTimeInMillis = Math.max(0L, length - JUMP_TIME_MILLIS) + + pausePlayer() + mMediaPlayer?.setEventListener(null) + releasePlayer() + recreateAndPreloadPlayer() + + } else { + var currentTime = mMediaPlayer?.time ?: 0L + if (currentTime == 0L && mCurrentTimeInMillis > 0L) { + currentTime = mCurrentTimeInMillis + } + mCurrentTimeInMillis = Math.max(0L, currentTime.toLong() - JUMP_TIME_MILLIS) + + pausePlayer() + mMediaPlayer?.setEventListener(null) + releasePlayer() + recreateAndPreloadPlayer() + } + } + + private fun destroyVideoView() { + mVideoView = null + } + + private fun destroyController() { + mPlayPauseSwitcher?.setOnClickListener(null) + mJumpBackwardImageView?.setOnClickListener(null) + mJumpForwardImageView?.setOnClickListener(null) + mVideoRateChooser?.setOnClickListener(null) + mFragmentContainer?.setOnClickListener(null) + + mFragmentContainer = null + mVideoRateChooser = null + mJumpBackwardImageView = null + mJumpForwardImageView = null + mPlayerSeekBar = null + mCurrentTime = null + mMaxTime = null + mPlayPauseSwitcher = null + mPauseImageView = null + mPlayImageView = null + } + + private fun initSeekBar(view: View?) { + mPlayerSeekBar = view?.findViewById(R.id.player_controller_progress) as? AppCompatSeekBar + mPlayerSeekBar?.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + var newPosition = -1f + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + if (fromUser) { + if (mMediaPlayer?.isReleased ?: true) { + createPlayer() + bindViewWithPlayer() + } + newPosition = progress.toFloat() / seekBar.max.toFloat() + + } + } + + override fun onStartTrackingTouch(seekBar: SeekBar) { + isSeekBarDragging = true + } + + override fun onStopTrackingTouch(seekBar: SeekBar) { + if (newPosition >= 0 && mMediaPlayer?.isPlaying ?: false) { + val seekToTime: Float = mMaxTimeInMillis?.toFloat() ?: 0f + mCurrentTimeInMillis = (newPosition * seekToTime).toLong() + + pausePlayer() + mMediaPlayer?.setEventListener(null) + releasePlayer() + recreateAndPreloadPlayer() + newPosition = -1f + } + isSeekBarDragging = false + } + }) + } + + private class MyPlayerListener(owner: VideoFragment) : MediaPlayer.EventListener { + private var mOwner: VideoFragment? + + init { + mOwner = owner + } + + override fun onEvent(event: MediaPlayer.Event) { + val player = mOwner?.mMediaPlayer + when (event.type) { + MediaPlayer.Event.Paused -> { + mOwner?.showPlay() + } + MediaPlayer.Event.Playing -> { + mOwner?.stopLoading() + if (player?.isPlaying ?: false) { + mOwner?.showPause() + player?.length?.let { + mOwner?.mSlashTime?.visibility = View.VISIBLE + mOwner?.mMaxTimeInMillis = it + mOwner?.mMaxTime?.text = TimeUtil.getFormattedVideoTime(it) + } + } + } + MediaPlayer.Event.EndReached -> { + mOwner?.isEndReached = true + mOwner?.showController(true) + mOwner?.showPlay() + player?.length?.let { + mOwner?.mCurrentTime?.text = TimeUtil.getFormattedVideoTime(it) + } + mOwner?.mPlayerSeekBar?.let { + if (!(mOwner?.isSeekBarDragging ?: false)) { + val max = it.max + it.progress = max + } + } + mOwner?.releasePlayer() + } + MediaPlayer.Event.PositionChanged -> { + val currentPos = player?.position + currentPos?.let { + mOwner?.mPlayerSeekBar?.let { + if (!(mOwner?.isSeekBarDragging ?: false)) { + val max = it.max + it.progress = (max.toFloat() * currentPos).toInt() + } + } + } + } + MediaPlayer.Event.TimeChanged -> { + player?.time?.let { + mOwner?.mCurrentTime?.text = + TimeUtil.getFormattedVideoTime(it) + } + } + } + } + } + + private fun showPlay() { + if (mPlayPauseSwitcher?.displayedChild == INDEX_PAUSE_IMAGE) { + mPlayPauseSwitcher?.showNext() + } + } + + private fun showPause() { + if (mPlayPauseSwitcher?.displayedChild == INDEX_PLAY_IMAGE) { + mPlayPauseSwitcher?.showNext() + } + } + + private fun pausePlayer() { + if (mMediaPlayer?.isPlaying ?: false) { + showController(true, isInfiniteShow = true) + mFragmentContainer?.keepScreenOn = false + mMediaPlayer?.pause() + mAudioFocusHelper.releaseAudioFocus() + } + } + + private val preRollListener = PreRollListener(this) + private var needPlay = false + + private class PreRollListener(owner: VideoFragment) : MediaPlayer.EventListener { + private var mOwner: VideoFragment? + + init { + mOwner = owner + } + + override fun onEvent(event: MediaPlayer.Event) { + val player = mOwner?.mMediaPlayer + when (event.type) { + MediaPlayer.Event.Playing -> { + //mOwner?.pausePlayer()//it is not need, because we do not want change button + player?.pause() + player?.setEventListener (mOwner?.mPlayerListener) + mOwner?.stopLoading() + player?.length?.let { + mOwner?.mSlashTime?.visibility = View.VISIBLE + mOwner?.mMaxTimeInMillis = it + mOwner?.mMaxTime?.text = TimeUtil.getFormattedVideoTime(it) + mOwner?.mCurrentTime?.text = TimeUtil.getFormattedVideoTime(mOwner?.mCurrentTimeInMillis ?: 0L) + player.time = mOwner?.mCurrentTimeInMillis ?: 0L + } + if (mOwner?.needPlay ?: false) { + mOwner?.mFragmentContainer?.keepScreenOn = true + mOwner?.mAudioFocusHelper?.requestAudioFocus() + player?.play() + } + } + } + } + } + + private fun playPlayer() { + if (!(mMediaPlayer?.isPlaying ?: true)) { + mFragmentContainer?.keepScreenOn = true + mAudioFocusHelper.requestAudioFocus() + mMediaPlayer?.play() + } + } + + private fun hideNavigationBar(dim: Boolean = true) { + if (!AndroidUtil.isHoneycombOrLater()) + return + var visibility = 0 + var navbar = 0 + + if (AndroidUtil.isJellyBeanOrLater()) { + visibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + navbar = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + } + if (dim) { + activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + @Suppress("DEPRECATION") + if (AndroidUtil.isICSOrLater()) + navbar = navbar or View.SYSTEM_UI_FLAG_LOW_PROFILE + else + visibility = visibility or View.STATUS_BAR_HIDDEN + if (!AndroidDevices.hasCombBar()) { + navbar = navbar or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + if (AndroidUtil.isKitKatOrLater()) + visibility = visibility or View.SYSTEM_UI_FLAG_IMMERSIVE + if (AndroidUtil.isJellyBeanOrLater()) + visibility = visibility or View.SYSTEM_UI_FLAG_FULLSCREEN + } + } else { + activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + @Suppress("DEPRECATION") + if (AndroidUtil.isICSOrLater()) + visibility = visibility or View.SYSTEM_UI_FLAG_VISIBLE + else + visibility = visibility or View.STATUS_BAR_VISIBLE + } + + if (AndroidDevices.hasNavBar()) + visibility = visibility or navbar + activity?.window?.decorView?.systemUiVisibility = visibility + } + + private fun showController(needShow: Boolean, isInfiniteShow: Boolean = false) { + if (needShow) { + mController?.visibility = View.VISIBLE + hideNavigationBar(false) + + if (isEndReached || isInfiniteShow || !(mMediaPlayer?.isPlaying ?: false)) { + autoHideController(-1) + } else { + autoHideController() + } + isControllerVisible = true + } else { + mController?.visibility = View.GONE + hideNavigationBar(true) + isControllerVisible = false + } + } + + + @Subscribe + fun onIncomingCall(event: IncomingCallEvent) { + pausePlayer() + } + + fun initPhoneStateListener() { + try { + tmgr?.listen(myStatePhoneListener, PhoneStateListener.LISTEN_CALL_STATE) + } catch (ex: Exception) { + YandexMetrica.reportError("initPhoneStateListener", ex) + } + } + + fun removePhoneStateCallbacks() { + try { + tmgr?.listen(myStatePhoneListener, PhoneStateListener.LISTEN_NONE) + } catch(ex: Exception) { + YandexMetrica.reportError("removePhoneStateCallbacks", ex) + } + } + + + @Subscribe + fun onAudioFocusLoss(event: AudioFocusLossEvent) { + pausePlayer() + } + + fun startLoading() { + YandexMetrica.reportEvent(TAG + "startLoading") + isLoading = true + showController(false) + mProgressBar?.visibility = View.VISIBLE + } + + fun stopLoading() { + YandexMetrica.reportEvent(TAG + "stopLoading") + mProgressBar?.visibility = View.GONE + showController(true) + isLoading = false + } +} \ No newline at end of file diff --git a/app/src/main/java/org/stepic/droid/view/fragments/VideoStepFragment.java b/app/src/main/java/org/stepic/droid/view/fragments/VideoStepFragment.java index df72cb9a96..5c158229cc 100644 --- a/app/src/main/java/org/stepic/droid/view/fragments/VideoStepFragment.java +++ b/app/src/main/java/org/stepic/droid/view/fragments/VideoStepFragment.java @@ -1,6 +1,5 @@ package org.stepic.droid.view.fragments; -import android.content.Intent; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; @@ -163,18 +162,7 @@ private void setmThumbnail(String thumbnail) { @Subscribe public void onVideoResolved(VideoResolvedEvent e) { if (e.getStepId() != mStep.getId()) return; - Uri videoUri = Uri.parse(e.getPathToVideo()); - - Intent intent = new Intent(Intent.ACTION_VIEW, videoUri); - intent.setDataAndType(videoUri, "video/*"); - //todo change icon to play - try { - startActivity(intent); - } catch (Exception ex) { - YandexMetrica.reportError("NotPlayer", ex); - Toast.makeText(getContext(), R.string.not_video_player_error, Toast.LENGTH_LONG).show(); - } - + mShell.getScreenProvider().showVideo(getActivity(), e.getPathToVideo()); } } diff --git a/app/src/main/java/org/stepic/droid/web/HttpManager.java b/app/src/main/java/org/stepic/droid/web/HttpManager.java deleted file mode 100644 index 3d70cf128e..0000000000 --- a/app/src/main/java/org/stepic/droid/web/HttpManager.java +++ /dev/null @@ -1,167 +0,0 @@ -package org.stepic.droid.web; - -import android.content.Context; -import android.os.Bundle; - -import com.google.gson.JsonObject; -import com.squareup.okhttp.Authenticator; -import com.squareup.okhttp.Credentials; -import com.squareup.okhttp.MediaType; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.RequestBody; -import com.squareup.okhttp.Response; - -import org.jetbrains.annotations.Nullable; -import org.stepic.droid.base.MainApplication; -import org.stepic.droid.configuration.IConfig; -import org.stepic.droid.preferences.SharedPreferenceHelper; - -import java.io.IOException; -import java.net.Proxy; -import java.util.concurrent.TimeUnit; - -import javax.inject.Inject; -import javax.inject.Singleton; - -@Deprecated -@Singleton -public class HttpManager implements IHttpManager { - - private final static int CONNECTION_TIMEOUT = 5000; - private final static int READ_TIMEOUT = 5000; - - @Inject - IConfig mConfig; - - @Inject - SharedPreferenceHelper mSharedPreferencesHelper; - @Inject - Context mContext; - - OkHttpClient mOkHttpClient = new OkHttpClient(); - private static final MediaType DEFAULT_MEDIA_TYPE - = MediaType.parse("application/x-www-form-urlencoded; charset=utf-8"); - - private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8"); - - @Inject - public HttpManager(Context context) { - MainApplication.component(context).inject(this); - mOkHttpClient.setConnectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS); - mOkHttpClient.setReadTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS); - } - - @Override - public String post(String url, Bundle params) throws IOException { - - String query = makeQueryFromBundle(params); - setAuthenticatorClientIDAndPassword(); - - RequestBody body = RequestBody.create(DEFAULT_MEDIA_TYPE, query); - Request request = new Request.Builder() - .post(body) - .url(url) - .build(); - - Response response = mOkHttpClient.newCall(request).execute(); - - return response.body().string(); - } - - @Override - public Response postJson(String url, JsonObject jsonObject) throws IOException { - -// String credential = Credentials.basic(mConfig.getOAuthClientId(), mConfig.getOAuthClientSecret());//obsolete? - - String jsonStr = jsonObject.toString(); - RequestBody requestBody = RequestBody.create(JSON_MEDIA_TYPE, jsonStr); - Request request = new Request.Builder() - .url(url) - .post(requestBody) - .build(); - - return mOkHttpClient.newCall(request).execute(); - - } - - @Override - public Response postJson(String url, String jsonStr) throws IOException { - - RequestBody requestBody = RequestBody.create(JSON_MEDIA_TYPE, jsonStr); - Request request = new Request.Builder() - .header("Authorization", getAuthHeaderValue()) - .url(url) - .post(requestBody) - .build(); - - return mOkHttpClient.newCall(request).execute(); - } - - @Override - public String get(String baseUrl, @Nullable Bundle params) throws IOException { - - - String url; - if (params != null && !params.keySet().isEmpty()) - url = baseUrl + "?" + makeQueryFromBundle(params); - else - url = baseUrl; - - String authValue = getAuthHeaderValue(); - Request request = new Request.Builder() - .header("Authorization", authValue) - .url(url) - .get() - .build(); - - Response response = mOkHttpClient.newCall(request).execute(); - if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); - - return response.body().string(); - } - - - private String getAuthHeaderValue() { - AuthenticationStepicResponse resp = getAuthInfo(); - String access_token = resp.getAccess_token(); - String type = resp.getToken_type(); - return type + " " + access_token; - } - - private AuthenticationStepicResponse getAuthInfo() { - return mSharedPreferencesHelper.getAuthResponseFromStore(); - } - - private String makeQueryFromBundle(Bundle params) { - - StringBuilder queryMaker = new StringBuilder(); - int i = 0; - for (String key : params.keySet()) { - i++; - queryMaker.append(key); - queryMaker.append('='); - queryMaker.append(params.get(key)); - if (params.keySet().size() != i) { - //if not last element - queryMaker.append('&'); - } - } - return queryMaker.toString(); - } - - private void setAuthenticatorClientIDAndPassword() { - mOkHttpClient.setAuthenticator(new Authenticator() { - @Override - public Request authenticate(Proxy proxy, Response response) throws IOException { - String credential = Credentials.basic(mConfig.getOAuthClientId(IApi.TokenType.loginPassword), mConfig.getOAuthClientSecret(IApi.TokenType.loginPassword)); - return response.request().newBuilder().header("Authorization", credential).build(); - } - - @Override - public Request authenticateProxy(Proxy proxy, Response response) throws IOException { - return null; - } - }); - } -} diff --git a/app/src/main/java/org/stepic/droid/web/IHttpManager.java b/app/src/main/java/org/stepic/droid/web/IHttpManager.java deleted file mode 100644 index 168f05109d..0000000000 --- a/app/src/main/java/org/stepic/droid/web/IHttpManager.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.stepic.droid.web; - -import android.os.Bundle; - -import com.google.gson.JsonObject; -import com.squareup.okhttp.Response; - -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; - -@Deprecated -public interface IHttpManager { - String post(String url, Bundle params) throws IOException; - - //todo: change this architecture to universal post - Response postJson(String url, JsonObject jsonObject) throws IOException; - - Response postJson(String url, String jsonString) throws IOException; - - String get(String url, @Nullable Bundle params) throws IOException; -} diff --git a/app/src/main/java/org/stepic/droid/web/RetrofitRESTApi.java b/app/src/main/java/org/stepic/droid/web/RetrofitRESTApi.java index 228d9ea396..669faa9165 100644 --- a/app/src/main/java/org/stepic/droid/web/RetrofitRESTApi.java +++ b/app/src/main/java/org/stepic/droid/web/RetrofitRESTApi.java @@ -28,6 +28,7 @@ import org.stepic.droid.model.Course; import org.stepic.droid.model.DatasetWrapper; import org.stepic.droid.model.EnrollmentWrapper; +import org.stepic.droid.model.Profile; import org.stepic.droid.model.RegistrationUser; import org.stepic.droid.model.Reply; import org.stepic.droid.preferences.SharedPreferenceHelper; @@ -70,7 +71,7 @@ public class RetrofitRESTApi implements IApi { @Inject IConfig mConfig; @Inject - UserPreferences userPreferences; + UserPreferences mUserPreferences; private StepicRestLoggedService mLoggedService; private StepicRestOAuthService mOAuthService; @@ -364,7 +365,14 @@ public Call createNewSubmission(Reply reply, long attemptId) @Override public Call getExistingAttempts(long stepId) { - return mLoggedService.getExistingAttempts(stepId); + Profile profile = mSharedPreference.getProfile(); + long userId = 0; + if (profile == null){ + YandexMetrica.reportEvent("profile is null, when attempt"); + } else{ + userId = profile.getId(); + } + return mLoggedService.getExistingAttempts(stepId, userId); } @Override diff --git a/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java b/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java index 3fe3d58a9d..3e9b7a9d85 100644 --- a/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java +++ b/app/src/main/java/org/stepic/droid/web/StepicRestLoggedService.java @@ -72,7 +72,7 @@ Call getSearchResults(@Query("page") int page, Call createNewSubmission(@Body SubmissionRequest submissionRequest); @GET("api/attempts") - Call getExistingAttempts(@Query("step") long stepId); + Call getExistingAttempts(@Query("step") long stepId, @Query("user") long userId); @GET("api/submissions") Call getExistingSubmissions(@Query("attempt") long attemptId, @Query("order") String desc); diff --git a/app/src/main/res/drawable-hdpi/ic_forward_10_white_48dp.png b/app/src/main/res/drawable-hdpi/ic_forward_10_white_48dp.png new file mode 100644 index 0000000000..01632b69c2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_forward_10_white_48dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_pause_circle_outline_white_48dp.png b/app/src/main/res/drawable-hdpi/ic_pause_circle_outline_white_48dp.png new file mode 100644 index 0000000000..9be6ac154b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_pause_circle_outline_white_48dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_pause_circle_outline_white_72.png b/app/src/main/res/drawable-hdpi/ic_pause_circle_outline_white_72.png new file mode 100644 index 0000000000..4f85bda3c2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_pause_circle_outline_white_72.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_play_circle_outline_white_48dp.png b/app/src/main/res/drawable-hdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000000..6e1b578c54 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_play_circle_outline_white_72.png b/app/src/main/res/drawable-hdpi/ic_play_circle_outline_white_72.png new file mode 100644 index 0000000000..75715fe495 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_play_circle_outline_white_72.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_replay_10_white_48dp.png b/app/src/main/res/drawable-hdpi/ic_replay_10_white_48dp.png new file mode 100644 index 0000000000..2b0f59463f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_replay_10_white_48dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_forward_10_white_48dp.png b/app/src/main/res/drawable-mdpi/ic_forward_10_white_48dp.png new file mode 100644 index 0000000000..5c5998a73b Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_forward_10_white_48dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_pause_circle_outline_white_48dp.png b/app/src/main/res/drawable-mdpi/ic_pause_circle_outline_white_48dp.png new file mode 100644 index 0000000000..92e6431fd9 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_pause_circle_outline_white_48dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_pause_circle_outline_white_72.png b/app/src/main/res/drawable-mdpi/ic_pause_circle_outline_white_72.png new file mode 100644 index 0000000000..714b401a06 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_pause_circle_outline_white_72.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_play_circle_outline_white_48dp.png b/app/src/main/res/drawable-mdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000000..615b80d085 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_play_circle_outline_white_72.png b/app/src/main/res/drawable-mdpi/ic_play_circle_outline_white_72.png new file mode 100644 index 0000000000..0d4970e882 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_play_circle_outline_white_72.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_replay_10_white_48dp.png b/app/src/main/res/drawable-mdpi/ic_replay_10_white_48dp.png new file mode 100644 index 0000000000..2a192e29db Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_replay_10_white_48dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_forward_10_white_48dp.png b/app/src/main/res/drawable-xhdpi/ic_forward_10_white_48dp.png new file mode 100644 index 0000000000..2b9b7d695f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_forward_10_white_48dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_pause_circle_outline_white_48dp.png b/app/src/main/res/drawable-xhdpi/ic_pause_circle_outline_white_48dp.png new file mode 100644 index 0000000000..92b26908cd Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_pause_circle_outline_white_48dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_pause_circle_outline_white_72.png b/app/src/main/res/drawable-xhdpi/ic_pause_circle_outline_white_72.png new file mode 100644 index 0000000000..80d74d9fff Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_pause_circle_outline_white_72.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_play_circle_outline_white_48dp.png b/app/src/main/res/drawable-xhdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000000..516f643269 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_play_circle_outline_white_72.png b/app/src/main/res/drawable-xhdpi/ic_play_circle_outline_white_72.png new file mode 100644 index 0000000000..5a9854e067 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play_circle_outline_white_72.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_replay_10_white_48dp.png b/app/src/main/res/drawable-xhdpi/ic_replay_10_white_48dp.png new file mode 100644 index 0000000000..0da1241907 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_replay_10_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_forward_10_white_48dp.png b/app/src/main/res/drawable-xxhdpi/ic_forward_10_white_48dp.png new file mode 100644 index 0000000000..3ffc56e654 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_forward_10_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_pause_circle_outline_white_48dp.png b/app/src/main/res/drawable-xxhdpi/ic_pause_circle_outline_white_48dp.png new file mode 100644 index 0000000000..cf27e05221 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_pause_circle_outline_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_pause_circle_outline_white_72.png b/app/src/main/res/drawable-xxhdpi/ic_pause_circle_outline_white_72.png new file mode 100644 index 0000000000..c14642d1e5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_pause_circle_outline_white_72.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_play_circle_outline_white_48dp.png b/app/src/main/res/drawable-xxhdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000000..0311f899dd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_play_circle_outline_white_72.png b/app/src/main/res/drawable-xxhdpi/ic_play_circle_outline_white_72.png new file mode 100644 index 0000000000..f1de476407 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_play_circle_outline_white_72.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_replay_10_white_48dp.png b/app/src/main/res/drawable-xxhdpi/ic_replay_10_white_48dp.png new file mode 100644 index 0000000000..4c31ab13dd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_replay_10_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_forward_10_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_forward_10_white_48dp.png new file mode 100644 index 0000000000..114357c842 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_forward_10_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_pause_circle_outline_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_pause_circle_outline_white_48dp.png new file mode 100644 index 0000000000..2693f256d9 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_pause_circle_outline_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_pause_circle_outline_white_72.png b/app/src/main/res/drawable-xxxhdpi/ic_pause_circle_outline_white_72.png new file mode 100644 index 0000000000..bbc89fc6c8 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_pause_circle_outline_white_72.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_play_circle_outline_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_play_circle_outline_white_48dp.png new file mode 100644 index 0000000000..7a5a16858b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_play_circle_outline_white_48dp.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_play_circle_outline_white_72.png b/app/src/main/res/drawable-xxxhdpi/ic_play_circle_outline_white_72.png new file mode 100644 index 0000000000..4ddcb64cde Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_play_circle_outline_white_72.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_replay_10_white_48dp.png b/app/src/main/res/drawable-xxxhdpi/ic_replay_10_white_48dp.png new file mode 100644 index 0000000000..8761fe095e Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_replay_10_white_48dp.png differ diff --git a/app/src/main/res/drawable/ic_playbackrate_0_5_light.png b/app/src/main/res/drawable/ic_playbackrate_0_5_light.png new file mode 100644 index 0000000000..44789312d1 Binary files /dev/null and b/app/src/main/res/drawable/ic_playbackrate_0_5_light.png differ diff --git a/app/src/main/res/drawable/ic_playbackrate_0_75_light.png b/app/src/main/res/drawable/ic_playbackrate_0_75_light.png new file mode 100644 index 0000000000..6bcbd2619d Binary files /dev/null and b/app/src/main/res/drawable/ic_playbackrate_0_75_light.png differ diff --git a/app/src/main/res/drawable/ic_playbackrate_1_25_light.png b/app/src/main/res/drawable/ic_playbackrate_1_25_light.png new file mode 100644 index 0000000000..5f38595a16 Binary files /dev/null and b/app/src/main/res/drawable/ic_playbackrate_1_25_light.png differ diff --git a/app/src/main/res/drawable/ic_playbackrate_1_5_light.png b/app/src/main/res/drawable/ic_playbackrate_1_5_light.png new file mode 100644 index 0000000000..faff464e53 Binary files /dev/null and b/app/src/main/res/drawable/ic_playbackrate_1_5_light.png differ diff --git a/app/src/main/res/drawable/ic_playbackrate_1_light.png b/app/src/main/res/drawable/ic_playbackrate_1_light.png new file mode 100644 index 0000000000..99814c7439 Binary files /dev/null and b/app/src/main/res/drawable/ic_playbackrate_1_light.png differ diff --git a/app/src/main/res/drawable/ic_playbackrate_2_0_light.png b/app/src/main/res/drawable/ic_playbackrate_2_0_light.png new file mode 100644 index 0000000000..b4bed93025 Binary files /dev/null and b/app/src/main/res/drawable/ic_playbackrate_2_0_light.png differ diff --git a/app/src/main/res/drawable/ic_seek_background.png b/app/src/main/res/drawable/ic_seek_background.png new file mode 100755 index 0000000000..a333b0192b Binary files /dev/null and b/app/src/main/res/drawable/ic_seek_background.png differ diff --git a/app/src/main/res/drawable/ic_seek_progress.png b/app/src/main/res/drawable/ic_seek_progress.png new file mode 100755 index 0000000000..e0c6487c02 Binary files /dev/null and b/app/src/main/res/drawable/ic_seek_progress.png differ diff --git a/app/src/main/res/drawable/ic_seek_secondary.png b/app/src/main/res/drawable/ic_seek_secondary.png new file mode 100755 index 0000000000..a57f1782e6 Binary files /dev/null and b/app/src/main/res/drawable/ic_seek_secondary.png differ diff --git a/app/src/main/res/drawable/video_progressbar_layers.xml b/app/src/main/res/drawable/video_progressbar_layers.xml new file mode 100644 index 0000000000..a29f5e5c3f --- /dev/null +++ b/app/src/main/res/drawable/video_progressbar_layers.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/video_seekbar_thumb.xml b/app/src/main/res/drawable/video_seekbar_thumb.xml new file mode 100644 index 0000000000..5bc9ff922b --- /dev/null +++ b/app/src/main/res/drawable/video_seekbar_thumb.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index fb4b89d250..904227baaf 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -118,7 +118,6 @@ android:gravity="center" android:maxWidth="@dimen/min_max_width_for_tablets" android:text="@string/by_creating_you_agree" - android:textColor="@color/grey_redirected_txt" android:textSize="11sp" android:visibility="gone" /> @@ -130,7 +129,7 @@ android:gravity="center" android:linksClickable="true" android:text="@string/terms" - android:textColor="@color/cyan_text_navigation" + android:textColor="?attr/colorAccent" android:textSize="11sp" android:visibility="gone" /> diff --git a/app/src/main/res/layout/fragment_downloads.xml b/app/src/main/res/layout/fragment_downloads.xml index 9512049b2e..df4233512e 100644 --- a/app/src/main/res/layout/fragment_downloads.xml +++ b/app/src/main/res/layout/fragment_downloads.xml @@ -1,8 +1,8 @@ + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + android:scrollbarStyle="outsideOverlay"/> + + diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index c53cb3d8c7..3edaaae52a 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -1,96 +1,114 @@ - + - + android:layout_height="wrap_content"> - + android:layout_margin="5dp" + app:cardCornerRadius="2dp"> - + android:orientation="vertical"> - + - + - + android:contentDescription="@string/wi_fi_setting" + android:foreground="@drawable/non_radius_foreground" + android:padding="16dp" + android:text="@string/wi_fi_setting" + android:textColor="@color/stepic_regular_text" + android:textSize="16sp"/> - + - - - + android:orientation="vertical"> - - + - + + + - + + + + + + - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_video.xml b/app/src/main/res/layout/fragment_video.xml new file mode 100644 index 0000000000..f0b1c30fe2 --- /dev/null +++ b/app/src/main/res/layout/fragment_video.xml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/video_rate_menu.xml b/app/src/main/res/menu/video_rate_menu.xml new file mode 100644 index 0000000000..9c14fcbf03 --- /dev/null +++ b/app/src/main/res/menu/video_rate_menu.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index f25e4d1467..3d7440cdd6 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -109,6 +109,8 @@ Сообщение отправлено Упс… что-то пошло не так. Попробуйте позже Интернет недоступен. Попробуйте позже + Заполните пусты поля Вы не можете покинуть курс + Открывать в системном плеере \ No newline at end of file diff --git a/app/src/main/res/values-v19/styles.xml b/app/src/main/res/values-v19/styles.xml new file mode 100644 index 0000000000..d649883dc3 --- /dev/null +++ b/app/src/main/res/values-v19/styles.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml new file mode 100644 index 0000000000..88a61c0444 --- /dev/null +++ b/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 9c252ed851..cae84982a2 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,55 +3,14 @@ #FFFFFF - #E6FFFFFF - #B2FFFFFF - #33FFFFFF - #90000000 - #B2000000 - #CC000000 - #80000000 - #B3FFFFFF - #80FFFFFF - #4DFFFFFF - #CCCCCC - #3C4045 - #F47676 - #BA2E6E #E2E3E5 - #299ED7 - #7DC88F - #E5F4E9 - #65C89D - #BA2E6E - #B82669 - #CC3c4045 - #E6BA2E6E - #F1F1F2 - #E63E4247 - #3E4247 - #F6FCFF - #fd7575 - #3E4247 - #1F2124 - #CC1F2124 - #C3C3C3 - #3C4045 - #8A8C8F - #661F2124 - - - #c8c8c8 - #8c8e90 - #00FFFFFF #5E5E5E #A5A5A5 + #000000 - - #FF9900 - #66CC66 #329932 @@ -73,4 +32,5 @@ #D3D7D7 #D64338 #66CC66 + #4D000000 \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index e570a58712..dfa82d2fa5 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -30,5 +30,8 @@ 200dp 16sp 6dp + 8dp + 2dp + 18dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 91f5d1fefc..e925d38d9e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -111,6 +111,14 @@ Feedback is sent Sorry, service is not available. Try later Sorry, internet is not available. Try again later + Fill empty fields + / + x0.5 + x1.0 + x0.75 + x1.25 + x1.5 + x2.0 You can not drop course - + Open in external player diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 080ef442b1..55b013b0bb 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -16,6 +16,19 @@ @style/StepicButton + +