mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-30 21:45:20 +00:00
Merge branch 'dev' into ui
This commit is contained in:
commit
3f83cc9450
@ -680,7 +680,7 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<!-- Session -->
|
<!-- Session -->
|
||||||
<receiver android:name="org.thoughtcrime.securesms.loki.api.BackgroundPollWorker">
|
<receiver android:name="org.thoughtcrime.securesms.loki.api.BackgroundPollListener" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@ -694,7 +694,6 @@
|
|||||||
<service
|
<service
|
||||||
android:name="org.thoughtcrime.securesms.jobmanager.KeepAliveService"
|
android:name="org.thoughtcrime.securesms.jobmanager.KeepAliveService"
|
||||||
android:enabled="@bool/enable_alarm_manager" />
|
android:enabled="@bool/enable_alarm_manager" />
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name="org.thoughtcrime.securesms.jobmanager.AlarmManagerScheduler$RetryReceiver"
|
android:name="org.thoughtcrime.securesms.jobmanager.AlarmManagerScheduler$RetryReceiver"
|
||||||
android:enabled="@bool/enable_alarm_manager" /> <!-- Probably don't need this one -->
|
android:enabled="@bool/enable_alarm_manager" /> <!-- Probably don't need this one -->
|
||||||
|
@ -181,8 +181,8 @@ dependencies {
|
|||||||
testImplementation 'org.robolectric:shadows-multidex:4.2'
|
testImplementation 'org.robolectric:shadows-multidex:4.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 93
|
def canonicalVersionCode = 95
|
||||||
def canonicalVersionName = "1.5.2"
|
def canonicalVersionName = "1.5.3"
|
||||||
|
|
||||||
def postFixSize = 10
|
def postFixSize = 10
|
||||||
def abiPostFix = ['armeabi-v7a' : 1,
|
def abiPostFix = ['armeabi-v7a' : 1,
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
android:layout_marginLeft="@dimen/large_spacing"
|
android:layout_marginLeft="@dimen/large_spacing"
|
||||||
android:layout_marginTop="@dimen/large_spacing"
|
android:layout_marginTop="@dimen/large_spacing"
|
||||||
android:layout_marginRight="@dimen/large_spacing"
|
android:layout_marginRight="@dimen/large_spacing"
|
||||||
|
android:inputType="textWebEmailAddress"
|
||||||
android:hint="Enter an open group URL" />
|
android:hint="Enter an open group URL" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
android:layout_marginLeft="@dimen/large_spacing"
|
android:layout_marginLeft="@dimen/large_spacing"
|
||||||
android:layout_marginTop="@dimen/large_spacing"
|
android:layout_marginTop="@dimen/large_spacing"
|
||||||
android:layout_marginRight="@dimen/large_spacing"
|
android:layout_marginRight="@dimen/large_spacing"
|
||||||
|
android:inputType="textWebEmailAddress"
|
||||||
android:hint="@string/fragment_enter_chat_url_edit_text_hint" />
|
android:hint="@string/fragment_enter_chat_url_edit_text_hint" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
@ -31,6 +31,6 @@
|
|||||||
|
|
||||||
<color name="app_icon_background">#333132</color>
|
<color name="app_icon_background">#333132</color>
|
||||||
<color name="progress_bar_background">#0A000000</color>
|
<color name="progress_bar_background">#0A000000</color>
|
||||||
<color name="quote_not_found_background">#99FFFFFF</color>
|
<color name="quote_not_found_background">#8000E97B</color>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
@ -23,4 +23,9 @@
|
|||||||
<item name="android:elevation">4dp</item>
|
<item name="android:elevation">4dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.Session.Button.Dialog.Destructive">
|
||||||
|
<item name="android:background">@android:color/transparent</item>
|
||||||
|
<item name="android:textColor">@color/destructive</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
@ -33,7 +33,6 @@
|
|||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<!-- Session light theme (these values overlays their dark theme counterparts from Theme.TextSecure) -->
|
<!-- Session light theme (these values overlays their dark theme counterparts from Theme.TextSecure) -->
|
||||||
<style name="Theme.TextSecure.DayNight" parent="Base.Theme.TextSecure">
|
<style name="Theme.TextSecure.DayNight" parent="Base.Theme.TextSecure">
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<color name="progress_bar_background">#0AFFFFFF</color>
|
<color name="progress_bar_background">#0AFFFFFF</color>
|
||||||
<color name="compose_view_background">#1B1B1B</color>
|
<color name="compose_view_background">#1B1B1B</color>
|
||||||
<color name="compose_text_view_background">#141414</color>
|
<color name="compose_text_view_background">#141414</color>
|
||||||
<color name="quote_not_found_background">#99FFFFFF</color>
|
<color name="quote_not_found_background">#80FFFFFF</color>
|
||||||
<color name="new_conversation_button_collapsed_background">#1F1F1F</color>
|
<color name="new_conversation_button_collapsed_background">#1F1F1F</color>
|
||||||
<color name="new_conversation_button_shadow">#077C44</color>
|
<color name="new_conversation_button_shadow">#077C44</color>
|
||||||
<color name="pn_option_background">#1B1B1B</color>
|
<color name="pn_option_background">#1B1B1B</color>
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
android:dependency="pref_timeout_passphrase" />
|
android:dependency="pref_timeout_passphrase" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
||||||
android:defaultValue="false"
|
android:defaultValue="true"
|
||||||
android:key="pref_screen_security"
|
android:key="pref_screen_security"
|
||||||
android:title="@string/preferences__screen_security"
|
android:title="@string/preferences__screen_security"
|
||||||
android:summary="@string/preferences__disable_screen_security_to_allow_screen_shots" />
|
android:summary="@string/preferences__disable_screen_security_to_allow_screen_shots" />
|
||||||
|
@ -16,16 +16,17 @@
|
|||||||
*/
|
*/
|
||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||||
import androidx.multidex.MultiDexApplication;
|
import androidx.multidex.MultiDexApplication;
|
||||||
|
|
||||||
import com.google.firebase.iid.FirebaseInstanceId;
|
import com.google.firebase.iid.FirebaseInstanceId;
|
||||||
@ -60,7 +61,7 @@ import org.thoughtcrime.securesms.logging.Log;
|
|||||||
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
||||||
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
|
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
|
||||||
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
||||||
import org.thoughtcrime.securesms.loki.api.BackgroundPollWorker;
|
import org.thoughtcrime.securesms.loki.api.BackgroundPollListener;
|
||||||
import org.thoughtcrime.securesms.loki.api.ClosedGroupPoller;
|
import org.thoughtcrime.securesms.loki.api.ClosedGroupPoller;
|
||||||
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager;
|
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager;
|
||||||
import org.thoughtcrime.securesms.loki.api.PublicChatManager;
|
import org.thoughtcrime.securesms.loki.api.PublicChatManager;
|
||||||
@ -115,10 +116,10 @@ import org.whispersystems.signalservice.loki.protocol.closedgroups.SharedSenderK
|
|||||||
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
|
import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager;
|
||||||
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
|
import org.whispersystems.signalservice.loki.protocol.meta.SessionMetaProtocol;
|
||||||
import org.whispersystems.signalservice.loki.protocol.meta.TTLUtilities;
|
import org.whispersystems.signalservice.loki.protocol.meta.TTLUtilities;
|
||||||
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink;
|
|
||||||
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
|
|
||||||
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol;
|
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocol;
|
||||||
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocolDelegate;
|
import org.whispersystems.signalservice.loki.protocol.sessionmanagement.SessionManagementProtocolDelegate;
|
||||||
|
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.DeviceLink;
|
||||||
|
import org.whispersystems.signalservice.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
|
||||||
import org.whispersystems.signalservice.loki.protocol.shelved.syncmessages.SyncMessagesProtocol;
|
import org.whispersystems.signalservice.loki.protocol.shelved.syncmessages.SyncMessagesProtocol;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -253,6 +254,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
messageNotifier.setVisibleThread(-1);
|
messageNotifier.setVisibleThread(-1);
|
||||||
// Loki
|
// Loki
|
||||||
if (poller != null) { poller.stopIfNeeded(); }
|
if (poller != null) { poller.stopIfNeeded(); }
|
||||||
|
if (closedGroupPoller != null) { closedGroupPoller.stopIfNeeded(); }
|
||||||
if (publicChatManager != null) { publicChatManager.stopPollers(); }
|
if (publicChatManager != null) { publicChatManager.stopPollers(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,7 +385,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
RotateSignedPreKeyListener.schedule(this);
|
RotateSignedPreKeyListener.schedule(this);
|
||||||
LocalBackupListener.schedule(this);
|
LocalBackupListener.schedule(this);
|
||||||
RotateSenderCertificateListener.schedule(this);
|
RotateSenderCertificateListener.schedule(this);
|
||||||
BackgroundPollWorker.schedule(this); // Loki
|
BackgroundPollListener.schedule(this); // Loki
|
||||||
|
|
||||||
if (BuildConfig.PLAY_STORE_DISABLED) {
|
if (BuildConfig.PLAY_STORE_DISABLED) {
|
||||||
UpdateApkRefreshListener.schedule(this);
|
UpdateApkRefreshListener.schedule(this);
|
||||||
@ -513,9 +515,9 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
Context context = this;
|
Context context = this;
|
||||||
SwarmAPI.Companion.configureIfNeeded(apiDB);
|
SwarmAPI.Companion.configureIfNeeded(apiDB);
|
||||||
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
|
SnodeAPI.Companion.configureIfNeeded(userPublicKey, apiDB, broadcaster);
|
||||||
poller = new Poller(userPublicKey, apiDB, protos -> {
|
poller = new Poller(userPublicKey, apiDB, envelopes -> {
|
||||||
for (SignalServiceProtos.Envelope proto : protos) {
|
for (SignalServiceProtos.Envelope envelope : envelopes) {
|
||||||
new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(proto), false);
|
new PushContentReceiveJob(context).processEnvelope(new SignalServiceEnvelope(envelope), false);
|
||||||
}
|
}
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
});
|
});
|
||||||
@ -533,6 +535,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
public void stopPolling() {
|
public void stopPolling() {
|
||||||
if (poller != null) { poller.stopIfNeeded(); }
|
if (poller != null) { poller.stopIfNeeded(); }
|
||||||
if (closedGroupPoller != null) { closedGroupPoller.stopIfNeeded(); }
|
if (closedGroupPoller != null) { closedGroupPoller.stopIfNeeded(); }
|
||||||
|
if (publicChatManager != null) { publicChatManager.stopPollers(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resubmitProfilePictureIfNeeded() {
|
private void resubmitProfilePictureIfNeeded() {
|
||||||
|
@ -18,6 +18,7 @@ package org.thoughtcrime.securesms.conversation;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.ClipData;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@ -26,19 +27,6 @@ import android.net.Uri;
|
|||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.core.app.ActivityOptionsCompat;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.loader.app.LoaderManager;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.view.ActionMode;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
|
|
||||||
import android.text.ClipboardManager;
|
import android.text.ClipboardManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -55,6 +43,20 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.widget.ViewSwitcher;
|
import android.widget.ViewSwitcher;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.view.ActionMode;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.loader.app.LoaderManager;
|
||||||
|
import androidx.loader.content.Loader;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.OnScrollListener;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
@ -407,7 +409,7 @@ public class ConversationFragment extends Fragment
|
|||||||
for (MessageRecord message : messageRecords) {
|
for (MessageRecord message : messageRecords) {
|
||||||
if (!message.isOutgoing()) { areAllSentByUser = false; }
|
if (!message.isOutgoing()) { areAllSentByUser = false; }
|
||||||
}
|
}
|
||||||
menu.findItem(R.id.menu_context_copy_public_key).setVisible(isPublicChat && selectedMessageCount == 1 && !areAllSentByUser);
|
menu.findItem(R.id.menu_context_copy_public_key).setVisible(selectedMessageCount == 1 && !areAllSentByUser);
|
||||||
menu.findItem(R.id.menu_context_reply).setVisible(selectedMessageCount == 1);
|
menu.findItem(R.id.menu_context_reply).setVisible(selectedMessageCount == 1);
|
||||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
|
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(getContext());
|
||||||
boolean userCanModerate = isPublicChat && PublicChatAPI.Companion.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer());
|
boolean userCanModerate = isPublicChat && PublicChatAPI.Companion.isUserModerator(userHexEncodedPublicKey, publicChat.getChannel(), publicChat.getServer());
|
||||||
@ -490,8 +492,11 @@ public class ConversationFragment extends Fragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleCopyPublicKey(MessageRecord messageRecord) {
|
private void handleCopyPublicKey(MessageRecord messageRecord) {
|
||||||
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
String sessionID = messageRecord.getRecipient().getAddress().toPhoneString();
|
||||||
clipboard.setText(messageRecord.getRecipient().getAddress().toString());
|
android.content.ClipboardManager clipboard = (android.content.ClipboardManager)requireActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
ClipData clip = ClipData.newPlainText("Session ID", sessionID);
|
||||||
|
clipboard.setPrimaryClip(clip);
|
||||||
|
Toast.makeText(getContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDeleteMessages(final Set<MessageRecord> messageRecords) {
|
private void handleDeleteMessages(final Set<MessageRecord> messageRecords) {
|
||||||
|
@ -797,14 +797,21 @@ public class ConversationItem extends LinearLayout
|
|||||||
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)bodyBubble.getLayoutParams();
|
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)bodyBubble.getLayoutParams();
|
||||||
int groupThreadMargin = (int)((12 * getResources().getDisplayMetrics().density) + getResources().getDimension(R.dimen.small_profile_picture_size));
|
int groupThreadMargin = (int)((12 * getResources().getDisplayMetrics().density) + getResources().getDimension(R.dimen.small_profile_picture_size));
|
||||||
int defaultMargin = 0;
|
int defaultMargin = 0;
|
||||||
Recipient r = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(messageRecord.getThreadId());
|
long threadID = messageRecord.getThreadId();
|
||||||
|
Recipient r = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadID);
|
||||||
String threadName = r != null ? r.getName() : "";
|
String threadName = r != null ? r.getName() : "";
|
||||||
boolean isRSSFeed = threadName != null && (threadName.equals("Loki News") || threadName.equals("Session Updates"));
|
boolean isRSSFeed = threadName != null && (threadName.equals("Loki News") || threadName.equals("Session Updates"));
|
||||||
layoutParams.setMarginStart((groupThread && !isRSSFeed) ? groupThreadMargin : defaultMargin);
|
layoutParams.setMarginStart((groupThread && !isRSSFeed) ? groupThreadMargin : defaultMargin);
|
||||||
bodyBubble.setLayoutParams(layoutParams);
|
bodyBubble.setLayoutParams(layoutParams);
|
||||||
if (profilePictureView == null) return;
|
if (profilePictureView == null) return;
|
||||||
profilePictureView.setPublicKey(recipient.getAddress().toString());
|
String publicKey = recipient.getAddress().toString();
|
||||||
profilePictureView.setDisplayName(recipient.getName());
|
profilePictureView.setPublicKey(publicKey);
|
||||||
|
String displayName = recipient.getName();
|
||||||
|
PublicChat publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID);
|
||||||
|
if (displayName == null && publicChat != null) {
|
||||||
|
displayName = DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.getId(), publicKey);
|
||||||
|
}
|
||||||
|
profilePictureView.setDisplayName(displayName);
|
||||||
profilePictureView.setAdditionalPublicKey(null);
|
profilePictureView.setAdditionalPublicKey(null);
|
||||||
profilePictureView.setRSSFeed(false);
|
profilePictureView.setRSSFeed(false);
|
||||||
profilePictureView.setGlide(glideRequests);
|
profilePictureView.setGlide(glideRequests);
|
||||||
|
@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.jobs.SmsSentJob;
|
|||||||
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
|
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
|
||||||
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
||||||
import org.thoughtcrime.securesms.jobs.UpdateApkJob;
|
import org.thoughtcrime.securesms.jobs.UpdateApkJob;
|
||||||
|
import org.thoughtcrime.securesms.loki.api.BackgroundPollJob;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupUpdateMessageSendJob;
|
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupUpdateMessageSendJob;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.NullMessageSendJob;
|
import org.thoughtcrime.securesms.loki.protocol.NullMessageSendJob;
|
||||||
|
|
||||||
@ -85,6 +86,7 @@ public class WorkManagerFactoryMappings {
|
|||||||
put(RetrieveProfileAvatarJob.class.getName(), RetrieveProfileAvatarJob.KEY);
|
put(RetrieveProfileAvatarJob.class.getName(), RetrieveProfileAvatarJob.KEY);
|
||||||
put(RetrieveProfileJob.class.getName(), RetrieveProfileJob.KEY);
|
put(RetrieveProfileJob.class.getName(), RetrieveProfileJob.KEY);
|
||||||
put(RotateCertificateJob.class.getName(), RotateCertificateJob.KEY);
|
put(RotateCertificateJob.class.getName(), RotateCertificateJob.KEY);
|
||||||
|
put(BackgroundPollJob.class.getName(), BackgroundPollJob.KEY);
|
||||||
put(RotateProfileKeyJob.class.getName(), RotateProfileKeyJob.KEY);
|
put(RotateProfileKeyJob.class.getName(), RotateProfileKeyJob.KEY);
|
||||||
put(RotateSignedPreKeyJob.class.getName(), RotateSignedPreKeyJob.KEY);
|
put(RotateSignedPreKeyJob.class.getName(), RotateSignedPreKeyJob.KEY);
|
||||||
put(SendDeliveryReceiptJob.class.getName(), SendDeliveryReceiptJob.KEY);
|
put(SendDeliveryReceiptJob.class.getName(), SendDeliveryReceiptJob.KEY);
|
||||||
|
@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver;
|
|||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint;
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
|
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
|
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
|
||||||
|
import org.thoughtcrime.securesms.loki.api.BackgroundPollJob;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupUpdateMessageSendJob;
|
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupUpdateMessageSendJob;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.shelved.MultiDeviceOpenGroupUpdateJob;
|
import org.thoughtcrime.securesms.loki.protocol.shelved.MultiDeviceOpenGroupUpdateJob;
|
||||||
import org.thoughtcrime.securesms.loki.protocol.NullMessageSendJob;
|
import org.thoughtcrime.securesms.loki.protocol.NullMessageSendJob;
|
||||||
@ -61,6 +62,7 @@ public final class JobManagerFactories {
|
|||||||
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application));
|
put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application));
|
||||||
put(RetrieveProfileJob.KEY, new RetrieveProfileJob.Factory(application));
|
put(RetrieveProfileJob.KEY, new RetrieveProfileJob.Factory(application));
|
||||||
put(RotateCertificateJob.KEY, new RotateCertificateJob.Factory());
|
put(RotateCertificateJob.KEY, new RotateCertificateJob.Factory());
|
||||||
|
put(BackgroundPollJob.KEY, new BackgroundPollJob.Factory());
|
||||||
put(RotateProfileKeyJob.KEY, new RotateProfileKeyJob.Factory());
|
put(RotateProfileKeyJob.KEY, new RotateProfileKeyJob.Factory());
|
||||||
put(RotateSignedPreKeyJob.KEY, new RotateSignedPreKeyJob.Factory());
|
put(RotateSignedPreKeyJob.KEY, new RotateSignedPreKeyJob.Factory());
|
||||||
put(SendDeliveryReceiptJob.KEY, new SendDeliveryReceiptJob.Factory());
|
put(SendDeliveryReceiptJob.KEY, new SendDeliveryReceiptJob.Factory());
|
||||||
|
84
src/org/thoughtcrime/securesms/loki/api/BackgroundPollJob.kt
Normal file
84
src/org/thoughtcrime/securesms/loki/api/BackgroundPollJob.kt
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki.api
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import nl.komponents.kovenant.Promise
|
||||||
|
import nl.komponents.kovenant.all
|
||||||
|
import nl.komponents.kovenant.functional.map
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.dependencies.InjectableType
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Data
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Job
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||||
|
import org.thoughtcrime.securesms.jobs.BaseJob
|
||||||
|
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
|
||||||
|
import org.thoughtcrime.securesms.jobs.RotateCertificateJob
|
||||||
|
import org.thoughtcrime.securesms.logging.Log
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||||
|
import org.whispersystems.signalservice.loki.api.SnodeAPI
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class BackgroundPollJob private constructor(parameters: Parameters) : BaseJob(parameters) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KEY = "BackgroundPollJob"
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context) : this(Parameters.Builder()
|
||||||
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
|
.setQueue(KEY)
|
||||||
|
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
||||||
|
.setMaxAttempts(Parameters.UNLIMITED)
|
||||||
|
.build()) {
|
||||||
|
setContext(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(): Data {
|
||||||
|
return Data.EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFactoryKey(): String { return KEY }
|
||||||
|
|
||||||
|
public override fun onRun() {
|
||||||
|
try {
|
||||||
|
Log.d("Loki", "Performing background poll.")
|
||||||
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
|
val promises = mutableListOf<Promise<Unit, Exception>>()
|
||||||
|
val promise = SnodeAPI.shared.getMessages(userPublicKey).map { envelopes ->
|
||||||
|
envelopes.forEach {
|
||||||
|
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
promises.add(promise)
|
||||||
|
promises.addAll(ClosedGroupPoller.shared.pollOnce())
|
||||||
|
val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { it.value }
|
||||||
|
for (openGroup in openGroups) {
|
||||||
|
val poller = PublicChatPoller(context, openGroup)
|
||||||
|
poller.stop()
|
||||||
|
promises.add(poller.pollForNewMessages())
|
||||||
|
}
|
||||||
|
all(promises).get()
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
Log.d("Loki", "Background poll failed due to error: $exception.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun onShouldRetry(e: Exception): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCanceled() { }
|
||||||
|
|
||||||
|
class Factory : Job.Factory<BackgroundPollJob> {
|
||||||
|
|
||||||
|
override fun create(parameters: Parameters, data: Data): BackgroundPollJob {
|
||||||
|
return BackgroundPollJob(parameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki.api
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import nl.komponents.kovenant.functional.map
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
|
||||||
|
import org.thoughtcrime.securesms.service.PersistentAlarmManagerListener
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
|
||||||
|
import org.whispersystems.signalservice.loki.api.SnodeAPI
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class BackgroundPollListener : PersistentAlarmManagerListener() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val pollInterval = TimeUnit.MINUTES.toMillis(15)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun schedule(context: Context) {
|
||||||
|
BackgroundPollListener().onReceive(context, Intent())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNextScheduledExecutionTime(context: Context): Long {
|
||||||
|
return TextSecurePreferences.getBackgroundPollTime(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAlarm(context: Context, scheduledTime: Long): Long {
|
||||||
|
ApplicationContext.getInstance(context).jobManager.add(BackgroundPollJob(context))
|
||||||
|
val nextTime = System.currentTimeMillis() + pollInterval
|
||||||
|
TextSecurePreferences.setBackgroundPollTime(context, nextTime)
|
||||||
|
return nextTime
|
||||||
|
}
|
||||||
|
}
|
@ -1,59 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.loki.api
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import nl.komponents.kovenant.functional.map
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
|
||||||
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
|
|
||||||
import org.thoughtcrime.securesms.service.PersistentAlarmManagerListener
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
|
|
||||||
import org.whispersystems.signalservice.loki.api.SnodeAPI
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class BackgroundPollWorker : PersistentAlarmManagerListener() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val pollInterval = TimeUnit.MINUTES.toMillis(30)
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun schedule(context: Context) {
|
|
||||||
BackgroundPollWorker().onReceive(context, Intent())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getNextScheduledExecutionTime(context: Context): Long {
|
|
||||||
return TextSecurePreferences.getBackgroundPollTime(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAlarm(context: Context, scheduledTime: Long): Long {
|
|
||||||
if (scheduledTime != 0L) {
|
|
||||||
if (!TextSecurePreferences.isUsingFCM(context)) {
|
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
|
||||||
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
|
||||||
try {
|
|
||||||
val applicationContext = context.applicationContext as ApplicationContext
|
|
||||||
val broadcaster = applicationContext.broadcaster
|
|
||||||
SnodeAPI.configureIfNeeded(userPublicKey, lokiAPIDatabase, broadcaster)
|
|
||||||
SnodeAPI.shared.getMessages(userPublicKey).map { messages ->
|
|
||||||
messages.forEach {
|
|
||||||
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (exception: Throwable) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val openGroups = DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().map { it.value }
|
|
||||||
for (openGroup in openGroups) {
|
|
||||||
val poller = PublicChatPoller(context, openGroup)
|
|
||||||
poller.stop()
|
|
||||||
poller.pollForNewMessages()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val nextTime = System.currentTimeMillis() + pollInterval
|
|
||||||
TextSecurePreferences.setBackgroundPollTime(context, nextTime)
|
|
||||||
return nextTime
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.loki.api
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.functional.bind
|
import nl.komponents.kovenant.functional.bind
|
||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
|
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
|
||||||
@ -9,6 +10,7 @@ import org.thoughtcrime.securesms.logging.Log
|
|||||||
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase
|
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase
|
||||||
import org.thoughtcrime.securesms.loki.utilities.successBackground
|
import org.thoughtcrime.securesms.loki.utilities.successBackground
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
|
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
|
||||||
|
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
|
||||||
import org.whispersystems.signalservice.loki.api.SnodeAPI
|
import org.whispersystems.signalservice.loki.api.SnodeAPI
|
||||||
import org.whispersystems.signalservice.loki.api.SwarmAPI
|
import org.whispersystems.signalservice.loki.api.SwarmAPI
|
||||||
import org.whispersystems.signalservice.loki.utilities.getRandomElementOrNull
|
import org.whispersystems.signalservice.loki.utilities.getRandomElementOrNull
|
||||||
@ -50,6 +52,12 @@ class ClosedGroupPoller private constructor(private val context: Context, privat
|
|||||||
task.run()
|
task.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun pollOnce(): List<Promise<Unit, Exception>> {
|
||||||
|
if (isPolling) { return listOf() }
|
||||||
|
isPolling = true
|
||||||
|
return poll()
|
||||||
|
}
|
||||||
|
|
||||||
public fun stopIfNeeded() {
|
public fun stopIfNeeded() {
|
||||||
isPolling = false
|
isPolling = false
|
||||||
handler.removeCallbacks(task)
|
handler.removeCallbacks(task)
|
||||||
@ -57,24 +65,27 @@ class ClosedGroupPoller private constructor(private val context: Context, privat
|
|||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Private API
|
// region Private API
|
||||||
private fun poll() {
|
private fun poll(): List<Promise<Unit, Exception>> {
|
||||||
if (!isPolling) { return }
|
if (!isPolling) { return listOf() }
|
||||||
val publicKeys = database.getAllClosedGroupPublicKeys()
|
val publicKeys = database.getAllClosedGroupPublicKeys()
|
||||||
publicKeys.forEach { publicKey ->
|
return publicKeys.map { publicKey ->
|
||||||
SwarmAPI.shared.getSwarm(publicKey).bind { swarm ->
|
val promise = SwarmAPI.shared.getSwarm(publicKey).bind { swarm ->
|
||||||
val snode = swarm.getRandomElementOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure
|
val snode = swarm.getRandomElementOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure
|
||||||
if (!isPolling) { throw PollingCanceledException() }
|
if (!isPolling) { throw PollingCanceledException() }
|
||||||
SnodeAPI.shared.getRawMessages(snode, publicKey).map {SnodeAPI.shared.parseRawMessagesResponse(it, snode, publicKey) }
|
SnodeAPI.shared.getRawMessages(snode, publicKey).map {SnodeAPI.shared.parseRawMessagesResponse(it, snode, publicKey) }
|
||||||
}.successBackground { messages ->
|
}
|
||||||
|
promise.successBackground { messages ->
|
||||||
if (messages.isNotEmpty()) {
|
if (messages.isNotEmpty()) {
|
||||||
Log.d("Loki", "Received ${messages.count()} new message(s) in closed group with public key: $publicKey.")
|
Log.d("Loki", "Received ${messages.count()} new message(s) in closed group with public key: $publicKey.")
|
||||||
}
|
}
|
||||||
messages.forEach {
|
messages.forEach {
|
||||||
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false)
|
PushContentReceiveJob(context).processEnvelope(SignalServiceEnvelope(it), false)
|
||||||
}
|
}
|
||||||
}.fail {
|
}
|
||||||
|
promise.fail {
|
||||||
Log.d("Loki", "Polling failed for closed group with public key: $publicKey due to error: $it.")
|
Log.d("Loki", "Polling failed for closed group with public key: $publicKey due to error: $it.")
|
||||||
}
|
}
|
||||||
|
promise.map { Unit }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
@ -2,23 +2,41 @@ package org.thoughtcrime.securesms.loki.api
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.Data
|
||||||
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||||
|
import org.thoughtcrime.securesms.jobs.BaseJob
|
||||||
|
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupUpdateMessageSendJob
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.whispersystems.libsignal.logging.Log
|
import org.whispersystems.libsignal.logging.Log
|
||||||
import org.whispersystems.signalservice.internal.util.JsonUtil
|
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||||
import org.whispersystems.signalservice.loki.api.PushNotificationAcknowledgement
|
import org.whispersystems.signalservice.loki.api.PushNotificationAcknowledgement
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.lang.Exception
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
object LokiPushNotificationManager {
|
object LokiPushNotificationManager {
|
||||||
private val connection = OkHttpClient()
|
private val connection = OkHttpClient()
|
||||||
|
private val tokenExpirationInterval = 12 * 60 * 60 * 1000
|
||||||
|
|
||||||
private val server by lazy {
|
private val server by lazy {
|
||||||
PushNotificationAcknowledgement.shared.server
|
PushNotificationAcknowledgement.shared.server
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val tokenExpirationInterval = 12 * 60 * 60 * 1000
|
enum class ClosedGroupOperation {
|
||||||
|
Subscribe, Unsubscribe;
|
||||||
|
|
||||||
|
val rawValue: String
|
||||||
|
get() {
|
||||||
|
return when (this) {
|
||||||
|
Subscribe -> "subscribe_closed_group"
|
||||||
|
Unsubscribe -> "unsubscribe_closed_group"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun unregister(token: String, context: Context?) {
|
fun unregister(token: String, context: Context) {
|
||||||
val parameters = mapOf( "token" to token )
|
val parameters = mapOf( "token" to token )
|
||||||
val url = "$server/register"
|
val url = "$server/register"
|
||||||
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||||
@ -44,10 +62,16 @@ object LokiPushNotificationManager {
|
|||||||
Log.d("Loki", "Couldn't disable FCM.")
|
Log.d("Loki", "Couldn't disable FCM.")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// Unsubscribe from all closed groups
|
||||||
|
val allClosedGroupPublicKeys = DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys()
|
||||||
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
|
allClosedGroupPublicKeys.forEach { closedGroup ->
|
||||||
|
performOperation(context, ClosedGroupOperation.Unsubscribe, closedGroup, userPublicKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun register(token: String, publicKey: String, context: Context?, force: Boolean) {
|
fun register(token: String, publicKey: String, context: Context, force: Boolean) {
|
||||||
val oldToken = TextSecurePreferences.getFCMToken(context)
|
val oldToken = TextSecurePreferences.getFCMToken(context)
|
||||||
val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context)
|
val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context)
|
||||||
if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return }
|
if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return }
|
||||||
@ -78,5 +102,41 @@ object LokiPushNotificationManager {
|
|||||||
Log.d("Loki", "Couldn't register for FCM.")
|
Log.d("Loki", "Couldn't register for FCM.")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// Subscribe to all closed groups
|
||||||
|
val allClosedGroupPublicKeys = DatabaseFactory.getSSKDatabase(context).getAllClosedGroupPublicKeys()
|
||||||
|
allClosedGroupPublicKeys.forEach { closedGroup ->
|
||||||
|
performOperation(context, ClosedGroupOperation.Subscribe, closedGroup, publicKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun performOperation(context: Context, operation: ClosedGroupOperation, closedGroupPublicKey: String, publicKey: String) {
|
||||||
|
if (!TextSecurePreferences.isUsingFCM(context)) { return }
|
||||||
|
val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey )
|
||||||
|
val url = "$server/${operation.rawValue}"
|
||||||
|
val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters))
|
||||||
|
val request = Request.Builder().url(url).post(body).build()
|
||||||
|
connection.newCall(request).enqueue(object : Callback {
|
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
when (response.code()) {
|
||||||
|
200 -> {
|
||||||
|
val bodyAsString = response.body()!!.string()
|
||||||
|
val json = JsonUtil.fromJson(bodyAsString, Map::class.java)
|
||||||
|
val code = json?.get("code") as? Int
|
||||||
|
if (code == null || code == 0) {
|
||||||
|
Log.d("Loki", "Couldn't subscribe/unsubscribe to/from PNs for closed group with ID: $closedGroupPublicKey due to error: ${json?.get("message") as? String ?: "null"}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.d("Loki", "Couldn't subscribe/unsubscribe to/from PNs for closed group with ID: $closedGroupPublicKey.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call, exception: IOException) {
|
||||||
|
Log.d("Loki", "Couldn't subscribe/unsubscribe to/from PNs for closed group with ID: $closedGroupPublicKey due to error: $exception.")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import android.os.Handler
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.functional.bind
|
import nl.komponents.kovenant.functional.bind
|
||||||
|
import nl.komponents.kovenant.functional.map
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
import org.thoughtcrime.securesms.database.Address
|
import org.thoughtcrime.securesms.database.Address
|
||||||
@ -156,7 +157,7 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh
|
|||||||
return SignalServiceDataMessage(message.timestamp, serviceGroup, attachments, body, false, 0, false, null, false, quote, null, signalLinkPreviews, null)
|
return SignalServiceDataMessage(message.timestamp, serviceGroup, attachments, body, false, 0, false, null, false, quote, null, signalLinkPreviews, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pollForNewMessages() {
|
fun pollForNewMessages(): Promise<Unit, Exception> {
|
||||||
fun processIncomingMessage(message: PublicChatMessage) {
|
fun processIncomingMessage(message: PublicChatMessage) {
|
||||||
// If the sender of the current message is not a slave device, set the display name in the database
|
// If the sender of the current message is not a slave device, set the display name in the database
|
||||||
val masterHexEncodedPublicKey = MultiDeviceProtocol.shared.getMasterDevice(message.senderPublicKey)
|
val masterHexEncodedPublicKey = MultiDeviceProtocol.shared.getMasterDevice(message.senderPublicKey)
|
||||||
@ -217,7 +218,7 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isPollOngoing) { return }
|
if (isPollOngoing) { return Promise.of(Unit) }
|
||||||
isPollOngoing = true
|
isPollOngoing = true
|
||||||
val userDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userHexEncodedPublicKey)
|
val userDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userHexEncodedPublicKey)
|
||||||
var uniqueDevices = setOf<String>()
|
var uniqueDevices = setOf<String>()
|
||||||
@ -225,7 +226,7 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh
|
|||||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||||
FileServerAPI.configure(userHexEncodedPublicKey, userPrivateKey, apiDB)
|
FileServerAPI.configure(userHexEncodedPublicKey, userPrivateKey, apiDB)
|
||||||
// Kovenant propagates a context to chained promises, so LokiPublicChatAPI.sharedContext should be used for all of the below
|
// Kovenant propagates a context to chained promises, so LokiPublicChatAPI.sharedContext should be used for all of the below
|
||||||
api.getMessages(group.channel, group.server).bind(PublicChatAPI.sharedContext) { messages ->
|
val promise = api.getMessages(group.channel, group.server).bind(PublicChatAPI.sharedContext) { messages ->
|
||||||
/*
|
/*
|
||||||
if (messages.isNotEmpty()) {
|
if (messages.isNotEmpty()) {
|
||||||
// We need to fetch the device mapping for any devices we don't have
|
// We need to fetch the device mapping for any devices we don't have
|
||||||
@ -237,7 +238,8 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
Promise.of(messages)
|
Promise.of(messages)
|
||||||
}.successBackground {
|
}
|
||||||
|
promise.successBackground {
|
||||||
/*
|
/*
|
||||||
val newDisplayNameUpdatees = uniqueDevices.mapNotNull {
|
val newDisplayNameUpdatees = uniqueDevices.mapNotNull {
|
||||||
// This will return null if the current device is a master device
|
// This will return null if the current device is a master device
|
||||||
@ -246,7 +248,8 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh
|
|||||||
// Fetch the display names of the master devices
|
// Fetch the display names of the master devices
|
||||||
displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees)
|
displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees)
|
||||||
*/
|
*/
|
||||||
}.successBackground { messages ->
|
}
|
||||||
|
promise.successBackground { messages ->
|
||||||
// Process messages in the background
|
// Process messages in the background
|
||||||
messages.forEach { message ->
|
messages.forEach { message ->
|
||||||
if (userDevices.contains(message.senderPublicKey)) {
|
if (userDevices.contains(message.senderPublicKey)) {
|
||||||
@ -257,10 +260,12 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh
|
|||||||
}
|
}
|
||||||
isCaughtUp = true
|
isCaughtUp = true
|
||||||
isPollOngoing = false
|
isPollOngoing = false
|
||||||
}.fail {
|
}
|
||||||
|
promise.fail {
|
||||||
Log.d("Loki", "Failed to get messages for group chat with ID: ${group.channel} on server: ${group.server}.")
|
Log.d("Loki", "Failed to get messages for group chat with ID: ${group.channel} on server: ${group.server}.")
|
||||||
isPollOngoing = false
|
isPollOngoing = false
|
||||||
}
|
}
|
||||||
|
return promise.map { Unit }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pollForDisplayNames() {
|
private fun pollForDisplayNames() {
|
||||||
|
@ -26,7 +26,7 @@ class PushNotificationService : FirebaseMessagingService() {
|
|||||||
val envelope = MessageWrapper.unwrap(data)
|
val envelope = MessageWrapper.unwrap(data)
|
||||||
PushContentReceiveJob(this).processEnvelope(SignalServiceEnvelope(envelope), true)
|
PushContentReceiveJob(this).processEnvelope(SignalServiceEnvelope(envelope), true)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d("Loki", "Failed to unwrap data for message.")
|
Log.d("Loki", "Failed to unwrap data for message due to error: $e.")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.d("Loki", "Failed to decode data for message.")
|
Log.d("Loki", "Failed to decode data for message.")
|
||||||
|
@ -132,7 +132,6 @@ class ClosedGroupUpdateMessageSendJob private constructor(parameters: Parameters
|
|||||||
useFallbackEncryption, false, false)
|
useFallbackEncryption, false, false)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d("Loki", "Failed to send closed group update message to: $destination due to error: $e.")
|
Log.d("Loki", "Failed to send closed group update message to: $destination due to error: $e.")
|
||||||
throw e
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ import nl.komponents.kovenant.deferred
|
|||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.database.Address
|
import org.thoughtcrime.securesms.database.Address
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager
|
||||||
|
import org.thoughtcrime.securesms.loki.api.LokiPushNotificationManager.ClosedGroupOperation
|
||||||
import org.thoughtcrime.securesms.loki.utilities.recipient
|
import org.thoughtcrime.securesms.loki.utilities.recipient
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage
|
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
@ -66,13 +68,15 @@ object ClosedGroupsProtocol {
|
|||||||
if (member == userPublicKey) { continue }
|
if (member == userPublicKey) { continue }
|
||||||
val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind)
|
val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind)
|
||||||
job.setContext(context)
|
job.setContext(context)
|
||||||
job.onRun() // Run the job immediately
|
job.onRun() // Run the job immediately to make all of this sync
|
||||||
}
|
}
|
||||||
// Add the group to the user's set of public keys to poll for
|
// Add the group to the user's set of public keys to poll for
|
||||||
DatabaseFactory.getSSKDatabase(context).setClosedGroupPrivateKey(groupPublicKey, groupKeyPair.hexEncodedPrivateKey)
|
DatabaseFactory.getSSKDatabase(context).setClosedGroupPrivateKey(groupPublicKey, groupKeyPair.hexEncodedPrivateKey)
|
||||||
// Notify the user
|
// Notify the user
|
||||||
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
|
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false))
|
||||||
insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID)
|
insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID)
|
||||||
|
// Notify the PN server
|
||||||
|
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
||||||
// Fulfill the promise
|
// Fulfill the promise
|
||||||
deferred.resolve(groupID)
|
deferred.resolve(groupID)
|
||||||
}.start()
|
}.start()
|
||||||
@ -137,6 +141,8 @@ object ClosedGroupsProtocol {
|
|||||||
sskDatabase.removeClosedGroupPrivateKey(groupPublicKey)
|
sskDatabase.removeClosedGroupPrivateKey(groupPublicKey)
|
||||||
groupDB.setActive(groupID, false)
|
groupDB.setActive(groupID, false)
|
||||||
groupDB.remove(groupID, Address.fromSerialized(userPublicKey))
|
groupDB.remove(groupID, Address.fromSerialized(userPublicKey))
|
||||||
|
// Notify the PN server
|
||||||
|
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey)
|
||||||
} else {
|
} else {
|
||||||
// Establish sessions if needed
|
// Establish sessions if needed
|
||||||
establishSessionsWithMembersIfNeeded(context, members)
|
establishSessionsWithMembersIfNeeded(context, members)
|
||||||
@ -231,6 +237,7 @@ object ClosedGroupsProtocol {
|
|||||||
|
|
||||||
public fun handleNewClosedGroup(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) {
|
public fun handleNewClosedGroup(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) {
|
||||||
// Prepare
|
// Prepare
|
||||||
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
val sskDatabase = DatabaseFactory.getSSKDatabase(context)
|
val sskDatabase = DatabaseFactory.getSSKDatabase(context)
|
||||||
// Unwrap the message
|
// Unwrap the message
|
||||||
val groupPublicKey = closedGroupUpdate.groupPublicKey.toByteArray().toHexString()
|
val groupPublicKey = closedGroupUpdate.groupPublicKey.toByteArray().toHexString()
|
||||||
@ -258,6 +265,8 @@ object ClosedGroupsProtocol {
|
|||||||
insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins)
|
insertIncomingInfoMessage(context, senderPublicKey, groupID, GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins)
|
||||||
// Establish sessions if needed
|
// Establish sessions if needed
|
||||||
establishSessionsWithMembersIfNeeded(context, members)
|
establishSessionsWithMembersIfNeeded(context, members)
|
||||||
|
// Notify the PN server
|
||||||
|
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun handleClosedGroupUpdate(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) {
|
public fun handleClosedGroupUpdate(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate, senderPublicKey: String) {
|
||||||
@ -303,6 +312,8 @@ object ClosedGroupsProtocol {
|
|||||||
sskDatabase.removeClosedGroupPrivateKey(groupPublicKey)
|
sskDatabase.removeClosedGroupPrivateKey(groupPublicKey)
|
||||||
groupDB.setActive(groupID, false)
|
groupDB.setActive(groupID, false)
|
||||||
groupDB.remove(groupID, Address.fromSerialized(userPublicKey))
|
groupDB.remove(groupID, Address.fromSerialized(userPublicKey))
|
||||||
|
// Notify the PN server
|
||||||
|
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey)
|
||||||
} else {
|
} else {
|
||||||
establishSessionsWithMembersIfNeeded(context, members)
|
establishSessionsWithMembersIfNeeded(context, members)
|
||||||
val userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, userPublicKey)
|
val userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, userPublicKey)
|
||||||
|
@ -60,7 +60,12 @@ class ProfilePictureView : RelativeLayout {
|
|||||||
if (publicKey == null || publicKey.isBlank()) {
|
if (publicKey == null || publicKey.isBlank()) {
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey!!)
|
var result = DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey)
|
||||||
|
val publicChat = DatabaseFactory.getLokiThreadDatabase(context).getPublicChat(threadID)
|
||||||
|
if (result == null && publicChat != null) {
|
||||||
|
result = DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChat.id, publicKey)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (recipient.isGroupRecipient) {
|
if (recipient.isGroupRecipient) {
|
||||||
@ -76,7 +81,11 @@ class ProfilePictureView : RelativeLayout {
|
|||||||
if (masterPublicKey != null) {
|
if (masterPublicKey != null) {
|
||||||
users.remove(masterPublicKey)
|
users.remove(masterPublicKey)
|
||||||
}
|
}
|
||||||
val randomUsers = users.sorted() // Sort to provide a level of stability
|
val randomUsers = users.sorted().toMutableList() // Sort to provide a level of stability
|
||||||
|
if (users.count() == 1) {
|
||||||
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
|
randomUsers.add(0, userPublicKey) // Ensure the current user is at the back visually
|
||||||
|
}
|
||||||
val pk = randomUsers.getOrNull(0) ?: ""
|
val pk = randomUsers.getOrNull(0) ?: ""
|
||||||
publicKey = pk
|
publicKey = pk
|
||||||
displayName = getUserDisplayName(pk)
|
displayName = getUserDisplayName(pk)
|
||||||
|
@ -8,14 +8,14 @@ import android.os.AsyncTask;
|
|||||||
import android.provider.MediaStore.Images;
|
import android.provider.MediaStore.Images;
|
||||||
import android.provider.MediaStore.Video;
|
import android.provider.MediaStore.Video;
|
||||||
import android.provider.OpenableColumns;
|
import android.provider.OpenableColumns;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
@ -30,6 +30,8 @@ import java.util.LinkedList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the retrieval of media present on the user's device.
|
* Handles the retrieval of media present on the user's device.
|
||||||
*/
|
*/
|
||||||
@ -139,7 +141,6 @@ class MediaRepository {
|
|||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private @NonNull List<Media> getMediaInBucket(@NonNull Context context, @NonNull String bucketId, @NonNull Uri contentUri, boolean hasOrientation) {
|
private @NonNull List<Media> getMediaInBucket(@NonNull Context context, @NonNull String bucketId, @NonNull Uri contentUri, boolean hasOrientation) {
|
||||||
//TODO Constrain media file size to match the Loki protocol limit.
|
|
||||||
List<Media> media = new LinkedList<>();
|
List<Media> media = new LinkedList<>();
|
||||||
String selection = Images.Media.BUCKET_ID + " = ? AND " + Images.Media.DATA + " NOT NULL";
|
String selection = Images.Media.BUCKET_ID + " = ? AND " + Images.Media.DATA + " NOT NULL";
|
||||||
String[] selectionArgs = new String[] { bucketId };
|
String[] selectionArgs = new String[] { bucketId };
|
||||||
|
@ -30,13 +30,14 @@ import android.os.AsyncTask;
|
|||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.provider.OpenableColumns;
|
import android.provider.OpenableColumns;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.MediaPreviewActivity;
|
import org.thoughtcrime.securesms.MediaPreviewActivity;
|
||||||
import org.thoughtcrime.securesms.TransportOption;
|
import org.thoughtcrime.securesms.TransportOption;
|
||||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||||
@ -378,7 +379,6 @@ public class AttachmentManager {
|
|||||||
Permissions.with(activity)
|
Permissions.with(activity)
|
||||||
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
|
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
.withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
|
.withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
|
||||||
// .onAllGranted(() -> selectMediaType(activity, "image/*", new String[] {"image/*", "video/*"}, requestCode))
|
|
||||||
.onAllGranted(() -> activity.startActivityForResult(MediaSendActivity.buildGalleryIntent(activity, recipient, body, transport), requestCode))
|
.onAllGranted(() -> activity.startActivityForResult(MediaSendActivity.buildGalleryIntent(activity, recipient, body, transport), requestCode))
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
@ -455,7 +455,6 @@ public class AttachmentManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void selectMediaType(Activity activity, @NonNull String type, @Nullable String[] extraMimeType, int requestCode) {
|
private static void selectMediaType(Activity activity, @NonNull String type, @Nullable String[] extraMimeType, int requestCode) {
|
||||||
//TODO Constrain media file size to match the Loki protocol limit.
|
|
||||||
final Intent intent = new Intent();
|
final Intent intent = new Intent();
|
||||||
intent.setType(type);
|
intent.setType(type);
|
||||||
|
|
||||||
|
@ -8,20 +8,20 @@ import android.content.pm.PackageManager;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import androidx.annotation.DrawableRes;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
import com.annimon.stream.function.Consumer;
|
import com.annimon.stream.function.Consumer;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
import org.thoughtcrime.securesms.util.LRUCache;
|
import org.thoughtcrime.securesms.util.LRUCache;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
|
|
||||||
@ -31,6 +31,8 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
public class Permissions {
|
public class Permissions {
|
||||||
|
|
||||||
private static final Map<Integer, PermissionsRequest> OUTSTANDING = new LRUCache<>(2);
|
private static final Map<Integer, PermissionsRequest> OUTSTANDING = new LRUCache<>(2);
|
||||||
@ -140,9 +142,9 @@ public class Permissions {
|
|||||||
PermissionsRequest request = new PermissionsRequest(allGrantedListener, anyDeniedListener, anyPermanentlyDeniedListener, anyResultListener,
|
PermissionsRequest request = new PermissionsRequest(allGrantedListener, anyDeniedListener, anyPermanentlyDeniedListener, anyResultListener,
|
||||||
someGrantedListener, someDeniedListener, somePermanentlyDeniedListener);
|
someGrantedListener, someDeniedListener, somePermanentlyDeniedListener);
|
||||||
|
|
||||||
boolean targetSdk = Build.VERSION.SDK_INT >= minSdkVersion && Build.VERSION.SDK_INT <= maxSdkVersion;
|
boolean isInTargetSDKRange = (Build.VERSION.SDK_INT >= minSdkVersion && Build.VERSION.SDK_INT <= maxSdkVersion);
|
||||||
|
|
||||||
if (!targetSdk || permissionObject.hasAll(requestedPermissions)) {
|
if (!isInTargetSDKRange || permissionObject.hasAll(requestedPermissions)) {
|
||||||
executePreGrantedPermissionsRequest(request);
|
executePreGrantedPermissionsRequest(request);
|
||||||
} else if (rationaleDialogMessage != null && rationalDialogHeader != null) {
|
} else if (rationaleDialogMessage != null && rationalDialogHeader != null) {
|
||||||
executePermissionsRequestWithRationale(request);
|
executePermissionsRequestWithRationale(request);
|
||||||
|
@ -13,6 +13,7 @@ public abstract class PersistentAlarmManagerListener extends BroadcastReceiver {
|
|||||||
private static final String TAG = PersistentAlarmManagerListener.class.getSimpleName();
|
private static final String TAG = PersistentAlarmManagerListener.class.getSimpleName();
|
||||||
|
|
||||||
protected abstract long getNextScheduledExecutionTime(Context context);
|
protected abstract long getNextScheduledExecutionTime(Context context);
|
||||||
|
|
||||||
protected abstract long onAlarm(Context context, long scheduledTime);
|
protected abstract long onAlarm(Context context, long scheduledTime);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -157,7 +157,7 @@ class SaveAttachmentTask : ProgressDialogAsyncTask<SaveAttachmentTask.Attachment
|
|||||||
val mimeTypeMap = MimeTypeMap.getSingleton()
|
val mimeTypeMap = MimeTypeMap.getSingleton()
|
||||||
val extension = mimeTypeMap.getExtensionFromMimeType(contentType) ?: "attach"
|
val extension = mimeTypeMap.getExtensionFromMimeType(contentType) ?: "attach"
|
||||||
val dateFormatter = SimpleDateFormat("yyyy-MM-dd-HHmmss")
|
val dateFormatter = SimpleDateFormat("yyyy-MM-dd-HHmmss")
|
||||||
val base = "signal-${dateFormatter.format(timestamp)}"
|
val base = "session-${dateFormatter.format(timestamp)}"
|
||||||
|
|
||||||
return "${base}.${extension}";
|
return "${base}.${extension}";
|
||||||
}
|
}
|
||||||
|
@ -826,7 +826,7 @@ public class TextSecurePreferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isScreenSecurityEnabled(Context context) {
|
public static boolean isScreenSecurityEnabled(Context context) {
|
||||||
return getBooleanPreference(context, SCREEN_SECURITY_PREF, false);
|
return getBooleanPreference(context, SCREEN_SECURITY_PREF, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isLegacyUseLocalApnsEnabled(Context context) {
|
public static boolean isLegacyUseLocalApnsEnabled(Context context) {
|
||||||
|
Loading…
Reference in New Issue
Block a user