diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 7ec50c0674..cb41231c1a 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -60,6 +60,9 @@
`ImaServerSideAdInsertionMediaSource.AdsLoader.Builder` to request
focusing the skip button on TV devices and set it to true by default.
* Bump IMA SDK version to 3.29.0.
+* Demo app
+ * Request notification permission for download notifications at runtime
+ ([#10884](https://github.com/google/ExoPlayer/issues/10884)).
### 1.0.0-beta03 (2022-11-22)
diff --git a/demos/main/src/main/AndroidManifest.xml b/demos/main/src/main/AndroidManifest.xml
index 401d73a8e6..21d07e4ee5 100644
--- a/demos/main/src/main/AndroidManifest.xml
+++ b/demos/main/src/main/AndroidManifest.xml
@@ -23,6 +23,7 @@
+
diff --git a/demos/main/src/main/java/androidx/media3/demo/main/SampleChooserActivity.java b/demos/main/src/main/java/androidx/media3/demo/main/SampleChooserActivity.java
index fc7144dc91..ef01b148ca 100644
--- a/demos/main/src/main/java/androidx/media3/demo/main/SampleChooserActivity.java
+++ b/demos/main/src/main/java/androidx/media3/demo/main/SampleChooserActivity.java
@@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
+import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -41,8 +42,10 @@ import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.DoNotInline;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
+import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaItem.ClippingConfiguration;
@@ -76,6 +79,7 @@ public class SampleChooserActivity extends AppCompatActivity
private static final String TAG = "SampleChooserActivity";
private static final String GROUP_POSITION_PREFERENCE_KEY = "sample_chooser_group_position";
private static final String CHILD_POSITION_PREFERENCE_KEY = "sample_chooser_child_position";
+ private static final int POST_NOTIFICATION_PERMISSION_REQUEST_CODE = 100;
private String[] uris;
private boolean useExtensionRenderers;
@@ -83,6 +87,8 @@ public class SampleChooserActivity extends AppCompatActivity
private SampleAdapter sampleAdapter;
private MenuItem preferExtensionDecodersMenuItem;
private ExpandableListView sampleListView;
+ @Nullable private MediaItem downloadMediaItemWaitingForNotificationPermission;
+ private boolean notificationPermissionToastShown;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -172,12 +178,34 @@ public class SampleChooserActivity extends AppCompatActivity
public void onRequestPermissionsResult(
int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (requestCode == POST_NOTIFICATION_PERMISSION_REQUEST_CODE) {
+ handlePostNotificationPermissionGrantResults(grantResults);
+ } else {
+ handleExternalStoragePermissionGrantResults(grantResults);
+ }
+ }
+
+ private void handlePostNotificationPermissionGrantResults(int[] grantResults) {
+ if (!notificationPermissionToastShown
+ && (grantResults.length == 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED)) {
+ Toast.makeText(
+ getApplicationContext(), R.string.post_notification_not_granted, Toast.LENGTH_LONG)
+ .show();
+ notificationPermissionToastShown = true;
+ }
+ if (downloadMediaItemWaitingForNotificationPermission != null) {
+ // Download with or without permission to post notifications.
+ toggleDownload(downloadMediaItemWaitingForNotificationPermission);
+ downloadMediaItemWaitingForNotificationPermission = null;
+ }
+ }
+
+ private void handleExternalStoragePermissionGrantResults(int[] grantResults) {
if (grantResults.length == 0) {
// Empty results are triggered if a permission is requested while another request was already
// pending and can be safely ignored in this case.
return;
- }
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
loadSample();
} else {
Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG)
@@ -244,15 +272,26 @@ public class SampleChooserActivity extends AppCompatActivity
if (downloadUnsupportedStringId != 0) {
Toast.makeText(getApplicationContext(), downloadUnsupportedStringId, Toast.LENGTH_LONG)
.show();
+ } else if (!notificationPermissionToastShown
+ && Util.SDK_INT >= 33
+ && checkSelfPermission(Api33.getPostNotificationPermissionString())
+ != PackageManager.PERMISSION_GRANTED) {
+ downloadMediaItemWaitingForNotificationPermission = playlistHolder.mediaItems.get(0);
+ requestPermissions(
+ new String[] {Api33.getPostNotificationPermissionString()},
+ /* requestCode= */ POST_NOTIFICATION_PERMISSION_REQUEST_CODE);
} else {
- RenderersFactory renderersFactory =
- DemoUtil.buildRenderersFactory(
- /* context= */ this, isNonNullAndChecked(preferExtensionDecodersMenuItem));
- downloadTracker.toggleDownload(
- getSupportFragmentManager(), playlistHolder.mediaItems.get(0), renderersFactory);
+ toggleDownload(playlistHolder.mediaItems.get(0));
}
}
+ private void toggleDownload(MediaItem mediaItem) {
+ RenderersFactory renderersFactory =
+ DemoUtil.buildRenderersFactory(
+ /* context= */ this, isNonNullAndChecked(preferExtensionDecodersMenuItem));
+ downloadTracker.toggleDownload(getSupportFragmentManager(), mediaItem, renderersFactory);
+ }
+
private int getDownloadUnsupportedStringId(PlaylistHolder playlistHolder) {
if (playlistHolder.mediaItems.size() > 1) {
return R.string.download_playlist_unsupported;
@@ -630,4 +669,13 @@ public class SampleChooserActivity extends AppCompatActivity
this.playlists = new ArrayList<>();
}
}
+
+ @RequiresApi(33)
+ private static class Api33 {
+
+ @DoNotInline
+ public static String getPostNotificationPermissionString() {
+ return Manifest.permission.POST_NOTIFICATIONS;
+ }
+ }
}
diff --git a/demos/main/src/main/res/values/strings.xml b/demos/main/src/main/res/values/strings.xml
index 49441ef7da..ce9c90d0c2 100644
--- a/demos/main/src/main/res/values/strings.xml
+++ b/demos/main/src/main/res/values/strings.xml
@@ -45,6 +45,8 @@
One or more sample lists failed to load
+ Notifications suppressed. Grant permission to see download notifications.
+
Failed to start download
Failed to obtain offline license