Commit e3981ec4 by olly Committed by Oliver Woodman

Fix notifications to avoid flicker on KitKat

On KitKat you need to reuse the same notification builder when
generating a notification that's intended to replace a previous
one. See:

https://stackoverflow.com/questions/6406730/updating-an-ongoing-notification-quietly

PiperOrigin-RevId: 232503682
parent 391f2bb6
...@@ -43,6 +43,10 @@ ...@@ -43,6 +43,10 @@
and `useSurfaceYuvOutput`. and `useSurfaceYuvOutput`.
* Change signature of `PlayerNotificationManager.NotificationListener` to better * Change signature of `PlayerNotificationManager.NotificationListener` to better
fit service requirements. Remove ability to set a custom stop action. fit service requirements. Remove ability to set a custom stop action.
* Fix issues with flickering notifications on KitKat.
`PlayerNotificationManager` has been fixed. Apps using
`DownloadNotificationUtil` should switch to using
`DownloadNotificationHelper`.
### 2.9.5 ### ### 2.9.5 ###
......
...@@ -20,7 +20,7 @@ import com.google.android.exoplayer2.offline.DownloadManager; ...@@ -20,7 +20,7 @@ import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloadService; import com.google.android.exoplayer2.offline.DownloadService;
import com.google.android.exoplayer2.offline.DownloadState; import com.google.android.exoplayer2.offline.DownloadState;
import com.google.android.exoplayer2.scheduler.PlatformScheduler; import com.google.android.exoplayer2.scheduler.PlatformScheduler;
import com.google.android.exoplayer2.ui.DownloadNotificationUtil; import com.google.android.exoplayer2.ui.DownloadNotificationHelper;
import com.google.android.exoplayer2.util.NotificationUtil; import com.google.android.exoplayer2.util.NotificationUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -33,6 +33,8 @@ public class DemoDownloadService extends DownloadService { ...@@ -33,6 +33,8 @@ public class DemoDownloadService extends DownloadService {
private static int nextNotificationId = FOREGROUND_NOTIFICATION_ID + 1; private static int nextNotificationId = FOREGROUND_NOTIFICATION_ID + 1;
private DownloadNotificationHelper notificationHelper;
public DemoDownloadService() { public DemoDownloadService() {
super( super(
FOREGROUND_NOTIFICATION_ID, FOREGROUND_NOTIFICATION_ID,
...@@ -43,6 +45,12 @@ public class DemoDownloadService extends DownloadService { ...@@ -43,6 +45,12 @@ public class DemoDownloadService extends DownloadService {
} }
@Override @Override
public void onCreate() {
super.onCreate();
notificationHelper = new DownloadNotificationHelper(this, CHANNEL_ID);
}
@Override
protected DownloadManager getDownloadManager() { protected DownloadManager getDownloadManager() {
return ((DemoApplication) getApplication()).getDownloadManager(); return ((DemoApplication) getApplication()).getDownloadManager();
} }
...@@ -54,13 +62,8 @@ public class DemoDownloadService extends DownloadService { ...@@ -54,13 +62,8 @@ public class DemoDownloadService extends DownloadService {
@Override @Override
protected Notification getForegroundNotification(DownloadState[] downloadStates) { protected Notification getForegroundNotification(DownloadState[] downloadStates) {
return DownloadNotificationUtil.buildProgressNotification( return notificationHelper.buildProgressNotification(
/* context= */ this, R.drawable.ic_download, /* contentIntent= */ null, /* message= */ null, downloadStates);
R.drawable.ic_download,
CHANNEL_ID,
/* contentIntent= */ null,
/* message= */ null,
downloadStates);
} }
@Override @Override
...@@ -68,18 +71,14 @@ public class DemoDownloadService extends DownloadService { ...@@ -68,18 +71,14 @@ public class DemoDownloadService extends DownloadService {
Notification notification; Notification notification;
if (downloadState.state == DownloadState.STATE_COMPLETED) { if (downloadState.state == DownloadState.STATE_COMPLETED) {
notification = notification =
DownloadNotificationUtil.buildDownloadCompletedNotification( notificationHelper.buildDownloadCompletedNotification(
/* context= */ this,
R.drawable.ic_download_done, R.drawable.ic_download_done,
CHANNEL_ID,
/* contentIntent= */ null, /* contentIntent= */ null,
Util.fromUtf8Bytes(downloadState.customMetadata)); Util.fromUtf8Bytes(downloadState.customMetadata));
} else if (downloadState.state == DownloadState.STATE_FAILED) { } else if (downloadState.state == DownloadState.STATE_FAILED) {
notification = notification =
DownloadNotificationUtil.buildDownloadFailedNotification( notificationHelper.buildDownloadFailedNotification(
/* context= */ this,
R.drawable.ic_download_done, R.drawable.ic_download_done,
CHANNEL_ID,
/* contentIntent= */ null, /* contentIntent= */ null,
Util.fromUtf8Bytes(downloadState.customMetadata)); Util.fromUtf8Bytes(downloadState.customMetadata));
} else { } else {
......
/*
* Copyright (C) 2018 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.ui;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.app.NotificationCompat;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.offline.DownloadState;
/** Helper for creating download notifications. */
public final class DownloadNotificationHelper {
private static final @StringRes int NULL_STRING_ID = 0;
private final Context context;
private final NotificationCompat.Builder notificationBuilder;
/**
* @param context A context.
* @param channelId The id of the notification channel to use.
*/
public DownloadNotificationHelper(Context context, String channelId) {
context = context.getApplicationContext();
this.context = context;
this.notificationBuilder = new NotificationCompat.Builder(context, channelId);
}
/**
* Returns a progress notification for the given download states.
*
* @param smallIcon A small icon for the notification.
* @param contentIntent An optional content intent to send when the notification is clicked.
* @param message An optional message to display on the notification.
* @param downloadStates The download states.
* @return The notification.
*/
public Notification buildProgressNotification(
@DrawableRes int smallIcon,
@Nullable PendingIntent contentIntent,
@Nullable String message,
DownloadState[] downloadStates) {
float totalPercentage = 0;
int downloadTaskCount = 0;
boolean allDownloadPercentagesUnknown = true;
boolean haveDownloadedBytes = false;
boolean haveDownloadTasks = false;
boolean haveRemoveTasks = false;
for (DownloadState downloadState : downloadStates) {
if (downloadState.state == DownloadState.STATE_REMOVING
|| downloadState.state == DownloadState.STATE_RESTARTING
|| downloadState.state == DownloadState.STATE_REMOVED) {
haveRemoveTasks = true;
continue;
}
if (downloadState.state != DownloadState.STATE_DOWNLOADING
&& downloadState.state != DownloadState.STATE_COMPLETED) {
continue;
}
haveDownloadTasks = true;
if (downloadState.downloadPercentage != C.PERCENTAGE_UNSET) {
allDownloadPercentagesUnknown = false;
totalPercentage += downloadState.downloadPercentage;
}
haveDownloadedBytes |= downloadState.downloadedBytes > 0;
downloadTaskCount++;
}
int titleStringId =
haveDownloadTasks
? R.string.exo_download_downloading
: (haveRemoveTasks ? R.string.exo_download_removing : NULL_STRING_ID);
int progress = 0;
boolean indeterminate = true;
if (haveDownloadTasks) {
progress = (int) (totalPercentage / downloadTaskCount);
indeterminate = allDownloadPercentagesUnknown && haveDownloadedBytes;
}
return buildNotification(
smallIcon,
contentIntent,
message,
titleStringId,
progress,
indeterminate,
/* ongoing= */ true,
/* showWhen= */ false);
}
/**
* Returns a notification for a completed download.
*
* @param smallIcon A small icon for the notifications.
* @param contentIntent An optional content intent to send when the notification is clicked.
* @param message An optional message to display on the notification.
* @return The notification.
*/
public Notification buildDownloadCompletedNotification(
@DrawableRes int smallIcon, @Nullable PendingIntent contentIntent, @Nullable String message) {
int titleStringId = R.string.exo_download_completed;
return buildEndStateNotification(smallIcon, contentIntent, message, titleStringId);
}
/**
* Returns a notification for a failed download.
*
* @param smallIcon A small icon for the notifications.
* @param contentIntent An optional content intent to send when the notification is clicked.
* @param message An optional message to display on the notification.
* @return The notification.
*/
public Notification buildDownloadFailedNotification(
@DrawableRes int smallIcon, @Nullable PendingIntent contentIntent, @Nullable String message) {
@StringRes int titleStringId = R.string.exo_download_failed;
return buildEndStateNotification(smallIcon, contentIntent, message, titleStringId);
}
private Notification buildEndStateNotification(
@DrawableRes int smallIcon,
@Nullable PendingIntent contentIntent,
@Nullable String message,
@StringRes int titleStringId) {
return buildNotification(
smallIcon,
contentIntent,
message,
titleStringId,
/* progress= */ 0,
/* indeterminateProgress= */ false,
/* ongoing= */ false,
/* showWhen= */ true);
}
private Notification buildNotification(
@DrawableRes int smallIcon,
@Nullable PendingIntent contentIntent,
@Nullable String message,
@StringRes int titleStringId,
int progress,
boolean indeterminateProgress,
boolean ongoing,
boolean showWhen) {
notificationBuilder.setSmallIcon(smallIcon);
notificationBuilder.setContentTitle(
titleStringId == NULL_STRING_ID ? null : context.getResources().getString(titleStringId));
notificationBuilder.setContentIntent(contentIntent);
notificationBuilder.setStyle(
message == null ? null : new NotificationCompat.BigTextStyle().bigText(message));
notificationBuilder.setProgress(/* max= */ 100, progress, indeterminateProgress);
notificationBuilder.setOngoing(ongoing);
notificationBuilder.setShowWhen(showWhen);
return notificationBuilder.build();
}
}
...@@ -20,16 +20,16 @@ import android.app.PendingIntent; ...@@ -20,16 +20,16 @@ import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.support.annotation.DrawableRes; import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.app.NotificationCompat;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.offline.DownloadState; import com.google.android.exoplayer2.offline.DownloadState;
import com.google.android.exoplayer2.util.Util;
/** Helper for creating download notifications. */ /**
* @deprecated Using this class can cause notifications to flicker on devices with {@link
* Util#SDK_INT} < 21. Use {@link DownloadNotificationHelper} instead.
*/
@Deprecated
public final class DownloadNotificationUtil { public final class DownloadNotificationUtil {
private static final @StringRes int NULL_STRING_ID = 0;
private DownloadNotificationUtil() {} private DownloadNotificationUtil() {}
/** /**
...@@ -37,8 +37,7 @@ public final class DownloadNotificationUtil { ...@@ -37,8 +37,7 @@ public final class DownloadNotificationUtil {
* *
* @param context A context for accessing resources. * @param context A context for accessing resources.
* @param smallIcon A small icon for the notification. * @param smallIcon A small icon for the notification.
* @param channelId The id of the notification channel to use. Only required for API level 26 and * @param channelId The id of the notification channel to use.
* above.
* @param contentIntent An optional content intent to send when the notification is clicked. * @param contentIntent An optional content intent to send when the notification is clicked.
* @param message An optional message to display on the notification. * @param message An optional message to display on the notification.
* @param downloadStates The download states. * @param downloadStates The download states.
...@@ -51,50 +50,8 @@ public final class DownloadNotificationUtil { ...@@ -51,50 +50,8 @@ public final class DownloadNotificationUtil {
@Nullable PendingIntent contentIntent, @Nullable PendingIntent contentIntent,
@Nullable String message, @Nullable String message,
DownloadState[] downloadStates) { DownloadState[] downloadStates) {
float totalPercentage = 0; return new DownloadNotificationHelper(context, channelId)
int downloadTaskCount = 0; .buildProgressNotification(smallIcon, contentIntent, message, downloadStates);
boolean allDownloadPercentagesUnknown = true;
boolean haveDownloadedBytes = false;
boolean haveDownloadTasks = false;
boolean haveRemoveTasks = false;
for (DownloadState downloadState : downloadStates) {
if (downloadState.state == DownloadState.STATE_REMOVING
|| downloadState.state == DownloadState.STATE_RESTARTING
|| downloadState.state == DownloadState.STATE_REMOVED) {
haveRemoveTasks = true;
continue;
}
if (downloadState.state != DownloadState.STATE_DOWNLOADING
&& downloadState.state != DownloadState.STATE_COMPLETED) {
continue;
}
haveDownloadTasks = true;
if (downloadState.downloadPercentage != C.PERCENTAGE_UNSET) {
allDownloadPercentagesUnknown = false;
totalPercentage += downloadState.downloadPercentage;
}
haveDownloadedBytes |= downloadState.downloadedBytes > 0;
downloadTaskCount++;
}
int titleStringId =
haveDownloadTasks
? R.string.exo_download_downloading
: (haveRemoveTasks ? R.string.exo_download_removing : NULL_STRING_ID);
NotificationCompat.Builder notificationBuilder =
newNotificationBuilder(
context, smallIcon, channelId, contentIntent, message, titleStringId);
int progress = 0;
boolean indeterminate = true;
if (haveDownloadTasks) {
progress = (int) (totalPercentage / downloadTaskCount);
indeterminate = allDownloadPercentagesUnknown && haveDownloadedBytes;
}
notificationBuilder.setProgress(/* max= */ 100, progress, indeterminate);
notificationBuilder.setOngoing(true);
notificationBuilder.setShowWhen(false);
return notificationBuilder.build();
} }
/** /**
...@@ -102,8 +59,7 @@ public final class DownloadNotificationUtil { ...@@ -102,8 +59,7 @@ public final class DownloadNotificationUtil {
* *
* @param context A context for accessing resources. * @param context A context for accessing resources.
* @param smallIcon A small icon for the notifications. * @param smallIcon A small icon for the notifications.
* @param channelId The id of the notification channel to use. Only required for API level 26 and * @param channelId The id of the notification channel to use.
* above.
* @param contentIntent An optional content intent to send when the notification is clicked. * @param contentIntent An optional content intent to send when the notification is clicked.
* @param message An optional message to display on the notification. * @param message An optional message to display on the notification.
* @return The notification. * @return The notification.
...@@ -114,10 +70,8 @@ public final class DownloadNotificationUtil { ...@@ -114,10 +70,8 @@ public final class DownloadNotificationUtil {
String channelId, String channelId,
@Nullable PendingIntent contentIntent, @Nullable PendingIntent contentIntent,
@Nullable String message) { @Nullable String message) {
int titleStringId = R.string.exo_download_completed; return new DownloadNotificationHelper(context, channelId)
return newNotificationBuilder( .buildDownloadCompletedNotification(smallIcon, contentIntent, message);
context, smallIcon, channelId, contentIntent, message, titleStringId)
.build();
} }
/** /**
...@@ -125,8 +79,7 @@ public final class DownloadNotificationUtil { ...@@ -125,8 +79,7 @@ public final class DownloadNotificationUtil {
* *
* @param context A context for accessing resources. * @param context A context for accessing resources.
* @param smallIcon A small icon for the notifications. * @param smallIcon A small icon for the notifications.
* @param channelId The id of the notification channel to use. Only required for API level 26 and * @param channelId The id of the notification channel to use.
* above.
* @param contentIntent An optional content intent to send when the notification is clicked. * @param contentIntent An optional content intent to send when the notification is clicked.
* @param message An optional message to display on the notification. * @param message An optional message to display on the notification.
* @return The notification. * @return The notification.
...@@ -137,30 +90,7 @@ public final class DownloadNotificationUtil { ...@@ -137,30 +90,7 @@ public final class DownloadNotificationUtil {
String channelId, String channelId,
@Nullable PendingIntent contentIntent, @Nullable PendingIntent contentIntent,
@Nullable String message) { @Nullable String message) {
@StringRes int titleStringId = R.string.exo_download_failed; return new DownloadNotificationHelper(context, channelId)
return newNotificationBuilder( .buildDownloadFailedNotification(smallIcon, contentIntent, message);
context, smallIcon, channelId, contentIntent, message, titleStringId)
.build();
}
private static NotificationCompat.Builder newNotificationBuilder(
Context context,
@DrawableRes int smallIcon,
String channelId,
@Nullable PendingIntent contentIntent,
@Nullable String message,
@StringRes int titleStringId) {
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(context, channelId).setSmallIcon(smallIcon);
if (titleStringId != NULL_STRING_ID) {
notificationBuilder.setContentTitle(context.getResources().getString(titleStringId));
}
if (contentIntent != null) {
notificationBuilder.setContentIntent(contentIntent);
}
if (message != null) {
notificationBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(message));
}
return notificationBuilder;
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment