Skip to content

Commit

Permalink
Unifiedpush (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
valldrac authored Nov 2, 2023
2 parents ffc6000 + ee30bc4 commit cfa7d32
Show file tree
Hide file tree
Showing 36 changed files with 1,217 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:

- name: Fetch and merge upstream
run: |
git remote add upstream https://github.com/signalapp/Signal-Android.git
git remote add upstream https://github.com/mollyim/mollyim-android.git
git fetch upstream
git merge --ff-only upstream/main
Expand Down
6 changes: 6 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ android {
androidTest {
java.srcDirs += "$projectDir/src/testShared"
}

main {
java.srcDirs += "$projectDir/src/unifiedpush/java"
}
}

compileOptions {
Expand Down Expand Up @@ -440,6 +444,8 @@ dependencies {
implementation libs.androidx.asynclayoutinflater.appcompat
implementation libs.androidx.webkit

implementation 'com.github.UnifiedPush:android-connector:2.1.1'

gmsImplementation (libs.firebase.messaging) {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
Expand Down
1 change: 1 addition & 0 deletions app/proguard/proguard.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
-keep class org.whispersystems.** { *; }
-keep class org.signal.libsignal.protocol.** { *; }
-keep class org.thoughtcrime.securesms.** { *; }
-keep class im.molly.unifiedpush.** { *; }
-keepclassmembers class ** {
public void onEvent*(**);
}
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,15 @@
android:enabled="@bool/enable_alarm_manager"
android:exported="false"/>

<receiver android:exported="true" android:enabled="true" android:name="im.molly.unifiedpush.receiver.UnifiedPushReceiver">
<intent-filter>
<action android:name="org.unifiedpush.android.connector.MESSAGE"/>
<action android:name="org.unifiedpush.android.connector.UNREGISTERED"/>
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT"/>
<action android:name="org.unifiedpush.android.connector.REGISTRATION_FAILED"/>
</intent-filter>
</receiver>

<uses-library android:name="org.apache.http.legacy" android:required="false"/>

</application>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,15 @@
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;

import im.molly.unifiedpush.util.UnifiedPushHelper;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.security.Security;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import im.molly.unifiedpush.jobs.UnifiedPushRefreshJob;
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
import io.reactivex.rxjava3.exceptions.UndeliverableException;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
Expand Down Expand Up @@ -462,14 +464,19 @@ private void initializeNetworkSettings() {
}
}

private void initializeFcmCheck() {
public void initializeFcmCheck() {
if (!SignalStore.account().isRegistered()) {
return;
}

PlayServicesUtil.PlayServicesStatus fcmStatus = PlayServicesUtil.getPlayServicesStatus(this);

if (fcmStatus == PlayServicesUtil.PlayServicesStatus.DISABLED) {
if (UnifiedPushHelper.isUnifiedPushAvailable()
|| fcmStatus == PlayServicesUtil.PlayServicesStatus.DISABLED) {
if (!SignalStore.unifiedpush().getAirGaped()) {
ApplicationDependencies.getJobManager().add(new UnifiedPushRefreshJob());
}
ApplicationDependencies.getJobManager().cancel(new FcmRefreshJob().getId());
if (SignalStore.account().isFcmEnabled()) {
Log.i(TAG, "Play Services are disabled. Disabling FCM.");
SignalStore.account().setFcmEnabled(false);
Expand All @@ -484,10 +491,12 @@ private void initializeFcmCheck() {
SignalStore.account().getFcmTokenLastSetTime() < 0) {
Log.i(TAG, "Play Services are newly-available. Updating to use FCM.");
SignalStore.account().setFcmEnabled(true);
ApplicationDependencies.getJobManager().cancel(new UnifiedPushRefreshJob().getId());
ApplicationDependencies.getJobManager().startChain(new FcmRefreshJob())
.then(new RefreshAttributesJob())
.enqueue();
} else {
ApplicationDependencies.getJobManager().cancel(new UnifiedPushRefreshJob().getId());
long nextSetTime = SignalStore.account().getFcmTokenLastSetTime() + TimeUnit.HOURS.toMillis(6);

if (SignalStore.account().getFcmToken() == null || nextSetTime <= System.currentTimeMillis()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import org.thoughtcrime.securesms.util.PowerManagerCompat;
import org.thoughtcrime.securesms.util.TextSecurePreferences;

import im.molly.unifiedpush.util.UnifiedPushHelper;

@SuppressLint("BatteryLife")
public class DozeReminder extends Reminder {

Expand All @@ -34,6 +36,7 @@ public DozeReminder(@NonNull final Context context) {
public static boolean isEligible(Context context) {
return !SignalStore.account().isFcmEnabled() &&
!TextSecurePreferences.hasPromptedOptimizeDoze(context) &&
!UnifiedPushHelper.isUnifiedPushAvailable() &&
!((PowerManager)context.getSystemService(Context.POWER_SERVICE)).isIgnoringBatteryOptimizations(context.getPackageName());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.components.settings.RadioListPreference
import org.thoughtcrime.securesms.components.settings.RadioListPreferenceViewHolder
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.components.settings.models.Banner
import org.thoughtcrime.securesms.keyvalue.SettingsValues.NotificationDeliveryMethod
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.TurnOnNotificationsBottomSheet
Expand Down Expand Up @@ -62,6 +63,11 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__
private val ledBlinkValues by lazy { resources.getStringArray(R.array.pref_led_blink_pattern_values) }
private val ledBlinkLabels by lazy { resources.getStringArray(R.array.pref_led_blink_pattern_entries) }

private val notificationMethodValues = NotificationDeliveryMethod.values().filterNot {
!SignalStore.account().fcmEnabled && it == NotificationDeliveryMethod.FCM
}
private val notificationMethodLabels by lazy { notificationMethodValues.map { resources.getString(it.getStringId()) }.toTypedArray() }

private lateinit var viewModel: NotificationsSettingsViewModel

override fun onResume() {
Expand Down Expand Up @@ -283,6 +289,30 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__
viewModel.setNotifyWhenContactJoinsSignal(!state.notifyWhenContactJoinsSignal)
}
)

if (!state.isLinkedDevice) {
dividerPref()

sectionHeaderPref(R.string.NotificationsSettingsFragment__pushStrategy)

radioListPref(
title = DSLSettingsText.from(R.string.NotificationsSettingsFragment__deliveryMethod),
listItems = notificationMethodLabels,
selected = notificationMethodValues.indexOf(state.notificationDeliveryMethod),
onSelected = {
viewModel.setNotificationDeliveryMethod(notificationMethodValues[it])
}
)

clickPref(
title = DSLSettingsText.from(R.string.NotificationsSettingsFragment__unifiedpush),
summary = DSLSettingsText.from(R.string.NotificationsSettingsFragment__unifiedpushDescription),
isEnabled = state.notificationDeliveryMethod == NotificationDeliveryMethod.UNIFIEDPUSH,
onClick = {
findNavController().safeNavigate(R.id.action_notificationsSettingsFragment_to_unifiedPushFragment)
}
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package org.thoughtcrime.securesms.components.settings.app.notifications

import android.net.Uri
import org.thoughtcrime.securesms.keyvalue.SettingsValues.NotificationDeliveryMethod

data class NotificationsSettingsState(
val isLinkedDevice: Boolean,
val messageNotificationsState: MessageNotificationsState,
val callNotificationsState: CallNotificationsState,
val notifyWhenContactJoinsSignal: Boolean
val notifyWhenContactJoinsSignal: Boolean,
val notificationDeliveryMethod: NotificationDeliveryMethod
)

data class MessageNotificationsState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,26 @@ import android.os.Build
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

import im.molly.unifiedpush.util.UnifiedPushHelper
import org.signal.core.util.concurrent.SignalExecutors
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SettingsValues.NotificationDeliveryMethod
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor
import org.thoughtcrime.securesms.util.livedata.Store
import org.unifiedpush.android.connector.UnifiedPush

class NotificationsSettingsViewModel(private val sharedPreferences: SharedPreferences) : ViewModel() {

private val store = Store(getState())

val state: LiveData<NotificationsSettingsState> = store.stateLiveData
private val EXECUTOR = SerialMonoLifoExecutor(SignalExecutors.UNBOUNDED)

init {
if (NotificationChannels.supported()) {
Expand Down Expand Up @@ -101,12 +109,36 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer
refresh()
}

fun setNotificationDeliveryMethod(method: NotificationDeliveryMethod) {
SignalStore.settings().notificationDeliveryMethod = method
SignalStore.unifiedpush().enabled = method == NotificationDeliveryMethod.UNIFIEDPUSH
SignalStore.internalValues().isWebsocketModeForced = method == NotificationDeliveryMethod.WEBSOCKET
val context = ApplicationContext.getInstance()
if (method == NotificationDeliveryMethod.UNIFIEDPUSH) {
UnifiedPush.getDistributors(context).getOrNull(0)?.let {
store.update { getState() }
EXECUTOR.enqueue {
UnifiedPush.saveDistributor(context, it)
UnifiedPush.registerApp(context)
UnifiedPushHelper.initializeMollySocketLinkedDevice(context)
}
// Do not enable if there is no distributor
} ?: return
} else {
UnifiedPush.unregisterApp(context)
SignalStore.unifiedpush().airGaped = false
SignalStore.unifiedpush().mollySocketUrl = null
}
refresh()
}

/**
* @param currentState If provided and [calculateSlowNotifications] = false, then we will copy the slow notification state from it
* @param calculateSlowNotifications If true, calculate the true slow notification state (this is not main-thread safe). Otherwise, it will copy from
* [currentState] or default to false.
*/
private fun getState(currentState: NotificationsSettingsState? = null, calculateSlowNotifications: Boolean = false): NotificationsSettingsState = NotificationsSettingsState(
isLinkedDevice = SignalStore.account().isLinkedDevice,
messageNotificationsState = MessageNotificationsState(
notificationsEnabled = SignalStore.settings().isMessageNotificationsEnabled && canEnableNotifications(),
canEnableNotifications = canEnableNotifications(),
Expand All @@ -125,7 +157,8 @@ class NotificationsSettingsViewModel(private val sharedPreferences: SharedPrefer
ringtone = SignalStore.settings().callRingtone,
vibrateEnabled = SignalStore.settings().isCallVibrateEnabled
),
notifyWhenContactJoinsSignal = SignalStore.settings().isNotifyWhenContactJoinsSignal
notifyWhenContactJoinsSignal = SignalStore.settings().isNotifyWhenContactJoinsSignal,
notificationDeliveryMethod = SignalStore.settings().notificationDeliveryMethod
)

private fun canEnableNotifications(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import im.molly.unifiedpush.util.UnifiedPushHelper;

/**
* Implementation of {@link ApplicationDependencies.Provider} that provides real app dependencies.
*/
Expand Down Expand Up @@ -285,7 +287,7 @@ public ApplicationDependencyProvider(@NonNull Application context) {

@Override
public @NonNull SignalWebSocket provideSignalWebSocket(@NonNull Supplier<SignalServiceConfiguration> signalServiceConfigurationSupplier) {
SleepTimer sleepTimer = !SignalStore.account().isFcmEnabled() || SignalStore.internalValues().isWebsocketModeForced() ? new AlarmSleepTimer(context) : new UptimeSleepTimer() ;
SleepTimer sleepTimer = !UnifiedPushHelper.isPushAvailable() || SignalStore.internalValues().isWebsocketModeForced() ? new AlarmSleepTimer(context) : new UptimeSleepTimer() ;
SignalWebSocketHealthMonitor healthMonitor = new SignalWebSocketHealthMonitor(context, sleepTimer);
SignalWebSocket signalWebSocket = new SignalWebSocket(provideWebSocketFactory(signalServiceConfigurationSupplier, healthMonitor));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ public void onSendError(@NonNull String s, @NonNull Exception e) {
Log.w(TAG, "onSendError()", e);
}

private static void handleReceivedNotification(Context context, @Nullable RemoteMessage remoteMessage) {
// MOLLY: Make this function public to use it from UnifiedPushReceiver
public static void handleReceivedNotification(Context context, @Nullable RemoteMessage remoteMessage) {
boolean highPriority = remoteMessage != null && remoteMessage.getPriority() == RemoteMessage.PRIORITY_HIGH;
try {
Log.d(TAG, String.format(Locale.US, "[handleReceivedNotification] API: %s, RemoteMessagePriority: %s", Build.VERSION.SDK_INT, remoteMessage != null ? remoteMessage.getPriority() : "n/a"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@
import java.util.List;
import java.util.Map;

import im.molly.unifiedpush.jobs.UnifiedPushRefreshJob;

public final class JobManagerFactories {

public static Map<String, Job.Factory> getJobFactories(@NonNull Application application) {
Expand Down Expand Up @@ -116,6 +118,7 @@ public static Map<String, Job.Factory> getJobFactories(@NonNull Application appl
put(DownloadLatestEmojiDataJob.KEY, new DownloadLatestEmojiDataJob.Factory());
put(EmojiSearchIndexDownloadJob.KEY, new EmojiSearchIndexDownloadJob.Factory());
put(FcmRefreshJob.KEY, new FcmRefreshJob.Factory());
put(UnifiedPushRefreshJob.KEY, new UnifiedPushRefreshJob.Factory());
put(FetchRemoteMegaphoneImageJob.KEY, new FetchRemoteMegaphoneImageJob.Factory());
put(FontDownloaderJob.KEY, new FontDownloaderJob.Factory());
put(ForceUpdateGroupV2Job.KEY, new ForceUpdateGroupV2Job.Factory());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,11 @@ public synchronized boolean callingDisableTelecom() {
* Whether or not the system is forced to be in 'websocket mode', where FCM is ignored and we use a foreground service to keep the app alive.
*/
public boolean isWebsocketModeForced() {
if (FeatureFlags.internalUser()) {
return getBoolean(FORCE_WEBSOCKET_MODE, false);
} else {
return false;
}
return getBoolean(FORCE_WEBSOCKET_MODE, false);
}

public void setWebsocketModeForced(boolean value) { putBoolean(FORCE_WEBSOCKET_MODE, value); }

public void setLastScrollPosition(int position) {
putInteger(LAST_SCROLL_POSITION, position);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ public final class SettingsValues extends SignalStoreValues {
private static final String KEEP_MUTED_CHATS_ARCHIVED = "settings.keepMutedChatsArchived";
private static final String USE_COMPACT_NAVIGATION_BAR = "settings.useCompactNavigationBar";

private static final String NOTIFICATION_DELIVERY_METHOD = "settings.notificationDeliveryMethod";

public static final int BACKUP_DEFAULT_HOUR = 2;
public static final int BACKUP_DEFAULT_MINUTE = 0;

Expand Down Expand Up @@ -462,6 +464,12 @@ public boolean getUseCompactNavigationBar() {
return getBoolean(USE_COMPACT_NAVIGATION_BAR, false);
}

public NotificationDeliveryMethod getNotificationDeliveryMethod() { return NotificationDeliveryMethod.valueOf(
getString(NOTIFICATION_DELIVERY_METHOD, NotificationDeliveryMethod.WEBSOCKET.toString())
);}

public void setNotificationDeliveryMethod(NotificationDeliveryMethod method) { putString(NOTIFICATION_DELIVERY_METHOD, method.toString()); };

private @Nullable Uri getUri(@NonNull String key) {
String uri = getString(key, "");

Expand Down Expand Up @@ -525,4 +533,20 @@ public enum Theme {
}
}
}

public enum NotificationDeliveryMethod {
FCM, WEBSOCKET, UNIFIEDPUSH;

public int getStringId() {
switch (this) {
case FCM:
return R.string.NotificationMethod__label_fcm;
case WEBSOCKET:
return R.string.NotificationMethod__label_websocket;
case UNIFIEDPUSH:
return R.string.NotificationMethod__label_unifiedpush;
}
throw new IllegalArgumentException("Unrecognized value " + this.toString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public final class SignalStore {
private final NotificationProfileValues notificationProfileValues;
private final ReleaseChannelValues releaseChannelValues;
private final StoryValues storyValues;
private final UnifiedPushValues unifiedPushValues;

private final PlainTextSharedPrefsDataStore plainTextValues;

Expand Down Expand Up @@ -88,6 +89,7 @@ private SignalStore(@NonNull KeyValueStore store) {
this.releaseChannelValues = new ReleaseChannelValues(store);
this.storyValues = new StoryValues(store);
this.plainTextValues = new PlainTextSharedPrefsDataStore(ApplicationDependencies.getApplication());
this.unifiedPushValues = new UnifiedPushValues(store);
}

public static void onFirstEverAppLaunch() {
Expand Down Expand Up @@ -276,6 +278,10 @@ public static void onPostBackupRestore() {
return getInstance().plainTextValues;
}

public static @NonNull UnifiedPushValues unifiedpush() {
return getInstance().unifiedPushValues;
}

/**
* Ensures any pending writes are finished. Only intended to be called by
* {@link SignalUncaughtExceptionHandler}.
Expand Down
Loading

0 comments on commit cfa7d32

Please sign in to comment.