diff --git a/.gitignore b/.gitignore
index 5ecda83bd..e8751f9f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
# Gradle
.gradle/
build/
+release/
# Android Studio
.idea/
@@ -23,3 +24,6 @@ signing.properties
amahi-release-key.keystore
.DS*
+
+# Acra
+acra.properties
diff --git a/.travis.yml b/.travis.yml
index bb4eb1c6d..a2d460a82 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,12 +1,13 @@
language: android
before_install:
+ - yes | sdkmanager "platforms;android-29"
- yes | sdkmanager "platforms;android-28"
+ - yes | sdkmanager "platforms;android-19"
jdk:
- oraclejdk8
-sudo: required
android:
components:
- tools
@@ -14,7 +15,6 @@ android:
- platform-tools
- build-tools-28.0.3
- android-28
- - android-26
- android-19
- extra-android-support
- extra-android-m2repository
diff --git a/README.md b/README.md
index 9a9e7001b..c5a85eaa5 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,17 @@
-# Amahi Android App [](https://travis-ci.org/amahi/android)
+
Amahi Android App
+
+
+
+
+
+
+[](https://travis-ci.org/amahi/android)
+[](https://twitter.com/amahi)
+[](https://webchat.freenode.net/?channels=amahi)
This repository contains the source code for the Amahi Android app.
-You will need JDK 1.7+ installed to work with it. Gradle, Android SDK, and project dependencies will be downloaded automatically.
+You will need JDK 1.8+ installed to work with it. Gradle, Android SDK, and project dependencies will be downloaded automatically.
## Building the app
@@ -20,18 +29,26 @@ You will need JDK 1.7+ installed to work with it. Gradle, Android SDK, and proje
chromecast.app.id = APP_ID
```
-This is something to keep **private** and you obtain it by sending a message to `support at Amahi dot org`.
+This is something to keep **private** and you obtain it by sending a message to .
+2. Set ACRA information
-2. Build the application using a command line or using GUI.
+ ```
+ $ vim acra.properties
+ ```
+ ```
+ mailto.email = myemail@mydomain.com
+ ```
+
+Enter your personal email in place of `myemail@mydomain.com`
+
+3. Build the application using a command line or using GUI.
```
$ ./gradlew clean assembleDebug
-
```
-
-3. Once you have built the application, you will be needing credentials to use the application. Go to Amahi website and create your account, once the account is activated, you will see the description on how to set up the Amahi server. However, this is not strictly needed, as we have a set up of a demo server called "Welcome to Amahi" which you should see even without your own server installed.
+4. Once you have built the application, you will be needing credentials to use the application. Go to Amahi website and create your account, once the account is activated, you will see the description on how to set up the Amahi server. However, this is not strictly needed, as we have a set up of a demo server called "Welcome to Amahi" which you should see even without your own server installed.
## Code Style Convention
diff --git a/acra.properties.sample b/acra.properties.sample
new file mode 100644
index 000000000..1cbfc975a
--- /dev/null
+++ b/acra.properties.sample
@@ -0,0 +1,4 @@
+# Set ACRA mailing information in acra.properties.
+# This is something to keep private and can be created by following steps in README.md
+
+mailto.email = myemail@mydomain.com
diff --git a/build.gradle b/build.gradle
index b7c5f9285..edc93039e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,8 +6,8 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.4.1'
- classpath 'io.fabric.tools:gradle:1.26.1'
+ classpath 'com.android.tools.build:gradle:4.0.1'
+ classpath 'io.fabric.tools:gradle:1.28.1'
}
}
@@ -52,10 +52,13 @@ android {
targetSdkVersion 28
def apiFile = file("api.properties")
+ def acraFile = file("acra.properties")
def apiProperties = new Properties()
+ def acraProperties = new Properties()
- // NOTE-cpg: this fake api is here for the tests to pass
+ // NOTE-cpg: this fakeApi and fakeAcra is here for the tests to pass
def fakeApiFile = file("fakeApi.properties")
+ def fakeAcraFile = file("fakeAcra.properties")
if (apiFile.exists()) {
apiProperties.load(apiFile.newInputStream())
@@ -63,11 +66,20 @@ android {
apiProperties.load(fakeApiFile.newInputStream())
}
+ if (acraFile.exists()) {
+ acraProperties.load(acraFile.newInputStream())
+ } else {
+ acraProperties.load(fakeAcraFile.newInputStream())
+ }
+
buildConfigField "String", "API_URL_AMAHI", formatStringField(apiProperties["url.amahi"])
buildConfigField "String", "API_URL_PROXY", formatStringField(apiProperties["url.proxy"])
buildConfigField "String", "API_CLIENT_ID", formatStringField(apiProperties["client.id"])
buildConfigField "String", "API_CLIENT_SECRET", formatStringField(apiProperties["client.secret"])
buildConfigField "String", "CHROMECAST_APP_ID", formatStringField(apiProperties["chromecast.app.id"])
+ buildConfigField "String", "ACRA_EMAIL", formatStringField(acraProperties["mailto.email"])
+
+ multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -88,8 +100,8 @@ android {
}
}
compileOptions {
- targetCompatibility 1.8
- sourceCompatibility 1.8
+ targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility JavaVersion.VERSION_1_8
}
}
@@ -147,7 +159,6 @@ dependencies {
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.8.0'
-
implementation 'org.videolan:libvlc:2.1.1'
implementation 'pub.devrel:easypermissions:3.0.0'
testImplementation 'org.robolectric:robolectric:3.1.2'
@@ -163,14 +174,21 @@ dependencies {
compileOnly 'com.squareup.dagger:dagger-compiler:1.2.5'
annotationProcessor 'com.squareup.dagger:dagger-compiler:1.2.5'
-
implementation 'com.github.wseemann:FFmpegMediaMetadataRetriever:1.0.14'
- implementation 'com.google.android.exoplayer:exoplayer-core:2.7.3'
- implementation 'com.google.android.exoplayer:exoplayer-ui:2.7.3'
+
+ def EXOPLAYER_VERSION = "2.9.0"
+ implementation "com.google.android.exoplayer:exoplayer-core:${EXOPLAYER_VERSION}"
+ implementation "com.google.android.exoplayer:exoplayer-ui:${EXOPLAYER_VERSION}"
implementation 'android.arch.persistence.room:runtime:' + rootProject.archRoomVersion
annotationProcessor 'android.arch.persistence.room:compiler:' + rootProject.archRoomVersion
+ def acraVersion = '5.6.1'
+ implementation "ch.acra:acra-mail:$acraVersion"
+ implementation "ch.acra:acra-dialog:$acraVersion"
+ implementation "ch.acra:acra-notification:$acraVersion"
+ implementation "ch.acra:acra-toast:$acraVersion"
+
// Uncomment the dependencies below to enable Chuck Interceptor for logging
/*
debugImplementation 'com.readystatesoftware.chuck:library:1.1.0'
diff --git a/fakeAcra.properties b/fakeAcra.properties
new file mode 100644
index 000000000..eaeda3d7c
--- /dev/null
+++ b/fakeAcra.properties
@@ -0,0 +1,5 @@
+# Set ACRA mailing information in acra.properties.
+# This is something to keep private and can be created by following steps in README.md
+# Purpose: This file is required for Tests and Travis checks to pass
+
+mailto.email = myemail@mydomain.com
diff --git a/fakeApi.properties b/fakeApi.properties
index 5d0473ebe..b0f2d2482 100644
--- a/fakeApi.properties
+++ b/fakeApi.properties
@@ -1,6 +1,8 @@
# Set API information in api.properties.
# This is something to keep private and you obtain it by asking in the Amahi IRC channel or
# send a message to support at Amahi dot org
+# Purpose: This file is required for Tests and Travis checks to pass
+
url.amahi=https://amahi.org
url.proxy=https://amahi.org
client.id=1234567890
diff --git a/fakeSigning.properties b/fakeSigning.properties
index 86d6879d8..5bb671b88 100644
--- a/fakeSigning.properties
+++ b/fakeSigning.properties
@@ -1,6 +1,8 @@
# Set API information in api.properties.
# This is something to keep private and you obtain it by asking in the Amahi IRC channel or
# send a message to support at Amahi dot org
+# Purpose: This file is required for Tests and Travis checks to pass
+
keystore.file=debug.keystore
keystore.password=android
key.alias=androiddebugkey
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index fe7c2b518..ea4655882 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sun Jun 09 01:34:57 IST 2019
+#Sat Jul 18 05:49:07 IST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index fd718f4bf..d6626655c 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -33,6 +33,7 @@
+
diff --git a/src/main/java/org/amahi/anywhere/AmahiApplication.java b/src/main/java/org/amahi/anywhere/AmahiApplication.java
index f3446dd94..41e4b3845 100644
--- a/src/main/java/org/amahi/anywhere/AmahiApplication.java
+++ b/src/main/java/org/amahi/anywhere/AmahiApplication.java
@@ -27,13 +27,21 @@
import android.os.Build;
import android.os.StrictMode;
import android.preference.PreferenceManager;
+
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatDelegate;
import com.crashlytics.android.Crashlytics;
+import org.acra.ACRA;
+import org.acra.ReportField;
+import org.acra.config.CoreConfigurationBuilder;
+import org.acra.config.MailSenderConfigurationBuilder;
+import org.acra.config.ToastConfigurationBuilder;
+import org.acra.data.StringFormat;
import org.amahi.anywhere.job.NetConnectivityJob;
import org.amahi.anywhere.job.PhotosContentJob;
+import org.amahi.anywhere.server.Api;
import dagger.ObjectGraph;
import io.fabric.sdk.android.Fabric;
@@ -43,6 +51,7 @@
* Application declaration. Basically sets things up at the startup time,
* such as dependency injection, logging, crash reporting and possible ANR detecting.
*/
+
public class AmahiApplication extends Application {
private ObjectGraph injector;
@@ -162,7 +171,34 @@ private void createNotificationChannel() {
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(uploadChannel);
notificationManager.createNotificationChannel(downloadChannel);
-
}
+ @Override
+ protected void attachBaseContext(Context base) {
+ super.attachBaseContext(base);
+ if (isDebugging()) {
+
+ CoreConfigurationBuilder builder = new CoreConfigurationBuilder(this)
+ .setBuildConfigClass(BuildConfig.class)
+ .setReportFormat(StringFormat.JSON)
+ .setAlsoReportToAndroidFramework(true)
+ .setReportContent(ReportField.APP_VERSION_CODE)
+ .setReportContent(ReportField.APP_VERSION_NAME)
+ .setReportContent(ReportField.ANDROID_VERSION)
+ .setReportContent(ReportField.PHONE_MODEL)
+ .setReportContent(ReportField.CUSTOM_DATA)
+ .setReportContent(ReportField.STACK_TRACE)
+ .setReportContent(ReportField.LOGCAT);
+
+ builder.getPluginConfigurationBuilder(MailSenderConfigurationBuilder.class)
+ .setMailTo(Api.getAcraEmail())
+ .setEnabled(true);
+
+ builder.getPluginConfigurationBuilder(ToastConfigurationBuilder.class)
+ .setResText(R.string.acra_report_toast)
+ .setEnabled(true);
+
+ ACRA.init(this, builder);
+ }
+ }
}
diff --git a/src/main/java/org/amahi/anywhere/activity/IntroductionActivity.java b/src/main/java/org/amahi/anywhere/activity/IntroductionActivity.java
index 01c900d01..618bc2a65 100644
--- a/src/main/java/org/amahi/anywhere/activity/IntroductionActivity.java
+++ b/src/main/java/org/amahi/anywhere/activity/IntroductionActivity.java
@@ -27,26 +27,26 @@
import androidx.fragment.app.Fragment;
import androidx.core.content.ContextCompat;
-import com.github.paolorotolo.appintro.AppIntro;
-import com.github.paolorotolo.appintro.AppIntroFragment;
+import com.github.paolorotolo.appintro.AppIntro2;
+import com.github.paolorotolo.appintro.AppIntro2Fragment;
import org.amahi.anywhere.R;
import org.amahi.anywhere.util.CheckTV;
import org.amahi.anywhere.util.LocaleHelper;
import org.amahi.anywhere.util.Preferences;
-import org.amahi.anywhere.util.SampleSlide;
-public class IntroductionActivity extends AppIntro {
+public class IntroductionActivity extends AppIntro2 {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- addSlide(SampleSlide.newInstance(R.layout.intro_first_layout));
- addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_2), getString(R.string.intro_desc_2), R.drawable.network, ContextCompat.getColor(this, R.color.intro_2)));
- addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_3), getString(R.string.intro_desc_phone_3), R.drawable.photos, Color.DKGRAY));
- addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_4), getString(R.string.intro_desc_phone_4), R.drawable.music, ContextCompat.getColor(this, R.color.intro_4)));
- addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_5), getString(R.string.intro_desc_phone_5), R.drawable.movies, ContextCompat.getColor(this, R.color.intro_5)));
- addSlide(AppIntroFragment.newInstance(getString(R.string.intro_title_6), getString(R.string.intro_desc_6), R.drawable.tick, ContextCompat.getColor(this, R.color.intro_6)));
- setFlowAnimation();
+ addSlide(AppIntro2Fragment.newInstance(getString(R.string.intro_phone_1), getString(R.string.intro_desc_phone_1), R.drawable.ic_banner_white, ContextCompat.getColor(this, R.color.intro_1)));
+ addSlide(AppIntro2Fragment.newInstance(getString(R.string.intro_title_2), getString(R.string.intro_desc_2), R.drawable.network, ContextCompat.getColor(this, R.color.intro_2)));
+ addSlide(AppIntro2Fragment.newInstance(getString(R.string.intro_title_3), getString(R.string.intro_desc_phone_3), R.drawable.photos, Color.DKGRAY));
+ addSlide(AppIntro2Fragment.newInstance(getString(R.string.intro_title_4), getString(R.string.intro_desc_phone_4), R.drawable.music, ContextCompat.getColor(this, R.color.intro_4)));
+ addSlide(AppIntro2Fragment.newInstance(getString(R.string.intro_title_5), getString(R.string.intro_desc_phone_5), R.drawable.movies, ContextCompat.getColor(this, R.color.intro_5)));
+ addSlide(AppIntro2Fragment.newInstance(getString(R.string.intro_title_6), getString(R.string.intro_desc_6), R.drawable.tick, ContextCompat.getColor(this, R.color.intro_6)));
+ setColorTransitionsEnabled(true);
+ setFadeAnimation();
}
@Override
diff --git a/src/main/java/org/amahi/anywhere/activity/ServerAppActivity.java b/src/main/java/org/amahi/anywhere/activity/ServerAppActivity.java
index 386a4731b..7f7fc1712 100644
--- a/src/main/java/org/amahi/anywhere/activity/ServerAppActivity.java
+++ b/src/main/java/org/amahi/anywhere/activity/ServerAppActivity.java
@@ -22,7 +22,6 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
-import androidx.appcompat.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
@@ -31,6 +30,13 @@
import android.webkit.WebView;
import android.webkit.WebViewClient;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.SimpleTarget;
+
import org.amahi.anywhere.AmahiApplication;
import org.amahi.anywhere.R;
import org.amahi.anywhere.server.client.ServerClient;
@@ -54,6 +60,8 @@ public class ServerAppActivity extends AppCompatActivity {
@Inject
ServerClient serverClient;
+ Context ctx;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -67,6 +75,7 @@ protected void onCreate(Bundle savedInstanceState) {
private void setUpInjections() {
AmahiApplication.from(this).inject(this);
+ ctx = this;
}
private void setUpApp(Bundle state) {
@@ -83,7 +92,7 @@ private void setUpAppWebCookie() {
String appCookies = Preferences.ofCookie(this).getAppCookies(appHost);
for (String appCookie : TextUtils.split(appCookies, ";")) {
- CookieManager.getInstance().setCookie(getAppUrl(), appCookie);
+ CookieManager.getInstance().setCookie(getServerUrl(), appCookie);
}
}
@@ -91,10 +100,19 @@ private ServerApp getApp() {
return getIntent().getParcelableExtra(Intents.Extras.SERVER_APP);
}
- private String getAppUrl() {
+ private String getServerUrl() {
return serverClient.getServerAddress();
}
+ private String getAppUrl() {
+ String host = getApp().getHost();
+ if (host.matches("^(http|https)://")) {
+ return host;
+ } else {
+ return "http://" + host;
+ }
+ }
+
private void setUpAppWebAgent() {
getWebView().getSettings().setUserAgentString(getAppWebAgent());
}
@@ -130,6 +148,18 @@ private void setUpAppWebTitle() {
getSupportActionBar().setTitle(getApp().getName());
}
+ private void setUpAppWebIcon() {
+ getSupportActionBar().setIcon(R.drawable.ic_launcher);
+ if (!TextUtils.isEmpty(getApp().getLogoUrl())) {
+ Glide.with(ctx).load(getApp().getLogoUrl()).into(new SimpleTarget() {
+ @Override
+ public void onResourceReady(GlideDrawable resource, GlideAnimation super GlideDrawable> glideAnimation) {
+ getSupportActionBar().setIcon(resource);
+ }
+ });
+ }
+ }
+
private void setUpAppWebContent(Bundle state) {
if (state == null) {
getWebView().loadUrl(getAppUrl());
@@ -192,9 +222,8 @@ protected void onPause() {
@Override
protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
-
getWebView().saveState(outState);
+ super.onSaveInstanceState(outState);
}
@Override
@@ -210,7 +239,7 @@ protected void onDestroy() {
private void tearDownAppWebCookie() {
String appHost = getApp().getHost();
- String appCookies = CookieManager.getInstance().getCookie(getAppUrl());
+ String appCookies = CookieManager.getInstance().getCookie(getServerUrl());
Preferences.ofCookie(this).setAppCookies(appHost, appCookies);
@@ -227,6 +256,11 @@ private void showApp() {
ViewDirector.of(this, R.id.animator).show(R.id.web_content);
}
+ @Override
+ protected void attachBaseContext(Context newBase) {
+ super.attachBaseContext(LocaleHelper.onAttach(newBase));
+ }
+
private static final class AppWebAgentField {
public static final String HOST = "Vhost";
@@ -255,9 +289,4 @@ public void onPageFinished(WebView appWebView, String appUrl) {
activity.showApp();
}
}
-
- @Override
- protected void attachBaseContext(Context newBase) {
- super.attachBaseContext(LocaleHelper.onAttach(newBase));
- }
}
diff --git a/src/main/java/org/amahi/anywhere/activity/ServerFileAudioActivity.java b/src/main/java/org/amahi/anywhere/activity/ServerFileAudioActivity.java
index f4fbca10f..2deea40d7 100644
--- a/src/main/java/org/amahi/anywhere/activity/ServerFileAudioActivity.java
+++ b/src/main/java/org/amahi/anywhere/activity/ServerFileAudioActivity.java
@@ -78,6 +78,7 @@
import org.amahi.anywhere.util.Fragments;
import org.amahi.anywhere.util.Intents;
import org.amahi.anywhere.util.LocaleHelper;
+import org.amahi.anywhere.util.Preferences;
import java.util.ArrayList;
import java.util.Arrays;
@@ -99,6 +100,9 @@ public class ServerFileAudioActivity extends AppCompatActivity implements
View.OnClickListener,
SessionManagerListener {
private static final Set SUPPORTED_FORMATS;
+ private static final int REPEAT_ALL = 0;
+ private static final int REPEAT_ONE = 1;
+ private static final String AUDIO_LIST_VISIBLE = "audio_list_visible";
static {
SUPPORTED_FORMATS = new HashSet<>(Arrays.asList(
@@ -111,6 +115,7 @@ public class ServerFileAudioActivity extends AppCompatActivity implements
@Inject
ServerClient serverClient;
+ private int REPEAT_MODE = 0;
private CastContext mCastContext;
private CastSession mCastSession;
private AudioMetadataFormatter metadataFormatter;
@@ -118,7 +123,6 @@ public class ServerFileAudioActivity extends AppCompatActivity implements
private AudioService audioService;
private boolean changeAudio = true;
private boolean audioControlsAvailable = false;
- private static final String AUDIO_LIST_VISIBLE = "audio_list_visible";
private boolean audioListVisible = false;
public static boolean supports(String mime_type) {
@@ -152,7 +156,7 @@ private void setUpInjections() {
private void setUpHomeNavigation() {
Toolbar toolbar = findViewById(R.id.toolbar_audio);
setSupportActionBar(toolbar);
- if(getSupportActionBar()!=null) {
+ if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@@ -358,8 +362,11 @@ private void setUpAudioControls() {
getPlayerControlView().setPlayer(audioService.getAudioPlayer());
getNextButton().setOnClickListener(this);
getPreviousButton().setOnClickListener(this);
+ getRepeatButton().setOnClickListener(this);
+ getPlayerControlView().setShowShuffleButton(true);
audioControlsAvailable = true;
}
+ setUpRepeatToggleIcon();
getPlayerControlView().setShowTimeoutMs(0);
}
@@ -375,6 +382,21 @@ private ImageButton getPreviousButton() {
return getPlayerControlView().findViewById(R.id.exo_prev);
}
+ private ImageButton getRepeatButton() {
+ return getPlayerControlView().findViewById(R.id.m_exo_repeat_toggle);
+ }
+
+ private void setUpRepeatToggleIcon() {
+ if (Preferences.getAudioRepeatMode(this) == REPEAT_ONE) {
+ REPEAT_MODE = REPEAT_ONE;
+ getRepeatButton().setImageResource(R.drawable.exo_controls_repeat_one);
+ } else {
+ REPEAT_MODE = REPEAT_ALL;
+ getRepeatButton().setImageResource(R.drawable.exo_controls_repeat_all);
+ }
+
+ }
+
private boolean areAudioControlsAvailable() {
return audioControlsAvailable;
}
@@ -392,6 +414,7 @@ private void setUpAudioPlayback() {
}
audioService.startAudio(getShare(), getAudioFiles(), getFile());
+ audioService.enableShuffle(Preferences.getAudioShuffleMode(this));
audioService.setPlayPosition(0);
}
@@ -418,15 +441,27 @@ public void onAudioPrepared(AudioPreparedEvent event) {
@Subscribe
public void onNextAudio(AudioControlNextEvent event) {
+ int audioPosition;
+ if (REPEAT_MODE == REPEAT_ONE) {
+ audioPosition = getAudioPager().getCurrentItem();
+ } else if (audioService.isShuffleEnabled()) {
+ List shuffledAudioFiles = audioService.getShuffledAudioFiles();
+ audioPosition = shuffledAudioFiles.indexOf(getFile()) + 1;
+ if (audioPosition == shuffledAudioFiles.size()) {
+ audioPosition = 0;
+ }
- int audioPosition = getAudioPager().getCurrentItem();
- audioPosition += 1;
- if (audioPosition == getAudioFiles().size()) {
- audioPosition = 0;
+ audioPosition = getAudioFiles().indexOf(shuffledAudioFiles.get(audioPosition));
+ } else {
+ audioPosition = getAudioPager().getCurrentItem();
+ audioPosition += 1;
+ if (audioPosition == getAudioFiles().size()) {
+ audioPosition = 0;
+ }
}
changeAudio = false;
getAudioPager().setCurrentItem(audioPosition);
- saveAudioFileState(getFiles().get(audioPosition));
+ saveAudioFileState(getAudioFiles().get(audioPosition));
}
private void saveAudioFileState(ServerFile file) {
@@ -435,14 +470,27 @@ private void saveAudioFileState(ServerFile file) {
@Subscribe
public void onPreviousAudio(AudioControlPreviousEvent event) {
- int audioPosition = getAudioPager().getCurrentItem();
- audioPosition -= 1;
- if (audioPosition == -1) {
- audioPosition = getAudioFiles().size() - 1;
+ int audioPosition;
+ if (REPEAT_MODE == REPEAT_ONE) {
+ audioPosition = getAudioPager().getCurrentItem();
+ } else if (audioService.isShuffleEnabled()) {
+ List shuffledAudioFiles = audioService.getShuffledAudioFiles();
+ audioPosition = shuffledAudioFiles.indexOf(getFile()) - 1;
+ if (audioPosition == -1) {
+ audioPosition = shuffledAudioFiles.size() - 1;
+ }
+
+ audioPosition = getAudioFiles().indexOf(shuffledAudioFiles.get(audioPosition));
+ } else {
+ audioPosition = getAudioPager().getCurrentItem();
+ audioPosition -= 1;
+ if (audioPosition == -1) {
+ audioPosition = getAudioFiles().size() - 1;
+ }
}
changeAudio = false;
getAudioPager().setCurrentItem(audioPosition);
- saveAudioFileState(getFiles().get(audioPosition));
+ saveAudioFileState(getAudioFiles().get(audioPosition));
}
@Subscribe
@@ -454,13 +502,29 @@ public void onChangeAudio(AudioControlChangeEvent event) {
@Subscribe
public void onAudioCompleted(AudioCompletedEvent event) {
- int audioPosition = getAudioPager().getCurrentItem();
- audioPosition += 1;
- if (audioPosition == getAudioFiles().size()) {
- audioPosition = 0;
+
+ int audioPosition;
+ if (REPEAT_MODE == REPEAT_ONE) {
+ audioPosition = getAudioPager().getCurrentItem();
+ } else if (audioService.isShuffleEnabled()) {
+ List shuffledAudioFiles = audioService.getShuffledAudioFiles();
+ audioPosition = shuffledAudioFiles.indexOf(getFile()) + 1;
+ if (audioPosition == shuffledAudioFiles.size()) {
+ audioPosition = 0;
+ }
+
+ audioPosition = getAudioFiles().indexOf(shuffledAudioFiles.get(audioPosition));
+ } else {
+ audioPosition = getAudioPager().getCurrentItem();
+ audioPosition += 1;
+ if (audioPosition == getAudioFiles().size()) {
+ audioPosition = 0;
+ }
+
}
changeAudio = false;
getAudioPager().setCurrentItem(audioPosition);
+ saveAudioFileState(getAudioFiles().get(audioPosition));
}
@Override
@@ -836,11 +900,6 @@ private String getRemoteUri(ServerFile serverFile) {
}
}
- private enum PlaybackLocation {
- LOCAL,
- REMOTE
- }
-
@Override
public void onClick(View v) {
if (v == getNextButton()) {
@@ -849,10 +908,27 @@ public void onClick(View v) {
if (v == getPreviousButton()) {
BusProvider.getBus().post(new AudioControlPreviousEvent());
}
+ if (v == getRepeatButton()) {
+ if (isAudioServiceAvailable()) {
+ if (REPEAT_MODE == REPEAT_ALL) {
+ REPEAT_MODE = REPEAT_ONE;
+ getRepeatButton().setImageResource(R.drawable.exo_controls_repeat_one);
+ } else if (REPEAT_MODE == REPEAT_ONE) {
+ REPEAT_MODE = REPEAT_ALL;
+ getRepeatButton().setImageResource(R.drawable.exo_controls_repeat_all);
+ }
+ Preferences.setAudioRepeatMode(this, REPEAT_MODE);
+ }
+ }
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(LocaleHelper.onAttach(newBase));
}
+
+ private enum PlaybackLocation {
+ LOCAL,
+ REMOTE
+ }
}
diff --git a/src/main/java/org/amahi/anywhere/server/Api.java b/src/main/java/org/amahi/anywhere/server/Api.java
index 2ef21718d..e25b87b5f 100644
--- a/src/main/java/org/amahi/anywhere/server/Api.java
+++ b/src/main/java/org/amahi/anywhere/server/Api.java
@@ -43,4 +43,8 @@ public static String getClientId() {
public static String getClientSecret() {
return BuildConfig.API_CLIENT_SECRET;
}
+
+ public static String getAcraEmail() {
+ return BuildConfig.ACRA_EMAIL;
+ }
}
diff --git a/src/main/java/org/amahi/anywhere/service/AudioService.java b/src/main/java/org/amahi/anywhere/service/AudioService.java
index e0bc20c33..3ff2c474f 100644
--- a/src/main/java/org/amahi/anywhere/service/AudioService.java
+++ b/src/main/java/org/amahi/anywhere/service/AudioService.java
@@ -31,16 +31,17 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import android.support.v4.media.MediaBrowserCompat;
-import androidx.media.MediaBrowserServiceCompat;
import android.support.v4.media.MediaMetadataCompat;
-import androidx.media.session.MediaButtonReceiver;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.media.MediaBrowserServiceCompat;
+import androidx.media.session.MediaButtonReceiver;
+
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
@@ -51,6 +52,7 @@
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
+import com.google.android.exoplayer2.source.LoopingMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
@@ -89,6 +91,8 @@
import org.amahi.anywhere.util.Preferences;
import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -102,18 +106,19 @@
public class AudioService extends MediaBrowserServiceCompat implements
AudioManager.OnAudioFocusChangeListener,
Player.EventListener {
+ private static final int REPEAT_ALL = 0;
+ private static final int REPEAT_ONE = 1;
@Inject
ServerClient serverClient;
private MediaNotificationManager mMediaNotificationManager;
private SimpleExoPlayer audioPlayer;
private MediaSessionCompat mediaSession;
private AudioFocus audioFocus;
-
private ServerShare audioShare;
private List audioFiles;
+ private List shuffledAudioFiles = new ArrayList<>();
private ServerFile audioFile;
private boolean isPreparing = false;
-
private AudioMetadataFormatter audioMetadataFormatter;
private Bitmap audioAlbumArt;
@@ -166,7 +171,7 @@ private void setUpBus() {
private void setUpAudioPlayer() {
audioPlayer =
- ExoPlayerFactory.newSimpleInstance(new DefaultRenderersFactory(this), new DefaultTrackSelector(), new DefaultLoadControl());
+ ExoPlayerFactory.newSimpleInstance(this, new DefaultRenderersFactory(this), new DefaultTrackSelector(), new DefaultLoadControl());
audioPlayer.addListener(this);
audioPlayer.setPlayWhenReady(true);
audioPlayer.setVolume(1.0f);
@@ -416,13 +421,30 @@ private void startNextAudio() {
setUpAudioMetadata();
}
+ public List getShuffledAudioFiles() {
+ return shuffledAudioFiles;
+ }
+
private ServerFile getNextAudioFile() {
- int audioPosition = audioFiles.indexOf(audioFile);
- if (audioPosition == audioFiles.size() - 1) {
- return audioFiles.get(0);
+ if (getRepeatModePreference() == REPEAT_ONE) {
+ return audioFile;
}
- return audioFiles.get(audioPosition + 1);
+
+ if (!isShuffleEnabled()) {
+ int audioPosition = audioFiles.indexOf(audioFile);
+ if (audioPosition == audioFiles.size() - 1) {
+ return audioFiles.get(0);
+ }
+ return audioFiles.get(audioPosition + 1);
+ } else {
+ int audioPosition = shuffledAudioFiles.indexOf(audioFile);
+ if (audioPosition == shuffledAudioFiles.size() - 1) {
+ return shuffledAudioFiles.get(0);
+ }
+ return shuffledAudioFiles.get(audioPosition + 1);
+ }
+
}
private void startPreviousAudio() {
@@ -434,17 +456,52 @@ private void startPreviousAudio() {
}
private ServerFile getPreviousAudioFile() {
- int audioPosition = audioFiles.indexOf(audioFile);
- if (audioPosition == 0) {
- audioPosition = audioFiles.size();
+ if (getRepeatModePreference() == REPEAT_ONE) {
+ return audioFile;
+ }
+
+ if (!isShuffleEnabled()) {
+ int audioPosition = audioFiles.indexOf(audioFile);
+ if (audioPosition == 0) {
+ audioPosition = audioFiles.size();
+ }
+ return audioFiles.get(audioPosition - 1);
+ } else {
+ int audioPosition = shuffledAudioFiles.indexOf(audioFile);
+ if (audioPosition == 0) {
+ audioPosition = shuffledAudioFiles.size();
+ }
+ return shuffledAudioFiles.get(audioPosition - 1);
+
}
- return audioFiles.get(audioPosition - 1);
}
@Subscribe
public void onAudioCompleted(AudioCompletedEvent event) {
- startNextAudio();
+ if (getRepeatModePreference() == REPEAT_ONE) {
+ playAudioInLoop();
+ } else {
+ startNextAudio();
+ }
+ }
+
+ public int getRepeatModePreference() {
+ return Preferences.getAudioRepeatMode(this);
+ }
+
+ public void playAudioInLoop() {
+ MediaSource mediaSource;
+ if (isFileAvailableOffline(getAudioFile())) {
+ mediaSource = new ExtractorMediaSource.Factory(
+ new DefaultDataSourceFactory(this, Identifier.getUserAgent(this)))
+ .createMediaSource(getOfflineFileUri(getAudioFile().getName()));
+ } else {
+ mediaSource = buildMediaSource(getAudioUri());
+ }
+ LoopingMediaSource loopingMediaSource = new LoopingMediaSource(mediaSource);
+ audioPlayer.prepare(loopingMediaSource, true, true);
+
}
public void playAudio() {
@@ -459,6 +516,19 @@ public void pauseAudio() {
setMediaPlaybackState(PlaybackStateCompat.STATE_PAUSED);
}
+ public void enableShuffle(boolean isShuffleEnabled) {
+ audioPlayer.setShuffleModeEnabled(isShuffleEnabled);
+ }
+
+ public boolean isShuffleEnabled() {
+ return audioPlayer.getShuffleModeEnabled();
+ }
+
+ public void shuffleAudioList() {
+ shuffledAudioFiles = new ArrayList<>(audioFiles);
+ Collections.shuffle(shuffledAudioFiles);
+ }
+
private void setMediaPlaybackState(int state) {
PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder();
playbackStateBuilder.setActions(getAvailableActions());
@@ -627,6 +697,11 @@ public void onRepeatModeChanged(int repeatMode) {
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
+ if (shuffleModeEnabled) {
+ shuffleAudioList();
+ }
+ Preferences.setAudioShuffleMode(this, shuffleModeEnabled);
+
}
diff --git a/src/main/java/org/amahi/anywhere/util/Preferences.java b/src/main/java/org/amahi/anywhere/util/Preferences.java
index ec02b65c9..4356f2750 100644
--- a/src/main/java/org/amahi/anywhere/util/Preferences.java
+++ b/src/main/java/org/amahi/anywhere/util/Preferences.java
@@ -98,6 +98,30 @@ public static Preferences ofCookie(Context context) {
return new Preferences(context, Locations.COOKIE);
}
+ public static int getSortOption(Context context) {
+ return getPreference(context).getInt(Defaults.SORTING_OPTION, ServerFilesFragment.SORT_MODIFICATION_TIME);
+ }
+
+ public static void setSortOption(Context context, int filesSort) {
+ getPreference(context).edit().putInt(Defaults.SORTING_OPTION, filesSort).apply();
+ }
+
+ public static void setAudioShuffleMode(Context context, boolean mode) {
+ getPreference(context).edit().putBoolean(Defaults.AUDIO_SHUFFLE, mode).apply();
+ }
+
+ public static boolean getAudioShuffleMode(Context context) {
+ return getPreference(context).getBoolean(Defaults.AUDIO_SHUFFLE, false);
+ }
+
+ public static void setAudioRepeatMode(Context context, int mode) {
+ getPreference(context).edit().putInt(Defaults.AUDIO_REPEAT, mode).apply();
+ }
+
+ public static int getAudioRepeatMode(Context context) {
+ return getPreference(context).getInt(Defaults.AUDIO_REPEAT, 0);
+ }
+
public String getAppCookies(String appHost) {
return getString(appHost);
}
@@ -114,14 +138,6 @@ private void setString(String key, String value) {
preferences.edit().putString(key, value).apply();
}
- public static int getSortOption(Context context) {
- return getPreference(context).getInt(Defaults.SORTING_OPTION, ServerFilesFragment.SORT_MODIFICATION_TIME);
- }
-
- public static void setSortOption(Context context, int filesSort) {
- getPreference(context).edit().putInt(Defaults.SORTING_OPTION, filesSort).apply();
- }
-
private static final class Locations {
public static final String COOKIE = "cookie";
@@ -132,6 +148,8 @@ private Locations() {
private static final class Defaults {
public static final String STRING = "";
public static final String SORTING_OPTION = "sorting_option";
+ public static final String AUDIO_SHUFFLE = "audio_shuffle";
+ public static final String AUDIO_REPEAT = "audio_repeat";
private Defaults() {
}
diff --git a/src/main/java/org/amahi/anywhere/util/SampleSlide.java b/src/main/java/org/amahi/anywhere/util/SampleSlide.java
deleted file mode 100644
index 7b989279f..000000000
--- a/src/main/java/org/amahi/anywhere/util/SampleSlide.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (c) 2014 Amahi
- *
- * This file is part of Amahi.
- *
- * Amahi 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 3 of the License, or
- * (at your option) any later version.
- *
- * Amahi 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 Amahi. If not, see .
- */
-
-package org.amahi.anywhere.util;
-
-import android.os.Bundle;
-import androidx.annotation.ColorInt;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.core.content.ContextCompat;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.github.paolorotolo.appintro.ISlideBackgroundColorHolder;
-
-import org.amahi.anywhere.R;
-
-public class SampleSlide extends Fragment implements ISlideBackgroundColorHolder {
-
- private static final String ARG_LAYOUT_RES_ID = "layoutResId";
- ViewGroup mContainer;
- private int layoutResId;
-
- public static SampleSlide newInstance(int layoutResId) {
- SampleSlide sampleSlide = new SampleSlide();
-
- Bundle args = new Bundle();
- args.putInt(ARG_LAYOUT_RES_ID, layoutResId);
- sampleSlide.setArguments(args);
-
- return sampleSlide;
- }
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- if (getArguments() != null && getArguments().containsKey(ARG_LAYOUT_RES_ID)) {
- layoutResId = getArguments().getInt(ARG_LAYOUT_RES_ID);
- }
- }
-
- @Nullable
- @Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- mContainer = container;
- return inflater.inflate(layoutResId, container, false);
- }
-
- @Override
- public int getDefaultBackgroundColor() {
- return ContextCompat.getColor(getContext(), R.color.intro_1);
- }
-
- @Override
- public void setBackgroundColor(@ColorInt int i) {
- if (mContainer != null)
- mContainer.setBackgroundColor(i);
- }
-}
diff --git a/src/main/res/layout-land/intro_first_layout.xml b/src/main/res/layout-land/intro_first_layout.xml
deleted file mode 100644
index 296b1279b..000000000
--- a/src/main/res/layout-land/intro_first_layout.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/res/layout/activity_server_file_audio.xml b/src/main/res/layout/activity_server_file_audio.xml
index 70434f556..423ed9b8d 100644
--- a/src/main/res/layout/activity_server_file_audio.xml
+++ b/src/main/res/layout/activity_server_file_audio.xml
@@ -33,6 +33,7 @@
android:id="@+id/control_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ app:repeat_toggle_modes="all"
android:layout_gravity="bottom"
android:focusable="true"
app:shutter_background_color="@android:color/transparent" />
diff --git a/src/main/res/layout/exo_player_control_view.xml b/src/main/res/layout/exo_player_control_view.xml
index 3a33fae95..0ddaee09b 100644
--- a/src/main/res/layout/exo_player_control_view.xml
+++ b/src/main/res/layout/exo_player_control_view.xml
@@ -71,21 +71,13 @@
android:padding="8dp"
android:orientation="horizontal">
-
-
-
-
+ style="@style/ExoMediaButton.Shuffle" />
-
+ style="@style/ExoMediaButton.Previous"/>
-
-
+
+
diff --git a/src/main/res/layout/intro_first_layout.xml b/src/main/res/layout/intro_first_layout.xml
deleted file mode 100644
index 545d7319f..000000000
--- a/src/main/res/layout/intro_first_layout.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml
index db51cec49..44d9ee8b2 100644
--- a/src/main/res/values-fr/strings.xml
+++ b/src/main/res/values-fr/strings.xml
@@ -144,7 +144,7 @@
Bienvenue à Amahi pour Android TV
- Bienvenue à Amahi pour Android.
+ Bienvenue à Amahi pour Android
Une app Android qui vous permet d\'accéder à vos fichiers dans votre Amahi Home Server.
Accéder à votre HDA
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 69b752e70..769b6ac8b 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -151,7 +151,7 @@
Welcome to Amahi for Android TV
- Welcome to Amahi for Android.
+ Welcome to Amahi for Android
An Android app that lets you access the files in your Amahi Home Server.
Access your HDA
@@ -320,4 +320,6 @@
Go back
Select the type of connection you want. You can set it as Auto detect, remote or local area network.
+ ACRA Log Report Sent!
+