Turn MessageRetrievalService into IncomingMessageObserver.

Due to an Android P bug, we basically need to stop calling
startService() in onResume()/onPause(). That means I had to turn
MessageRetrieval service into a singlton instead of a service. I also
moved the offending KeyCachingService calls into static methods that
didn't have to start the service.
master
Greyson Parrelli 2018-10-15 13:27:21 -07:00
parent 7a6d863ff7
commit 45e0bb281f
11 changed files with 270 additions and 350 deletions

View File

@ -430,7 +430,7 @@
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
<service android:enabled="true" android:name=".service.MessageRetrievalService"/>
<service android:enabled="true" android:name=".service.IncomingMessageObserver$ForegroundService"/>
<service android:name=".service.QuickResponseService"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"

View File

@ -47,6 +47,8 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.LocalBackupListener;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
@ -77,10 +79,11 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
private static final String TAG = ApplicationContext.class.getSimpleName();
private ExpiringMessageManager expiringMessageManager;
private JobManager jobManager;
private ObjectGraph objectGraph;
private PersistentLogger persistentLogger;
private ExpiringMessageManager expiringMessageManager;
private JobManager jobManager;
private IncomingMessageObserver incomingMessageObserver;
private ObjectGraph objectGraph;
private PersistentLogger persistentLogger;
private volatile boolean isAppVisible;
@ -97,6 +100,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
initializeCrashHandling();
initializeDependencyInjection();
initializeJobManager();
initializeMessageRetrieval();
initializeExpiringMessageManager();
initializeGcmCheck();
initializeSignedPreKeyCheck();
@ -113,12 +117,14 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
isAppVisible = true;
Log.i(TAG, "App is now visible.");
executePendingContactSync();
KeyCachingService.onAppForegrounded(this);
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
isAppVisible = false;
Log.i(TAG, "App is no longer visible.");
KeyCachingService.onAppBackgrounded(this);
}
@Override
@ -168,6 +174,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
this.jobManager = new JobManager(WorkManager.getInstance());
}
public void initializeMessageRetrieval() {
this.incomingMessageObserver = new IncomingMessageObserver(this);
}
private void initializeDependencyInjection() {
this.objectGraph = ObjectGraph.create(new SignalCommunicationModule(this, new SignalServiceNetworkAccess(this)),
new AxolotlStorageModule(this));

View File

@ -15,7 +15,6 @@ import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.MessageRetrievalService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
@ -35,7 +34,6 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
private SignalServiceNetworkAccess networkAccess;
private BroadcastReceiver clearKeyReceiver;
private boolean isVisible;
@Override
protected final void onCreate(Bundle savedInstanceState) {
@ -61,28 +59,16 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
protected void onResume() {
Log.i(TAG, "onResume()");
super.onResume();
isVisible = true;
// Android P has a bug in foreground timings where starting a service in onResume() can still crash
Util.postToMain(() -> {
KeyCachingService.registerPassphraseActivityStarted(this);
if (!networkAccess.isCensored(this)) MessageRetrievalService.registerActivityStarted(this);
else ApplicationContext.getInstance(this).getJobManager().add(new PushNotificationReceiveJob(this));
});
if (networkAccess.isCensored(this)) {
ApplicationContext.getInstance(this).getJobManager().add(new PushNotificationReceiveJob(this));
}
}
@Override
protected void onPause() {
Log.i(TAG, "onPause()");
super.onPause();
isVisible = false;
// Android P has a bug in foreground timings where starting a service in onPause() can still crash
Util.postToMain(() -> {
KeyCachingService.registerPassphraseActivityStopped(this);
if (!networkAccess.isCensored(this)) MessageRetrievalService.registerActivityStopped(this);
});
}
@Override
@ -95,8 +81,8 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
@Override
public void onMasterSecretCleared() {
Log.i(TAG, "onMasterSecretCleared()");
if (isVisible) routeApplicationState(true);
else finish();
if (ApplicationContext.getInstance(this).isAppVisible()) routeApplicationState(true);
else finish();
}
protected <T extends Fragment> T initFragment(@IdRes int target,

View File

@ -44,7 +44,6 @@ import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.MessageRetrievalService;
import org.thoughtcrime.securesms.service.WebRtcCallService;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@ -67,7 +66,6 @@ public class WebRtcCallActivity extends Activity {
public static final String END_CALL_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".END_CALL_ACTION";
private WebRtcCallScreen callScreen;
private SignalServiceNetworkAccess networkAccess;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -89,12 +87,6 @@ public class WebRtcCallActivity extends Activity {
public void onResume() {
Log.i(TAG, "onResume()");
super.onResume();
// Android P has a bug in foreground timings where starting a service in onResume() can still crash
Util.postToMain(() -> {
if (!networkAccess.isCensored(this)) MessageRetrievalService.registerActivityStarted(this);
});
initializeScreenshotSecurity();
EventBus.getDefault().register(this);
}
@ -116,11 +108,6 @@ public class WebRtcCallActivity extends Activity {
Log.i(TAG, "onPause");
super.onPause();
EventBus.getDefault().unregister(this);
// Android P has a bug in foreground timings where starting a service in onPause() can still crash
Util.postToMain(() -> {
if (!networkAccess.isCensored(this)) MessageRetrievalService.registerActivityStopped(this);
});
}
@Override
@ -152,8 +139,6 @@ public class WebRtcCallActivity extends Activity {
callScreen.setCameraFlipButtonListener(new CameraFlipButtonListener());
callScreen.setSpeakerButtonListener(new SpeakerButtonListener());
callScreen.setBluetoothButtonListener(new BluetoothButtonListener());
networkAccess = new SignalServiceNetworkAccess(this);
}
private void handleSetMuteAudio(boolean enabled) {

View File

@ -38,7 +38,7 @@ import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.push.SecurityEventListener;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.MessageRetrievalService;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.service.WebRtcCallService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
@ -61,7 +61,7 @@ import dagger.Provides;
PushMediaSendJob.class,
AttachmentDownloadJob.class,
RefreshPreKeysJob.class,
MessageRetrievalService.class,
IncomingMessageObserver.class,
PushNotificationReceiveJob.class,
MultiDeviceContactUpdateJob.class,
MultiDeviceGroupUpdateJob.class,
@ -118,10 +118,10 @@ public class SignalCommunicationModule {
new DynamicCredentialsProvider(context),
new SignalProtocolStoreImpl(context),
BuildConfig.USER_AGENT,
Optional.fromNullable(MessageRetrievalService.getPipe()),
Optional.fromNullable(IncomingMessageObserver.getPipe()),
Optional.of(new SecurityEventListener(context)));
} else {
this.messageSender.setMessagePipe(MessageRetrievalService.getPipe());
this.messageSender.setMessagePipe(IncomingMessageObserver.getPipe());
}
return this.messageSender;
@ -142,6 +142,11 @@ public class SignalCommunicationModule {
return this.messageReceiver;
}
@Provides
synchronized SignalServiceNetworkAccess provideSignalServiceNetworkAccess() {
return networkAccess;
}
private static class DynamicCredentialsProvider implements CredentialsProvider {
private final Context context;

View File

@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.MessageRetrievalService;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.Util;
@ -107,7 +107,7 @@ public class RetrieveProfileJob extends ContextJob implements InjectableType {
}
private SignalServiceProfile retrieveProfile(@NonNull String number) throws IOException {
SignalServiceMessagePipe pipe = MessageRetrievalService.getPipe();
SignalServiceMessagePipe pipe = IncomingMessageObserver.getPipe();
if (pipe != null) {
try {

View File

@ -51,8 +51,8 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.MessageRetrievalService;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.SpanUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@ -164,7 +164,7 @@ public class MessageNotifier {
if (notification.getId() != SUMMARY_NOTIFICATION_ID &&
notification.getId() != CallNotificationBuilder.WEBRTC_NOTIFICATION &&
notification.getId() != KeyCachingService.SERVICE_RUNNING_ID &&
notification.getId() != MessageRetrievalService.FOREGROUND_ID &&
notification.getId() != IncomingMessageObserver.FOREGROUND_ID &&
notification.getId() != PENDING_MESSAGES_ID)
{
for (NotificationItem item : notificationState.getNotifications()) {

View File

@ -0,0 +1,202 @@
package org.thoughtcrime.securesms.service;
import android.app.Service;
import android.arch.lifecycle.DefaultLifecycleObserver;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.ProcessLifecycleOwner;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirementProvider;
import org.thoughtcrime.securesms.jobmanager.requirements.RequirementListener;
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.InvalidVersionException;
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.inject.Inject;
public class IncomingMessageObserver implements InjectableType, RequirementListener {
private static final String TAG = IncomingMessageObserver.class.getSimpleName();
public static final int FOREGROUND_ID = 313399;
private static final long REQUEST_TIMEOUT_MINUTES = 1;
private static SignalServiceMessagePipe pipe = null;
private final Context context;
private final NetworkRequirement networkRequirement;
private boolean appVisible;
@Inject SignalServiceMessageReceiver receiver;
@Inject SignalServiceNetworkAccess networkAccess;
public IncomingMessageObserver(@NonNull Context context) {
ApplicationContext.getInstance(context).injectDependencies(this);
this.context = context;
this.networkRequirement = new NetworkRequirement(context);
new NetworkRequirementProvider(context).setListener(this);
new MessageRetrievalThread().start();
if (TextSecurePreferences.isGcmDisabled(context)) {
ContextCompat.startForegroundService(context, new Intent(context, ForegroundService.class));
}
ProcessLifecycleOwner.get().getLifecycle().addObserver(new DefaultLifecycleObserver() {
@Override
public void onStart(@NonNull LifecycleOwner owner) {
onAppForegrounded();
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
onAppBackgrounded();
}
});
}
@Override
public void onRequirementStatusChanged() {
synchronized (this) {
notifyAll();
}
}
private synchronized void onAppForegrounded() {
appVisible = true;
notifyAll();
}
private synchronized void onAppBackgrounded() {
appVisible = false;
notifyAll();
}
private synchronized boolean isConnectionNecessary() {
boolean isGcmDisabled = TextSecurePreferences.isGcmDisabled(context);
Log.d(TAG, String.format("Network requirement: %s, app visible: %s, gcm disabled: %b",
networkRequirement.isPresent(), appVisible, isGcmDisabled));
return TextSecurePreferences.isPushRegistered(context) &&
TextSecurePreferences.isWebsocketRegistered(context) &&
(appVisible || isGcmDisabled) &&
networkRequirement.isPresent() &&
!networkAccess.isCensored(context);
}
private synchronized void waitForConnectionNecessary() {
try {
while (!isConnectionNecessary()) wait();
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
private void shutdown(SignalServiceMessagePipe pipe) {
try {
pipe.shutdown();
} catch (Throwable t) {
Log.w(TAG, t);
}
}
public static @Nullable SignalServiceMessagePipe getPipe() {
return pipe;
}
private class MessageRetrievalThread extends Thread implements Thread.UncaughtExceptionHandler {
MessageRetrievalThread() {
super("MessageRetrievalService");
setUncaughtExceptionHandler(this);
}
@Override
public void run() {
while (true) {
Log.i(TAG, "Waiting for websocket state change....");
waitForConnectionNecessary();
Log.i(TAG, "Making websocket connection....");
pipe = receiver.createMessagePipe();
SignalServiceMessagePipe localPipe = pipe;
try {
while (isConnectionNecessary()) {
try {
Log.i(TAG, "Reading message...");
localPipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES,
envelope -> {
Log.i(TAG, "Retrieved envelope! " + envelope.getSource());
new PushContentReceiveJob(context).processEnvelope(envelope);
});
} catch (TimeoutException e) {
Log.w(TAG, "Application level read timeout...");
} catch (InvalidVersionException e) {
Log.w(TAG, e);
}
}
} catch (Throwable e) {
Log.w(TAG, e);
} finally {
Log.w(TAG, "Shutting down pipe...");
shutdown(localPipe);
}
Log.i(TAG, "Looping...");
}
}
@Override
public void uncaughtException(Thread t, Throwable e) {
Log.w(TAG, "*** Uncaught exception!");
Log.w(TAG, e);
}
}
public static class ForegroundService extends Service {
@Override
public @Nullable IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), NotificationChannels.OTHER);
builder.setContentTitle(getApplicationContext().getString(R.string.MessageRetrievalService_signal));
builder.setContentText(getApplicationContext().getString(R.string.MessageRetrievalService_background_connection_enabled));
builder.setPriority(NotificationCompat.PRIORITY_MIN);
builder.setWhen(0);
builder.setSmallIcon(R.drawable.ic_signal_grey_24dp);
startForeground(FOREGROUND_ID, builder.build());
return Service.START_STICKY;
}
}
}

View File

@ -29,6 +29,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import org.thoughtcrime.securesms.logging.Log;
@ -68,14 +69,10 @@ public class KeyCachingService extends Service {
private static final String PASSPHRASE_EXPIRED_EVENT = "org.thoughtcrime.securesms.service.action.PASSPHRASE_EXPIRED_EVENT";
public static final String CLEAR_KEY_ACTION = "org.thoughtcrime.securesms.service.action.CLEAR_KEY";
public static final String DISABLE_ACTION = "org.thoughtcrime.securesms.service.action.DISABLE";
public static final String ACTIVITY_START_EVENT = "org.thoughtcrime.securesms.service.action.ACTIVITY_START_EVENT";
public static final String ACTIVITY_STOP_EVENT = "org.thoughtcrime.securesms.service.action.ACTIVITY_STOP_EVENT";
public static final String LOCALE_CHANGE_EVENT = "org.thoughtcrime.securesms.service.action.LOCALE_CHANGE_EVENT";
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
private PendingIntent pending;
private int activitiesRunning = 0;
private final IBinder binder = new KeySetBinder();
private static MasterSecret masterSecret;
@ -98,6 +95,14 @@ public class KeyCachingService extends Service {
return masterSecret;
}
public static void onAppForegrounded(@NonNull Context context) {
ServiceUtil.getAlarmManager(context).cancel(buildExpirationPendingIntent(context));
}
public static void onAppBackgrounded(@NonNull Context context) {
startTimeoutIfAppropriate(context);
}
@SuppressLint("StaticFieldLeak")
public void setMasterSecret(final MasterSecret masterSecret) {
synchronized (KeyCachingService.class) {
@ -105,7 +110,7 @@ public class KeyCachingService extends Service {
foregroundService();
broadcastNewSecret();
startTimeoutIfAppropriate();
startTimeoutIfAppropriate(this);
new AsyncTask<Void, Void, Void>() {
@Override
@ -127,8 +132,6 @@ public class KeyCachingService extends Service {
if (intent.getAction() != null) {
switch (intent.getAction()) {
case CLEAR_KEY_ACTION: handleClearKey(); break;
case ACTIVITY_START_EVENT: handleActivityStarted(); break;
case ACTIVITY_STOP_EVENT: handleActivityStopped(); break;
case PASSPHRASE_EXPIRED_EVENT: handleClearKey(); break;
case DISABLE_ACTION: handleDisableService(); break;
case LOCALE_CHANGE_EVENT: handleLocaleChanged(); break;
@ -143,8 +146,6 @@ public class KeyCachingService extends Service {
public void onCreate() {
Log.i(TAG, "onCreate()");
super.onCreate();
this.pending = PendingIntent.getService(this, 0, new Intent(PASSPHRASE_EXPIRED_EVENT, null,
this, KeyCachingService.class), 0);
if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) {
try {
@ -174,21 +175,6 @@ public class KeyCachingService extends Service {
startActivity(intent);
}
private void handleActivityStarted() {
Log.d(TAG, "Incrementing activity count...");
AlarmManager alarmManager = ServiceUtil.getAlarmManager(this);
alarmManager.cancel(pending);
activitiesRunning++;
}
private void handleActivityStopped() {
Log.d(TAG, "Decrementing activity count...");
activitiesRunning--;
startTimeoutIfAppropriate();
}
@SuppressLint("StaticFieldLeak")
private void handleClearKey() {
Log.i(TAG, "handleClearKey()");
@ -233,27 +219,29 @@ public class KeyCachingService extends Service {
foregroundService();
}
private void startTimeoutIfAppropriate() {
boolean timeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(this);
long screenTimeout = TextSecurePreferences.getScreenLockTimeout(this);
private static void startTimeoutIfAppropriate(@NonNull Context context) {
boolean timeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(context);
long screenTimeout = TextSecurePreferences.getScreenLockTimeout(context);
if ((activitiesRunning == 0) && (KeyCachingService.masterSecret != null) &&
(timeoutEnabled && !TextSecurePreferences.isPasswordDisabled(this)) ||
(screenTimeout >= 60 && TextSecurePreferences.isScreenLockEnabled(this)))
if ((KeyCachingService.masterSecret != null) &&
(timeoutEnabled && !TextSecurePreferences.isPasswordDisabled(context)) ||
(screenTimeout >= 60 && TextSecurePreferences.isScreenLockEnabled(context)))
{
long passphraseTimeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(this);
long screenLockTimeoutSeconds = TextSecurePreferences.getScreenLockTimeout(this);
long passphraseTimeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(context);
long screenLockTimeoutSeconds = TextSecurePreferences.getScreenLockTimeout(context);
long timeoutMillis;
if (!TextSecurePreferences.isPasswordDisabled(this)) timeoutMillis = TimeUnit.MINUTES.toMillis(passphraseTimeoutMinutes);
else timeoutMillis = TimeUnit.SECONDS.toMillis(screenLockTimeoutSeconds);
if (!TextSecurePreferences.isPasswordDisabled(context)) timeoutMillis = TimeUnit.MINUTES.toMillis(passphraseTimeoutMinutes);
else timeoutMillis = TimeUnit.SECONDS.toMillis(screenLockTimeoutSeconds);
Log.i(TAG, "Starting timeout: " + timeoutMillis);
AlarmManager alarmManager = ServiceUtil.getAlarmManager(this);
alarmManager.cancel(pending);
alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + timeoutMillis, pending);
AlarmManager alarmManager = ServiceUtil.getAlarmManager(context);
PendingIntent expirationIntent = buildExpirationPendingIntent(context);
alarmManager.cancel(expirationIntent);
alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + timeoutMillis, expirationIntent);
}
}
@ -338,6 +326,11 @@ public class KeyCachingService extends Service {
return PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
}
private static PendingIntent buildExpirationPendingIntent(@NonNull Context context) {
Intent expirationIntent = new Intent(PASSPHRASE_EXPIRED_EVENT, null, context, KeyCachingService.class);
return PendingIntent.getService(context, 0, expirationIntent, 0);
}
@Override
public IBinder onBind(Intent arg0) {
return binder;
@ -348,16 +341,4 @@ public class KeyCachingService extends Service {
return KeyCachingService.this;
}
}
public static void registerPassphraseActivityStarted(Context activity) {
Intent intent = new Intent(activity, KeyCachingService.class);
intent.setAction(KeyCachingService.ACTIVITY_START_EVENT);
activity.startService(intent);
}
public static void registerPassphraseActivityStopped(Context activity) {
Intent intent = new Intent(activity, KeyCachingService.class);
intent.setAction(KeyCachingService.ACTIVITY_STOP_EVENT);
activity.startService(intent);
}
}

View File

@ -1,247 +0,0 @@
package org.thoughtcrime.securesms.service;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.gcm.GcmBroadcastReceiver;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirementProvider;
import org.thoughtcrime.securesms.jobmanager.requirements.RequirementListener;
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.InvalidVersionException;
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
public class MessageRetrievalService extends Service implements InjectableType, RequirementListener {
private static final String TAG = MessageRetrievalService.class.getSimpleName();
public static final String ACTION_ACTIVITY_STARTED = "ACTIVITY_STARTED";
public static final String ACTION_ACTIVITY_FINISHED = "ACTIVITY_FINISHED";
public static final String ACTION_PUSH_RECEIVED = "PUSH_RECEIVED";
public static final String ACTION_INITIALIZE = "INITIALIZE";
public static final int FOREGROUND_ID = 313399;
private static final long REQUEST_TIMEOUT_MINUTES = 1;
private NetworkRequirement networkRequirement;
private NetworkRequirementProvider networkRequirementProvider;
@Inject
public SignalServiceMessageReceiver receiver;
private int activeActivities = 0;
private List<Intent> pushPending = new LinkedList<>();
private MessageRetrievalThread retrievalThread = null;
public static SignalServiceMessagePipe pipe = null;
@Override
public void onCreate() {
super.onCreate();
ApplicationContext.getInstance(this).injectDependencies(this);
networkRequirement = new NetworkRequirement(this);
networkRequirementProvider = new NetworkRequirementProvider(this);
networkRequirementProvider.setListener(this);
retrievalThread = new MessageRetrievalThread();
retrievalThread.start();
setForegroundIfNecessary();
}
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) return START_STICKY;
if (ACTION_ACTIVITY_STARTED.equals(intent.getAction())) incrementActive();
else if (ACTION_ACTIVITY_FINISHED.equals(intent.getAction())) decrementActive();
else if (ACTION_PUSH_RECEIVED.equals(intent.getAction())) incrementPushReceived(intent);
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
if (retrievalThread != null) {
retrievalThread.stopThread();
}
sendBroadcast(new Intent("org.thoughtcrime.securesms.RESTART"));
}
@Override
public void onRequirementStatusChanged() {
synchronized (this) {
notifyAll();
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void setForegroundIfNecessary() {
if (TextSecurePreferences.isGcmDisabled(this)) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationChannels.OTHER);
builder.setContentTitle(getString(R.string.MessageRetrievalService_signal));
builder.setContentText(getString(R.string.MessageRetrievalService_background_connection_enabled));
builder.setPriority(NotificationCompat.PRIORITY_MIN);
builder.setWhen(0);
builder.setSmallIcon(R.drawable.ic_signal_grey_24dp);
startForeground(FOREGROUND_ID, builder.build());
}
}
private synchronized void incrementActive() {
activeActivities++;
Log.d(TAG, "Active Count: " + activeActivities);
notifyAll();
}
private synchronized void decrementActive() {
activeActivities--;
Log.d(TAG, "Active Count: " + activeActivities);
notifyAll();
}
private synchronized void incrementPushReceived(Intent intent) {
pushPending.add(intent);
notifyAll();
}
private synchronized void decrementPushReceived() {
if (!pushPending.isEmpty()) {
Intent intent = pushPending.remove(0);
GcmBroadcastReceiver.completeWakefulIntent(intent);
notifyAll();
}
}
private synchronized boolean isConnectionNecessary() {
boolean isGcmDisabled = TextSecurePreferences.isGcmDisabled(this);
Log.d(TAG, String.format("Network requirement: %s, active activities: %s, push pending: %s, gcm disabled: %b",
networkRequirement.isPresent(), activeActivities, pushPending.size(), isGcmDisabled));
return TextSecurePreferences.isPushRegistered(this) &&
TextSecurePreferences.isWebsocketRegistered(this) &&
(activeActivities > 0 || !pushPending.isEmpty() || isGcmDisabled) &&
networkRequirement.isPresent();
}
private synchronized void waitForConnectionNecessary() {
try {
while (!isConnectionNecessary()) wait();
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
private void shutdown(SignalServiceMessagePipe pipe) {
try {
pipe.shutdown();
} catch (Throwable t) {
Log.w(TAG, t);
}
}
public static void registerActivityStarted(Context activity) {
Intent intent = new Intent(activity, MessageRetrievalService.class);
intent.setAction(MessageRetrievalService.ACTION_ACTIVITY_STARTED);
activity.startService(intent);
}
public static void registerActivityStopped(Context activity) {
Intent intent = new Intent(activity, MessageRetrievalService.class);
intent.setAction(MessageRetrievalService.ACTION_ACTIVITY_FINISHED);
activity.startService(intent);
}
public static @Nullable SignalServiceMessagePipe getPipe() {
return pipe;
}
private class MessageRetrievalThread extends Thread implements Thread.UncaughtExceptionHandler {
private AtomicBoolean stopThread = new AtomicBoolean(false);
MessageRetrievalThread() {
super("MessageRetrievalService");
setUncaughtExceptionHandler(this);
}
@Override
public void run() {
while (!stopThread.get()) {
Log.i(TAG, "Waiting for websocket state change....");
waitForConnectionNecessary();
Log.i(TAG, "Making websocket connection....");
pipe = receiver.createMessagePipe();
SignalServiceMessagePipe localPipe = pipe;
try {
while (isConnectionNecessary() && !stopThread.get()) {
try {
Log.i(TAG, "Reading message...");
localPipe.read(REQUEST_TIMEOUT_MINUTES, TimeUnit.MINUTES,
envelope -> {
Log.i(TAG, "Retrieved envelope! " + envelope.getSource());
new PushContentReceiveJob(getApplicationContext()).processEnvelope(envelope);
decrementPushReceived();
});
} catch (TimeoutException e) {
Log.w(TAG, "Application level read timeout...");
} catch (InvalidVersionException e) {
Log.w(TAG, e);
}
}
} catch (Throwable e) {
Log.w(TAG, e);
} finally {
Log.w(TAG, "Shutting down pipe...");
shutdown(localPipe);
}
Log.i(TAG, "Looping...");
}
Log.i(TAG, "Exiting...");
}
private void stopThread() {
stopThread.set(true);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
Log.w(TAG, "*** Uncaught exception!");
Log.w(TAG, e);
}
}
}

View File

@ -4,21 +4,19 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class PersistentConnectionBootListener extends BroadcastReceiver {
private static final String TAG = PersistentConnectionBootListener.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
if (TextSecurePreferences.isGcmDisabled(context)) {
Intent serviceIntent = new Intent(context, MessageRetrievalService.class);
serviceIntent.setAction(MessageRetrievalService.ACTION_INITIALIZE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) context.startForegroundService(serviceIntent);
else context.startService(serviceIntent);
}
Log.i(TAG, "Received boot event. Application should be started, allowing non-GCM devices to start a foreground service.");
}
}
}