Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4cda267
commit fe25d94
Showing
6 changed files
with
167 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package org.thoughtcrime.securesms.gcm; | ||
|
||
import android.app.Service; | ||
import android.content.Intent; | ||
import android.os.Build; | ||
import android.os.IBinder; | ||
|
||
import androidx.annotation.Nullable; | ||
|
||
import com.google.firebase.messaging.RemoteMessage; | ||
|
||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; | ||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob; | ||
import org.thoughtcrime.securesms.logging.Log; | ||
import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor; | ||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; | ||
|
||
import java.util.concurrent.atomic.AtomicInteger; | ||
|
||
/** | ||
* This service does the actual network fetch in response to an FCM message. | ||
* | ||
* Our goals with FCM processing are as follows: | ||
* (1) Ensure some service is active for the duration of the fetch and processing stages. | ||
* (2) Do not make unnecessary network requests. | ||
* | ||
* To fulfill goal 1, this service will not call {@link #stopSelf()} until there is no more running | ||
* requests. | ||
* | ||
* To fulfill goal 2, this service will not enqueue a fetch if there are already 2 active fetches | ||
* (or rather, 1 active and 1 waiting, since we use a single thread executor). | ||
* | ||
* Unfortunately we can't do this all in {@link FcmReceiveService} because it won't let us process | ||
* the next FCM message until {@link FcmReceiveService#onMessageReceived(RemoteMessage)} returns, | ||
* but as soon as that method returns, it could also destroy the service. By not letting us control | ||
* when the service is destroyed, we can't accomplish both goals within that service. | ||
*/ | ||
public class FcmFetchService extends Service { | ||
|
||
private static final String TAG = Log.tag(FcmFetchService.class); | ||
|
||
private static final SerialMonoLifoExecutor EXECUTOR = new SerialMonoLifoExecutor(SignalExecutors.UNBOUNDED); | ||
|
||
private final AtomicInteger activeCount = new AtomicInteger(0); | ||
|
||
@Override | ||
public int onStartCommand(Intent intent, int flags, int startId) { | ||
boolean performedReplace = EXECUTOR.enqueue(this::fetch); | ||
|
||
if (performedReplace) { | ||
Log.i(TAG, "Already have one running and one enqueued. Ignoring."); | ||
} else { | ||
int count = activeCount.incrementAndGet(); | ||
Log.i(TAG, "Incrementing active count to " + count); | ||
} | ||
|
||
return START_NOT_STICKY; | ||
} | ||
|
||
@Override | ||
public void onDestroy() { | ||
Log.i(TAG, "onDestroy()"); | ||
} | ||
|
||
@Override | ||
public @Nullable IBinder onBind(Intent intent) { | ||
return null; | ||
} | ||
|
||
private void fetch() { | ||
MessageRetriever retriever = ApplicationDependencies.getMessageRetriever(); | ||
boolean success = retriever.retrieveMessages(this, new RestStrategy(), new RestStrategy()); | ||
|
||
if (success) { | ||
Log.i(TAG, "Successfully retrieved messages."); | ||
} else { | ||
if (Build.VERSION.SDK_INT >= 26) { | ||
Log.w(TAG, "Failed to retrieve messages. Scheduling on the system JobScheduler (API " + Build.VERSION.SDK_INT + ")."); | ||
FcmJobService.schedule(this); | ||
} else { | ||
Log.w(TAG, "Failed to retrieve messages. Scheduling on JobManager (API " + Build.VERSION.SDK_INT + ")."); | ||
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this)); | ||
} | ||
} | ||
|
||
if (activeCount.decrementAndGet() == 0) { | ||
Log.e(TAG, "stopping"); | ||
stopSelf(); | ||
} | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
app/src/main/java/org/thoughtcrime/securesms/util/concurrent/SerialMonoLifoExecutor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package org.thoughtcrime.securesms.util.concurrent; | ||
|
||
import androidx.annotation.NonNull; | ||
|
||
import java.util.concurrent.Executor; | ||
|
||
/** | ||
* Wraps another executor to make a new executor that only keeps around two tasks: | ||
* - The actively running task | ||
* - A single enqueued task | ||
* | ||
* If multiple tasks are enqueued while one is running, only the latest task is kept. The rest are | ||
* dropped. | ||
* | ||
* This is useful when you want to enqueue a bunch of tasks at unknown intervals, but only the most | ||
* recent one is relevant. For instance, running a query in response to changing user input. | ||
* | ||
* Based on SerialExecutor | ||
* https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html | ||
*/ | ||
public final class SerialMonoLifoExecutor implements Executor { | ||
private final Executor executor; | ||
|
||
private Runnable next; | ||
private Runnable active; | ||
|
||
public SerialMonoLifoExecutor(@NonNull Executor executor) { | ||
this.executor = executor; | ||
} | ||
|
||
@Override | ||
public synchronized void execute(@NonNull Runnable command) { | ||
enqueue(command); | ||
} | ||
|
||
/** | ||
* @return True if a pending task was replaced by this one, otherwise false. | ||
*/ | ||
public synchronized boolean enqueue(@NonNull Runnable command) { | ||
boolean performedReplace = next != null; | ||
|
||
next = () -> { | ||
try { | ||
command.run(); | ||
} finally { | ||
scheduleNext(); | ||
} | ||
}; | ||
|
||
if (active == null) { | ||
scheduleNext(); | ||
} | ||
|
||
return performedReplace; | ||
} | ||
|
||
private synchronized void scheduleNext() { | ||
active = next; | ||
next = null; | ||
if (active != null) { | ||
executor.execute(active); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters