diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 0fd6f3c8ee..1d54fdaea2 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -246,6 +246,7 @@
([#3792](https://github.com/google/ExoPlayer/issues/3792).
* Support 14-bit mode and little endianness in DTS PES packets
([#3340](https://github.com/google/ExoPlayer/issues/3340)).
+* Demo app: Add ability to download not DRM protected content.
### 2.6.1 ###
diff --git a/demos/main/src/main/AndroidManifest.xml b/demos/main/src/main/AndroidManifest.xml
index cde95300ab..fb91de4328 100644
--- a/demos/main/src/main/AndroidManifest.xml
+++ b/demos/main/src/main/AndroidManifest.xml
@@ -19,6 +19,9 @@
+
+
+
@@ -73,6 +76,22 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java
new file mode 100644
index 0000000000..41fd165ebe
--- /dev/null
+++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.demo;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.util.Pair;
+import com.google.android.exoplayer2.offline.DownloadManager;
+import com.google.android.exoplayer2.offline.DownloadManager.DownloadState;
+import com.google.android.exoplayer2.offline.DownloadService;
+import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
+import com.google.android.exoplayer2.offline.ProgressiveDownloadAction;
+import com.google.android.exoplayer2.scheduler.PlatformScheduler;
+import com.google.android.exoplayer2.scheduler.Requirements;
+import com.google.android.exoplayer2.source.dash.offline.DashDownloadAction;
+import com.google.android.exoplayer2.source.hls.offline.HlsDownloadAction;
+import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadAction;
+import com.google.android.exoplayer2.ui.DownloadNotificationUtil;
+import com.google.android.exoplayer2.ui.NotificationUtil;
+import com.google.android.exoplayer2.util.ErrorMessageProvider;
+import java.io.File;
+
+/** Demo DownloadService implementation. */
+public class DemoDownloadService extends DownloadService {
+
+ private static final String CHANNEL_ID = "my_channel_01";
+
+ private static final int JOB_ID = 1;
+
+ private static final int FOREGROUND_NOTIFICATION_ID = 1;
+
+ private static DownloadManager downloadManager;
+
+ public DemoDownloadService() {
+ super(FOREGROUND_NOTIFICATION_ID);
+ }
+
+ @Override
+ public void onCreate() {
+ NotificationUtil.createNotificationChannel(
+ this,
+ CHANNEL_ID,
+ R.string.download_notifications_channel_name,
+ NotificationManager.IMPORTANCE_LOW);
+ super.onCreate();
+ }
+
+ @Override
+ protected DownloadManager getDownloadManager() {
+ if (downloadManager == null) {
+ DemoApplication application = (DemoApplication) getApplication();
+ DownloaderConstructorHelper constructorHelper =
+ new DownloaderConstructorHelper(
+ application.getDownloadCache(), application.buildHttpDataSourceFactory(null));
+ String actionFilePath = new File(getExternalCacheDir(), "actionFile").getAbsolutePath();
+ downloadManager =
+ new DownloadManager(
+ constructorHelper,
+ /*maxSimultaneousDownloads=*/ 2,
+ DownloadManager.DEFAULT_MIN_RETRY_COUNT,
+ actionFilePath,
+ DashDownloadAction.DESERIALIZER,
+ HlsDownloadAction.DESERIALIZER,
+ SsDownloadAction.DESERIALIZER,
+ ProgressiveDownloadAction.DESERIALIZER);
+ }
+ return downloadManager;
+ }
+
+ @Override
+ protected PlatformScheduler getScheduler() {
+ return new PlatformScheduler(
+ getApplicationContext(), getRequirements(), JOB_ID, ACTION_INIT, getPackageName());
+ }
+
+ @Override
+ protected Requirements getRequirements() {
+ return new Requirements(Requirements.NETWORK_TYPE_UNMETERED, false, false);
+ }
+
+ @Override
+ protected Notification getForegroundNotification(DownloadState[] downloadStates) {
+ return DownloadNotificationUtil.createProgressNotification(
+ downloadStates, this, R.drawable.exo_controls_play, CHANNEL_ID, null);
+ }
+
+ @Override
+ protected void onStateChange(DownloadState downloadState) {
+ int notificationId = FOREGROUND_NOTIFICATION_ID + 1 + downloadState.taskId;
+ Notification downloadNotification =
+ DownloadNotificationUtil.createDownloadFinishedNotification(
+ downloadState,
+ this,
+ R.drawable.exo_controls_play,
+ CHANNEL_ID,
+ downloadState.downloadAction.getData(),
+ new ErrorMessageProvider() {
+ @Override
+ public Pair getErrorMessage(Throwable throwable) {
+ return new Pair<>(0, throwable.getLocalizedMessage());
+ }
+ });
+ NotificationUtil.setNotification(this, notificationId, downloadNotification);
+ }
+}
diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloaderActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloaderActivity.java
new file mode 100644
index 0000000000..62bf3020bf
--- /dev/null
+++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloaderActivity.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.exoplayer2.demo;
+
+import static com.google.android.exoplayer2.demo.PlayerActivity.DRM_SCHEME_EXTRA;
+import static com.google.android.exoplayer2.demo.PlayerActivity.EXTENSION_EXTRA;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.offline.DownloadAction;
+import com.google.android.exoplayer2.offline.DownloadService;
+import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
+import com.google.android.exoplayer2.offline.ProgressiveDownloadAction;
+import com.google.android.exoplayer2.offline.ProgressiveDownloader;
+import com.google.android.exoplayer2.source.dash.manifest.Representation;
+import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey;
+import com.google.android.exoplayer2.source.dash.offline.DashDownloadAction;
+import com.google.android.exoplayer2.source.dash.offline.DashDownloader;
+import com.google.android.exoplayer2.source.hls.offline.HlsDownloadAction;
+import com.google.android.exoplayer2.source.hls.offline.HlsDownloader;
+import com.google.android.exoplayer2.source.smoothstreaming.manifest.TrackKey;
+import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloadAction;
+import com.google.android.exoplayer2.source.smoothstreaming.offline.SsDownloader;
+import com.google.android.exoplayer2.util.ParcelableArray;
+import com.google.android.exoplayer2.util.Util;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/** An activity that downloads streams. */
+public class DownloaderActivity extends Activity {
+
+ public static final String PLAYER_INTENT = "player_intent";
+ public static final String SAMPLE_NAME = "stream_name";
+
+ private static final String TAG = "DownloaderActivity";
+
+ private Intent playerIntent;
+
+ @SuppressWarnings("rawtypes")
+ private AsyncTask manifestDownloaderTask;
+
+ private DownloadUtilMethods downloadUtilMethods;
+
+ private ListView representationList;
+ private ArrayAdapter arrayAdapter;
+ private AlertDialog cancelDialog;
+ private String sampleName;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.downloader_activity);
+
+ Intent intent = getIntent();
+ playerIntent = intent.getParcelableExtra(PLAYER_INTENT);
+
+ TextView streamName = findViewById(R.id.sample_name);
+ sampleName = intent.getStringExtra(SAMPLE_NAME);
+ streamName.setText(sampleName);
+
+ arrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_multiple_choice);
+ representationList = findViewById(R.id.representation_list);
+ representationList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+ representationList.setAdapter(arrayAdapter);
+
+ if (playerIntent.hasExtra(DRM_SCHEME_EXTRA)) {
+ showToastAndFinish(R.string.not_supported_content_type);
+ }
+
+ Uri sampleUri = playerIntent.getData();
+ DemoApplication application = (DemoApplication) getApplication();
+ DownloaderConstructorHelper constructorHelper =
+ new DownloaderConstructorHelper(
+ application.getDownloadCache(), application.buildHttpDataSourceFactory(null));
+ String extension = playerIntent.getStringExtra(EXTENSION_EXTRA);
+ int type = Util.inferContentType(sampleUri, extension);
+ switch (type) {
+ case C.TYPE_DASH:
+ downloadUtilMethods = new DashDownloadUtilMethods(sampleUri, constructorHelper);
+ break;
+ case C.TYPE_HLS:
+ downloadUtilMethods = new HlsDownloadUtilMethods(sampleUri, constructorHelper);
+ break;
+ case C.TYPE_SS:
+ downloadUtilMethods = new SsDownloadUtilMethods(sampleUri, constructorHelper);
+ break;
+ case C.TYPE_OTHER:
+ downloadUtilMethods = new ProgressiveDownloadUtilMethods(sampleUri, constructorHelper);
+ break;
+ default:
+ showToastAndFinish(R.string.not_supported_content_type);
+ break;
+ }
+
+ updateRepresentationsList();
+ }
+
+ @Override
+ protected void onPause() {
+ stopAll();
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ updateRepresentationsList();
+ }
+
+ // This method is referenced in the layout file
+ public void onClick(View v) {
+ // switch-case doesn't work as in some compile configurations id definitions aren't constant
+ int id = v.getId();
+ if (id == R.id.download_button) {
+ startDownload();
+ } else if (id == R.id.remove_all_button) {
+ removeDownloaded();
+ } else if (id == R.id.refresh) {
+ updateRepresentationsList();
+ } else if (id == R.id.play_button) {
+ playDownloaded();
+ }
+ }
+
+ private void startDownload() {
+ ArrayList