diff --git a/.circleci/config.yml b/.circleci/config.yml index 1a97e73c5..fa0e1cef8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,7 +45,7 @@ workflows: destination: app-play-debug.apk - run: name: Execute debug unit tests - command: ./gradlew :core:testPlayDebugUnitTest -PdisablePreDex + command: ./gradlew testPlayDebugUnitTest -PdisablePreDex - build: name: Build release build-steps: @@ -54,13 +54,13 @@ workflows: command: ./gradlew assembleRelease -PdisablePreDex - run: name: Execute release unit tests - command: ./gradlew :core:testPlayReleaseUnitTest -PdisablePreDex + command: ./gradlew testPlayReleaseUnitTest -PdisablePreDex - build: name: Build integration tests build-steps: - run: name: Build integration tests - command: ./gradlew :app:assemblePlayDebugAndroidTest -PdisablePreDex + command: ./gradlew assemblePlayDebugAndroidTest -PdisablePreDex - build: name: Build free build-steps: @@ -85,19 +85,8 @@ workflows: curl -s -L https://github.com/yangziwen/diff-checkstyle/releases/download/0.0.4/diff-checkstyle.jar > diff-checkstyle.jar java -Dconfig_loc=config/checkstyle -jar diff-checkstyle.jar -c config/checkstyle/checkstyle-new-code.xml --git-dir . --base-rev $branchBaseCommit - build: - name: Lint app + name: Lint build-steps: - run: - name: Lint app - command: ./gradlew app:lintPlayRelease - - run: - name: Lint core - command: ./gradlew core:lintPlayRelease - - store_artifacts: - name: Uploading app lint reports - path: app/build/reports/lint-results-playRelease.html - destination: lint-results-app.html - - store_artifacts: - name: Uploading core lint reports - path: core/build/reports/lint-results-playRelease.html - destination: lint-results-core.html + name: Lint + command: ./gradlew lintPlayRelease diff --git a/app/build.gradle b/app/build.gradle index ef8e850b3..bc918ab3f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -164,6 +164,7 @@ dependencies { } else { System.out.println("app: free build hack, skipping some dependencies") } + implementation project(':ui:app-start-intent') annotationProcessor "androidx.annotation:annotation:$annotationVersion" implementation "androidx.appcompat:appcompat:$appcompatVersion" diff --git a/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java b/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java index 904e17ebf..739efcfd2 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java @@ -15,6 +15,7 @@ import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.fragment.QueueFragment; +import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter; import de.test.antennapod.EspressoTestUtils; import de.test.antennapod.IgnoreOnCi; import org.awaitility.Awaitility; @@ -71,7 +72,7 @@ public class SpeedChangeTest { UserPreferences.setPlaybackSpeedArray(Arrays.asList(1.0f, 2.0f, 3.0f)); EspressoTestUtils.tryKillPlaybackService(); - activityRule.launchActivity(new Intent().putExtra(MainActivity.EXTRA_OPEN_PLAYER, true)); + activityRule.launchActivity(new Intent().putExtra(MainActivityStarter.EXTRA_OPEN_PLAYER, true)); controller = new PlaybackController(activityRule.getActivity()); controller.init(); controller.getMedia(); // To load media diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ed585a406..79fcc430f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -106,6 +106,10 @@ android:pathPrefix="/deeplink/search" android:scheme="https" /> + + + + + + + + = Build.VERSION_CODES.LOLLIPOP) { - i.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); - } - return i; - } - } -} diff --git a/core/build.gradle b/core/build.gradle index f443ebb9b..1b1eb0a2f 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -71,6 +71,8 @@ android { } dependencies { + implementation project(':ui:app-start-intent') + annotationProcessor "androidx.annotation:annotation:$annotationVersion" implementation "androidx.appcompat:appcompat:$appcompatVersion" implementation 'androidx.documentfile:documentfile:1.0.1' diff --git a/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java b/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java index 5f7e6baaf..0193bf8ce 100644 --- a/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java +++ b/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java @@ -30,8 +30,6 @@ public class ClientConfig { public static DownloadServiceCallbacks downloadServiceCallbacks; - public static PlaybackServiceCallbacks playbackServiceCallbacks; - public static CastCallbacks castCallbacks; private static boolean initialized = false; diff --git a/core/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java b/core/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java deleted file mode 100644 index 3dcaac4dc..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java +++ /dev/null @@ -1,22 +0,0 @@ -package de.danoeh.antennapod.core; - -import android.content.Context; -import android.content.Intent; - -import de.danoeh.antennapod.core.feed.MediaType; - -/** - * Callbacks for the PlaybackService of the core module - */ -public interface PlaybackServiceCallbacks { - - /** - * Returns an intent which starts an audio- or videoplayer, depending on the - * type of media that is being played. - * - * @param mediaType The type of media that is being played. - * @param remotePlayback true if the media is played on a remote device. - * @return A non-null activity intent. - */ - Intent getPlayerActivityIntent(Context context, MediaType mediaType, boolean remotePlayback); -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index 5bc1b19f7..d5c85f5f4 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -50,7 +50,6 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; -import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.event.MessageEvent; import de.danoeh.antennapod.core.event.PlaybackPositionEvent; @@ -81,6 +80,8 @@ import de.danoeh.antennapod.core.util.playback.ExternalMedia; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter; import de.danoeh.antennapod.core.widget.WidgetUpdater; +import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter; +import de.danoeh.antennapod.ui.appstartintent.VideoPlayerActivityStarter; import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -245,24 +246,31 @@ public class PlaybackService extends MediaBrowserServiceCompat { * running, the type of the last played media will be looked up. */ public static Intent getPlayerActivityIntent(Context context) { + boolean showVideoPlayer; + if (isRunning) { - return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, currentMediaType, isCasting); + showVideoPlayer = currentMediaType == MediaType.VIDEO && !isCasting; } else { - if (PlaybackPreferences.getCurrentEpisodeIsVideo()) { - return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, MediaType.VIDEO, isCasting); - } else { - return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, MediaType.AUDIO, isCasting); - } + showVideoPlayer = PlaybackPreferences.getCurrentEpisodeIsVideo(); + } + + if (showVideoPlayer) { + return new VideoPlayerActivityStarter(context).getIntent(); + } else { + return new MainActivityStarter(context).withOpenPlayer().getIntent(); } } /** - * Same as getPlayerActivityIntent(context), but here the type of activity + * Same as {@link #getPlayerActivityIntent(Context)}, but here the type of activity * depends on the FeedMedia that is provided as an argument. */ public static Intent getPlayerActivityIntent(Context context, Playable media) { - MediaType mt = media.getMediaType(); - return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, mt, isCasting); + if (media.getMediaType() == MediaType.VIDEO && !isCasting) { + return new VideoPlayerActivityStarter(context).getIntent(); + } else { + return new MainActivityStarter(context).withOpenPlayer().getIntent(); + } } @Override @@ -796,7 +804,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { @Override public WidgetUpdater.WidgetState requestWidgetState() { return new WidgetUpdater.WidgetState(getPlayable(), getStatus(), - getCurrentPosition(), getDuration(), getCurrentPlaybackSpeed()); + getCurrentPosition(), getDuration(), getCurrentPlaybackSpeed(), isCasting()); } @Override diff --git a/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java b/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java index 1622fcb8f..d97271d1a 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java +++ b/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java @@ -19,15 +19,17 @@ import com.bumptech.glide.request.RequestOptions; import java.util.concurrent.TimeUnit; import de.danoeh.antennapod.core.R; +import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; import de.danoeh.antennapod.core.receiver.PlayerWidget; -import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.TimeSpeedConverter; import de.danoeh.antennapod.core.util.playback.Playable; +import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter; +import de.danoeh.antennapod.ui.appstartintent.VideoPlayerActivityStarter; /** * Updates the state of the player widget. @@ -41,17 +43,20 @@ public abstract class WidgetUpdater { final int position; final int duration; final float playbackSpeed; + final boolean isCasting; - public WidgetState(Playable media, PlayerStatus status, int position, int duration, float playbackSpeed) { + public WidgetState(Playable media, PlayerStatus status, int position, int duration, + float playbackSpeed, boolean isCasting) { this.media = media; this.status = status; this.position = position; this.duration = duration; this.playbackSpeed = playbackSpeed; + this.isCasting = isCasting; } public WidgetState(PlayerStatus status) { - this(null, status, Playable.INVALID_TIME, Playable.INVALID_TIME, 1.0f); + this(null, status, Playable.INVALID_TIME, Playable.INVALID_TIME, 1.0f, false); } } @@ -65,8 +70,14 @@ public abstract class WidgetUpdater { ComponentName playerWidget = new ComponentName(context, PlayerWidget.class); AppWidgetManager manager = AppWidgetManager.getInstance(context); int[] widgetIds = manager.getAppWidgetIds(playerWidget); - final PendingIntent startMediaPlayer = PendingIntent.getActivity(context, R.id.pending_intent_player_activity, - PlaybackService.getPlayerActivityIntent(context), PendingIntent.FLAG_UPDATE_CURRENT); + + PendingIntent startMediaPlayer; + if (widgetState.media != null && widgetState.media.getMediaType() == MediaType.VIDEO + && !widgetState.isCasting) { + startMediaPlayer = new VideoPlayerActivityStarter(context).getPendingIntent(); + } else { + startMediaPlayer = new MainActivityStarter(context).withOpenPlayer().getPendingIntent(); + } RemoteViews views; views = new RemoteViews(context.getPackageName(), R.layout.player_widget); diff --git a/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdaterJobService.java b/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdaterJobService.java index 1db7928b4..ab763cd8c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdaterJobService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdaterJobService.java @@ -5,6 +5,7 @@ import android.content.Intent; import androidx.annotation.NonNull; import androidx.core.app.SafeJobIntentService; import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; +import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.util.playback.Playable; @@ -24,7 +25,8 @@ public class WidgetUpdaterJobService extends SafeJobIntentService { Playable media = Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext()); if (media != null) { WidgetUpdater.updateWidget(this, new WidgetUpdater.WidgetState(media, PlayerStatus.STOPPED, - media.getPosition(), media.getDuration(), PlaybackSpeedUtils.getCurrentPlaybackSpeed(media))); + media.getPosition(), media.getDuration(), PlaybackSpeedUtils.getCurrentPlaybackSpeed(media), + PlaybackPreferences.getCurrentEpisodeIsStream())); } else { WidgetUpdater.updateWidget(this, new WidgetUpdater.WidgetState(PlayerStatus.STOPPED)); } diff --git a/core/src/main/res/values/ids.xml b/core/src/main/res/values/ids.xml index 3c173b72d..87046cc0f 100644 --- a/core/src/main/res/values/ids.xml +++ b/core/src/main/res/values/ids.xml @@ -23,14 +23,4 @@ - - - - - - - - - - \ No newline at end of file diff --git a/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java b/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java index e8c2b1dcd..0225c508a 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java +++ b/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java @@ -36,8 +36,6 @@ public class ClientConfig { public static DownloadServiceCallbacks downloadServiceCallbacks; - public static PlaybackServiceCallbacks playbackServiceCallbacks; - public static CastCallbacks castCallbacks; private static boolean initialized = false; diff --git a/settings.gradle b/settings.gradle index bdf2d88b9..fc57f08b0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,3 @@ -include ':app', ':core' +include ':app' +include ':core' +include ':ui:app-start-intent' diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 000000000..b20f459f1 --- /dev/null +++ b/ui/README.md @@ -0,0 +1,3 @@ +# :ui + +This folder contains modules that display or directly interact with the UI. diff --git a/ui/app-start-intent/README.md b/ui/app-start-intent/README.md new file mode 100644 index 000000000..b796a56cc --- /dev/null +++ b/ui/app-start-intent/README.md @@ -0,0 +1,3 @@ +# :ui:app-start-intent + +This module provides classes that can start the main activities of the app with specific arguments. It does not require a dependency on the actual implementation of the activities, so it can be used to decouple the services from the UI. diff --git a/ui/app-start-intent/build.gradle b/ui/app-start-intent/build.gradle new file mode 100644 index 000000000..fabd8937f --- /dev/null +++ b/ui/app-start-intent/build.gradle @@ -0,0 +1,50 @@ +apply plugin: "com.android.library" + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + + defaultConfig { + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + + multiDexEnabled false + + testApplicationId "de.danoeh.antennapod.core.tests" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile("proguard-android.txt") + } + debug { + // debug build has method count over 64k single-dex threshold. + // For building debug build to use on Android < 21 (pre-Android 5) devices, + // you need to manually change class + // de.danoeh.antennapod.PodcastApp to extend MultiDexApplication . + // See Issue #2813 + multiDexEnabled true + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + testOptions { + unitTests { + includeAndroidResources = true + } + } + + lintOptions { + warningsAsErrors true + abortOnError true + } +} + +dependencies { + annotationProcessor "androidx.annotation:annotation:$annotationVersion" + implementation "androidx.appcompat:appcompat:$appcompatVersion" +} diff --git a/ui/app-start-intent/src/main/AndroidManifest.xml b/ui/app-start-intent/src/main/AndroidManifest.xml new file mode 100644 index 000000000..9296616f9 --- /dev/null +++ b/ui/app-start-intent/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/ui/app-start-intent/src/main/java/de/danoeh/antennapod/ui/appstartintent/MainActivityStarter.java b/ui/app-start-intent/src/main/java/de/danoeh/antennapod/ui/appstartintent/MainActivityStarter.java new file mode 100644 index 000000000..33f96f141 --- /dev/null +++ b/ui/app-start-intent/src/main/java/de/danoeh/antennapod/ui/appstartintent/MainActivityStarter.java @@ -0,0 +1,41 @@ +package de.danoeh.antennapod.ui.appstartintent; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; + +/** + * Launches the main activity of the app with specific arguments. + * Does not require a dependency on the actual implementation of the activity. + */ +public class MainActivityStarter { + public static final String INTENT = "de.danoeh.antennapod.intents.MAIN_ACTIVITY"; + public static final String EXTRA_OPEN_PLAYER = "open_player"; + + private final Intent intent; + private final Context context; + + public MainActivityStarter(Context context) { + this.context = context; + intent = new Intent(INTENT); + intent.setPackage(context.getPackageName()); + } + + public Intent getIntent() { + return intent; + } + + public PendingIntent getPendingIntent() { + return PendingIntent.getActivity(context, R.id.pending_intent_player_activity, + getIntent(), PendingIntent.FLAG_UPDATE_CURRENT); + } + + public void start() { + context.startActivity(getIntent()); + } + + public MainActivityStarter withOpenPlayer() { + intent.putExtra(EXTRA_OPEN_PLAYER, true); + return this; + } +} diff --git a/ui/app-start-intent/src/main/java/de/danoeh/antennapod/ui/appstartintent/VideoPlayerActivityStarter.java b/ui/app-start-intent/src/main/java/de/danoeh/antennapod/ui/appstartintent/VideoPlayerActivityStarter.java new file mode 100644 index 000000000..7536d34b6 --- /dev/null +++ b/ui/app-start-intent/src/main/java/de/danoeh/antennapod/ui/appstartintent/VideoPlayerActivityStarter.java @@ -0,0 +1,38 @@ +package de.danoeh.antennapod.ui.appstartintent; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; + +/** + * Launches the video player activity of the app with specific arguments. + * Does not require a dependency on the actual implementation of the activity. + */ +public class VideoPlayerActivityStarter { + public static final String INTENT = "de.danoeh.antennapod.intents.VIDEO_PLAYER"; + private final Intent intent; + private final Context context; + + public VideoPlayerActivityStarter(Context context) { + this.context = context; + intent = new Intent(INTENT); + intent.setPackage(context.getPackageName()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); + } + } + + public Intent getIntent() { + return intent; + } + + public PendingIntent getPendingIntent() { + return PendingIntent.getActivity(context, R.id.pending_intent_video_player, + getIntent(), PendingIntent.FLAG_UPDATE_CURRENT); + } + + public void start() { + context.startActivity(getIntent()); + } +} diff --git a/ui/app-start-intent/src/main/res/values/pending_intent.xml b/ui/app-start-intent/src/main/res/values/pending_intent.xml new file mode 100644 index 000000000..1e426e954 --- /dev/null +++ b/ui/app-start-intent/src/main/res/values/pending_intent.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + +