Skip to content

Commit

Permalink
Always use new foreground service utils.
Browse files Browse the repository at this point in the history
  • Loading branch information
greyson-signal authored and cody-signal committed Nov 30, 2022
1 parent 7b13550 commit 2380404
Show file tree
Hide file tree
Showing 19 changed files with 350 additions and 126 deletions.
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Expand Up @@ -888,7 +888,7 @@
</intent-filter>
</receiver>

<receiver android:name="org.thoughtcrime.securesms.jobs.ForegroundUtil$Receiver" android:exported="false" />
<receiver android:name="org.thoughtcrime.securesms.jobs.ForegroundServiceUtil$Receiver" android:exported="false" />

<receiver android:name=".service.PersistentConnectionBootListener" android:exported="false">
<intent-filter>
Expand Down
Expand Up @@ -16,7 +16,6 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.media.MediaBrowserServiceCompat;

import com.google.android.exoplayer2.C;
Expand All @@ -34,8 +33,10 @@
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.ForegroundServiceUtil;
import org.thoughtcrime.securesms.jobs.MultiDeviceViewedUpdateJob;
import org.thoughtcrime.securesms.jobs.SendViewedReceiptJob;
import org.thoughtcrime.securesms.jobs.UnableToStartException;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.service.KeyCachingService;

Expand Down Expand Up @@ -275,9 +276,13 @@ private class VoiceNoteNotificationManagerListener implements PlayerNotification
@Override
public void onNotificationPosted(int notificationId, Notification notification, boolean ongoing) {
if (ongoing && !isForegroundService) {
ContextCompat.startForegroundService(getApplicationContext(), new Intent(getApplicationContext(), VoiceNotePlaybackService.class));
startForeground(notificationId, notification);
isForegroundService = true;
try {
ForegroundServiceUtil.startWhenCapable(getApplicationContext(), new Intent(getApplicationContext(), VoiceNotePlaybackService.class));
startForeground(notificationId, notification);
isForegroundService = true;
} catch (UnableToStartException e) {
Log.e(TAG, "Unable to start foreground service!", e);
}
}
}

Expand Down
Expand Up @@ -22,6 +22,7 @@
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.jobs.UnableToStartException;
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
import org.thoughtcrime.securesms.service.GenericForegroundService;
import org.thoughtcrime.securesms.service.NotificationController;
Expand Down Expand Up @@ -51,7 +52,7 @@ public static void migratePlaintext(@NonNull Context context,
copyTable("recipient_preferences", legacyDb, modernDb, null);
copyTable("group_receipts", legacyDb, modernDb, null);
modernDb.setTransactionSuccessful();
} catch (GenericForegroundService.UnableToStartException e) {
} catch (UnableToStartException e) {
throw new IllegalStateException(e);
} finally {
modernDb.endTransaction();
Expand Down Expand Up @@ -176,7 +177,7 @@ public static void migrateCiphertext(@NonNull Context context,
AttachmentSecretProvider.getInstance(context).setClassicKey(context, masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded());
TextSecurePreferences.setNeedsSqlCipherMigration(context, false);
modernDb.setTransactionSuccessful();
} catch (GenericForegroundService.UnableToStartException e) {
} catch (UnableToStartException e) {
throw new IllegalStateException(e);
} finally {
modernDb.endTransaction();
Expand Down
Expand Up @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.exporter
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import app.cash.exhaustive.Exhaustive
import org.signal.core.util.PendingIntentFlags
import org.signal.smsexporter.ExportableMessage
Expand All @@ -15,6 +14,7 @@ import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExportState
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.exporter.flow.SmsExportActivity
import org.thoughtcrime.securesms.jobs.ForegroundServiceUtil
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.NotificationIds
import org.thoughtcrime.securesms.notifications.v2.NotificationPendingIntentHelper
Expand All @@ -34,7 +34,7 @@ class SignalSmsExportService : SmsExportService() {
fun start(context: Context, clearPreviousExportState: Boolean) {
val intent = Intent(context, SignalSmsExportService::class.java)
.apply { putExtra(CLEAR_PREVIOUS_EXPORT_STATE_EXTRA, clearPreviousExportState) }
ContextCompat.startForegroundService(context, intent)
ForegroundServiceUtil.startOrThrow(context, intent)
}
}

Expand Down
Expand Up @@ -3,10 +3,10 @@ package org.thoughtcrime.securesms.gcm
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.content.ContextCompat
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.ForegroundServiceUtil
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob
import org.thoughtcrime.securesms.messages.RestStrategy
import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor
Expand Down Expand Up @@ -48,7 +48,7 @@ object FcmFetchManager {
try {
if (foreground) {
Log.i(TAG, "Starting in the foreground.")
ContextCompat.startForegroundService(context, Intent(context, FcmFetchForegroundService::class.java))
ForegroundServiceUtil.startWhenCapableOrThrow(context, Intent(context, FcmFetchForegroundService::class.java))
startedForeground = true
} else {
Log.i(TAG, "Starting in the background.")
Expand Down
Expand Up @@ -30,7 +30,6 @@
import org.thoughtcrime.securesms.mms.MediaStream;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.SentMediaQuality;
import org.thoughtcrime.securesms.service.GenericForegroundService;
import org.thoughtcrime.securesms.service.NotificationController;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
Expand Down Expand Up @@ -205,7 +204,7 @@ private void compress(@NonNull AttachmentTable attachmentDatabase,
return attachment;
}

try (NotificationController notification = ForegroundUtil.requireForegroundTask(context, context.getString(R.string.AttachmentUploadJob_compressing_video_start))) {
try (NotificationController notification = ForegroundServiceUtil.startGenericTaskWhenCapable(context, context.getString(R.string.AttachmentUploadJob_compressing_video_start))) {

notification.setIndeterminateProgress();

Expand Down Expand Up @@ -290,7 +289,7 @@ private void compress(@NonNull AttachmentTable attachmentDatabase,
throw new UndeliverableMessageException("Failed to transcode and cannot skip due to editing", e);
}
}
} catch (GenericForegroundService.UnableToStartException | IOException | MmsException e) {
} catch (UnableToStartException | IOException | MmsException e) {
throw new UndeliverableMessageException("Failed to transcode", e);
}
return attachment;
Expand Down
Expand Up @@ -25,7 +25,6 @@
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.net.NotPushRegisteredException;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.GenericForegroundService;
import org.thoughtcrime.securesms.service.NotificationController;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
Expand Down Expand Up @@ -160,8 +159,8 @@ public void onRun() throws Exception {
private @Nullable NotificationController getNotificationForAttachment(@NonNull Attachment attachment) {
if (attachment.getSize() >= FOREGROUND_LIMIT) {
try {
return ForegroundUtil.requireForegroundTask(context, context.getString(R.string.AttachmentUploadJob_uploading_media));
} catch (GenericForegroundService.UnableToStartException e) {
return ForegroundServiceUtil.startGenericTaskWhenCapable(context, context.getString(R.string.AttachmentUploadJob_uploading_media));
} catch (UnableToStartException e) {
Log.w(TAG, "Unable to start foreground service", e);
return null;
}
Expand Down
@@ -0,0 +1,166 @@
package org.thoughtcrime.securesms.jobs

import android.app.AlarmManager
import android.app.ForegroundServiceStartNotAllowedException
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.SystemClock
import androidx.core.content.ContextCompat
import org.signal.core.util.PendingIntentFlags
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.service.GenericForegroundService
import org.thoughtcrime.securesms.service.NotificationController
import org.thoughtcrime.securesms.util.ServiceUtil
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import kotlin.time.Duration.Companion.minutes

/**
* Helps start foreground services from the background.
*/
object ForegroundServiceUtil {

private val TAG = Log.tag(ForegroundServiceUtil::class.java)

private val updateMutex: ReentrantLock = ReentrantLock()
private var activeLatch: CountDownLatch? = null

private val DEFAULT_TIMEOUT: Long = 1.minutes.inWholeMilliseconds

/**
* A simple wrapper around [ContextCompat.startForegroundService], but makes the possible failure part of the contract by forcing the caller to handle the
* [UnableToStartException].
*/
@JvmStatic
@Throws(UnableToStartException::class)
fun start(context: Context, intent: Intent) {
if (Build.VERSION.SDK_INT < 31) {
ContextCompat.startForegroundService(context, intent)
} else {
try {
ContextCompat.startForegroundService(context, intent)
} catch (e: IllegalStateException) {
if (e is ForegroundServiceStartNotAllowedException) {
Log.e(TAG, "Unable to start foreground service", e)
throw UnableToStartException(e)
} else {
throw e
}
}
}
}

/**
* Literally just a wrapper around [ContextCompat.startForegroundService], but with a name to make the caller understand that this method could throw.
*/
@JvmStatic
fun startOrThrow(context: Context, intent: Intent) {
ContextCompat.startForegroundService(context, intent)
}

/**
* Does its best to start a foreground service, including possibly blocking and waiting until we are able to.
* However, it is always possible that the attempt will fail, so be sure to handle the [UnableToStartException].
*
* @param timeout The maximum time you're willing to wait to create the conditions for a foreground service to start.
*/
@JvmOverloads
@JvmStatic
@Throws(UnableToStartException::class)
fun startWhenCapable(context: Context, intent: Intent, timeout: Long = DEFAULT_TIMEOUT) {
try {
start(context, intent)
} catch (e: UnableToStartException) {
Log.w(TAG, "Failed to start normally. Blocking and then trying again.")
blockUntilCapable(context, timeout)
start(context, intent)
}
}

/**
* Identical to [startWhenCapable], except in this case we just throw if we're unable to start the service. Should only be used if there's no good way
* to gracefully handle the exception (or if you know for sure it won't get thrown).
*
* @param timeout The maximum time you're willing to wait to create the conditions for a foreground service to start.
*/
@JvmOverloads
@JvmStatic
fun startWhenCapableOrThrow(context: Context, intent: Intent, timeout: Long = DEFAULT_TIMEOUT) {
try {
startWhenCapable(context, intent, timeout)
} catch (e: UnableToStartException) {
throw IllegalStateException(e)
}
}

/**
* Does its best to start a foreground service with your task name, including possibly blocking and waiting until we are able to.
* However, it is always possible that the attempt will fail, so always handle the [UnableToStartException].
*/
@Throws(UnableToStartException::class)
@JvmStatic
fun startGenericTaskWhenCapable(context: Context, task: String): NotificationController {
blockUntilCapable(context)
return GenericForegroundService.startForegroundTask(context, task)
}

/**
* Waits until we're capable of starting a foreground service.
* @return True if you should expect to be able to start a foreground service, otherwise false. Please note that this isn't *guaranteed*, just what we believe
* given the information we have.
*/
private fun blockUntilCapable(context: Context, timeout: Long = DEFAULT_TIMEOUT): Boolean {
val alarmManager = ServiceUtil.getAlarmManager(context)

if (Build.VERSION.SDK_INT < 31 || ApplicationDependencies.getAppForegroundObserver().isForegrounded) {
return true
}

if (!alarmManager.canScheduleExactAlarms()) {
return false
}

val latch: CountDownLatch? = updateMutex.withLock {
if (activeLatch == null) {
if (alarmManager.canScheduleExactAlarms()) {
activeLatch = CountDownLatch(1)
val pendingIntent = PendingIntent.getBroadcast(context, 0, Intent(context, Receiver::class.java), PendingIntentFlags.mutable())
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 1000, pendingIntent)
} else {
Log.w(TAG, "Unable to schedule alarm")
}
}
activeLatch
}

if (latch != null) {
try {
if (!latch.await(timeout, TimeUnit.MILLISECONDS)) {
Log.w(TAG, "Time ran out waiting for foreground")
return false
}
} catch (e: InterruptedException) {
Log.w(TAG, "Interrupted while waiting for foreground")
}
}

return true
}

class Receiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
updateMutex.withLock {
activeLatch?.countDown()
activeLatch = null
}
}
}
}

class UnableToStartException(cause: Throwable) : Exception(cause)

0 comments on commit 2380404

Please sign in to comment.