Browse Source

Completely remove the obsolete extensions

merge-requests/1614/head
Nicolas Pomepuy 4 years ago
parent
commit
c858cd1bad
  1. 1
      application/resources/src/main/java/org/videolan/resources/Constants.kt
  2. 1
      application/television/src/main/java/org/videolan/television/ui/preferences/PreferencesFragment.kt
  3. 1
      application/vlc-android/build.gradle
  4. 104
      application/vlc-android/res/layout/extension_item_view.xml
  5. 56
      application/vlc-android/res/menu/navigation.xml
  6. 3
      application/vlc-android/res/xml/preferences.xml
  7. 7
      application/vlc-android/res/xml/preferences_extensions.xml
  8. 24
      application/vlc-android/res/xml/shortcuts.xml
  9. 144
      application/vlc-android/src/org/videolan/vlc/MediaSessionCallback.kt
  10. 2
      application/vlc-android/src/org/videolan/vlc/PlaybackService.kt
  11. 2
      application/vlc-android/src/org/videolan/vlc/StartActivity.kt
  12. 240
      application/vlc-android/src/org/videolan/vlc/extensions/ExtensionListing.java
  13. 243
      application/vlc-android/src/org/videolan/vlc/extensions/ExtensionManagerService.java
  14. 228
      application/vlc-android/src/org/videolan/vlc/extensions/ExtensionsManager.java
  15. 19
      application/vlc-android/src/org/videolan/vlc/extensions/Utils.java
  16. 5
      application/vlc-android/src/org/videolan/vlc/gui/ContentActivity.kt
  17. 9
      application/vlc-android/src/org/videolan/vlc/gui/MainActivity.kt
  18. 111
      application/vlc-android/src/org/videolan/vlc/gui/browser/ExtensionAdapter.kt
  19. 200
      application/vlc-android/src/org/videolan/vlc/gui/browser/ExtensionBrowser.kt
  20. 129
      application/vlc-android/src/org/videolan/vlc/gui/helpers/Navigator.kt
  21. 122
      application/vlc-android/src/org/videolan/vlc/gui/preferences/PreferencesExtensionFragment.kt
  22. 111
      application/vlc-android/src/org/videolan/vlc/gui/preferences/PreferencesExtensions.kt
  23. 5
      application/vlc-android/src/org/videolan/vlc/gui/preferences/PreferencesFragment.kt
  24. 529
      application/vlc-android/src/org/videolan/vlc/media/MediaSessionBrowser.kt
  25. 1
      extension-api/.gitignore
  26. 56
      extension-api/build.gradle
  27. 17
      extension-api/proguard-rules.pro
  28. 33
      extension-api/src/main/AndroidManifest.xml
  29. 11
      extension-api/src/main/aidl/org/videolan/vlc/extensions/api/IExtensionHost.aidl
  30. 11
      extension-api/src/main/aidl/org/videolan/vlc/extensions/api/IExtensionService.aidl
  31. 3
      extension-api/src/main/aidl/org/videolan/vlc/extensions/api/VLCExtensionItem.aidl
  32. 222
      extension-api/src/main/java/org/videolan/vlc/extensions/api/VLCExtensionItem.java
  33. 166
      extension-api/src/main/java/org/videolan/vlc/extensions/api/VLCExtensionService.java
  34. 37
      extension-api/src/main/java/org/videolan/vlc/extensions/api/WarningActivity.java
  35. 134
      extension-api/src/main/java/org/videolan/vlc/extensions/api/tools/Dialogs.java
  36. 49
      extension-api/src/main/java/org/videolan/vlc/extensions/api/tools/Helpers.java
  37. 6
      extension-api/src/main/res/values/strings.xml
  38. 14
      extension-api/src/main/res/values/styles.xml
  39. 38
      extension-api/src/test/java/org/videolan/vlc/extensions/api/ExampleUnitTest.java
  40. 2
      settings.gradle

1
application/resources/src/main/java/org/videolan/resources/Constants.kt

@ -67,7 +67,6 @@ val ACTION_REMOTE_GENERIC = "remote.".buildPkgString()
@JvmField val CUSTOM_ACTION_REWIND = "rewind".buildPkgString()
@JvmField val EXTRA_CUSTOM_ACTION_ID = "EXTRA_CUSTOM_ACTION_ID".buildPkgString()
@JvmField val EXTRA_SEEK_DELAY = "EXTRA_CUSTOM_ACTION_ID".buildPkgString()
const val ACTION_CAR_MODE_EXIT = "android.app.action.EXIT_CAR_MODE"
const val PLAYLIST_TYPE_AUDIO = 0
const val PLAYLIST_TYPE_VIDEO = 1
const val PLAYLIST_TYPE_VIDEO_RESUME = 2

1
application/television/src/main/java/org/videolan/television/ui/preferences/PreferencesFragment.kt

@ -49,7 +49,6 @@ class PreferencesFragment : BasePreferenceFragment(), SharedPreferences.OnShared
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
findPreference<Preference>(SCREEN_ORIENTATION)?.isVisible = false
findPreference<Preference>("extensions_category")?.isVisible = false
findPreference<Preference>("casting_category")?.isVisible = false
findPreference<Preference>(KEY_VIDEO_APP_SWITCH)?.isVisible = AndroidDevices.hasPiP
}

1
application/vlc-android/build.gradle

@ -147,7 +147,6 @@ dependencies {
releaseApi "org.videolan.android:medialibrary-all:$rootProject.ext.medialibraryVersion"
signedReleaseApi "org.videolan.android:libvlc-all:$rootProject.ext.libvlcVersion"
signedReleaseApi "org.videolan.android:medialibrary-all:$rootProject.ext.medialibraryVersion"
api project(':extension-api')
api project(':application:tools')
api project(':application:resources')
api project(':application:mediadb')

104
application/vlc-android/res/layout/extension_item_view.xml

@ -1,104 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:vlc="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<import type="android.text.TextUtils" />
<variable
name="holder"
type="org.videolan.vlc.gui.browser.ExtensionAdapter.ViewHolder" />
<variable
name="item"
type="org.videolan.vlc.extensions.api.VLCExtensionItem" />
<variable
name="cover"
type="android.graphics.drawable.BitmapDrawable" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_margin="5dp"
android:clickable="true"
android:onLongClick="@{holder::onLongClick}"
android:onClick="@{holder::onClick}">
<ImageView
android:id="@+id/extension_image"
android:layout_width="64dp"
android:layout_height="64dp"
vlc:imageUri="@{item.imageUri}"
vlc:layout_constraintEnd_toStartOf="@id/title"
vlc:layout_constraintBottom_toBottomOf="parent"
vlc:layout_constraintStart_toStartOf="parent"
vlc:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:visibility="@{item.imageUri == null || TextUtils.isEmpty(item.imageUri.toString()) ? View.GONE : View.VISIBLE}"
android:src="@{cover}"
tools:src="@drawable/icon" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="bottom"
android:text="@{item.title}"
android:fontFamily="sans-serif"
android:textColor="?attr/list_title"
android:textSize="16sp"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
android:maxLines="1"
vlc:layout_constraintEnd_toStartOf="@+id/item_more"
vlc:layout_constraintStart_toEndOf="@id/extension_image"
vlc:layout_constraintTop_toTopOf="parent"
vlc:layout_constraintBottom_toTopOf="@+id/author"
vlc:layout_constraintVertical_chainStyle="packed"
tools:text="Title"
tools:targetApi="jelly_bean" />
<TextView
android:id="@+id/author"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="top"
android:text="@{item.subTitle}"
android:fontFamily="sans-serif"
android:textColor="?attr/list_subtitle"
android:ellipsize="marquee"
android:singleLine="true"
android:maxLines="1"
android:layout_marginTop="2dp"
vlc:layout_constraintEnd_toStartOf="@id/item_more"
vlc:layout_constraintTop_toBottomOf="@+id/title"
vlc:layout_constraintStart_toStartOf="@id/title"
vlc:layout_constraintBottom_toBottomOf="parent"
android:visibility="@{TextUtils.isEmpty(item.subTitle) ? View.GONE : View.VISIBLE}"
tools:text="Author"
tools:targetApi="jelly_bean" />
<ImageView
android:id="@+id/item_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
vlc:srcCompat="@drawable/ic_more"
android:background="?attr/selectableItemBackgroundBorderless"
android:visibility="@{item.type != 0 ? View.VISIBLE : View.GONE}"
android:layout_marginEnd="1dp"
vlc:layout_constraintRight_toRightOf="parent"
vlc:layout_constraintTop_toTopOf="parent"
vlc:layout_constraintBottom_toBottomOf="parent"
android:layout_marginRight="1dp"
tools:src="@drawable/ic_more"
android:onClick="@{holder::onMoreClick}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

56
application/vlc-android/res/menu/navigation.xml

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:id="@+id/scrollable_group"
android:checkableBehavior="none"
android:orderInCategory="1">
<item
android:id="@+id/nav_video"
android:title="@string/video"
android:icon="@drawable/ic_menu_video" />
<item
android:id="@+id/nav_audio"
android:title="@string/audio"
android:icon="@drawable/ic_menu_audio" />
<item
android:id="@+id/nav_playlists"
android:title="@string/playlists"
android:icon="@drawable/ic_menu_playlist" />
<item
android:id="@+id/nav_directories"
android:title="@string/directories"
android:icon="@drawable/ic_menu_folder" />
<item
android:id="@+id/nav_network"
android:title="@string/network_browsing"
android:icon="@drawable/ic_menu_network" />
<item
android:id="@+id/nav_mrl"
android:title="@string/streams"
android:icon="@drawable/ic_menu_stream" />
<item
android:id="@+id/nav_history"
android:title="@string/history"
android:icon="@drawable/ic_menu_history" />
</group>
<item
android:id="@+id/extensions_group"
android:title="@string/plugins"
android:visible="false"
android:orderInCategory="2">
<menu>
</menu>
</item>
<group android:id="@+id/fixed_group"
android:orderInCategory="3">
<item
android:id="@+id/nav_settings"
android:title="@string/preferences"
android:icon="@drawable/ic_menu_preferences" />
</group>
</menu>

3
application/vlc-android/res/xml/preferences.xml

@ -86,9 +86,6 @@
<Preference
android:title="@string/casting_category"
android:key="casting_category"/>
<Preference
android:title="@string/extensions_prefs_category"
android:key="extensions_category"/>
<Preference
android:title="@string/advanced_prefs_category"
android:key="adv_category"

7
application/vlc-android/res/xml/preferences_extensions.xml

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="extensions_category"
android:title="@string/extensions_prefs_category">>
</androidx.preference.PreferenceScreen>

24
application/vlc-android/res/xml/shortcuts.xml

@ -26,18 +26,6 @@
android:targetClass="org.videolan.vlc.StartActivity" />
<categories android:name="vlc.shortcut.category" />
</shortcut>
<shortcut
android:shortcutId="playlists"
android:enabled="true"
android:icon="@drawable/ic_menu_playlist"
android:shortcutShortLabel="@string/playlists"
android:shortcutLongLabel="@string/playlists" >
<intent
android:action="vlc.shortcut.playlists"
android:targetPackage="org.videolan.vlc"
android:targetClass="org.videolan.vlc.StartActivity" />
<categories android:name="vlc.shortcut.category" />
</shortcut>
<shortcut
android:shortcutId="video"
android:enabled="false"
@ -63,16 +51,4 @@
android:targetClass="org.videolan.vlc.StartActivity" />
<categories android:name="vlc.shortcut.category" />
</shortcut>
<shortcut
android:shortcutId="network"
android:enabled="false"
android:icon="@drawable/ic_menu_network"
android:shortcutShortLabel="@string/network_browsing"
android:shortcutLongLabel="@string/network_browsing" >
<intent
android:action="vlc.shortcut.network"
android:targetPackage="org.videolan.vlc"
android:targetClass="org.videolan.vlc.StartActivity" />
<categories android:name="vlc.shortcut.category" />
</shortcut>
</shortcuts>

144
application/vlc-android/src/org/videolan/vlc/MediaSessionCallback.kt

@ -44,7 +44,6 @@ import org.videolan.resources.util.getFromMl
import org.videolan.tools.Settings
import org.videolan.tools.removeQuery
import org.videolan.tools.retrieveParent
import org.videolan.vlc.extensions.ExtensionsManager
import org.videolan.vlc.gui.helpers.MediaComparators
import org.videolan.vlc.media.MediaSessionBrowser
import org.videolan.vlc.util.VoiceSearchParams
@ -161,88 +160,83 @@ internal class MediaSessionCallback(private val playbackService: PlaybackService
playbackService.lifecycleScope.launch {
val context = playbackService.applicationContext
try {
if (mediaId.startsWith(ExtensionsManager.EXTENSION_PREFIX)) {
val id = mediaId.replace(ExtensionsManager.EXTENSION_PREFIX + "_" + mediaId.split("_".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1] + "_", "")
onPlayFromUri(id.toUri(), null)
} else {
val mediaIdUri = Uri.parse(mediaId)
val position = mediaIdUri.getQueryParameter("i")?.toInt() ?: 0
val page = mediaIdUri.getQueryParameter("p")
val pageOffset = page?.toInt()?.times(MediaSessionBrowser.MAX_RESULT_SIZE) ?: 0
when (mediaIdUri.removeQuery().toString()) {
MediaSessionBrowser.ID_NO_MEDIA -> playbackService.displayPlaybackError(R.string.search_no_result)
MediaSessionBrowser.ID_NO_PLAYLIST -> playbackService.displayPlaybackError(R.string.noplaylist)
MediaSessionBrowser.ID_SHUFFLE_ALL -> {
val tracks = context.getFromMl { audio }
if (tracks.isNotEmpty() && isActive) {
tracks.sortWith(MediaComparators.ANDROID_AUTO)
loadMedia(tracks.toList(), SecureRandom().nextInt(min(tracks.size, MEDIALIBRARY_PAGE_SIZE)))
if (!playbackService.isShuffling) playbackService.shuffle()
} else {
playbackService.displayPlaybackError(R.string.search_no_result)
}
val mediaIdUri = Uri.parse(mediaId)
val position = mediaIdUri.getQueryParameter("i")?.toInt() ?: 0
val page = mediaIdUri.getQueryParameter("p")
val pageOffset = page?.toInt()?.times(MediaSessionBrowser.MAX_RESULT_SIZE) ?: 0
when (mediaIdUri.removeQuery().toString()) {
MediaSessionBrowser.ID_NO_MEDIA -> playbackService.displayPlaybackError(R.string.search_no_result)
MediaSessionBrowser.ID_NO_PLAYLIST -> playbackService.displayPlaybackError(R.string.noplaylist)
MediaSessionBrowser.ID_SHUFFLE_ALL -> {
val tracks = context.getFromMl { audio }
if (tracks.isNotEmpty() && isActive) {
tracks.sortWith(MediaComparators.ANDROID_AUTO)
loadMedia(tracks.toList(), SecureRandom().nextInt(min(tracks.size, MEDIALIBRARY_PAGE_SIZE)))
if (!playbackService.isShuffling) playbackService.shuffle()
} else {
playbackService.displayPlaybackError(R.string.search_no_result)
}
MediaSessionBrowser.ID_LAST_ADDED -> {
val tracks = context.getFromMl { getPagedAudio(Medialibrary.SORT_INSERTIONDATE, true, false, MediaSessionBrowser.MAX_HISTORY_SIZE, 0) }
if (tracks.isNotEmpty() && isActive) {
loadMedia(tracks.toList(), position)
}
}
MediaSessionBrowser.ID_LAST_ADDED -> {
val tracks = context.getFromMl { getPagedAudio(Medialibrary.SORT_INSERTIONDATE, true, false, MediaSessionBrowser.MAX_HISTORY_SIZE, 0) }
if (tracks.isNotEmpty() && isActive) {
loadMedia(tracks.toList(), position)
}
MediaSessionBrowser.ID_HISTORY -> {
val tracks = context.getFromMl { lastMediaPlayed()?.toList()?.filter { MediaSessionBrowser.isMediaAudio(it) } }
if (!tracks.isNullOrEmpty() && isActive) {
val mediaList = tracks.subList(0, tracks.size.coerceAtMost(MediaSessionBrowser.MAX_HISTORY_SIZE))
loadMedia(mediaList, position)
}
}
MediaSessionBrowser.ID_HISTORY -> {
val tracks = context.getFromMl { lastMediaPlayed()?.toList()?.filter { MediaSessionBrowser.isMediaAudio(it) } }
if (!tracks.isNullOrEmpty() && isActive) {
val mediaList = tracks.subList(0, tracks.size.coerceAtMost(MediaSessionBrowser.MAX_HISTORY_SIZE))
loadMedia(mediaList, position)
}
MediaSessionBrowser.ID_STREAM -> {
val tracks = context.getFromMl { lastStreamsPlayed() }
if (tracks.isNotEmpty() && isActive) {
tracks.sortWith(MediaComparators.ANDROID_AUTO)
loadMedia(tracks.toList(), position)
}
}
MediaSessionBrowser.ID_STREAM -> {
val tracks = context.getFromMl { lastStreamsPlayed() }
if (tracks.isNotEmpty() && isActive) {
tracks.sortWith(MediaComparators.ANDROID_AUTO)
loadMedia(tracks.toList(), position)
}
MediaSessionBrowser.ID_TRACK -> {
val tracks = context.getFromMl { audio }
if (tracks.isNotEmpty() && isActive) {
tracks.sortWith(MediaComparators.ANDROID_AUTO)
loadMedia(tracks.toList(), pageOffset + position)
}
}
MediaSessionBrowser.ID_TRACK -> {
val tracks = context.getFromMl { audio }
if (tracks.isNotEmpty() && isActive) {
tracks.sortWith(MediaComparators.ANDROID_AUTO)
loadMedia(tracks.toList(), pageOffset + position)
}
}
MediaSessionBrowser.ID_SEARCH -> {
val query = mediaIdUri.getQueryParameter("query") ?: ""
val tracks = context.getFromMl {
search(query, false)?.tracks?.toList() ?: emptyList()
}
if (tracks.isNotEmpty() && isActive) {
loadMedia(tracks, position)
}
MediaSessionBrowser.ID_SEARCH -> {
val query = mediaIdUri.getQueryParameter("query") ?: ""
val tracks = context.getFromMl {
search(query, false)?.tracks?.toList() ?: emptyList()
}
else -> {
val id = ContentUris.parseId(mediaIdUri)
when (mediaIdUri.retrieveParent().toString()) {
MediaSessionBrowser.ID_ALBUM -> {
val tracks = context.getFromMl { getAlbum(id)?.tracks }
if (isActive) tracks?.let { loadMedia(it.toList(), position) }
}
if (tracks.isNotEmpty() && isActive) {
loadMedia(tracks, position)
MediaSessionBrowser.ID_ARTIST -> {
val tracks = context.getFromMl { getArtist(id)?.tracks }
if (isActive) tracks?.let { loadMedia(it.toList(), allowRandom = true) }
}
}
else -> {
val id = ContentUris.parseId(mediaIdUri)
when (mediaIdUri.retrieveParent().toString()) {
MediaSessionBrowser.ID_ALBUM -> {
val tracks = context.getFromMl { getAlbum(id)?.tracks }
if (isActive) tracks?.let { loadMedia(it.toList(), position) }
}
MediaSessionBrowser.ID_ARTIST -> {
val tracks = context.getFromMl { getArtist(id)?.tracks }
if (isActive) tracks?.let { loadMedia(it.toList(), allowRandom = true) }
}
MediaSessionBrowser.ID_GENRE -> {
val tracks = context.getFromMl { getGenre(id)?.albums?.flatMap { it.tracks.toList() } }
if (isActive) tracks?.let { loadMedia(it.toList(), allowRandom = true) }
}
MediaSessionBrowser.ID_PLAYLIST -> {
val tracks = context.getFromMl { getPlaylist(id, Settings.includeMissing)?.tracks }
if (isActive) tracks?.let { loadMedia(it.toList(), allowRandom = true) }
}
MediaSessionBrowser.ID_MEDIA -> {
val tracks = context.getFromMl { getMedia(id)?.tracks }
if (isActive) tracks?.let { loadMedia(it.toList()) }
}
else -> throw IllegalStateException("Failed to load: $mediaId")
MediaSessionBrowser.ID_GENRE -> {
val tracks = context.getFromMl { getGenre(id)?.albums?.flatMap { it.tracks.toList() } }
if (isActive) tracks?.let { loadMedia(it.toList(), allowRandom = true) }
}
MediaSessionBrowser.ID_PLAYLIST -> {
val tracks = context.getFromMl { getPlaylist(id, Settings.includeMissing)?.tracks }
if (isActive) tracks?.let { loadMedia(it.toList(), allowRandom = true) }
}
MediaSessionBrowser.ID_MEDIA -> {
val tracks = context.getFromMl { getMedia(id)?.tracks }
if (isActive) tracks?.let { loadMedia(it.toList()) }
}
else -> throw IllegalStateException("Failed to load: $mediaId")
}
}
}

2
application/vlc-android/src/org/videolan/vlc/PlaybackService.kt

@ -172,7 +172,6 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner, CoroutineSc
MiniPlayerAppWidgetProvider.ACTION_WIDGET_INIT -> updateWidget()
MiniPlayerAppWidgetProvider.ACTION_WIDGET_ENABLED, VLCAppWidgetProvider.ACTION_WIDGET_DISABLED -> updateHasWidget()
MiniPlayerAppWidgetProvider.ACTION_WIDGET_ENABLED, VLCAppWidgetProvider.ACTION_WIDGET_DISABLED -> updateHasWidget()
ACTION_CAR_MODE_EXIT -> MediaSessionBrowser.unbindExtensionConnection()
AudioManager.ACTION_AUDIO_BECOMING_NOISY -> if (detectHeadset) {
if (BuildConfig.DEBUG) Log.i(TAG, "Becoming noisy")
wasPlaying = isPlaying
@ -626,7 +625,6 @@ class PlaybackService : MediaBrowserServiceCompat(), LifecycleOwner, CoroutineSc
addAction(Intent.ACTION_HEADSET_PLUG)
addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
if (CarConnectionHandler.preferCarConnectionHandler()) addAction(CarConnectionHandler.RECEIVER_ACTION)
addAction(ACTION_CAR_MODE_EXIT)
addAction(CUSTOM_ACTION)
}
registerReceiver(receiver, filter)

2
application/vlc-android/src/org/videolan/vlc/StartActivity.kt

@ -72,8 +72,6 @@ class StartActivity : FragmentActivity() {
"vlc.shortcut.video" -> R.id.nav_video
"vlc.shortcut.audio" -> R.id.nav_audio
"vlc.shortcut.browser" -> R.id.nav_directories
"vlc.shortcut.network" -> R.id.nav_network
"vlc.shortcut.playlists" -> R.id.nav_playlists
"vlc.shortcut.resume" -> R.id.ml_menu_last_playlist
else -> 0
}

240
application/vlc-android/src/org/videolan/vlc/extensions/ExtensionListing.java

@ -1,240 +0,0 @@
/*
* *************************************************************************
* pluginListing.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.videolan.vlc.extensions;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
public class ExtensionListing implements Parcelable {
public static final int PARCELABLE_VERSION = 1;
private ComponentName mComponentName;
private int mProtocolVersion;
private boolean mCompatible;
private int menuIcon = 0;
private String mTitle;
private String mDescription;
private boolean mAndroidAutoEnabled = false;
private ComponentName mSettingsActivity;
private ExtensionManagerService.Connection connection;
public ExtensionListing(){}
@Override
public int describeContents() {
return 0;
}
/**
* Returns the full qualified component name of the extension.
*/
public ComponentName componentName() {
return mComponentName;
}
/**
* Sets the full qualified component name of the extension.
*/
public ExtensionListing componentName(ComponentName componentName) {
mComponentName = componentName;
return this;
}
/**
* Returns the version of the {@link org.videolan.vlc.extensions.api.VLCExtensionService}
* protocol used by the extension.
*/
public int protocolVersion() {
return mProtocolVersion;
}
/**
* Sets the resId of the icon displayed in VLC menu for this extension.
*/
public ExtensionListing menuIcon(int menuIcon) {
this.menuIcon = menuIcon;
return this;
}
/**
* Returns the resId of the icon displayed in VLC menu for this extension.
*/
public int menuIcon() {
return menuIcon;
}
/**
* Sets the version of the {@link org.videolan.vlc.extensions.api.VLCExtensionService}
* protocol used by the extension.
*/
public ExtensionListing protocolVersion(int protocolVersion) {
mProtocolVersion = protocolVersion;
return this;
}
/**
* Returns whether this extension is compatible to the host application; that is whether
* the version of the {@link org.videolan.vlc.extensions.api.VLCExtensionService}
* protocol used by the extension matches what is used by the host application.
*/
public boolean compatible() {
return mCompatible;
}
/**
* Sets whether this extension is considered compatible to the host application.
*/
public ExtensionListing compatible(boolean compatible) {
mCompatible = compatible;
return this;
}
/**
* Returns the label of the extension.
*/
public String title() {
return mTitle;
}
/**
* Sets the label of the extension.
*/
public ExtensionListing title(String title) {
mTitle = title;
return this;
}
/**
* Returns a description of the extension.
*/
public String description() {
return mDescription;
}
/**
* Sets a description of the extension.
*/
public ExtensionListing description(String description) {
mDescription = description;
return this;
}
/**
* Returns true if android-auto is supported.
*/
public boolean androidAutoEnabled() {
return mAndroidAutoEnabled;
}
/**
* Sets the support of android-auto.
*/
public ExtensionListing androidAutoEnabled(boolean androidAutoEnabled) {
mAndroidAutoEnabled = androidAutoEnabled;
return this;
}
/**
* Returns the full qualified component name of the settings class to configure
* the extension.
*/
public ComponentName settingsActivity() {
return mSettingsActivity;
}
/**
* Sets the full qualified component name of the settings class to configure
* the extension.
*/
public ExtensionListing settingsActivity(ComponentName settingsActivity) {
this.mSettingsActivity = settingsActivity;
return this;
}
public ExtensionManagerService.Connection getConnection() {
return connection;
}
public void setConnection(ExtensionManagerService.Connection connection) {
this.connection = connection;
}
/**
* @see android.os.Parcelable
*/
public static final Creator<ExtensionListing> CREATOR
= new Creator<ExtensionListing>() {
@Override
public ExtensionListing createFromParcel(Parcel in) {
return new ExtensionListing(in);
}
@Override
public ExtensionListing[] newArray(int size) {
return new ExtensionListing[size];
}
};
private ExtensionListing(Parcel in) {
int parcelableVersion = in.readInt();
// Version 1 below
if (parcelableVersion >= 1) {
mComponentName = ComponentName.readFromParcel(in);
mProtocolVersion = in.readInt();
mCompatible = in.readInt() == 1;
mTitle = in.readString();
mDescription = in.readString();
boolean hasSettings = in.readInt() == 1;
menuIcon = in.readInt();
if (hasSettings) {
mSettingsActivity = ComponentName.readFromParcel(in);
}
}
}
@Override
public void writeToParcel(Parcel parcel, int i) {
/**
* NOTE: When adding fields in the process of updating this API, make sure to bump
* {@link #PARCELABLE_VERSION}.
*/
parcel.writeInt(PARCELABLE_VERSION);
// Version 1 below
mComponentName.writeToParcel(parcel, 0);
parcel.writeInt(mProtocolVersion);
parcel.writeInt(mCompatible ? 1 : 0);
parcel.writeString(mTitle);
parcel.writeString(mDescription);
parcel.writeInt(mSettingsActivity != null ? 1 : 0);
parcel.writeInt(menuIcon);
if (mSettingsActivity != null) {
mSettingsActivity.writeToParcel(parcel, 0);
}
}
}

243
application/vlc-android/src/org/videolan/vlc/extensions/ExtensionManagerService.java

@ -1,243 +0,0 @@
/*
* *************************************************************************
* PluginService.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.videolan.vlc.extensions;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import org.videolan.medialibrary.MLServiceLocator;
import org.videolan.medialibrary.interfaces.media.MediaWrapper;
import org.videolan.vlc.extensions.api.IExtensionHost;
import org.videolan.vlc.extensions.api.IExtensionService;
import org.videolan.vlc.extensions.api.VLCExtensionItem;
import org.videolan.vlc.media.MediaUtils;
import java.util.List;
public class ExtensionManagerService extends Service {
private static final String TAG = "VLC/ExtensionManagerService";
private final IBinder mBinder = new LocalBinder();
private ExtensionManagerActivity mExtensionManagerActivity;
int mCurrentIndex = -1;
public interface ExtensionManagerActivity {
void displayExtensionItems(int extensionId, String title, List<VLCExtensionItem> items, boolean showParams, boolean isRefresh);
}
public void setExtensionManagerActivity(ExtensionManagerActivity activity) {
mExtensionManagerActivity = activity;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
setExtensionManagerActivity(null);
disconnect();
return false;
}
public class LocalBinder extends Binder {
public ExtensionManagerService getService() {
return ExtensionManagerService.this;
}
}
public void openExtension(int index) {
if (index == mCurrentIndex)
browse(null);
else
connectService(index);
}
public ExtensionListing getCurrentExtension() {
return getExtensions().get(mCurrentIndex);
}
public void connectService(final int index) {
ExtensionListing info = getExtensions().get(index);
if (mCurrentIndex != -1) {
disconnect();
}
final Connection conn = new Connection();
ComponentName cn = info.componentName();
conn.componentName = cn;
conn.hostInterface = makeHostInterface();
conn.serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
conn.ready = true;
conn.binder = IExtensionService.Stub.asInterface(service);
try {
conn.binder.onInitialize(index, conn.hostInterface);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mCurrentIndex = -1;
}
};
info.setConnection(conn);
try {
if (!bindService(new Intent().setComponent(cn), conn.serviceConnection,
Context.BIND_AUTO_CREATE)) {
Log.e(TAG, "Error binding to extension " + cn.flattenToShortString());
info.setConnection(null);
} else
mCurrentIndex = index;
} catch (SecurityException e) {
Log.e(TAG, "Error binding to extension " + cn.flattenToShortString(), e);
info.setConnection(null);
}
}
public void refresh() {
try {
ExtensionListing extension = getExtensions().get(mCurrentIndex);
if (extension == null)
return;
IExtensionService service = extension.getConnection().binder;
if (service == null)
return;
service.refresh();
} catch (RemoteException e) {}
}
public void browse(String stringId) {
try {
ExtensionListing extension = getExtensions().get(mCurrentIndex);
if (extension == null)
return;
IExtensionService service = extension.getConnection().binder;
if (service == null)
return;
service.browse(stringId);
} catch (RemoteException e) {}
}
public void disconnect() {
if (mCurrentIndex == -1)
return;
ExtensionListing extension = getCurrentExtension();
Connection conn = extension.getConnection();
if (conn != null) {
try {
unbindService(conn.serviceConnection);
} catch (Exception e) {} // In case of extension service crashed
}
extension.setConnection(null);
}
private IExtensionHost makeHostInterface() {
return new IExtensionHost.Stub(){
@Override
public void updateList(final String title, final List<VLCExtensionItem> items, final boolean showParams, final boolean isRefresh) throws RemoteException {
mHandler.post(new Runnable() {
@Override
public void run() {
if (mExtensionManagerActivity != null)
mExtensionManagerActivity.displayExtensionItems(mCurrentIndex, title, items, showParams, isRefresh);
}
});
}
@Override
public void playUri(Uri uri, String title) throws RemoteException {
final MediaWrapper media = MLServiceLocator.getAbstractMediaWrapper(uri);
if (!TextUtils.isEmpty(title));
media.setDisplayTitle(title);
mHandler.post(new Runnable() {
@Override
public void run() {
MediaUtils.INSTANCE.openMediaNoUi(ExtensionManagerService.this, media);
}
});
}
@Override
public void unBind(int index) throws RemoteException {
if (mCurrentIndex == index)
mCurrentIndex = -1;
}
};
}
public static class Connection {
boolean ready = false;
ComponentName componentName;
ServiceConnection serviceConnection;
IExtensionService binder;
IExtensionHost hostInterface;
/**
* Only access on the async thread. The pair is (collapse token, operation)
*/
// final Queue<Pair<Object, Operation>> deferredOps
// = new LinkedList<Pair<Object, Operation>>();
}
private final Handler mHandler = new Handler();
public List<ExtensionListing> getExtensions() {
return ExtensionsManager.getInstance().getExtensions(getApplication(), false);
}
}

228
application/vlc-android/src/org/videolan/vlc/extensions/ExtensionsManager.java

@ -1,228 +0,0 @@
package org.videolan.vlc.extensions;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import com.google.android.material.navigation.NavigationView;
import androidx.appcompat.app.AlertDialog;
import android.text.TextUtils;
import android.view.MenuItem;
import org.videolan.vlc.R;
import org.videolan.vlc.gui.MainActivity;
import org.videolan.tools.Settings;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class ExtensionsManager {
private static final String KEY_PROTOCOL_VERSION = "protocolVersion";
private static final String KEY_LISTING_TITLE = "title";
private static final String KEY_DESCRIPTION = "description";
private static final String KEY_MENU_ICON = "menuicon";
private static final String KEY_SETTINGS_ACTIVITY = "settingsActivity";
private static final String KEY_ANDROID_AUTO_ENABLED = "androidAutoEnabled";
private static final String ACTION_EXTENSION = "org.videolan.vlc.Extension";
private static final int PROTOCOLE_VERSION = 1;
public final static String EXTENSION_PREFIX = "extension";
public final static String ANDROID_AUTO_SUFFIX = "androidAuto";
public static boolean androidAutoInstalled = false;
private static ExtensionsManager sExtensionsManager;
private final List<ExtensionListing> mExtensions = new ArrayList<>();
public static ExtensionsManager getInstance() {
if (sExtensionsManager == null)
sExtensionsManager = new ExtensionsManager();
return sExtensionsManager;
}
private List<ExtensionListing> updateAvailableExtensions(Context context) {
androidAutoInstalled = isAndroidAutoInstalled(context);
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfos = pm.queryIntentServices(
new Intent(ACTION_EXTENSION), PackageManager.GET_META_DATA);
List<ExtensionListing> extensions = new ArrayList<>();
for (ResolveInfo resolveInfo : resolveInfos) {
ExtensionListing extension = new ExtensionListing();
extension.componentName(new ComponentName(resolveInfo.serviceInfo.packageName,
resolveInfo.serviceInfo.name));
Bundle metaData = resolveInfo.serviceInfo.metaData;
if (metaData != null) {
extension.compatible(metaData.getInt(KEY_PROTOCOL_VERSION) == PROTOCOLE_VERSION);
if (!extension.compatible())
continue;
String title = metaData.getString(KEY_LISTING_TITLE);
extension.title(title != null ? title : resolveInfo.loadLabel(pm).toString());
extension.description(metaData.getString(KEY_DESCRIPTION));
String settingsActivity = metaData.getString(KEY_SETTINGS_ACTIVITY);
if (!TextUtils.isEmpty(settingsActivity)) {
extension.settingsActivity(ComponentName.unflattenFromString(
resolveInfo.serviceInfo.packageName + "/" + settingsActivity));
}
extension.androidAutoEnabled(metaData.getBoolean(KEY_ANDROID_AUTO_ENABLED, false));
extension.menuIcon(metaData.getInt(KEY_MENU_ICON, 0));
extensions.add(extension);
}
}
SharedPreferences settings = Settings.INSTANCE.getInstance(context);
deleteUnusedExtensionPreferences(extensions, settings);
if (context instanceof MainActivity && ((MainActivity)context).currentIdIsExtension()) {
if (previousExtensionIsEnabled(context)) {
String lastExtensionTitle = settings.getString("current_extension_name", null);
for (int i = 0; i < extensions.size(); ++i) {
if (TextUtils.equals(extensions.get(i).title(), lastExtensionTitle)) {
((MainActivity)context).setCurrentFragmentId(i);
settings.edit().putInt("fragment_id", i).apply();
break;
}
}
} else {
((MainActivity)context).setCurrentFragmentId(-1);
settings.edit().putInt("fragment_id", -1).apply();
}
}
synchronized (mExtensions) {
mExtensions.clear();
mExtensions.addAll(extensions);
}
return extensions;
}
public List<ExtensionListing> getExtensions(Context context, boolean update) {
if (mExtensions.size() == 0 || update)
return updateAvailableExtensions(context);
else
return mExtensions;
}
public boolean previousExtensionIsEnabled(Context context) {
SharedPreferences settings = Settings.INSTANCE.getInstance(context);
String key = EXTENSION_PREFIX + "_" + settings.getString("current_extension_name", null);
return settings.contains(key) && settings.getBoolean(key, false);
}
private boolean deleteUnusedExtensionPreferences(List<ExtensionListing> list, SharedPreferences settings) {
boolean extensionMissing = false;
List<String> extensionNames = new ArrayList<>();
for (ExtensionListing extension : list)
extensionNames.add(extension.componentName().getPackageName());
for (Map.Entry<String, ?> entry : settings.getAll().entrySet())
if (entry.getKey().startsWith(EXTENSION_PREFIX + "_")
&& !entry.getKey().endsWith("_" + ANDROID_AUTO_SUFFIX)
&& !extensionNames.contains(entry.getKey().replace(EXTENSION_PREFIX + "_", ""))) {
settings.edit().remove(entry.getKey()).apply();
settings.edit().remove(entry.getKey() + "_" + ANDROID_AUTO_SUFFIX).apply();
extensionMissing = true;
}
return extensionMissing;
}
public void displayPlugin(Activity activity, int id, ExtensionListing extension, boolean visible) {
if (visible) {
MenuItem extensionGroup = ((NavigationView)activity.findViewById(R.id.navigation)).getMenu().findItem(R.id.extensions_group);
extensionGroup.setVisible(true);
MenuItem item = extensionGroup.getSubMenu().add(R.id.extensions_group, id, 0, extension.title());
item.setCheckable(false);
int iconRes = extension.menuIcon();
Drawable extensionIcon = null;
if (iconRes != 0) {
try {
Resources res = activity.getPackageManager().getResourcesForApplication(extension.componentName().getPackageName());
extensionIcon = res.getDrawable(extension.menuIcon());
} catch (PackageManager.NameNotFoundException ignored) {}
}
if (extensionIcon != null)
item.setIcon(extensionIcon);
else
try {
item.setIcon(activity.getPackageManager().getApplicationIcon(extension.componentName().getPackageName()));
} catch (PackageManager.NameNotFoundException e) {
item.setIcon(R.drawable.icon);
}
}
}
public void showExtensionPermissionDialog(final Activity activity, final int id, final ExtensionListing extension, final String key) {
final List<CharSequence> extraTitles = new ArrayList<>();
final List<String> extraKeys = new ArrayList<>();
//Add necessary checkboxes
if (androidAutoInstalled && extension.androidAutoEnabled()) {
extraTitles.add(activity.getString(R.string.extension_permission_checkbox_title, activity.getString(R.string.android_auto)));
extraKeys.add(key + "_" + ANDROID_AUTO_SUFFIX);
}
final boolean[] extraCheckedStates = new boolean[extraTitles.size()];
Arrays.fill(extraCheckedStates, Boolean.TRUE);
new AlertDialog.Builder(activity).setTitle(activity.getString(R.string.extension_permission_title, extension.title()))
.setMultiChoiceItems(extraTitles.toArray(new CharSequence[extraTitles.size()]),
extraCheckedStates,
new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int pos, boolean b) {
extraCheckedStates[pos] = b;
}
})
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int which) {
Settings.INSTANCE.getInstance(activity.getApplication()).edit().putBoolean(key, true).apply();
for (int i=0; i<extraTitles.size(); i++)
Settings.INSTANCE.getInstance(activity.getApplication()).edit().putBoolean(extraKeys.get(i), extraCheckedStates[i]).apply();
displayPlugin(activity, id, extension, true);
activity.findViewById(R.id.navigation).postInvalidate();
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.cancel();
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
Settings.INSTANCE.getInstance(activity.getApplication()).edit().putBoolean(key, false).apply();
for (int i=0; i<extraTitles.size(); i++)
Settings.INSTANCE.getInstance(activity.getApplication()).edit().putBoolean(extraKeys.get(i), false).apply();
}
})
.show();
}
public int getExtensionId(String packageName) {
if (mExtensions == null || mExtensions.isEmpty())
return 0;
for (int i=0; i<mExtensions.size(); i++)
if (mExtensions.get(i).componentName().getPackageName().equals(packageName))
return i;
return 0;
}
private boolean isAndroidAutoInstalled(Context context) {
final PackageManager packageManager = context.getPackageManager();
try {
packageManager.getPackageInfo("com.google.android.projection.gearhead", 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
}

19
application/vlc-android/src/org/videolan/vlc/extensions/Utils.java

@ -1,19 +0,0 @@
package org.videolan.vlc.extensions;
import android.net.Uri;
import org.videolan.medialibrary.MLServiceLocator;
import org.videolan.medialibrary.interfaces.media.MediaWrapper;
import org.videolan.vlc.extensions.api.VLCExtensionItem;
public class Utils {
public static MediaWrapper mediawrapperFromExtension(VLCExtensionItem vlcItem) {
MediaWrapper media = MLServiceLocator.getAbstractMediaWrapper(Uri.parse(vlcItem.link));
media.setDisplayTitle(vlcItem.title);
if (vlcItem.type != VLCExtensionItem.TYPE_OTHER_FILE)
media.setType(vlcItem.type);
return media;
}
}

5
application/vlc-android/src/org/videolan/vlc/gui/ContentActivity.kt

@ -35,7 +35,6 @@ import org.videolan.tools.Settings
import org.videolan.vlc.PlaybackService
import org.videolan.vlc.R
import org.videolan.vlc.RendererDelegate
import org.videolan.vlc.gui.browser.ExtensionBrowser
import org.videolan.vlc.gui.browser.MLStorageBrowserFragment
import org.videolan.vlc.gui.dialogs.RenderersDialog
import org.videolan.vlc.gui.helpers.UiTools
@ -82,10 +81,6 @@ open class ContentActivity : AudioPlayerContainerActivity(), SearchView.OnQueryT
super.onCreateOptionsMenu(menu)
if (current is AboutFragment) return true
menuInflater.inflate(R.menu.activity_option, menu)
if (current is ExtensionBrowser) {
menu.findItem(R.id.ml_menu_last_playlist).isVisible = false
menu.findItem(R.id.ml_menu_sortby).isVisible = false
}
if (current is Filterable) {
val filterable = current as Filterable?
searchItem = menu.findItem(R.id.ml_menu_filter)

9
application/vlc-android/src/org/videolan/vlc/gui/MainActivity.kt

@ -42,11 +42,8 @@ import org.videolan.tools.*
import org.videolan.vlc.BuildConfig
import org.videolan.vlc.R
import org.videolan.vlc.StartActivity
import org.videolan.vlc.extensions.ExtensionManagerService
import org.videolan.vlc.extensions.ExtensionsManager
import org.videolan.vlc.gui.audio.AudioBrowserFragment
import org.videolan.vlc.gui.browser.BaseBrowserFragment
import org.videolan.vlc.gui.browser.ExtensionBrowser
import org.videolan.vlc.gui.dialogs.AllAccessPermissionDialog
import org.videolan.vlc.gui.dialogs.NotificationPermissionManager
import org.videolan.vlc.gui.helpers.INavigator
@ -67,7 +64,6 @@ import java.util.concurrent.TimeUnit
private const val TAG = "VLC/MainActivity"
class MainActivity : ContentActivity(),
ExtensionManagerService.ExtensionManagerActivity,
INavigator by Navigator()
{
var refreshing: Boolean = false
@ -95,7 +91,6 @@ class MainActivity : ContentActivity(),
prepareActionBar()
/* Reload the latest preferences */
scanNeeded = savedInstanceState == null && settings.getBoolean(KEY_MEDIALIBRARY_AUTO_RESCAN, true)
if (BuildConfig.DEBUG) extensionsManager = ExtensionsManager.getInstance()
mediaLibrary = Medialibrary.getInstance()
// VLCBilling.getInstance(application).retrieveSkus()
@ -142,7 +137,6 @@ class MainActivity : ContentActivity(),
override fun onSaveInstanceState(outState: Bundle) {
val current = currentFragment
if (current !is ExtensionBrowser) supportFragmentManager.putFragment(outState, "current_fragment", current!!)
outState.putInt(EXTRA_TARGET, currentFragmentId)
super.onSaveInstanceState(outState)
}
@ -165,9 +159,6 @@ class MainActivity : ContentActivity(),
val fragment = currentFragment
if (fragment is BaseBrowserFragment && fragment.goBack()) {
return
} else if (fragment is ExtensionBrowser) {
fragment.goBack()
return
}
if (AndroidUtil.isNougatOrLater && isInMultiWindowMode) {
UiTools.confirmExit(this)

111
application/vlc-android/src/org/videolan/vlc/gui/browser/ExtensionAdapter.kt

@ -1,111 +0,0 @@
package org.videolan.vlc.gui.browser
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.net.toUri
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import org.videolan.medialibrary.MLServiceLocator
import org.videolan.medialibrary.interfaces.media.MediaWrapper
import org.videolan.vlc.R
import org.videolan.vlc.databinding.ExtensionItemViewBinding
import org.videolan.vlc.extensions.api.VLCExtensionItem
import org.videolan.vlc.media.MediaUtils
import java.util.*
class ExtensionAdapter(internal var fragment: ExtensionBrowser?) : RecyclerView.Adapter<ExtensionAdapter.ViewHolder>() {
private var itemsList: MutableList<VLCExtensionItem> = ArrayList()
val all: List<VLCExtensionItem>
get() = itemsList
inner class ViewHolder(var binding: ExtensionItemViewBinding) : RecyclerView.ViewHolder(binding.root), View.OnLongClickListener {
init {
binding.holder = this
}
fun onClick(v: View) {
val item = getItem(layoutPosition)
if (item.type == VLCExtensionItem.TYPE_DIRECTORY) {
fragment!!.browseItem(item)
} else if (item.type == VLCExtensionItem.TYPE_AUDIO || item.type == VLCExtensionItem.TYPE_VIDEO) {
val mw = MLServiceLocator.getAbstractMediaWrapper(item.link.toUri())
mw.setDisplayTitle(item.getTitle())
mw.description = item.getSubTitle()
mw.type = getTypeAccordingToItem(item.type)
MediaUtils.openMedia(v.context, mw)
}
}
fun onMoreClick(@Suppress("UNUSED_PARAMETER") v: View) {
openContextMenu()
}
override fun onLongClick(v: View): Boolean {
return openContextMenu()
}
private fun openContextMenu(): Boolean {
if (fragment == null)
return false
fragment!!.openContextMenu(layoutPosition)
return true
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(DataBindingUtil.inflate<ViewDataBinding>(LayoutInflater.from(parent.context), R.layout.extension_item_view, parent, false) as ExtensionItemViewBinding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.binding.item = item
holder.binding.executePendingBindings()
}
private fun getIconResId(item: VLCExtensionItem): Int {
return when (item.type) {
VLCExtensionItem.TYPE_AUDIO -> R.drawable.ic_browser_audio_normal
VLCExtensionItem.TYPE_DIRECTORY -> R.drawable.ic_menu_folder
VLCExtensionItem.TYPE_VIDEO -> R.drawable.ic_browser_video_normal
VLCExtensionItem.TYPE_SUBTITLE -> R.drawable.ic_browser_subtitle_normal
else -> R.drawable.ic_browser_unknown_normal
}
}
fun getItem(position: Int): VLCExtensionItem {
return itemsList[position]
}
override fun getItemCount(): Int {
return itemsList.size
}
fun addAll(list: List<VLCExtensionItem>) {
itemsList.clear()
itemsList.addAll(list)
notifyDataSetChanged()
}
fun clear() {
itemsList.clear()
}
private fun getTypeAccordingToItem(type: Int): Int {
return when (type) {
VLCExtensionItem.TYPE_DIRECTORY -> MediaWrapper.TYPE_DIR
VLCExtensionItem.TYPE_VIDEO -> MediaWrapper.TYPE_VIDEO
VLCExtensionItem.TYPE_AUDIO -> MediaWrapper.TYPE_AUDIO
VLCExtensionItem.TYPE_PLAYLIST -> MediaWrapper.TYPE_PLAYLIST
VLCExtensionItem.TYPE_SUBTITLE -> MediaWrapper.TYPE_SUBTITLE
else -> MediaWrapper.TYPE_ALL
}
}
companion object {
private const val TAG = "VLC/ExtensionAdapter"
}
}

200
application/vlc-android/src/org/videolan/vlc/gui/browser/ExtensionBrowser.kt

@ -1,200 +0,0 @@
package org.videolan.vlc.gui.browser
import android.content.Intent
import android.os.Bundle
import android.os.Message
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.floatingactionbutton.FloatingActionButton
import org.videolan.medialibrary.interfaces.media.MediaWrapper
import org.videolan.resources.CTX_APPEND
import org.videolan.resources.CTX_ITEM_DL
import org.videolan.resources.CTX_PLAY_ALL
import org.videolan.resources.CTX_PLAY_AS_AUDIO
import org.videolan.tools.WeakHandler
import org.videolan.vlc.R
import org.videolan.vlc.extensions.ExtensionManagerService
import org.videolan.vlc.extensions.Utils
import org.videolan.vlc.extensions.api.VLCExtensionItem
import org.videolan.vlc.gui.dialogs.CtxActionReceiver
import org.videolan.vlc.gui.view.SwipeRefreshLayout
import org.videolan.vlc.media.MediaUtils
class ExtensionBrowser : Fragment(), View.OnClickListener, androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener, CtxActionReceiver {
private var mTitle: String? = null
private var mAddDirectoryFAB: FloatingActionButton? = null
private val mAdapter: ExtensionAdapter = ExtensionAdapter(this)
private lateinit var mRecyclerView: RecyclerView
private lateinit var mEmptyView: TextView
private lateinit var mSwipeRefreshLayout: SwipeRefreshLayout
private var mExtensionManagerService: ExtensionManagerService? = null
private var showSettings = false
private var mustBeTerminated = false
private val mHandler = ExtensionBrowserHandler(this)
fun setExtensionService(service: ExtensionManagerService) {
mExtensionManagerService = service
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val bundle = savedInstanceState ?: arguments
if (bundle != null) {
mTitle = bundle.getString(KEY_TITLE)
showSettings = bundle.getBoolean(KEY_SHOW_FAB)
val list = bundle.getParcelableArrayList<VLCExtensionItem>(KEY_ITEMS_LIST)
if (list != null) mAdapter.addAll(list)
}
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val v = inflater.inflate(R.layout.directory_browser, container, false)
mRecyclerView = v.findViewById(R.id.network_list)
mEmptyView = v.findViewById(android.R.id.empty)
mEmptyView.setText(R.string.extension_empty)
mRecyclerView.layoutManager = LinearLayoutManager(activity)
mRecyclerView.adapter = mAdapter
registerForContextMenu(mRecyclerView)
mSwipeRefreshLayout = v.findViewById(R.id.swipeLayout)
mSwipeRefreshLayout.setOnRefreshListener(this)
return v
}
override fun onResume() {
super.onResume()
if (mustBeTerminated)
requireActivity().supportFragmentManager.beginTransaction().remove(this).commit()
mustBeTerminated = true
}
override fun onStart() {
super.onStart()
setTitle(mTitle)
updateDisplay()
if (showSettings) {
if (mAddDirectoryFAB == null) mAddDirectoryFAB = requireActivity().findViewById(R.id.fab)
mAddDirectoryFAB!!.setImageResource(R.drawable.ic_fab_add)
mAddDirectoryFAB!!.show()
mAddDirectoryFAB!!.setOnClickListener(this)
}
}
override fun onStop() {
super.onStop()
if (showSettings) {
mAddDirectoryFAB!!.hide()
mAddDirectoryFAB!!.setOnClickListener(null)
}
}
private fun setTitle(title: String?) {
val activity = activity as AppCompatActivity?
if (activity != null && activity.supportActionBar != null) {
activity.supportActionBar!!.title = title
requireActivity().invalidateOptionsMenu()
}
}
fun goBack() {
val activity = activity
if (activity != null && activity.supportFragmentManager.popBackStackImmediate()) requireActivity().finish()
}
fun doRefresh(title: String, items: List<VLCExtensionItem>) {
setTitle(title)
mAdapter.addAll(items)
}
private fun updateDisplay() {
if (mAdapter.itemCount > 0) {
mEmptyView.visibility = View.GONE
mRecyclerView.visibility = View.VISIBLE
} else {
mEmptyView.visibility = View.VISIBLE
mRecyclerView.visibility = View.GONE
}
}
fun browseItem(item: VLCExtensionItem) {
mExtensionManagerService!!.browse(item.stringId)
}
override fun onClick(v: View) {
if (v.id == mAddDirectoryFAB!!.id) {
val extension = mExtensionManagerService!!.currentExtension ?: return
val intent = Intent(Intent.ACTION_VIEW)
intent.component = extension.settingsActivity()
startActivity(intent)
}
}
override fun onRefresh() {
mExtensionManagerService!!.refresh()
mHandler.sendEmptyMessageDelayed(ACTION_HIDE_REFRESH, REFRESH_TIMEOUT.toLong())
}
fun openContextMenu(position: Int) {
//showContext(requireActivity(), this, position, mAdapter.getItem(position), CTX_PLAY_ALL or CTX_APPEND or CTX_PLAY_AS_AUDIO or CTX_ITEM_DL)
}
override fun onCtxAction(position: Int, option: Long) {
when (option) {
CTX_PLAY_ALL -> {
val items = mAdapter.all
val medias = ArrayList<MediaWrapper>(items.size)
for (vlcItem in items) medias.add(Utils.mediawrapperFromExtension(vlcItem))
MediaUtils.openList(activity, medias, position)
}
CTX_APPEND -> MediaUtils.appendMedia(activity, Utils.mediawrapperFromExtension(mAdapter.getItem(position)))
CTX_PLAY_AS_AUDIO -> {
val mw = Utils.mediawrapperFromExtension(mAdapter.getItem(position))
mw.addFlags(MediaWrapper.MEDIA_FORCE_AUDIO)
MediaUtils.openMedia(activity, mw)
}
CTX_ITEM_DL -> {
}
}//TODO
}
private inner class ExtensionBrowserHandler(owner: ExtensionBrowser) : WeakHandler<ExtensionBrowser>(owner) {
override fun handleMessage(msg: Message) {
when (msg.what) {
ACTION_HIDE_REFRESH -> {
removeMessages(ACTION_SHOW_REFRESH)
owner?.mSwipeRefreshLayout?.isRefreshing = false
}
ACTION_SHOW_REFRESH -> {
removeMessages(ACTION_HIDE_REFRESH)
owner?.mSwipeRefreshLayout?.isRefreshing = true
sendEmptyMessageDelayed(ACTION_HIDE_REFRESH, REFRESH_TIMEOUT.toLong())
}
}
}
}
companion object {
const val TAG = "VLC/ExtensionBrowser"
const val KEY_ITEMS_LIST = "key_items_list"
const val KEY_SHOW_FAB = "key_fab"
const val KEY_TITLE = "key_title"
private const val ACTION_HIDE_REFRESH = 42
private const val ACTION_SHOW_REFRESH = 43
private const val REFRESH_TIMEOUT = 5000
}
}

129
application/vlc-android/src/org/videolan/vlc/gui/helpers/Navigator.kt

@ -28,7 +28,6 @@ import android.content.*
import android.os.Bundle
import android.view.MenuItem
import androidx.core.content.edit
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.DefaultLifecycleObserver
@ -40,37 +39,27 @@ import org.videolan.resources.*
import org.videolan.tools.*
import org.videolan.vlc.BuildConfig
import org.videolan.vlc.R
import org.videolan.vlc.extensions.ExtensionManagerService
import org.videolan.vlc.extensions.ExtensionsManager
import org.videolan.vlc.extensions.api.VLCExtensionItem
import org.videolan.vlc.gui.*
import org.videolan.vlc.gui.audio.AudioBrowserFragment
import org.videolan.vlc.gui.browser.BaseBrowserFragment
import org.videolan.vlc.gui.browser.ExtensionBrowser
import org.videolan.vlc.gui.browser.MainBrowserFragment
import org.videolan.vlc.gui.helpers.UiTools.isTablet
import org.videolan.vlc.gui.preferences.PreferencesActivity
import org.videolan.vlc.gui.video.VideoGridFragment
import org.videolan.vlc.util.getScreenWidth
private const val TAG = "Navigator"
class Navigator : NavigationBarView.OnItemSelectedListener, DefaultLifecycleObserver, INavigator {
private val defaultFragmentId= R.id.nav_video
override var currentFragmentId : Int = 0
private val defaultFragmentId = R.id.nav_video
override var currentFragmentId: Int = 0
private var currentFragment: Fragment? = null
private set
private lateinit var activity: MainActivity
private lateinit var settings: SharedPreferences
private var extensionsService: ExtensionManagerService? = null
override lateinit var navigationView: List<NavigationBarView>
override lateinit var appbarLayout: AppBarLayout
override lateinit var extensionsManager: ExtensionsManager
override var extensionServiceConnection: ServiceConnection? = null
override var extensionManagerService: ExtensionManagerService? = null
private val isExtensionServiceBinded: Boolean
get() = extensionServiceConnection != null
override fun MainActivity.setupNavigation(state: Bundle?) {
activity = this
@ -80,7 +69,7 @@ class Navigator : NavigationBarView.OnItemSelectedListener, DefaultLifecycleObse
currentFragment = supportFragmentManager.getFragment(state, "current_fragment")
}
lifecycle.addObserver(this@Navigator)
navigationView = listOf(findViewById(R.id.navigation),findViewById(R.id.navigation_rail))
navigationView = listOf(findViewById(R.id.navigation), findViewById(R.id.navigation_rail))
appbarLayout = findViewById(R.id.appbar)
}
@ -91,14 +80,6 @@ class Navigator : NavigationBarView.OnItemSelectedListener, DefaultLifecycleObse
override fun onStop(owner: LifecycleOwner) {
navigationView.forEach { it.setOnItemSelectedListener(null) }
if (isExtensionServiceBinded) {
activity.unbindService(extensionServiceConnection!!)
extensionServiceConnection = null
}
if (currentIdIsExtension()) {
val name = extensionsManager.getExtensions(activity.application, false)[currentFragmentId].componentName().packageName
settings.putSingle("current_extension_name", name)
}
}
private fun getNewFragment(id: Int): Fragment {
@ -153,17 +134,12 @@ class Navigator : NavigationBarView.OnItemSelectedListener, DefaultLifecycleObse
override fun getFragmentWidth(activity: Activity): Int {
val screenWidth = activity.getScreenWidth()
return screenWidth - activity.resources.getDimension(R.dimen.navigation_margin).toInt()
return screenWidth - activity.resources.getDimension(R.dimen.navigation_margin).toInt()
}
private fun getTag(id: Int) = when (id) {
R.id.nav_settings -> ID_PREFERENCES
R.id.nav_audio -> ID_AUDIO
R.id.nav_playlists -> ID_PLAYLISTS
R.id.nav_directories -> ID_DIRECTORIES
R.id.nav_history -> ID_HISTORY
R.id.nav_mrl -> ID_MRL
R.id.nav_network -> ID_NETWORK
else -> ID_VIDEO
}
@ -172,75 +148,40 @@ class Navigator : NavigationBarView.OnItemSelectedListener, DefaultLifecycleObse
val current = currentFragment
appbarLayout.setExpanded(true, true)
if (item.groupId == R.id.extensions_group) {
if (currentFragmentId == id) {
clearBackstackFromClass(ExtensionBrowser::class.java)
return false
} else
extensionsService?.openExtension(id)
} else {
if (isExtensionServiceBinded) extensionsService?.disconnect()
if (current == null) {
if (current == null) {
return false
}
if (current is BaseFragment && current.actionMode != null) current.stopActionMode()
if (currentFragmentId == id) { /* Already selected */
// Go back at root level of current mProvider
if ((current as? BaseBrowserFragment)?.isStarted() == false) {
activity.supportFragmentManager.popBackStackImmediate("root", FragmentManager.POP_BACK_STACK_INCLUSIVE)
} else {
return false
}
if (current is BaseFragment && current.actionMode != null) current.stopActionMode()
if (currentFragmentId == id) { /* Already selected */
// Go back at root level of current mProvider
if ((current as? BaseBrowserFragment)?.isStarted() == false) {
activity.supportFragmentManager.popBackStackImmediate("root", FragmentManager.POP_BACK_STACK_INCLUSIVE)
} else {
return false
}
} else when (id) {
R.id.nav_settings -> activity.startActivityForResult(Intent(activity, PreferencesActivity::class.java), ACTIVITY_RESULT_PREFERENCES)
else -> {
activity.slideDownAudioPlayer()
showFragment(id)
}
} else when (id) {
// R.id.nav_settings -> activity.startActivityForResult(Intent(activity, PreferencesActivity::class.java), ACTIVITY_RESULT_PREFERENCES)
else -> {
activity.slideDownAudioPlayer()
showFragment(id)
}
}
return true
}
override fun displayExtensionItems(extensionId: Int, title: String, items: List<VLCExtensionItem>, showParams: Boolean, refresh: Boolean) {
if (refresh && currentFragment is ExtensionBrowser) {
(currentFragment as ExtensionBrowser).doRefresh(title, items)
} else {
val fragment = ExtensionBrowser()
fragment.arguments = bundleOf(ExtensionBrowser.KEY_ITEMS_LIST to ArrayList(items),
ExtensionBrowser.KEY_SHOW_FAB to showParams, ExtensionBrowser.KEY_TITLE to title)
extensionsService?.let { fragment.setExtensionService(it) }
when {
currentFragment !is ExtensionBrowser -> //case: non-extension to extension root
showFragment(fragment, extensionId, title)
currentFragmentId == extensionId -> //case: extension root to extension sub dir
showFragment(fragment, extensionId, title)
else -> { //case: extension to other extension root
clearBackstackFromClass(ExtensionBrowser::class.java)
showFragment(fragment, extensionId, title)
}
}
}
updateCheckedItem(extensionId)
}
private fun updateCheckedItem(id: Int) {
when (id) {
R.id.nav_settings -> return
else -> {
val currentId = currentFragmentId
navigationView.forEach {
val target = it.menu.findItem(id)
if (id != it.selectedItemId && target != null) {
val current = it.menu.findItem(currentId)
if (current != null) current.isChecked = false
target.isChecked = true
/* Save the tab status in pref */
settings.edit { putInt("fragment_id", id) }
}
}
val currentId = currentFragmentId
navigationView.forEach {
val target = it.menu.findItem(id)
if (id != it.selectedItemId && target != null) {
val current = it.menu.findItem(currentId)
if (current != null) current.isChecked = false
target.isChecked = true
/* Save the tab status in pref */
settings.edit { putInt("fragment_id", id) }
}
}
}
@ -250,17 +191,11 @@ class Navigator : NavigationBarView.OnItemSelectedListener, DefaultLifecycleObse
interface INavigator {
var navigationView: List<NavigationBarView>
var appbarLayout: AppBarLayout
var currentFragmentId : Int
var extensionsManager: ExtensionsManager
var extensionServiceConnection: ServiceConnection?
var extensionManagerService: ExtensionManagerService?
var currentFragmentId: Int
fun MainActivity.setupNavigation(state: Bundle?)
fun currentIdIsExtension() : Boolean
fun displayExtensionItems(extensionId: Int, title: String, items: List<VLCExtensionItem>, showParams: Boolean, refresh: Boolean)
fun currentIdIsExtension(): Boolean
fun reloadPreferences()
fun configurationChanged(size:Int)
fun configurationChanged(size: Int)
fun getFragmentWidth(activity: Activity): Int
}

122
application/vlc-android/src/org/videolan/vlc/gui/preferences/PreferencesExtensionFragment.kt

@ -1,122 +0,0 @@
package org.videolan.vlc.gui.preferences
import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import androidx.preference.CheckBoxPreference
import androidx.preference.Preference
import androidx.preference.SwitchPreferenceCompat
import androidx.preference.TwoStatePreference
import com.google.android.material.appbar.AppBarLayout
import org.videolan.tools.Settings
import org.videolan.tools.putSingle
import org.videolan.vlc.R
import org.videolan.vlc.extensions.ExtensionListing
import org.videolan.vlc.extensions.ExtensionsManager
import java.util.*
class PreferencesExtensionFragment : BasePreferenceFragment() {
private var extension: ExtensionListing? = null
private var extensionId: Int = 0
private var extensionTitle: String? = null
private var extensionKey: String? = null
private var extensionPackageName: String? = null
private lateinit var settings: SharedPreferences
// private var preferenceScreen: PreferenceScreen? = null
private var androidAutoAvailable = false
private val preferences = ArrayList<Preference>()
override fun onCreate(bundle: Bundle?) {
var newBundle = bundle
super.onCreate(newBundle)
settings = Settings.getInstance(requireActivity())
setHasOptionsMenu(true)
if (newBundle == null)
newBundle = arguments
if (newBundle != null) {
extensionKey = newBundle.getString("extension_key")
extensionPackageName = extensionKey!!.replace(ExtensionsManager.EXTENSION_PREFIX + "_", "")
extensionId = ExtensionsManager.getInstance().getExtensionId(extensionPackageName)
extension = ExtensionsManager.getInstance().getExtensions(requireActivity().application, false)[extensionId]
extensionTitle = extension!!.title()
setTitle(extensionTitle)
androidAutoAvailable = ExtensionsManager.androidAutoInstalled && extension!!.androidAutoEnabled()
createCheckboxes()
}
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putString("extension_key", extensionKey)
super.onSaveInstanceState(outState)
}
override fun onStart() {
super.onStart()
((activity as PreferencesActivity).findViewById<View>(R.id.appbar) as AppBarLayout).setExpanded(true, false)
}
override fun getXml(): Int {
return R.xml.preferences_extension_page
}
override fun getTitleId(): Int {
return 0
}
private fun setTitle(title: String?) {
val activity = activity as PreferencesActivity?
if (activity != null && activity.supportActionBar != null)
(getActivity() as PreferencesActivity).supportActionBar!!.title = title
}
private fun createCheckboxes() {
preferenceScreen = this.preferenceScreen
//Main switch
val switchPreference = SwitchPreferenceCompat(preferenceScreen!!.context)
switchPreference.title = preferenceScreen!!.context.getString(R.string.extension_prefs_activation_title).uppercase(Locale.getDefault())
switchPreference.key = extensionKey
switchPreference.isChecked = settings.getBoolean(extensionKey, false)
switchPreference.onPreferenceChangeListener = null
preferenceScreen!!.addPreference(switchPreference)
//Android-auto
if (androidAutoAvailable) {
val checkbox = CheckBoxPreference(preferenceScreen!!.context)
checkbox.setTitle(R.string.android_auto)
val key = extensionKey + "_" + ExtensionsManager.ANDROID_AUTO_SUFFIX
checkbox.key = key
checkbox.isChecked = switchPreference.isChecked && settings.getBoolean(key, false)
checkbox.isEnabled = switchPreference.isChecked
preferences.add(checkbox)
preferenceScreen!!.addPreference(checkbox)
}
}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
val key = preference.key
if (key == null || !key.startsWith(extensionKey!!))
return false
if (key == extensionKey) {
val switchPreference = preference as CheckBoxPreference
settings.putSingle(key, switchPreference.isChecked)
if (switchPreference.isChecked) {
for (checkbox in preferences)
checkbox.isEnabled = true
} else {
for (checkbox in preferences) {
(checkbox as CheckBoxPreference).isChecked = false
settings.putSingle(checkbox.getKey(), false)
checkbox.setEnabled(false)
}
}
} else if (key.endsWith("_" + ExtensionsManager.ANDROID_AUTO_SUFFIX)) {
settings.putSingle(preference.key, (preference as TwoStatePreference).isChecked)
}
return super.onPreferenceTreeClick(preference)
}
}

111
application/vlc-android/src/org/videolan/vlc/gui/preferences/PreferencesExtensions.kt

@ -1,111 +0,0 @@
package org.videolan.vlc.gui.preferences
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.View
import androidx.appcompat.widget.SwitchCompat
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import com.google.android.material.appbar.AppBarLayout
import org.videolan.vlc.R
import org.videolan.vlc.extensions.ExtensionListing
import org.videolan.vlc.extensions.ExtensionsManager
import org.videolan.vlc.gui.view.ClickableSwitchPreference
import org.videolan.tools.Settings
import org.videolan.tools.putSingle
import java.util.*
class PreferencesExtensions : BasePreferenceFragment() {
private var extensions: List<ExtensionListing> = ArrayList()
private lateinit var settings: SharedPreferences
private var count = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
settings = Settings.getInstance(requireContext())
extensions = ExtensionsManager.getInstance().getExtensions(requireActivity().applicationContext, false)
preferenceScreen = this.preferenceScreen
}
override fun onStart() {
super.onStart()
((activity as PreferencesActivity).findViewById<View>(R.id.appbar) as AppBarLayout).setExpanded(true, false)
createCheckboxes()
}
override fun onStop() {
super.onStop()
preferenceScreen!!.removeAll()
}
override fun getXml(): Int {
return R.xml.preferences_extensions
}
override fun getTitleId(): Int {
return R.string.extensions_prefs_category
}
private fun createCheckboxes() {
val pm = requireActivity().applicationContext.packageManager
for (i in extensions.indices) {
val extension = extensions[i]
val switchPreference = ClickableSwitchPreference(preferenceScreen!!.context)
switchPreference.title = extension.title()
switchPreference.summary = extension.description()
val key = ExtensionsManager.EXTENSION_PREFIX + "_" + extension.componentName().packageName
switchPreference.key = key
val iconRes = extension.menuIcon()
var extensionIcon: Drawable? = null
if (iconRes != 0) {
try {
extensionIcon = ContextCompat.getDrawable(requireActivity(), extension.menuIcon())
} catch (e: PackageManager.NameNotFoundException) {
}
}
if (extensionIcon != null)
switchPreference.icon = extensionIcon
else
try {
switchPreference.icon = pm.getApplicationIcon(extensions[i].componentName().packageName)
} catch (e: PackageManager.NameNotFoundException) {
switchPreference.setIcon(R.drawable.icon)
}
val checked = settings.getBoolean(key, false)
switchPreference.isChecked = checked
preferenceScreen!!.addPreference(switchPreference)
switchPreference.setOnSwitchClickListener(View.OnClickListener {
if ((view as SwitchCompat).isChecked)
settings.putSingle(key, true)
else
for ((key1) in settings.all)
if (key1.startsWith(ExtensionsManager.EXTENSION_PREFIX + "_"))
settings.putSingle(key1, false)
})
count++
}
if (count == 0) {
val emptyCategory = PreferenceCategory(preferenceScreen!!.context)
emptyCategory.setTitle(R.string.extensions_empty)
preferenceScreen!!.addPreference(emptyCategory)
}
}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
val key = preference.key
if (key == null || !key.startsWith(ExtensionsManager.EXTENSION_PREFIX + "_"))
return false
val fragment = PreferencesExtensionFragment()
fragment.arguments = bundleOf("extension_key" to key)
loadFragment(fragment)
return super.onPreferenceTreeClick(preference)
}
}

5
application/vlc-android/src/org/videolan/vlc/gui/preferences/PreferencesFragment.kt

@ -62,7 +62,6 @@ class PreferencesFragment : BasePreferenceFragment(), SharedPreferences.OnShared
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
findPreference<Preference>("extensions_category")?.isVisible = BuildConfig.DEBUG
arguments?.getParcelable<PreferenceItem>(EXTRA_PREF_END_POINT)?.let { endPoint ->
when (endPoint.parentScreen) {
R.xml.preferences_ui -> loadFragment(PreferencesUi().apply {
@ -77,9 +76,6 @@ class PreferencesFragment : BasePreferenceFragment(), SharedPreferences.OnShared
R.xml.preferences_audio -> loadFragment(PreferencesAudio().apply {
arguments = bundleOf(EXTRA_PREF_END_POINT to endPoint)
})
R.xml.preferences_extensions -> loadFragment(PreferencesExtensions().apply {
arguments = bundleOf(EXTRA_PREF_END_POINT to endPoint)
})
R.xml.preferences_adv -> loadFragment(PreferencesAdvanced().apply {
arguments = bundleOf(EXTRA_PREF_END_POINT to endPoint)
})
@ -109,7 +105,6 @@ class PreferencesFragment : BasePreferenceFragment(), SharedPreferences.OnShared
"video_category" -> loadFragment(PreferencesVideo())
"subtitles_category" -> loadFragment(PreferencesSubtitles())
"audio_category" -> loadFragment(PreferencesAudio())
"extensions_category" -> loadFragment(PreferencesExtensions())
"adv_category" -> loadFragment(PreferencesAdvanced())
"casting_category" -> loadFragment(PreferencesCasting())
PLAYBACK_HISTORY -> {

529
application/vlc-android/src/org/videolan/vlc/media/MediaSessionBrowser.kt

@ -23,15 +23,12 @@
*/
package org.videolan.vlc.media
import android.content.*
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context
import android.content.ServiceConnection
import android.net.Uri
import android.os.Bundle
import android.os.IBinder
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaDescriptionCompat
import androidx.annotation.StringRes
@ -42,19 +39,12 @@ import org.videolan.medialibrary.interfaces.media.Album
import org.videolan.medialibrary.interfaces.media.MediaWrapper
import org.videolan.medialibrary.media.MediaLibraryItem
import org.videolan.resources.*
import org.videolan.resources.AppContextProvider.appContext
import org.videolan.tools.*
import org.videolan.vlc.ArtworkProvider
import org.videolan.vlc.BuildConfig
import org.videolan.vlc.R
import org.videolan.vlc.extensions.ExtensionManagerService
import org.videolan.vlc.extensions.ExtensionManagerService.ExtensionManagerActivity
import org.videolan.vlc.extensions.ExtensionsManager
import org.videolan.vlc.extensions.api.VLCExtensionItem
import org.videolan.vlc.gui.helpers.MediaComparators
import org.videolan.vlc.gui.helpers.MediaComparators.formatArticles
import org.videolan.vlc.gui.helpers.UiTools.getDefaultAudioDrawable
import org.videolan.vlc.gui.helpers.getBitmapFromDrawable
import org.videolan.vlc.isPathValid
import org.videolan.vlc.media.MediaUtils.getMediaAlbum
import org.videolan.vlc.media.MediaUtils.getMediaArtist
@ -104,34 +94,7 @@ import java.util.concurrent.Semaphore
* Search
* //org.videolan.vlc/search?query=<query>
*/
class MediaSessionBrowser : ExtensionManagerActivity {
override fun displayExtensionItems(extensionId: Int, title: String, items: List<VLCExtensionItem>, showParams: Boolean, isRefresh: Boolean) {
if (showParams && items.size == 1 && items[0].getType() == VLCExtensionItem.TYPE_DIRECTORY) {
extensionManagerService?.browse(items[0].stringId)
return
}
var mediaItem: MediaDescriptionCompat.Builder
var extensionItem: VLCExtensionItem
for ((i, extensionItem) in items.withIndex()) {
if (extensionItem.getType() != VLCExtensionItem.TYPE_AUDIO && extensionItem.getType() != VLCExtensionItem.TYPE_DIRECTORY) continue
mediaItem = MediaDescriptionCompat.Builder()
val coverUri = extensionItem.getImageUri()
if (coverUri == null) mediaItem.setIconBitmap(getDefaultAudioDrawable(appContext).bitmap)
else mediaItem.setIconUri(coverUri)
mediaItem.setTitle(extensionItem.getTitle())
mediaItem.setSubtitle(extensionItem.getSubTitle())
val playable = extensionItem.getType() == VLCExtensionItem.TYPE_AUDIO
if (playable) {
mediaItem.setMediaId("${ExtensionsManager.EXTENSION_PREFIX}_${extensionId}_${extensionItem.getLink()}")
extensionItems.add(MediaBrowserCompat.MediaItem(mediaItem.build(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE))
} else {
mediaItem.setMediaId("${ExtensionsManager.EXTENSION_PREFIX}_${extensionId}_${extensionItem.stringId}")
extensionItems.add(MediaBrowserCompat.MediaItem(mediaItem.build(), MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
}
if (i == MAX_EXTENSION_SIZE - 1) break
}
extensionLock.release()
}
class MediaSessionBrowser {
companion object {
private const val TAG = "VLC/MediaSessionBrowser"
@ -165,15 +128,10 @@ class MediaSessionBrowser : ExtensionManagerActivity {
const val ID_GENRE = "$ID_LIBRARY/g"
const val MAX_HISTORY_SIZE = 100
const val MAX_COVER_ART_ITEMS = 50
private const val MAX_EXTENSION_SIZE = 100
private const val MAX_SUGGESTED_SIZE = 15
const val MAX_RESULT_SIZE = 800
// Extensions management
private var extensionServiceConnection: ServiceConnection? = null
private var extensionManagerService: ExtensionManagerService? = null
private val extensionItems: ArrayList<MediaBrowserCompat.MediaItem> = ArrayList()
private val extensionLock = Semaphore(0)
@WorkerThread
fun browse(context: Context, parentId: String, isShuffling: Boolean): List<MediaBrowserCompat.MediaItem>? {
@ -182,273 +140,215 @@ class MediaSessionBrowser : ExtensionManagerActivity {
var limitSize = false
val res = context.resources
//Extensions
if (parentId.startsWith(ExtensionsManager.EXTENSION_PREFIX)) {
if (extensionServiceConnection == null) {
createExtensionServiceConnection(context)
try {
extensionLock.acquire()
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
if (extensionServiceConnection == null) return null
val data = parentId.split("_").toTypedArray()
val index = Integer.valueOf(data[1])
extensionItems.clear()
if (data.size == 2) {
//case extension root
extensionManagerService?.connectService(index)
} else {
//case sub-directory
val stringId = parentId.replace("${ExtensionsManager.EXTENSION_PREFIX}_${index}_", "")
extensionManagerService?.browse(stringId)
}
try {
extensionLock.acquire()
} catch (e: InterruptedException) {
e.printStackTrace()
}
results = extensionItems
} else {
val ml = Medialibrary.getInstance()
val parentIdUri = parentId.toUri()
val page = parentIdUri.getQueryParameter("p")
val pageOffset = page?.toInt()?.times(MAX_RESULT_SIZE) ?: 0
val flatten = parentIdUri.getBooleanQueryParameter("f", false)
when (parentIdUri.removeQuery().toString()) {
ID_ROOT -> {
//List of Extensions
val extensions = ExtensionsManager.getInstance().getExtensions(context, true)
for ((i, extension) in extensions.withIndex()) {
val item = MediaDescriptionCompat.Builder()
if (extension.androidAutoEnabled()
&& Settings.getInstance(context).getBoolean(ExtensionsManager.EXTENSION_PREFIX + "_" + extension.componentName().packageName + "_" + ExtensionsManager.ANDROID_AUTO_SUFFIX, false)) {
item.setMediaId(ExtensionsManager.EXTENSION_PREFIX + "_" + i)
.setTitle(extension.title())
val iconRes = extension.menuIcon()
var b: Bitmap? = null
var extensionRes: Resources?
if (iconRes != 0) {
try {
extensionRes = context.packageManager
.getResourcesForApplication(extension.componentName().packageName)
b = BitmapFactory.decodeResource(extensionRes, iconRes)
} catch (ignored: PackageManager.NameNotFoundException) {
}
}
if (b != null) item.setIconBitmap(b) else try {
b = (context.packageManager.getApplicationIcon(extension.componentName().packageName) as BitmapDrawable).bitmap
item.setIconBitmap(b)
} catch (e: PackageManager.NameNotFoundException) {
b = context.getBitmapFromDrawable(R.drawable.icon)
item.setIconBitmap(b)
}
results.add(MediaBrowserCompat.MediaItem(item.build(), MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
}
}
//Home
if (flatten) browse(context, ID_HOME, isShuffling)?.let { results.addAll(it) }
else {
val homeMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_HOME)
.setTitle(res.getString(R.string.auto_home))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_home))
.setExtras(getContentStyle(CONTENT_STYLE_GRID_ITEM_HINT_VALUE, CONTENT_STYLE_GRID_ITEM_HINT_VALUE))
.build()
results.add(MediaBrowserCompat.MediaItem(homeMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
}
//Playlists
val playlistMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_PLAYLIST)
.setTitle(res.getString(R.string.playlists))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_playlist))
val ml = Medialibrary.getInstance()
val parentIdUri = parentId.toUri()
val page = parentIdUri.getQueryParameter("p")
val pageOffset = page?.toInt()?.times(MAX_RESULT_SIZE) ?: 0
val flatten = parentIdUri.getBooleanQueryParameter("f", false)
when (parentIdUri.removeQuery().toString()) {
ID_ROOT -> {
//Home
if (flatten) browse(context, ID_HOME, isShuffling)?.let { results.addAll(it) }
else {
val homeMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_HOME)
.setTitle(res.getString(R.string.auto_home))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_home))
.setExtras(getContentStyle(CONTENT_STYLE_GRID_ITEM_HINT_VALUE, CONTENT_STYLE_GRID_ITEM_HINT_VALUE))
.build()
results.add(MediaBrowserCompat.MediaItem(playlistMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
//My library
if (flatten) browse(context, ID_LIBRARY, isShuffling)?.let { results.addAll(it) }
else {
val libraryMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_LIBRARY)
.setTitle(res.getString(R.string.auto_my_library))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_audio))
.setExtras(getContentStyle(CONTENT_STYLE_CATEGORY_ITEM_HINT_VALUE))
.build()
results.add(MediaBrowserCompat.MediaItem(libraryMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
}
//Streams
val streamsMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_STREAM)
.setTitle(res.getString(R.string.streams))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_stream))
.build()
results.add(MediaBrowserCompat.MediaItem(streamsMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
return results
results.add(MediaBrowserCompat.MediaItem(homeMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
}
ID_HOME -> {
/* Shuffle All */
val audioCount = ml.audioCount
val shuffleAllPath = if (audioCount > 0) {
Uri.Builder()
.appendPath(ArtworkProvider.SHUFFLE_ALL)
.appendPath(ArtworkProvider.computeExpiration())
.appendPath("$audioCount")
.build()
} else null
val shuffleAllMediaDesc = getPlayAllBuilder(context, ID_SHUFFLE_ALL, R.string.shuffle_all_title, audioCount, shuffleAllPath).build()
results.add(MediaBrowserCompat.MediaItem(shuffleAllMediaDesc, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE))
/* Last Added */
val recentAudio = ml.getPagedAudio(Medialibrary.SORT_INSERTIONDATE, true, false, MAX_HISTORY_SIZE, 0)
val recentAudioSize = recentAudio.size
val lastAddedPath = if (recentAudioSize > 0) {
Uri.Builder()
.appendPath(ArtworkProvider.LAST_ADDED)
.appendPath("${ArtworkProvider.computeChecksum(recentAudio.toList())}")
.appendPath("$recentAudioSize")
.build()
} else null
val lastAddedMediaDesc = getPlayAllBuilder(context, ID_LAST_ADDED, R.string.auto_last_added_media, recentAudioSize, lastAddedPath).build()
results.add(MediaBrowserCompat.MediaItem(lastAddedMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
/* History */
if (Settings.getInstance(context).getBoolean(PLAYBACK_HISTORY, true)) {
val lastMediaPlayed = ml.lastMediaPlayed()?.toList()?.filter { isMediaAudio(it) }
if (!lastMediaPlayed.isNullOrEmpty()) {
val lastMediaSize = lastMediaPlayed.size.coerceAtMost(MAX_HISTORY_SIZE)
val historyPath = Uri.Builder()
.appendPath(ArtworkProvider.HISTORY)
.appendPath("${ArtworkProvider.computeChecksum(lastMediaPlayed)}")
.appendPath("$lastMediaSize")
.build()
val historyMediaDesc = getPlayAllBuilder(context, ID_HISTORY, R.string.history, lastMediaSize, historyPath).build()
results.add(MediaBrowserCompat.MediaItem(historyMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
}
}
return results
}
ID_LIBRARY -> {
//Artists
val artistsShowAll = Settings.getInstance(context).getBoolean(KEY_ARTISTS_SHOW_ALL, false)
val artistsCount = ml.getArtistsCount(artistsShowAll)
val artistsMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_ARTIST)
.setTitle(res.getString(R.string.artists))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_artist))
.setExtras(if (artistsCount > MAX_RESULT_SIZE) getContentStyle(CONTENT_STYLE_CATEGORY_ITEM_HINT_VALUE) else null)
.build()
results.add(MediaBrowserCompat.MediaItem(artistsMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
//Albums
val albumsMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_ALBUM)
.setTitle(res.getString(R.string.albums))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_album))
.setExtras(getContentStyle(if(ml.albumsCount > MAX_RESULT_SIZE) CONTENT_STYLE_CATEGORY_ITEM_HINT_VALUE else CONTENT_STYLE_GRID_ITEM_HINT_VALUE))
.build()
results.add(MediaBrowserCompat.MediaItem(albumsMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
//Tracks
val tracksMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_TRACK)
.setTitle(res.getString(R.string.tracks))
//Playlists
val playlistMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_PLAYLIST)
.setTitle(res.getString(R.string.playlists))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_playlist))
.setExtras(getContentStyle(CONTENT_STYLE_GRID_ITEM_HINT_VALUE, CONTENT_STYLE_GRID_ITEM_HINT_VALUE))
.build()
results.add(MediaBrowserCompat.MediaItem(playlistMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
//My library
if (flatten) browse(context, ID_LIBRARY, isShuffling)?.let { results.addAll(it) }
else {
val libraryMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_LIBRARY)
.setTitle(res.getString(R.string.auto_my_library))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_audio))
.setExtras(getContentStyle(if (ml.audioCount > MAX_RESULT_SIZE) CONTENT_STYLE_CATEGORY_ITEM_HINT_VALUE else CONTENT_STYLE_LIST_ITEM_HINT_VALUE))
.setExtras(getContentStyle(CONTENT_STYLE_CATEGORY_ITEM_HINT_VALUE))
.build()
results.add(MediaBrowserCompat.MediaItem(tracksMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
//Genres
val genresMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_GENRE)
.setTitle(res.getString(R.string.genres))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_genre))
.setExtras(getContentStyle(if (ml.genresCount > MAX_RESULT_SIZE) CONTENT_STYLE_CATEGORY_ITEM_HINT_VALUE else CONTENT_STYLE_LIST_ITEM_HINT_VALUE))
.build()
results.add(MediaBrowserCompat.MediaItem(genresMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
return results
}
ID_ARTIST -> {
val artistsShowAll = Settings.getInstance(context).getBoolean(KEY_ARTISTS_SHOW_ALL, false)
val artists = ml.getArtists(artistsShowAll, Medialibrary.SORT_ALPHA, false, false)
artists.sortWith(MediaComparators.ANDROID_AUTO)
if (page == null && artists.size > MAX_RESULT_SIZE)
return paginateLibrary(artists, parentIdUri, res.getResourceUri(R.drawable.ic_auto_artist))
list = artists.copyOfRange(pageOffset.coerceAtMost(artists.size), (pageOffset + MAX_RESULT_SIZE).coerceAtMost(artists.size))
}
ID_ALBUM -> {
val albums = ml.getAlbums(Medialibrary.SORT_ALPHA, false, false)
albums.sortWith(MediaComparators.ANDROID_AUTO)
if (page == null && albums.size > MAX_RESULT_SIZE)
return paginateLibrary(albums, parentIdUri, res.getResourceUri(R.drawable.ic_auto_album), getContentStyle(CONTENT_STYLE_GRID_ITEM_HINT_VALUE))
list = albums.copyOfRange(pageOffset.coerceAtMost(albums.size), (pageOffset + MAX_RESULT_SIZE).coerceAtMost(albums.size))
}
ID_TRACK -> {
val tracks = ml.getAudio(Medialibrary.SORT_ALPHA, false, false)
tracks.sortWith(MediaComparators.ANDROID_AUTO)
if (page == null && tracks.size > MAX_RESULT_SIZE)
return paginateLibrary(tracks, parentIdUri, res.getResourceUri(R.drawable.ic_auto_audio))
list = tracks.copyOfRange(pageOffset.coerceAtMost(tracks.size), (pageOffset + MAX_RESULT_SIZE).coerceAtMost(tracks.size))
results.add(MediaBrowserCompat.MediaItem(libraryMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
}
ID_GENRE -> {
val genres = ml.getGenres(Medialibrary.SORT_ALPHA, false, false)
genres.sortWith(MediaComparators.ANDROID_AUTO)
if (page == null && genres.size > MAX_RESULT_SIZE)
return paginateLibrary(genres, parentIdUri, res.getResourceUri(R.drawable.ic_auto_genre))
list = genres.copyOfRange(pageOffset.coerceAtMost(genres.size), (pageOffset + MAX_RESULT_SIZE).coerceAtMost(genres.size))
//Streams
val streamsMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_STREAM)
.setTitle(res.getString(R.string.streams))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_stream))
.build()
results.add(MediaBrowserCompat.MediaItem(streamsMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
return results
}
ID_HOME -> {
/* Shuffle All */
val audioCount = ml.audioCount
val shuffleAllPath = if (audioCount > 0) {
Uri.Builder()
.appendPath(ArtworkProvider.SHUFFLE_ALL)
.appendPath(ArtworkProvider.computeExpiration())
.appendPath("$audioCount")
.build()
} else null
val shuffleAllMediaDesc = getPlayAllBuilder(context, ID_SHUFFLE_ALL, R.string.shuffle_all_title, audioCount, shuffleAllPath).build()
results.add(MediaBrowserCompat.MediaItem(shuffleAllMediaDesc, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE))
/* Last Added */
val recentAudio = ml.getPagedAudio(Medialibrary.SORT_INSERTIONDATE, true, false, MAX_HISTORY_SIZE, 0)
val recentAudioSize = recentAudio.size
val lastAddedPath = if (recentAudioSize > 0) {
Uri.Builder()
.appendPath(ArtworkProvider.LAST_ADDED)
.appendPath("${ArtworkProvider.computeChecksum(recentAudio.toList())}")
.appendPath("$recentAudioSize")
.build()
} else null
val lastAddedMediaDesc = getPlayAllBuilder(context, ID_LAST_ADDED, R.string.auto_last_added_media, recentAudioSize, lastAddedPath).build()
results.add(MediaBrowserCompat.MediaItem(lastAddedMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
/* History */
if (Settings.getInstance(context).getBoolean(PLAYBACK_HISTORY, true)) {
val lastMediaPlayed = ml.lastMediaPlayed()?.toList()?.filter { isMediaAudio(it) }
if (!lastMediaPlayed.isNullOrEmpty()) {
val lastMediaSize = lastMediaPlayed.size.coerceAtMost(MAX_HISTORY_SIZE)
val historyPath = Uri.Builder()
.appendPath(ArtworkProvider.HISTORY)
.appendPath("${ArtworkProvider.computeChecksum(lastMediaPlayed)}")
.appendPath("$lastMediaSize")
.build()
val historyMediaDesc = getPlayAllBuilder(context, ID_HISTORY, R.string.history, lastMediaSize, historyPath).build()
results.add(MediaBrowserCompat.MediaItem(historyMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
}
}
ID_PLAYLIST -> {
return results
}
ID_LIBRARY -> {
//Artists
val artistsShowAll = Settings.getInstance(context).getBoolean(KEY_ARTISTS_SHOW_ALL, false)
val artistsCount = ml.getArtistsCount(artistsShowAll)
val artistsMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_ARTIST)
.setTitle(res.getString(R.string.artists))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_artist))
.setExtras(if (artistsCount > MAX_RESULT_SIZE) getContentStyle(CONTENT_STYLE_CATEGORY_ITEM_HINT_VALUE) else null)
.build()
results.add(MediaBrowserCompat.MediaItem(artistsMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
//Albums
val albumsMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_ALBUM)
.setTitle(res.getString(R.string.albums))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_album))
.setExtras(getContentStyle(if (ml.albumsCount > MAX_RESULT_SIZE) CONTENT_STYLE_CATEGORY_ITEM_HINT_VALUE else CONTENT_STYLE_GRID_ITEM_HINT_VALUE))
.build()
results.add(MediaBrowserCompat.MediaItem(albumsMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
//Tracks
val tracksMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_TRACK)
.setTitle(res.getString(R.string.tracks))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_audio))
.setExtras(getContentStyle(if (ml.audioCount > MAX_RESULT_SIZE) CONTENT_STYLE_CATEGORY_ITEM_HINT_VALUE else CONTENT_STYLE_LIST_ITEM_HINT_VALUE))
.build()
results.add(MediaBrowserCompat.MediaItem(tracksMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
//Genres
val genresMediaDesc = MediaDescriptionCompat.Builder()
.setMediaId(ID_GENRE)
.setTitle(res.getString(R.string.genres))
.setIconUri(res.getResourceUri(R.drawable.ic_auto_genre))
.setExtras(getContentStyle(if (ml.genresCount > MAX_RESULT_SIZE) CONTENT_STYLE_CATEGORY_ITEM_HINT_VALUE else CONTENT_STYLE_LIST_ITEM_HINT_VALUE))
.build()
results.add(MediaBrowserCompat.MediaItem(genresMediaDesc, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE))
return results
}
ID_ARTIST -> {
val artistsShowAll = Settings.getInstance(context).getBoolean(KEY_ARTISTS_SHOW_ALL, false)
val artists = ml.getArtists(artistsShowAll, Medialibrary.SORT_ALPHA, false, false)
artists.sortWith(MediaComparators.ANDROID_AUTO)
if (page == null && artists.size > MAX_RESULT_SIZE)
return paginateLibrary(artists, parentIdUri, res.getResourceUri(R.drawable.ic_auto_artist))
list = artists.copyOfRange(pageOffset.coerceAtMost(artists.size), (pageOffset + MAX_RESULT_SIZE).coerceAtMost(artists.size))
}
ID_ALBUM -> {
val albums = ml.getAlbums(Medialibrary.SORT_ALPHA, false, false)
albums.sortWith(MediaComparators.ANDROID_AUTO)
if (page == null && albums.size > MAX_RESULT_SIZE)
return paginateLibrary(albums, parentIdUri, res.getResourceUri(R.drawable.ic_auto_album), getContentStyle(CONTENT_STYLE_GRID_ITEM_HINT_VALUE))
list = albums.copyOfRange(pageOffset.coerceAtMost(albums.size), (pageOffset + MAX_RESULT_SIZE).coerceAtMost(albums.size))
}
ID_TRACK -> {
val tracks = ml.getAudio(Medialibrary.SORT_ALPHA, false, false)
tracks.sortWith(MediaComparators.ANDROID_AUTO)
if (page == null && tracks.size > MAX_RESULT_SIZE)
return paginateLibrary(tracks, parentIdUri, res.getResourceUri(R.drawable.ic_auto_audio))
list = tracks.copyOfRange(pageOffset.coerceAtMost(tracks.size), (pageOffset + MAX_RESULT_SIZE).coerceAtMost(tracks.size))
}
ID_GENRE -> {
val genres = ml.getGenres(Medialibrary.SORT_ALPHA, false, false)
genres.sortWith(MediaComparators.ANDROID_AUTO)
if (page == null && genres.size > MAX_RESULT_SIZE)
return paginateLibrary(genres, parentIdUri, res.getResourceUri(R.drawable.ic_auto_genre))
list = genres.copyOfRange(pageOffset.coerceAtMost(genres.size), (pageOffset + MAX_RESULT_SIZE).coerceAtMost(genres.size))
}
ID_PLAYLIST -> {
list = ml.playlists
list.sortWith(MediaComparators.ANDROID_AUTO)
}
ID_STREAM -> {
list = ml.lastStreamsPlayed()
list.sortWith(MediaComparators.ANDROID_AUTO)
}
ID_LAST_ADDED -> {
limitSize = true
list = ml.getPagedAudio(Medialibrary.SORT_INSERTIONDATE, true, false, MAX_HISTORY_SIZE, 0)
}
ID_HISTORY -> {
limitSize = true
list = ml.lastMediaPlayed()?.toList()?.filter { isMediaAudio(it) }?.toTypedArray()
}
ID_SUGGESTED -> return buildSuggestions(context, parentId, ml)
else -> {
val id = ContentUris.parseId(parentIdUri)
when (parentIdUri.retrieveParent().toString()) {
ID_ALBUM -> list = ml.getAlbum(id).tracks
ID_ARTIST -> {
val artist = ml.getArtist(id)
list = artist.albums
if (list != null && list.size > 1) {
val hasArtwork = list.any { !it.artworkMrl.isNullOrEmpty() && isPathValid(it.artworkMrl) }
val shuffleMode = isShuffling && artist.tracksCount > 2
val playAllPath = if (hasArtwork) {
Uri.Builder()
.appendPath(ArtworkProvider.PLAY_ALL)
.appendPath(ArtworkProvider.ARTIST)
.appendPath("${artist.tracksCount}")
.appendPath("$id")
.appendQueryParameter(ArtworkProvider.SHUFFLE, "${shuffleMode.toInt()}")
.build()
} else null
val title = if (shuffleMode) R.string.shuffle_all_title else R.string.play_all
val playAllMediaDesc = getPlayAllBuilder(context, parentId, title, artist.tracksCount, playAllPath).build()
results.add(MediaBrowserCompat.MediaItem(playAllMediaDesc, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE))
}
}
ID_GENRE -> {
val genre = ml.getGenre(id)
list = genre.albums
val tracksCount = list.sumOf { it.tracksCount }
if (list != null && list.size > 1) {
val shuffleMode = isShuffling && tracksCount > 2
val playAllPath = Uri.Builder()
list.sortWith(MediaComparators.ANDROID_AUTO)
}
ID_STREAM -> {
list = ml.lastStreamsPlayed()
list.sortWith(MediaComparators.ANDROID_AUTO)
}
ID_LAST_ADDED -> {
limitSize = true
list = ml.getPagedAudio(Medialibrary.SORT_INSERTIONDATE, true, false, MAX_HISTORY_SIZE, 0)
}
ID_HISTORY -> {
limitSize = true
list = ml.lastMediaPlayed()?.toList()?.filter { isMediaAudio(it) }?.toTypedArray()
}
ID_SUGGESTED -> return buildSuggestions(context, parentId, ml)
else -> {
val id = ContentUris.parseId(parentIdUri)
when (parentIdUri.retrieveParent().toString()) {
ID_ALBUM -> list = ml.getAlbum(id).tracks
ID_ARTIST -> {
val artist = ml.getArtist(id)
list = artist.albums
if (list != null && list.size > 1) {
val hasArtwork = list.any { !it.artworkMrl.isNullOrEmpty() && isPathValid(it.artworkMrl) }
val shuffleMode = isShuffling && artist.tracksCount > 2
val playAllPath = if (hasArtwork) {
Uri.Builder()
.appendPath(ArtworkProvider.PLAY_ALL)
.appendPath(ArtworkProvider.GENRE)
.appendPath("$tracksCount")
.appendPath(ArtworkProvider.ARTIST)
.appendPath("${artist.tracksCount}")
.appendPath("$id")
.appendQueryParameter(ArtworkProvider.SHUFFLE, "${shuffleMode.toInt()}")
.build()
val title = if (shuffleMode) R.string.shuffle_all_title else R.string.play_all
val playAllMediaDesc = getPlayAllBuilder(context, parentId, title, tracksCount, playAllPath).build()
results.add(MediaBrowserCompat.MediaItem(playAllMediaDesc, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE))
}
} else null
val title = if (shuffleMode) R.string.shuffle_all_title else R.string.play_all
val playAllMediaDesc = getPlayAllBuilder(context, parentId, title, artist.tracksCount, playAllPath).build()
results.add(MediaBrowserCompat.MediaItem(playAllMediaDesc, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE))
}
}
ID_GENRE -> {
val genre = ml.getGenre(id)
list = genre.albums
val tracksCount = list.sumOf { it.tracksCount }
if (list != null && list.size > 1) {
val shuffleMode = isShuffling && tracksCount > 2
val playAllPath = Uri.Builder()
.appendPath(ArtworkProvider.PLAY_ALL)
.appendPath(ArtworkProvider.GENRE)
.appendPath("$tracksCount")
.appendPath("$id")
.appendQueryParameter(ArtworkProvider.SHUFFLE, "${shuffleMode.toInt()}")
.build()
val title = if (shuffleMode) R.string.shuffle_all_title else R.string.play_all
val playAllMediaDesc = getPlayAllBuilder(context, parentId, title, tracksCount, playAllPath).build()
results.add(MediaBrowserCompat.MediaItem(playAllMediaDesc, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE))
}
}
}
@ -754,30 +654,5 @@ class MediaSessionBrowser : ExtensionManagerActivity {
.setSubtitle(res.getString(R.string.track_number, trackCount))
.setIconUri(if (uri != null) ArtworkProvider.buildUri(ctx, uri) else res.getResourceUri(R.drawable.ic_auto_playall))
}
private fun createExtensionServiceConnection(context: Context) {
extensionServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
extensionManagerService = (service as ExtensionManagerService.LocalBinder).service.apply {
setExtensionManagerActivity(instance)
}
extensionLock.release()
}
override fun onServiceDisconnected(name: ComponentName) {
context.unbindService(extensionServiceConnection!!)
extensionServiceConnection = null
extensionManagerService!!.stopSelf()
}
}
extensionServiceConnection?.let {
val intent = Intent(context, ExtensionManagerService::class.java)
if (!context.bindService(intent, it, Context.BIND_AUTO_CREATE)) extensionServiceConnection = null
}
}
fun unbindExtensionConnection() {
extensionManagerService?.disconnect()
}
}
}

1
extension-api/.gitignore

@ -1 +0,0 @@
/build

56
extension-api/build.gradle

@ -1,56 +0,0 @@
/*
* *************************************************************************
* build.gradle.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.
* ***************************************************************************
*/
apply plugin: 'com.android.library'
ext {
library_version = '0.1'
repoName = 'Android'
libraryName = 'VLC-API'
desc = 'SDK for VLC-Android extensions'
lib_artifact = 'vlc-extensions'
libraryDescription = 'SDK for VLC-Android extensions'
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
namespace 'org.videolan.vlc.plugin.api'
}
dependencies {
api "androidx.appcompat:appcompat:$rootProject.ext.appCompatVersion"
testImplementation "junit:junit:$rootProject.ext.junitVersion"
}
apply from: '../buildsystem/publish.gradle'

17
extension-api/proguard-rules.pro

@ -1,17 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /home/dekans/SDK/android-sdk-linux/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

33
extension-api/src/main/AndroidManifest.xml

@ -1,33 +0,0 @@
<!--
~ **************************************************************************
~ AndroidManifest.xml.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.
~ ***************************************************************************
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:supportsRtl="true">
<activity android:name="org.videolan.vlc.extensions.api.WarningActivity"
android:theme="@style/TransparentTheme" />
</application>
</manifest>

11
extension-api/src/main/aidl/org/videolan/vlc/extensions/api/IExtensionHost.aidl

@ -1,11 +0,0 @@
package org.videolan.vlc.extensions.api;
import org.videolan.vlc.extensions.api.VLCExtensionItem;
import android.net.Uri;
interface IExtensionHost {
// Protocol version 1
oneway void updateList(in String title, in List<VLCExtensionItem> items, boolean showParams, boolean isRefresh);
oneway void playUri(in Uri uri, String title);
oneway void unBind(int index);
}

11
extension-api/src/main/aidl/org/videolan/vlc/extensions/api/IExtensionService.aidl

@ -1,11 +0,0 @@
package org.videolan.vlc.extensions.api;
import org.videolan.vlc.extensions.api.IExtensionHost;
import org.videolan.vlc.extensions.api.VLCExtensionItem;
interface IExtensionService {
// Protocol version 1
oneway void onInitialize(int index, in IExtensionHost host);
oneway void browse(String stringId);
oneway void refresh();
}

3
extension-api/src/main/aidl/org/videolan/vlc/extensions/api/VLCExtensionItem.aidl

@ -1,3 +0,0 @@
package org.videolan.vlc.extensions.api;
parcelable VLCExtensionItem;

222
extension-api/src/main/java/org/videolan/vlc/extensions/api/VLCExtensionItem.java

@ -1,222 +0,0 @@
/*
* *************************************************************************
* VLCExtensionItem.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.videolan.vlc.extensions.api;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
/**
* A parcelable, serializable object containing information about medium to be sent to VLC
* for browsing of playback
*
* <p>
* This class follows the <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent
* interface</a> style, using method chaining to provide for more readable code. For example, to set
* the title and link of this data, use {@link #setTitle(String)} and {@link #setLink(String)}
* methods like so:
*
* <pre class="prettyprint">
* VLCExtensionItem data = new VLCExtensionItem(id, null)
* .setTitle("My Video")
* .setLink("http://mysite.net/myvideo.ogv");
* </pre>
*
*/
public class VLCExtensionItem implements Parcelable {
/**
* Item type to show it as a directory in VLC browser
*/
public static final int TYPE_DIRECTORY = 0;
/**
* Item type to show it as a video medium in VLC browser
*/
public static final int TYPE_VIDEO = 1;
/**
* Item type to show it as an audio medium in VLC browser
*/
public static final int TYPE_AUDIO = 2;
/**
* Item type to show it as a playlist item in VLC browser
*/
public static final int TYPE_PLAYLIST = 3;
/**
* Item type to show it as a subtitle file in VLC browser
*/
public static final int TYPE_SUBTITLE = 4;
/**
* Unknown type, VLC will guess from its {#link link} or title
*/
public static final int TYPE_OTHER_FILE = 5;
public String stringId;
public String link;
public String title;
public String subTitle;
public Uri imageUri; // for content provider
public int type; // Using VLC icons. maybe with iconRes?
/**
* Simple constructor, with only ids.
* You have to provide a String or int id for browsable elements (with type #TYPE_DIRECTORY)
*
* @param stringId The String to use as an ID, set to #null if you prefer to use the #intId
*/
public VLCExtensionItem(String stringId) {
this.stringId = stringId;
}
public VLCExtensionItem() {}
private VLCExtensionItem(Parcel in) {
readFromParcel(in);
}
/**
* Returns the subTitle of this item. e.g. media artist or album
*/
public String getSubTitle() {
return subTitle;
}
/**
* Sets the subTitles string of this item.
*
* @param subTitle The subTitle string to set.
*/
public VLCExtensionItem setSubTitle(String subTitle) {
this.subTitle = subTitle;
return this;
}
/**
* Returns the uri string of the {#link VLCExtensionItem} for playback or download
*/
public String getLink() {
return link;
}
/**
* Sets the uri String of the {#link VLCExtensionItem}
*
* @param link The medium link.
*/
public VLCExtensionItem setLink(String link) {
this.link = link;
return this;
}
/**
* returns the {#link VLCExtensionItem} title
*/
public String getTitle() {
return title;
}
/**
* Sets the {#link VLCExtensionItem} title
*
* @param title The string to set as title.
*/
public VLCExtensionItem setTitle(String title) {
this.title = title;
return this;
}
/**
* Returns the {#link VLCExtensionItem} icon image link
*/
public Uri getImageUri() {
return imageUri;
}
/**
* Sets the uri string of the {#link VLCExtensionItem} icon image.
*
* @param imageUri The uri string to set.
*/
public VLCExtensionItem setImageUri(Uri imageUri) {
this.imageUri = imageUri;
return this;
}
/**
* Returns the {#link VLCExtensionItem} type
* @see {#link setType}.
*/
public int getType() {
return type;
}
/**
* Sets the type of the {#link VLCExtensionItem}
*
* @param type The type among {#link TYPE_DIRECTORY}, {#link TYPE_VIDEO},
* {#link TYPE_AUDIO} or {#link TYPE_OTHER_FILE}.
*/
public VLCExtensionItem setType(int type) {
this.type = type;
return this;
}
public static final Parcelable.Creator<VLCExtensionItem> CREATOR = new
Parcelable.Creator<VLCExtensionItem>() {
@Override
public VLCExtensionItem createFromParcel(Parcel in) {
return new VLCExtensionItem(in);
}
@Override
public VLCExtensionItem[] newArray(int size) {
return new VLCExtensionItem[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(stringId);
dest.writeString(link);
dest.writeString(title);
dest.writeString(subTitle);
dest.writeParcelable(imageUri, 0);
dest.writeInt(type);
}
public void readFromParcel(Parcel in) {
stringId = in.readString();
link = in.readString();
title = in.readString();
subTitle = in.readString();
imageUri = in.readParcelable(null);
type = in.readInt();
}
}

166
extension-api/src/main/java/org/videolan/vlc/extensions/api/VLCExtensionService.java

@ -1,166 +0,0 @@
/**
* **************************************************************************
* VLCExtensionService.java
* ****************************************************************************
* Copyright © 2016 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.videolan.vlc.extensions.api;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.List;
public abstract class VLCExtensionService extends Service{
private static final String TAG = "VLC/ExtensionService";
private int mIndex = -1;
private static final ComponentName VLC_HOST_SERVICE =
new ComponentName("org.videolan.vlc",
"org.videolan.vlc.plugin.PluginService");
IExtensionHost mHost;
Context mContext = this;
private volatile Looper mServiceLooper;
protected volatile Handler mServiceHandler;
/**
* Called by VLC when users wants to browse one of your {#link VLCExtensionItem.TYPE_DIRECTORY}
* VLC provides {#intId} and {#stringId} from chosen item
*
* @param stringId String id of the item to browse
*/
protected abstract void browse(@Nullable String stringId);
/**
* Called by VLC when user wants to refresh the current list displayed by the extension.
*/
protected abstract void refresh();
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread(
"VLCExtension:" + getClass().getSimpleName());
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new Handler(mServiceLooper);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onDestroy() {
super.onDestroy();
try {
mHost.unBind(mIndex);
} catch (RemoteException e) {}
mServiceHandler.removeCallbacksAndMessages(null); // remove all callbacks
mServiceLooper.quit();
}
/**
* Starts playback of the given uri by VLC
*
* @param uri The uri to play
* @param title Optional - Set the media title to be displayed.
* Otherwise, it will be guessed from uri.
*/
public void playUri(@NonNull Uri uri, @Nullable String title) {
try {
mHost.playUri(uri, title);
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* Displays given items in VLC browser.
*
* @param title The title shown in VLC action bar for this list display.
* @param items The items to show.
* @param showParams Wether you want to show the FAB to launch your extension settings activity.
*/
protected void updateList(String title, List<VLCExtensionItem> items, boolean showParams, boolean isrefresh){
try {
mHost.updateList(title, items, showParams, isrefresh);
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* Called once VLC is binded to your service.
* Use it to call {@link #updateList(String, List, boolean, boolean)} with root level elements
* if you want VLC to handle your extension browsing.
*/
protected void onInitialize() {
}
private final IExtensionService.Stub mBinder = new IExtensionService.Stub() {
@Override
public void onInitialize(int index, IExtensionHost host) throws RemoteException {
mIndex = index;
mHost = host;
mServiceHandler.post(new Runnable() {
@Override
public void run() {
VLCExtensionService.this.onInitialize();
}
});
}
@Override
public void browse(final String text) throws RemoteException {
mServiceHandler.post(new Runnable() {
@Override
public void run() {
VLCExtensionService.this.browse(text);
}
});
}
@Override
public void refresh() {
mServiceHandler.post(new Runnable() {
@Override
public void run() {
VLCExtensionService.this.refresh();
}
});
}
};
}

37
extension-api/src/main/java/org/videolan/vlc/extensions/api/WarningActivity.java

@ -1,37 +0,0 @@
/**
* **************************************************************************
* WarningActivity.java
* ****************************************************************************
* Copyright © 2016 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.videolan.vlc.extensions.api;
import android.app.Activity;
import android.os.Bundle;
import org.videolan.vlc.extensions.api.tools.Dialogs;
public class WarningActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Dialogs.showInstallVlc(this);
}
}

134
extension-api/src/main/java/org/videolan/vlc/extensions/api/tools/Dialogs.java

@ -1,134 +0,0 @@
/**
* **************************************************************************
* Dialogs.java
* ****************************************************************************
* Copyright © 2016 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.videolan.vlc.extensions.api.tools;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import org.videolan.vlc.plugin.api.R;
public class Dialogs {
/**
* Constant value to disable Dialog message.
*/
public static final int NO_MESSAGE = 0;
/**
* Facility function to show an AlertDialog
* @param context The context to use for Dialog display
* @param title Title for the AlertDialog
* @param message Message to display (set to NO_MESSAGE if you don't want to show any message)
* @param cancelListener The listener wich will be triggered after dialog cancelation.
* If null, the dialog is not cancelable
* @return the AlertDialog instance
*/
public static AlertDialog showAlertDialog(@NonNull Context context, @NonNull int title,
@Nullable int message,
@Nullable DialogInterface.OnCancelListener cancelListener) {
return showAlertDialog(context, title, message, cancelListener, null, null);
}
/**
* Facility function to show an AlertDialog
* @param context The context to use for Dialog display
* @param title Title for the AlertDialog
* @param message Message to display (set to NO_MESSAGE if you don't want to show any message)
* @param cancelListener The listener wich will be triggered after dialog cancelation.
* If null, the dialog is not cancelable
* @param positiveAction DialogInterface.OnClickListener wich will be triggered on 'OK' button click.
* If null, the AlertDialog doesn't have positive button.
* @param negativeAction ialogInterface.OnClickListener wich will be triggered on 'Cancel' button click.
* If null, the AlertDialog doesn't have negative button.
* @return the AlertDialog instance
*/
public static AlertDialog showAlertDialog(@NonNull Context context, @NonNull int title,
@NonNull int message,
@Nullable DialogInterface.OnCancelListener cancelListener,
@Nullable DialogInterface.OnClickListener positiveAction,
@Nullable DialogInterface.OnClickListener negativeAction) {
AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setTitle(title);
if (message > 0)
builder.setMessage(message);
if (cancelListener != null) {
builder.setCancelable(true)
.setOnCancelListener(cancelListener);
}
if (positiveAction != null) {
builder.setPositiveButton(android.R.string.ok, positiveAction);
}
if (negativeAction != null) {
builder.setPositiveButton(android.R.string.cancel, negativeAction);
}
AlertDialog alert = builder.create();
alert.show();
return alert;
}
/**
* Show an AlertDialog warning user the extension needs internet access,
* and offers him to activate wifi connection
* @param context The context to use for Dialog display
* @param cancelListener The listener wich will be triggered after dialog cancelation or positive action
* @return the AlertDialog instance
*/
public static AlertDialog showNetworkNeeded(@NonNull final Context context,
@NonNull final DialogInterface.OnCancelListener cancelListener) {
return showAlertDialog(context, R.string.network_error_title, R.string.network_error_message,
cancelListener, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
context.startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
dialog.cancel();
}
}, null);
}
public static AlertDialog showInstallVlc(@NonNull final Activity activity) {
return showAlertDialog(activity, R.string.vlc_error_title, R.string.vlc_error_message,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
dialog.dismiss();
activity.finish();
}
}, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=org.videolan.vlc")));
dialog.dismiss();
activity.finish();
}
}, null);
}
}

49
extension-api/src/main/java/org/videolan/vlc/extensions/api/tools/Helpers.java

@ -1,49 +0,0 @@
/**
* **************************************************************************
* Helpers.java
* ****************************************************************************
* Copyright © 2016 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.videolan.vlc.extensions.api.tools;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import org.videolan.vlc.extensions.api.WarningActivity;
public class Helpers {
/**
* Helper method to check if VLC is installed on device. If not, shows an AlertDialog and offers
* the user to install it from the Play Store.
* @param context A simple context reference
* @return true is VLC is installed, false if not.
*/
public static boolean checkVlc(Context context) {
PackageManager pm = context.getPackageManager();
try {
pm.getPackageInfo("org.videolan.vlc", 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
context.startActivity(new Intent(context, WarningActivity.class));
return false;
}
}
}

6
extension-api/src/main/res/values/strings.xml

@ -1,6 +0,0 @@
<resources>
<string name="network_error_title">No Internet connection</string>
<string name="network_error_message">This extension needs your device to be connected to internet</string>
<string name="vlc_error_title">VLC is not installed</string>
<string name="vlc_error_message">This feature needs VLC application to be installed on your device</string>
</resources>

14
extension-api/src/main/res/values/styles.xml

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="TransparentTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:background">@android:color/transparent</item>
<item name="background">@android:color/transparent</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@android:color/transparent</item>
<item name="android:windowContentOverlay">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@android:color/transparent</item>
</style>
</resources>

38
extension-api/src/test/java/org/videolan/vlc/extensions/api/ExampleUnitTest.java

@ -1,38 +0,0 @@
/*
* *************************************************************************
* ExampleUnitTest.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.videolan.vlc.extensions.api;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* To work on unit tests, switch the Test Artifact in the Build Variants view.
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

2
settings.gradle

@ -1,4 +1,4 @@
include ':libvlcjni:libvlc', ':extension-api', ':medialibrary'
include ':libvlcjni:libvlc', ':medialibrary'
include ':application:tools', ':application:resources', ':application:mediadb', ':application:app', ':application:live-plot-graph', ':application:television', ':application:donations'
include ':application:vlc-android'
include ':application:moviepedia'

Loading…
Cancel
Save