Skip to content

Commit

Permalink
fix: crashes when app in foreground and try to show downing progress …
Browse files Browse the repository at this point in the history
…notification (#26)
  • Loading branch information
NormanWangEndeavor authored Sep 29, 2024
1 parent 5a7ec0a commit b60fb05
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 9 deletions.
2 changes: 1 addition & 1 deletion demos/main/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
android:largeHeap="true"
android:allowBackup="false"
android:supportsRtl="true"
android:name="androidx.multidex.MultiDexApplication"
android:name=".DemoApplication"
tools:targetApi="29">

<activity android:name=".SampleChooserActivity"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package androidx.media3.demo.main;

import android.app.Activity;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;
import androidx.multidex.MultiDexApplication;

public final class DemoApplication extends MultiDexApplication {

@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
private int started = 0;
private int resumed = 0;
private int stopped = 0;

private boolean isAppBackFromBackground() {
return started == stopped + 1 && stopped > 0 && resumed == started;
}

private boolean isAppEnterBackground() {
return stopped == started;
}

@Override
public void onActivityStarted(@NonNull Activity activity) {
++started;
}

@OptIn(markerClass = UnstableApi.class)
@Override
public void onActivityResumed(@NonNull Activity activity) {
++resumed;
if (isAppBackFromBackground()) {
DemoDownloadService.sendRefreshForeground(activity, DemoDownloadService.class);
}
}

@Override
public void onActivityStopped(@NonNull Activity activity) {
++stopped;
resumed = started;
if (isAppEnterBackground()) {
// app enter background
}
}

@Override
public void onActivityCreated(@NonNull Activity activity,
@Nullable Bundle savedInstanceState) {
}

@Override
public void onActivityPaused(@NonNull Activity activity) {
}

@Override
public void onActivitySaveInstanceState(@NonNull Activity activity,
@NonNull Bundle outState) {
}

@Override
public void onActivityDestroyed(@NonNull Activity activity) {
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
import static androidx.media3.exoplayer.offline.Download.STOP_REASON_NONE;

import android.annotation.SuppressLint;
import android.app.ForegroundServiceStartNotAllowedException;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
Expand Down Expand Up @@ -149,6 +151,13 @@ public abstract class DownloadService extends Service {
public static final String ACTION_SET_REQUIREMENTS =
"androidx.media3.exoplayer.downloadService.action.SET_REQUIREMENTS";

/**
* Sets the service to foreground and show the downloading notification,
* if the service is not foreground but has downloading item.
*/
public static final String ACTION_REFRESH_FOREGROUND =
"androidx.media3.exoplayer.downloadService.action.REFRESH_FOREGROUND";

/** Key for the {@link DownloadRequest} in {@link #ACTION_ADD_DOWNLOAD} intents. */
public static final String KEY_DOWNLOAD_REQUEST = "download_request";

Expand Down Expand Up @@ -415,6 +424,19 @@ public static Intent buildSetRequirementsIntent(
.putExtra(KEY_REQUIREMENTS, requirements);
}

/**
* Builds an {@link Intent} for refreshing the service foreground state.
*
* @param context A {@link Context}.
* @param clazz The concrete download service being targeted by the intent.
* @return The created intent.
*/
public static Intent buildRefreshForegroundIntent(
Context context,
Class<? extends DownloadService> clazz) {
return getIntent(context, clazz, ACTION_REFRESH_FOREGROUND);
}

/**
* Starts the service if not started already and adds a new download.
*
Expand Down Expand Up @@ -543,6 +565,19 @@ public static void sendSetRequirements(
startService(context, intent, foreground);
}

/**
* Starts the service if not started already and refresh the foreground state.
*
* @param context A {@link Context}.
* @param clazz The concrete download service to be started.
*/
public static void sendRefreshForeground(
Context context,
Class<? extends DownloadService> clazz) {
Intent intent = buildRefreshForegroundIntent(context, clazz);
startService(context, intent, false);
}

/**
* Starts a download service to resume any ongoing downloads.
*
Expand Down Expand Up @@ -677,6 +712,9 @@ public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
downloadManager.setRequirements(requirements);
}
break;
case ACTION_REFRESH_FOREGROUND:
refreshForegroundNotification(downloadManager.getCurrentDownloads());
break;
default:
Log.e(TAG, "Ignored unrecognized action: " + intentAction);
break;
Expand Down Expand Up @@ -823,6 +861,19 @@ private void notifyDownloadRemoved() {
}
}

private void refreshForegroundNotification(List<Download> downloads) {
if (foregroundNotificationUpdater != null
&& !foregroundNotificationUpdater.periodicUpdatesStarted
&& !isDestroyed) {
for (int i = 0; i < downloads.size(); i++) {
if (needsStartedService(downloads.get(i).state)) {
foregroundNotificationUpdater.startPeriodicUpdates();
break;
}
}
}
}

/** Returns whether the service is stopped. */
private boolean isStopped() {
return isStopped;
Expand Down Expand Up @@ -912,21 +963,29 @@ public void invalidate() {
}
}

@SuppressLint("InlinedApi") // Using compile time constant FOREGROUND_SERVICE_TYPE_DATA_SYNC
private void update() {
DownloadManager downloadManager =
Assertions.checkNotNull(downloadManagerHelper).downloadManager;
List<Download> downloads = downloadManager.getCurrentDownloads();
@RequirementFlags int notMetRequirements = downloadManager.getNotMetRequirements();
Notification notification = getForegroundNotification(downloads, notMetRequirements);
if (!notificationDisplayed) {
Util.setForegroundServiceNotification(
/* service= */ DownloadService.this,
notificationId,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
"dataSync");
notificationDisplayed = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
try {
setForegroundServiceNotification(notification);
} catch (IllegalStateException e) {
if (!(e instanceof ForegroundServiceStartNotAllowedException)) {
throw e;
} else {
// Ignore app not in a valid state to start foreground service
// (e.g. started from bg)
Log.e(TAG, "app not in a valid state to start foreground service");
stopPeriodicUpdates();
}
}
} else {
setForegroundServiceNotification(notification);
}
} else {
// Update the notification via NotificationManager rather than by repeatedly calling
// startForeground, since the latter can cause ActivityManager log spam.
Expand All @@ -938,6 +997,17 @@ private void update() {
handler.postDelayed(this::update, updateInterval);
}
}

@SuppressLint("InlinedApi") // Using compile time constant FOREGROUND_SERVICE_TYPE_DATA_SYNC
private void setForegroundServiceNotification(Notification notification) {
Util.setForegroundServiceNotification(
/* service= */ DownloadService.this,
notificationId,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
"dataSync");
notificationDisplayed = true;
}
}

private static final class DownloadManagerHelper implements DownloadManager.Listener {
Expand Down

0 comments on commit b60fb05

Please sign in to comment.