mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-17 13:08:25 +00:00
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.
This commit is contained in:
parent
7a6d863ff7
commit
45e0bb281f
@ -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"
|
||||
|
@ -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));
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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()) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user