If set to false, the platform's default network stack is used with a {@link CookieManager}
+ * configured in {@link #getHttpDataSourceFactory}.
+ */
+ private static final boolean USE_CRONET_FOR_NETWORKING = true;
+
+ private static final String TAG = "DemoUtil";
+ private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
+
+ private static DataSource.Factory dataSourceFactory;
+ private static DataSource.Factory httpDataSourceFactory;
+ private static DatabaseProvider databaseProvider;
+ private static File downloadDirectory;
+ private static Cache downloadCache;
+ private static DownloadManager downloadManager;
+// private static @MonotonicNonNull DownloadTracker downloadTracker;
+ private static DownloadNotificationHelper downloadNotificationHelper;
+
+ /** Returns whether extension renderers should be used. */
+ public static boolean useExtensionRenderers() {
+ return true;
+ }
+
+ @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
+ public static RenderersFactory buildRenderersFactory(
+ Context context, boolean preferExtensionRenderer) {
+ @DefaultRenderersFactory.ExtensionRendererMode
+ int extensionRendererMode =
+ useExtensionRenderers()
+ ? (preferExtensionRenderer
+ ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
+ : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
+ : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF;
+ return new DefaultRenderersFactory(context.getApplicationContext())
+ .setExtensionRendererMode(extensionRendererMode);
+ }
+
+ public static synchronized DataSource.Factory getHttpDataSourceFactory(Context context) {
+ if (httpDataSourceFactory == null) {
+ if (USE_CRONET_FOR_NETWORKING) {
+ context = context.getApplicationContext();
+ @Nullable CronetEngine cronetEngine = CronetUtil.buildCronetEngine(context);
+ if (cronetEngine != null) {
+ httpDataSourceFactory =
+ new CronetDataSource.Factory(cronetEngine, Executors.newSingleThreadExecutor());
+ }
+ }
+ if (httpDataSourceFactory == null) {
+ // We don't want to use Cronet, or we failed to instantiate a CronetEngine.
+ CookieManager cookieManager = new CookieManager();
+ cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
+ CookieHandler.setDefault(cookieManager);
+ httpDataSourceFactory = new DefaultHttpDataSource.Factory();
+ }
+ }
+ return httpDataSourceFactory;
+ }
+
+ /** Returns a {@link DataSource.Factory}. */
+ public static synchronized DataSource.Factory getDataSourceFactory(Context context) {
+ if (dataSourceFactory == null) {
+ context = context.getApplicationContext();
+ DefaultDataSource.Factory upstreamFactory =
+ new DefaultDataSource.Factory(context, getHttpDataSourceFactory(context));
+ dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context));
+ }
+ return dataSourceFactory;
+ }
+
+ @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
+ public static synchronized DownloadNotificationHelper getDownloadNotificationHelper(
+ Context context) {
+ if (downloadNotificationHelper == null) {
+ downloadNotificationHelper =
+ new DownloadNotificationHelper(context, DOWNLOAD_NOTIFICATION_CHANNEL_ID);
+ }
+ return downloadNotificationHelper;
+ }
+
+ public static synchronized DownloadManager getDownloadManager(Context context) {
+ ensureDownloadManagerInitialized(context);
+ return downloadManager;
+ }
+
+// public static synchronized DownloadTracker getDownloadTracker(Context context) {
+// ensureDownloadManagerInitialized(context);
+// return downloadTracker;
+// }
+
+ @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
+ private static synchronized Cache getDownloadCache(Context context) {
+ if (downloadCache == null) {
+ File downloadContentDirectory =
+ new File(getDownloadDirectory(context), DOWNLOAD_CONTENT_DIRECTORY);
+ downloadCache =
+ new SimpleCache(
+ downloadContentDirectory, new NoOpCacheEvictor(), getDatabaseProvider(context));
+ }
+ return downloadCache;
+ }
+
+ @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
+ private static synchronized void ensureDownloadManagerInitialized(Context context) {
+ if (downloadManager == null) {
+ downloadManager =
+ new DownloadManager(
+ context,
+ getDatabaseProvider(context),
+ getDownloadCache(context),
+ getHttpDataSourceFactory(context),
+ Executors.newFixedThreadPool(/* nThreads= */ 6));
+// downloadTracker =
+// new DownloadTracker(context, getHttpDataSourceFactory(context), downloadManager);
+ }
+ }
+
+ @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
+ private static synchronized DatabaseProvider getDatabaseProvider(Context context) {
+ if (databaseProvider == null) {
+ databaseProvider = new StandaloneDatabaseProvider(context);
+ }
+ return databaseProvider;
+ }
+
+ private static synchronized File getDownloadDirectory(Context context) {
+ if (downloadDirectory == null) {
+ downloadDirectory = context.getExternalFilesDir(/* type= */ null);
+ if (downloadDirectory == null) {
+ downloadDirectory = context.getFilesDir();
+ }
+ }
+ return downloadDirectory;
+ }
+
+ @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
+ private static CacheDataSource.Factory buildReadOnlyCacheDataSource(
+ DataSource.Factory upstreamFactory, Cache cache) {
+ return new CacheDataSource.Factory()
+ .setCache(cache)
+ .setUpstreamDataSourceFactory(upstreamFactory)
+ .setCacheWriteDataSinkFactory(null)
+ .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
+ }
+
+ private DemoUtil() {}
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/download_icon.xml b/app/src/main/res/drawable/download_icon.xml
new file mode 100644
index 0000000..3af9d16
--- /dev/null
+++ b/app/src/main/res/drawable/download_icon.xml
@@ -0,0 +1,18 @@
+