mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 16:49:40 +01:00
Move all files to natural position.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package org.thoughtcrime.securesms.gcm;
|
||||
|
||||
import android.app.job.JobInfo;
|
||||
import android.app.job.JobParameters;
|
||||
import android.app.job.JobService;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
|
||||
/**
|
||||
* Pulls down messages. Used when we fail to pull down messages in {@link FcmService}.
|
||||
*/
|
||||
@RequiresApi(26)
|
||||
public class FcmJobService extends JobService {
|
||||
|
||||
private static final String TAG = FcmJobService.class.getSimpleName();
|
||||
|
||||
private static final int ID = 1337;
|
||||
|
||||
@RequiresApi(26)
|
||||
public static void schedule(@NonNull Context context) {
|
||||
JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(ID, new ComponentName(context, FcmJobService.class))
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
.setBackoffCriteria(0, JobInfo.BACKOFF_POLICY_LINEAR)
|
||||
.setPersisted(true);
|
||||
|
||||
ServiceUtil.getJobScheduler(context).schedule(jobInfoBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStartJob(JobParameters params) {
|
||||
Log.d(TAG, "onStartJob()");
|
||||
|
||||
if (MessageRetriever.shouldIgnoreFetch(this)) {
|
||||
Log.i(TAG, "App is foregrounded. No need to run.");
|
||||
return false;
|
||||
}
|
||||
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
Context context = getApplicationContext();
|
||||
MessageRetriever retriever = ApplicationDependencies.getMessageRetriever();
|
||||
boolean success = retriever.retrieveMessages(context, new RestStrategy(), new RestStrategy());
|
||||
|
||||
if (success) {
|
||||
Log.i(TAG, "Successfully retrieved messages.");
|
||||
jobFinished(params, false);
|
||||
} else {
|
||||
Log.w(TAG, "Failed to retrieve messages. Scheduling a retry.");
|
||||
jobFinished(params, true);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onStopJob(JobParameters params) {
|
||||
Log.d(TAG, "onStopJob()");
|
||||
return TextSecurePreferences.getNeedsMessagePull(getApplicationContext());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.thoughtcrime.securesms.gcm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessagingService;
|
||||
import com.google.firebase.messaging.RemoteMessage;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.registration.PushChallengeRequest;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
|
||||
public class FcmService extends FirebaseMessagingService {
|
||||
|
||||
private static final String TAG = FcmService.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(RemoteMessage remoteMessage) {
|
||||
Log.i(TAG, "FCM message... Delay: " + (System.currentTimeMillis() - remoteMessage.getSentTime()));
|
||||
|
||||
String challenge = remoteMessage.getData().get("challenge");
|
||||
if (challenge != null) {
|
||||
handlePushChallenge(challenge);
|
||||
} else {
|
||||
handleReceivedNotification(getApplicationContext());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewToken(String token) {
|
||||
Log.i(TAG, "onNewToken()");
|
||||
|
||||
if (!TextSecurePreferences.isPushRegistered(getApplicationContext())) {
|
||||
Log.i(TAG, "Got a new FCM token, but the user isn't registered.");
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationDependencies.getJobManager().add(new FcmRefreshJob());
|
||||
}
|
||||
|
||||
private static void handleReceivedNotification(Context context) {
|
||||
MessageRetriever retriever = ApplicationDependencies.getMessageRetriever();
|
||||
boolean success = retriever.retrieveMessages(context, 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(context);
|
||||
} else {
|
||||
Log.w(TAG, "Failed to retrieve messages. Scheduling on JobManager (API " + Build.VERSION.SDK_INT + ").");
|
||||
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(context));
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "Processing complete.");
|
||||
}
|
||||
|
||||
private static void handlePushChallenge(@NonNull String challenge) {
|
||||
Log.d(TAG, String.format("Got a push challenge \"%s\"", challenge));
|
||||
|
||||
PushChallengeRequest.postChallengeResponse(challenge);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.thoughtcrime.securesms.gcm;
|
||||
|
||||
import androidx.annotation.WorkerThread;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.google.firebase.iid.FirebaseInstanceId;
|
||||
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public final class FcmUtil {
|
||||
|
||||
private static final String TAG = FcmUtil.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Retrieves the current FCM token. If one isn't available, it'll be generated.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static Optional<String> getToken() {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<String> token = new AtomicReference<>(null);
|
||||
|
||||
FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(task -> {
|
||||
if (task.isSuccessful() && task.getResult() != null && !TextUtils.isEmpty(task.getResult().getToken())) {
|
||||
token.set(task.getResult().getToken());
|
||||
} else {
|
||||
Log.w(TAG, "Failed to get the token.", task.getException());
|
||||
}
|
||||
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "Was interrupted while waiting for the token.");
|
||||
}
|
||||
|
||||
return Optional.fromNullable(token.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.thoughtcrime.securesms.gcm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.PowerManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.PowerManagerCompat;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.WakeLockUtil;
|
||||
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Facilitates the retrieval of messages via provided {@link Strategy}'s.
|
||||
*/
|
||||
public class MessageRetriever {
|
||||
|
||||
private static final String TAG = Log.tag(MessageRetriever.class);
|
||||
|
||||
private static final String WAKE_LOCK_TAG = "MessageRetriever";
|
||||
|
||||
private static final Semaphore ACTIVE_LOCK = new Semaphore(2);
|
||||
|
||||
/**
|
||||
* @return False if the retrieval failed and should be rescheduled, otherwise true.
|
||||
*/
|
||||
@WorkerThread
|
||||
public boolean retrieveMessages(@NonNull Context context, Strategy... strategies) {
|
||||
if (shouldIgnoreFetch(context)) {
|
||||
Log.i(TAG, "Skipping retrieval -- app is in the foreground.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ACTIVE_LOCK.tryAcquire()) {
|
||||
Log.i(TAG, "Skipping retrieval -- there's already one enqueued.");
|
||||
return true;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
PowerManager.WakeLock wakeLock = null;
|
||||
|
||||
try {
|
||||
wakeLock = WakeLockUtil.acquire(context, PowerManager.PARTIAL_WAKE_LOCK, TimeUnit.SECONDS.toMillis(60), WAKE_LOCK_TAG);
|
||||
|
||||
TextSecurePreferences.setNeedsMessagePull(context, true);
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
PowerManager powerManager = ServiceUtil.getPowerManager(context);
|
||||
boolean doze = PowerManagerCompat.isDeviceIdleMode(powerManager);
|
||||
boolean network = new NetworkConstraint.Factory(ApplicationContext.getInstance(context)).create().isMet();
|
||||
|
||||
if (doze || !network) {
|
||||
Log.w(TAG, "We may be operating in a constrained environment. Doze: " + doze + " Network: " + network);
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
|
||||
for (Strategy strategy : strategies) {
|
||||
if (shouldIgnoreFetch(context)) {
|
||||
Log.i(TAG, "Stopping further strategy attempts -- app is in the foreground." + logSuffix(startTime));
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Attempting strategy: " + strategy.toString() + logSuffix(startTime));
|
||||
|
||||
if (strategy.run()) {
|
||||
Log.i(TAG, "Strategy succeeded: " + strategy.toString() + logSuffix(startTime));
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
Log.w(TAG, "Strategy failed: " + strategy.toString() + logSuffix(startTime));
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
TextSecurePreferences.setNeedsMessagePull(context, false);
|
||||
} else {
|
||||
Log.w(TAG, "All strategies failed!" + logSuffix(startTime));
|
||||
}
|
||||
|
||||
return success;
|
||||
} finally {
|
||||
WakeLockUtil.release(wakeLock, WAKE_LOCK_TAG);
|
||||
ACTIVE_LOCK.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if there is no need to execute a message fetch, because the websocket will take
|
||||
* care of it.
|
||||
*/
|
||||
public static boolean shouldIgnoreFetch(@NonNull Context context) {
|
||||
return ApplicationContext.getInstance(context).isAppVisible() &&
|
||||
!ApplicationDependencies.getSignalServiceNetworkAccess().isCensored(context);
|
||||
}
|
||||
|
||||
private static String logSuffix(long startTime) {
|
||||
return " (" + (System.currentTimeMillis() - startTime) + " ms elapsed)";
|
||||
}
|
||||
|
||||
/**
|
||||
* A method of retrieving messages.
|
||||
*/
|
||||
public interface Strategy {
|
||||
/**
|
||||
* @return False if the message retrieval failed and should be retried, otherwise true.
|
||||
*/
|
||||
@WorkerThread
|
||||
boolean run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package org.thoughtcrime.securesms.gcm;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.thoughtcrime.securesms.IncomingMessageProcessor;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker;
|
||||
import org.thoughtcrime.securesms.jobs.MarkerJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushProcessMessageJob;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Retrieves messages over the REST endpoint.
|
||||
*/
|
||||
public class RestStrategy implements MessageRetriever.Strategy {
|
||||
|
||||
private static final String TAG = Log.tag(RestStrategy.class);
|
||||
|
||||
private static final long SOCKET_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
|
||||
|
||||
@WorkerThread
|
||||
@Override
|
||||
public boolean run() {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
try (IncomingMessageProcessor.Processor processor = ApplicationDependencies.getIncomingMessageProcessor().acquire()) {
|
||||
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
|
||||
AtomicInteger jobCount = new AtomicInteger(0);
|
||||
|
||||
receiver.setSoTimeoutMillis(SOCKET_TIMEOUT);
|
||||
|
||||
receiver.retrieveMessages(envelope -> {
|
||||
Log.i(TAG, "Retrieved an envelope." + timeSuffix(startTime));
|
||||
String jobId = processor.processEnvelope(envelope);
|
||||
|
||||
if (jobId != null) {
|
||||
jobCount.incrementAndGet();
|
||||
}
|
||||
Log.i(TAG, "Successfully processed an envelope." + timeSuffix(startTime));
|
||||
});
|
||||
|
||||
Log.d(TAG, jobCount.get() + " PushDecryptMessageJob(s) were enqueued.");
|
||||
|
||||
long timeRemainingMs = blockUntilQueueDrained(PushDecryptMessageJob.QUEUE, TimeUnit.SECONDS.toMillis(10));
|
||||
|
||||
if (timeRemainingMs > 0) {
|
||||
blockUntilQueueDrained(PushProcessMessageJob.QUEUE, timeRemainingMs);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to retrieve messages. Resetting the SignalServiceMessageReceiver.", e);
|
||||
ApplicationDependencies.resetSignalServiceMessageReceiver();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static long blockUntilQueueDrained(@NonNull String queue, long timeoutMs) {
|
||||
final JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||
final MarkerJob markerJob = new MarkerJob(queue);
|
||||
|
||||
jobManager.add(markerJob);
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
jobManager.addListener(markerJob.getId(), new JobTracker.JobListener() {
|
||||
@Override
|
||||
public void onStateChanged(@NonNull JobTracker.JobState jobState) {
|
||||
if (jobState.isComplete()) {
|
||||
jobManager.removeListener(this);
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
if (!latch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
|
||||
Log.w(TAG, "Timed out waiting for " + queue + " job(s) to finish!");
|
||||
return 0;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
long duration = endTime - startTime;
|
||||
|
||||
Log.d(TAG, "Waited " + duration + " ms for the " + queue + " job(s) to finish.");
|
||||
return timeoutMs - duration;
|
||||
}
|
||||
|
||||
private static String timeSuffix(long startTime) {
|
||||
return " (" + (System.currentTimeMillis() - startTime) + " ms elapsed)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return RestStrategy.class.getSimpleName();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user