mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-03 12:32:17 +00:00
Merge branch 'dev' into share
This commit is contained in:
@@ -30,6 +30,8 @@ import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.multidex.MultiDexApplication;
|
||||
|
||||
import com.google.firebase.iid.FirebaseInstanceId;
|
||||
|
||||
import org.conscrypt.Conscrypt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||
@@ -61,13 +63,14 @@ import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.logging.PersistentLogger;
|
||||
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
|
||||
import org.thoughtcrime.securesms.loki.LokiPublicChatManager;
|
||||
import org.thoughtcrime.securesms.loki.LokiPushNotificationManager;
|
||||
import org.thoughtcrime.securesms.loki.MultiDeviceUtilities;
|
||||
import org.thoughtcrime.securesms.loki.redesign.activities.HomeActivity;
|
||||
import org.thoughtcrime.securesms.loki.redesign.messaging.BackgroundOpenGroupPollWorker;
|
||||
import org.thoughtcrime.securesms.loki.redesign.messaging.BackgroundPollWorker;
|
||||
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiAPIDatabase;
|
||||
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiRSSFeedPoller;
|
||||
import org.thoughtcrime.securesms.loki.redesign.messaging.LokiUserDatabase;
|
||||
import org.thoughtcrime.securesms.loki.redesign.shelved.LokiRSSFeedPoller;
|
||||
import org.thoughtcrime.securesms.loki.redesign.utilities.Broadcaster;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
@@ -97,6 +100,7 @@ import org.whispersystems.signalservice.loki.api.LokiAPIDatabaseProtocol;
|
||||
import org.whispersystems.signalservice.loki.api.LokiP2PAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiP2PAPIDelegate;
|
||||
import org.whispersystems.signalservice.loki.api.LokiPoller;
|
||||
import org.whispersystems.signalservice.loki.api.LokiPushNotificationAcknowledgement;
|
||||
import org.whispersystems.signalservice.loki.api.LokiSwarmAPI;
|
||||
import org.whispersystems.signalservice.loki.api.fileserver.LokiFileServerAPI;
|
||||
import org.whispersystems.signalservice.loki.api.publicchats.LokiPublicChat;
|
||||
@@ -107,7 +111,6 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -182,6 +185,8 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
||||
// Loki - Set up P2P API if needed
|
||||
setUpP2PAPI();
|
||||
// Loki - Set up push notification acknowledgement
|
||||
LokiPushNotificationAcknowledgement.Companion.configureIfNeeded(BuildConfig.DEBUG);
|
||||
// Loki - Update device mappings
|
||||
if (setUpStorageAPIIfNeeded()) {
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this);
|
||||
@@ -199,6 +204,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
// Loki - Set up public chat manager
|
||||
lokiPublicChatManager = new LokiPublicChatManager(this);
|
||||
updatePublicChatProfilePictureIfNeeded();
|
||||
registerForFCMIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -455,6 +461,24 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
}, this);
|
||||
}
|
||||
|
||||
public void registerForFCMIfNeeded(Boolean force) {
|
||||
Context context = this;
|
||||
FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(task -> {
|
||||
if (!task.isSuccessful()) {
|
||||
Log.w(TAG, "getInstanceId failed", task.getException());
|
||||
return;
|
||||
}
|
||||
String token = task.getResult().getToken();
|
||||
String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context);
|
||||
if (userHexEncodedPublicKey == null) return;
|
||||
if (TextSecurePreferences.isUsingFCM(this)) {
|
||||
LokiPushNotificationManager.register(token, userHexEncodedPublicKey, context, force);
|
||||
} else {
|
||||
LokiPushNotificationManager.unregister(token, context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ping(@NotNull String s) {
|
||||
// TODO: Implement
|
||||
@@ -509,8 +533,10 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
}
|
||||
|
||||
public void createRSSFeedsIfNeeded() {
|
||||
return;
|
||||
/*
|
||||
ArrayList<LokiRSSFeed> feeds = new ArrayList<>();
|
||||
// feeds.add(lokiNewsFeed());
|
||||
feeds.add(lokiNewsFeed());
|
||||
feeds.add(lokiMessengerUpdatesFeed());
|
||||
for (LokiRSSFeed feed : feeds) {
|
||||
boolean isFeedSetUp = TextSecurePreferences.isChatSetUp(this, feed.getId());
|
||||
@@ -519,9 +545,12 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
TextSecurePreferences.markChatSetUp(this, feed.getId());
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private void createRSSFeedPollersIfNeeded() {
|
||||
return;
|
||||
/*
|
||||
// Only create the RSS feed pollers if their threads aren't deleted
|
||||
LokiRSSFeed lokiNewsFeed = lokiNewsFeed();
|
||||
long lokiNewsFeedThreadID = GroupManager.getRSSFeedThreadId(lokiNewsFeed.getId(), this);
|
||||
@@ -537,6 +566,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
if (lokiMessengerUpdatesFeedPoller == null) {
|
||||
lokiMessengerUpdatesFeedPoller = new LokiRSSFeedPoller(this, lokiMessengerUpdatesFeed());
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private void setUpThreadDeletionListeners(long threadID, Runnable onDelete) {
|
||||
@@ -561,9 +591,12 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
||||
}
|
||||
|
||||
public void startRSSFeedPollersIfNeeded() {
|
||||
return;
|
||||
/*
|
||||
createRSSFeedPollersIfNeeded();
|
||||
if (lokiNewsFeedPoller != null) lokiNewsFeedPoller.startIfNeeded();
|
||||
if (lokiMessengerUpdatesFeedPoller != null) lokiMessengerUpdatesFeedPoller.startIfNeeded();
|
||||
*/
|
||||
}
|
||||
|
||||
private void resubmitProfilePictureIfNeeded() {
|
||||
|
||||
@@ -317,7 +317,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private Button makeDefaultSmsButton;
|
||||
private Button registerButton;
|
||||
private InputAwareLayout container;
|
||||
private View composePanel;
|
||||
protected Stub<ReminderView> reminderView;
|
||||
private Stub<UnverifiedBannerView> unverifiedBannerView;
|
||||
private Stub<GroupShareProfileView> groupShareProfileView;
|
||||
@@ -552,7 +551,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
updateTitleTextView(recipient);
|
||||
updateSubtitleTextView();
|
||||
setActionBarColor(recipient.getColor());
|
||||
setBlockedUserState(recipient, isSecureText, isDefaultSms);
|
||||
updateInputUI(recipient, isSecureText, isDefaultSms);
|
||||
setGroupShareProfileReminder(recipient);
|
||||
calculateCharactersRemaining();
|
||||
|
||||
@@ -645,7 +644,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
updateTitleTextView(recipient);
|
||||
updateSubtitleTextView();
|
||||
NotificationChannels.updateContactChannelName(this, recipient);
|
||||
setBlockedUserState(recipient, isSecureText, isDefaultSms);
|
||||
updateInputUI(recipient, isSecureText, isDefaultSms);
|
||||
supportInvalidateOptionsMenu();
|
||||
break;
|
||||
case TAKE_PHOTO:
|
||||
@@ -858,6 +857,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
searchViewModel.onSearchClosed();
|
||||
searchNav.setVisibility(View.GONE);
|
||||
inputPanel.setVisibility(View.VISIBLE);
|
||||
updateInputUI(recipient, isSecureText, isDefaultSms);
|
||||
fragment.onSearchQueryUpdated(null);
|
||||
invalidateOptionsMenu();
|
||||
return true;
|
||||
@@ -1343,7 +1343,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
calculateCharactersRemaining();
|
||||
supportInvalidateOptionsMenu();
|
||||
setBlockedUserState(recipient, isSecureText, isDefaultSms);
|
||||
updateInputUI(recipient, isSecureText, isDefaultSms);
|
||||
}
|
||||
|
||||
///// Initializers
|
||||
@@ -1627,7 +1627,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
unblockButton = ViewUtil.findById(this, R.id.unblock_button);
|
||||
makeDefaultSmsButton = ViewUtil.findById(this, R.id.make_default_sms_button);
|
||||
registerButton = ViewUtil.findById(this, R.id.register_button);
|
||||
composePanel = ViewUtil.findById(this, R.id.bottom_panel);
|
||||
container = ViewUtil.findById(this, R.id.layout_container);
|
||||
reminderView = ViewUtil.findStubById(this, R.id.reminder_stub);
|
||||
unverifiedBannerView = ViewUtil.findStubById(this, R.id.unverified_banner_stub);
|
||||
@@ -1835,7 +1834,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
updateTitleTextView(recipient);
|
||||
updateSubtitleTextView();
|
||||
// titleView.setVerified(identityRecords.isVerified());
|
||||
setBlockedUserState(recipient, isSecureText, isDefaultSms);
|
||||
updateInputUI(recipient, isSecureText, isDefaultSms);
|
||||
setActionBarColor(recipient.getColor());
|
||||
setGroupShareProfileReminder(recipient);
|
||||
updateReminders(recipient.hasSeenInviteReminder());
|
||||
@@ -2043,29 +2042,30 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
setStatusBarColor(getResources().getColor(R.color.action_bar_background));
|
||||
}
|
||||
|
||||
private void setBlockedUserState(Recipient recipient, boolean isSecureText, boolean isDefaultSms) {
|
||||
if (recipient.isGroupRecipient() && recipient.getAddress().isRSSFeed()) {
|
||||
// FIXME: This name is confusing because we also have updateInputPanel and setInputPanelEnabled
|
||||
private void updateInputUI(Recipient recipient, boolean isSecureText, boolean isDefaultSms) {
|
||||
if (recipient.isGroupRecipient() && !isActiveGroup()) {
|
||||
unblockButton.setVisibility(View.GONE);
|
||||
composePanel.setVisibility(View.GONE);
|
||||
inputPanel.setVisibility(View.GONE);
|
||||
makeDefaultSmsButton.setVisibility(View.GONE);
|
||||
registerButton.setVisibility(View.GONE);
|
||||
} else if (recipient.isBlocked()) {
|
||||
unblockButton.setVisibility(View.VISIBLE);
|
||||
composePanel.setVisibility(View.GONE);
|
||||
inputPanel.setVisibility(View.GONE);
|
||||
makeDefaultSmsButton.setVisibility(View.GONE);
|
||||
registerButton.setVisibility(View.GONE);
|
||||
} else if (!isSecureText && isPushGroupConversation()) {
|
||||
unblockButton.setVisibility(View.GONE);
|
||||
composePanel.setVisibility(View.GONE);
|
||||
inputPanel.setVisibility(View.GONE);
|
||||
makeDefaultSmsButton.setVisibility(View.GONE);
|
||||
registerButton.setVisibility(View.VISIBLE);
|
||||
} else if (!isSecureText && !isDefaultSms) {
|
||||
unblockButton.setVisibility(View.GONE);
|
||||
composePanel.setVisibility(View.GONE);
|
||||
inputPanel.setVisibility(View.GONE);
|
||||
makeDefaultSmsButton.setVisibility(View.VISIBLE);
|
||||
registerButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
composePanel.setVisibility(View.VISIBLE);
|
||||
inputPanel.setVisibility(View.VISIBLE);
|
||||
unblockButton.setVisibility(View.GONE);
|
||||
makeDefaultSmsButton.setVisibility(View.GONE);
|
||||
registerButton.setVisibility(View.GONE);
|
||||
@@ -2125,7 +2125,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private boolean isActiveGroup() {
|
||||
if (!isGroupConversation()) return false;
|
||||
if (!isGroupConversation() || recipient.getAddress().isRSSFeed()) return false;
|
||||
|
||||
Optional<GroupRecord> record = DatabaseFactory.getGroupDatabase(this).getGroup(getRecipient().getAddress().toGroupString());
|
||||
return record.isPresent() && record.get().isActive();
|
||||
@@ -2314,7 +2314,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
String hint = enabled ? "Message" : "Pending session request";
|
||||
inputPanel.setHint(hint);
|
||||
inputPanel.setEnabled(enabled);
|
||||
if (enabled) {
|
||||
if (enabled && inputPanel.getVisibility() == View.VISIBLE) {
|
||||
inputPanel.composeText.requestFocus();
|
||||
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
inputMethodManager.showSoftInput(inputPanel.composeText, 0);
|
||||
@@ -2939,6 +2939,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
@Override
|
||||
public void handleReplyMessage(MessageRecord messageRecord) {
|
||||
if (recipient.isGroupRecipient() && !isActiveGroup()) { return; }
|
||||
|
||||
Recipient author;
|
||||
|
||||
if (messageRecord.isOutgoing()) {
|
||||
|
||||
@@ -137,49 +137,57 @@ public class GroupMessageProcessor {
|
||||
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
|
||||
String id = GroupUtil.getEncodedId(group);
|
||||
|
||||
// Only update group if admin sent the message
|
||||
String ourHexEncodedPublicKey = getMasterHexEncodedPublicKey(context, TextSecurePreferences.getLocalNumber(context));
|
||||
|
||||
if (group.getGroupType() == SignalServiceGroup.GroupType.SIGNAL) {
|
||||
// Only update group if the group admin sent the message
|
||||
String hexEncodedPublicKey = getMasterHexEncodedPublicKey(context, content.getSender());
|
||||
if (!groupRecord.getAdmins().contains(Address.fromSerialized(hexEncodedPublicKey))) {
|
||||
Log.d("Loki - Group Message", "Received a group update message from a non-admin user for " + id +". Ignoring.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// We should only process update messages if we're in the group
|
||||
Address ourAddress = Address.fromSerialized(ourHexEncodedPublicKey);
|
||||
if (!groupRecord.getMembers().contains(ourAddress) &&
|
||||
!group.getMembers().or(Collections.emptyList()).contains(ourHexEncodedPublicKey)) {
|
||||
Log.d("Loki - Group Message", "Received a group update message from a group we are not a member of: " + id + "; ignoring.");
|
||||
database.setActive(id, false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Set<Address> recordMembers = new HashSet<>(groupRecord.getMembers());
|
||||
Set<Address> messageMembers = new HashSet<>();
|
||||
Set<Address> currentMembers = new HashSet<>(groupRecord.getMembers());
|
||||
Set<Address> newMembers = new HashSet<>();
|
||||
|
||||
for (String messageMember : group.getMembers().get()) {
|
||||
messageMembers.add(Address.fromExternal(context, messageMember));
|
||||
newMembers.add(Address.fromExternal(context, messageMember));
|
||||
}
|
||||
|
||||
Set<Address> addedMembers = new HashSet<>(messageMembers);
|
||||
addedMembers.removeAll(recordMembers);
|
||||
// Added members are the members who are present in newMembers but not in currentMembers
|
||||
Set<Address> addedMembers = new HashSet<>(newMembers);
|
||||
addedMembers.removeAll(currentMembers);
|
||||
|
||||
Set<Address> missingMembers = new HashSet<>(recordMembers);
|
||||
missingMembers.removeAll(messageMembers);
|
||||
// Kicked members are members who are present in currentMembers but not in newMembers
|
||||
Set<Address> removedMembers = new HashSet<>(currentMembers);
|
||||
removedMembers.removeAll(newMembers);
|
||||
|
||||
GroupContext.Builder builder = createGroupContext(group);
|
||||
builder.setType(GroupContext.Type.UPDATE);
|
||||
|
||||
if (addedMembers.size() > 0) {
|
||||
Set<Address> unionMembers = new HashSet<>(recordMembers);
|
||||
unionMembers.addAll(messageMembers);
|
||||
database.updateMembers(id, new LinkedList<>(unionMembers));
|
||||
|
||||
builder.clearMembers();
|
||||
|
||||
for (Address addedMember : addedMembers) {
|
||||
builder.addMembers(addedMember.serialize());
|
||||
}
|
||||
} else {
|
||||
builder.clearMembers();
|
||||
// Update our group members if they're different
|
||||
if (!currentMembers.equals(newMembers)) {
|
||||
database.updateMembers(id, new LinkedList<>(newMembers));
|
||||
}
|
||||
|
||||
if (missingMembers.size() > 0) {
|
||||
for (Address removedMember : missingMembers) {
|
||||
builder.addMembers(removedMember.serialize());
|
||||
}
|
||||
// We add any new or removed members to the group context
|
||||
// This will allow us later to iterate over them to check if they left or were added for UI purposes
|
||||
for (Address addedMember : addedMembers) {
|
||||
builder.addNewMembers(addedMember.serialize());
|
||||
}
|
||||
|
||||
for (Address removedMember : removedMembers) {
|
||||
builder.addRemovedMembers(removedMember.serialize());
|
||||
}
|
||||
|
||||
if (group.getName().isPresent() || group.getAvatar().isPresent()) {
|
||||
@@ -191,10 +199,15 @@ public class GroupMessageProcessor {
|
||||
builder.clearName();
|
||||
}
|
||||
|
||||
if (!groupRecord.isActive()) database.setActive(id, true);
|
||||
// If we were removed then we need to disable the chat
|
||||
if (removedMembers.contains(Address.fromSerialized(ourHexEncodedPublicKey))) {
|
||||
database.setActive(id, false);
|
||||
} else {
|
||||
if (!groupRecord.isActive()) database.setActive(id, true);
|
||||
|
||||
if (group.getMembers().isPresent()) {
|
||||
establishSessionsWithMembersIfNeeded(context, group.getMembers().get());
|
||||
if (group.getMembers().isPresent()) {
|
||||
establishSessionsWithMembersIfNeeded(context, group.getMembers().get());
|
||||
}
|
||||
}
|
||||
|
||||
return storeMessage(context, content, group, builder.build(), outgoing);
|
||||
|
||||
@@ -1895,7 +1895,28 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
boolean isGroupActive = groupId.isPresent() && groupDatabase.isActive(groupId.get());
|
||||
boolean isLeaveMessage = message.getGroupInfo().isPresent() && message.getGroupInfo().get().getType() == SignalServiceGroup.Type.QUIT;
|
||||
|
||||
return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage);
|
||||
boolean isClosedGroup = conversation.getAddress().isSignalGroup();
|
||||
boolean isGroupMember = true;
|
||||
|
||||
// Only allow messages from group members
|
||||
if (isClosedGroup) {
|
||||
String senderHexEncodedPublicKey = content.getSender();
|
||||
|
||||
try {
|
||||
String masterHexEncodedPublicKey = PromiseUtil.timeout(LokiDeviceLinkUtilities.INSTANCE.getMasterHexEncodedPublicKey(content.getSender()), 5000).get();
|
||||
if (masterHexEncodedPublicKey != null) {
|
||||
senderHexEncodedPublicKey = masterHexEncodedPublicKey;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Recipient senderMasterAddress = Recipient.from(context, Address.fromSerialized(senderHexEncodedPublicKey), false);
|
||||
|
||||
isGroupMember = groupId.isPresent() && groupDatabase.getGroupMembers(groupId.get(), true).contains(senderMasterAddress);
|
||||
}
|
||||
|
||||
return (isContentMessage && !isGroupActive) || (sender.isBlocked() && !isLeaveMessage) || (isContentMessage && !isGroupMember);
|
||||
} else {
|
||||
return sender.isBlocked();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.thoughtcrime.securesms.loki
|
||||
|
||||
import android.content.Context
|
||||
import okhttp3.*
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.libsignal.logging.Log
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||
import org.whispersystems.signalservice.loki.api.LokiPushNotificationAcknowledgement
|
||||
import java.io.IOException
|
||||
|
||||
object LokiPushNotificationManager {
|
||||
private val connection = OkHttpClient()
|
||||
|
||||
private val server by lazy {
|
||||
LokiPushNotificationAcknowledgement.shared.server
|
||||
}
|
||||
|
||||
private const val tokenExpirationInterval = 12 * 60 * 60 * 1000
|
||||
|
||||
@JvmStatic
|
||||
fun unregister(token: String, context: Context?) {
|
||||
val parameters = mapOf( "token" to token )
|
||||
val url = "${server}/register"
|
||||
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) {
|
||||
TextSecurePreferences.setIsUsingFCM(context, false)
|
||||
} else {
|
||||
Log.d("Loki", "Couldn't disable FCM due to error: ${json?.get("message") as? String ?: "null"}.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, exception: IOException) {
|
||||
Log.d("Loki", "Couldn't disable FCM.")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun register(token: String, hexEncodedPublicKey: String, context: Context?, force: Boolean) {
|
||||
val oldToken = TextSecurePreferences.getFCMToken(context)
|
||||
val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context)
|
||||
if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return }
|
||||
val parameters = mapOf( "token" to token, "pubKey" to hexEncodedPublicKey )
|
||||
val url = "${server}/register"
|
||||
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) {
|
||||
TextSecurePreferences.setIsUsingFCM(context, true)
|
||||
TextSecurePreferences.setFCMToken(context, token)
|
||||
TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis())
|
||||
} else {
|
||||
Log.d("Loki", "Couldn't register for FCM due to error: ${json?.get("message") as? String ?: "null"}.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, exception: IOException) {
|
||||
Log.d("Loki", "Couldn't register for FCM.")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
|
||||
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
||||
super.onCreate(savedInstanceState, isReady)
|
||||
setContentView(R.layout.activity_create_closed_group)
|
||||
supportActionBar!!.title = "New Closed Group"
|
||||
supportActionBar!!.title = "New Closed Group (Beta)"
|
||||
recyclerView.adapter = createClosedGroupAdapter
|
||||
recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
createNewPrivateChatButton.setOnClickListener { createNewPrivateChat() }
|
||||
|
||||
@@ -9,11 +9,9 @@ import android.widget.TextView.OnEditorActionListener
|
||||
import android.widget.Toast
|
||||
import kotlinx.android.synthetic.main.activity_display_name.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.redesign.utilities.push
|
||||
import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo
|
||||
import org.thoughtcrime.securesms.loki.redesign.utilities.show
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher
|
||||
|
||||
@@ -25,16 +23,14 @@ class DisplayNameActivity : BaseActionBarActivity() {
|
||||
setContentView(R.layout.activity_display_name)
|
||||
displayNameEditText.imeOptions = displayNameEditText.imeOptions or 16777216 // Always use incognito keyboard
|
||||
displayNameEditText.setOnEditorActionListener(
|
||||
OnEditorActionListener { _, actionId, event ->
|
||||
// Handle validation from keyboard to trigger registration
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH ||
|
||||
actionId == EditorInfo.IME_ACTION_DONE ||
|
||||
(event.action === KeyEvent.ACTION_DOWN
|
||||
&& event.keyCode === KeyEvent.KEYCODE_ENTER)) {
|
||||
this.register();
|
||||
OnEditorActionListener { _, actionID, event ->
|
||||
if (actionID == EditorInfo.IME_ACTION_SEARCH ||
|
||||
actionID == EditorInfo.IME_ACTION_DONE ||
|
||||
(event.action == KeyEvent.ACTION_DOWN &&
|
||||
event.keyCode == KeyEvent.KEYCODE_ENTER)) {
|
||||
this.register()
|
||||
return@OnEditorActionListener true
|
||||
}
|
||||
// Return true if you have consumed the action, else false.
|
||||
false
|
||||
})
|
||||
registerButton.setOnClickListener { register() }
|
||||
@@ -54,20 +50,7 @@ class DisplayNameActivity : BaseActionBarActivity() {
|
||||
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
inputMethodManager.hideSoftInputFromWindow(displayNameEditText.windowToken, 0)
|
||||
TextSecurePreferences.setProfileName(this, displayName)
|
||||
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
|
||||
TextSecurePreferences.setPromptedPushRegistration(this, true)
|
||||
val application = ApplicationContext.getInstance(this)
|
||||
application.setUpStorageAPIIfNeeded()
|
||||
application.setUpP2PAPI()
|
||||
val publicChatAPI = ApplicationContext.getInstance(this).lokiPublicChatAPI
|
||||
if (publicChatAPI != null) {
|
||||
// TODO: This won't be necessary anymore when we don't auto-join the Loki Public Chat anymore
|
||||
application.createDefaultPublicChatsIfNeeded()
|
||||
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
|
||||
servers.forEach { publicChatAPI.setDisplayName(displayName, it) }
|
||||
}
|
||||
val intent = Intent(this, HomeActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
show(intent)
|
||||
val intent = Intent(this, PNModeActivity::class.java)
|
||||
push(intent)
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||
import org.thoughtcrime.securesms.loki.getColorWithID
|
||||
import org.thoughtcrime.securesms.loki.redesign.dialogs.PNModeBottomSheet
|
||||
import org.thoughtcrime.securesms.loki.redesign.utilities.push
|
||||
import org.thoughtcrime.securesms.loki.redesign.utilities.show
|
||||
import org.thoughtcrime.securesms.loki.redesign.views.ConversationView
|
||||
@@ -111,6 +112,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
||||
recyclerView.adapter = homeAdapter
|
||||
recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
ItemTouchHelper(SwipeCallback(this)).attachToRecyclerView(recyclerView)
|
||||
// Set up empty state view
|
||||
createNewPrivateChatButton.setOnClickListener { createNewPrivateChat() }
|
||||
// This is a workaround for the fact that CursorRecyclerViewAdapter doesn't actually auto-update (even though it says it will)
|
||||
LoaderManager.getInstance(this).restartLoader(0, null, object : LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
@@ -120,6 +123,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
||||
|
||||
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor?) {
|
||||
homeAdapter.changeCursor(cursor)
|
||||
updateEmptyState()
|
||||
}
|
||||
|
||||
override fun onLoaderReset(cursor: Loader<Cursor>) {
|
||||
@@ -156,32 +160,20 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
||||
if (hasViewedSeed || !isMasterDevice) {
|
||||
seedReminderView.visibility = View.GONE
|
||||
}
|
||||
// if (!TextSecurePreferences.getHasSeenOpenGroupSuggestionSheet(this)) {
|
||||
// val bottomSheet = OpenGroupSuggestionBottomSheet()
|
||||
// bottomSheet.onJoinTapped = {
|
||||
// TextSecurePreferences.setHasSeenOpenGroupSuggestionSheet(this)
|
||||
// bottomSheet.dismiss()
|
||||
// // TODO: Duplication of the code in JoinPublicChatActivity
|
||||
// val application = ApplicationContext.getInstance(this)
|
||||
// val channel: Long = 1
|
||||
// val displayName = TextSecurePreferences.getProfileName(this)
|
||||
// val lokiPublicChatAPI = application.lokiPublicChatAPI!!
|
||||
// val url = "https://chat.getsession.org"
|
||||
// application.lokiPublicChatManager.addChat(url, channel).successUi {
|
||||
// lokiPublicChatAPI.getMessages(channel, url)
|
||||
// lokiPublicChatAPI.setDisplayName(displayName, url)
|
||||
// lokiPublicChatAPI.join(channel, url)
|
||||
// val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(this)
|
||||
// val profileUrl: String? = TextSecurePreferences.getProfileAvatarUrl(this)
|
||||
// lokiPublicChatAPI.setProfilePicture(url, profileKey, profileUrl)
|
||||
// }
|
||||
// }
|
||||
// bottomSheet.onDismissTapped = {
|
||||
// TextSecurePreferences.setHasSeenOpenGroupSuggestionSheet(this)
|
||||
// bottomSheet.dismiss()
|
||||
// }
|
||||
// bottomSheet.show(supportFragmentManager, bottomSheet.tag)
|
||||
// }
|
||||
if (!TextSecurePreferences.hasSeenPNModeSheet(this)) {
|
||||
val bottomSheet = PNModeBottomSheet()
|
||||
bottomSheet.onConfirmTapped = { isUsingFCM ->
|
||||
TextSecurePreferences.setHasSeenPNModeSheet(this, true)
|
||||
TextSecurePreferences.setIsUsingFCM(this, isUsingFCM)
|
||||
ApplicationContext.getInstance(this).registerForFCMIfNeeded(true)
|
||||
bottomSheet.dismiss()
|
||||
}
|
||||
bottomSheet.onSkipTapped = {
|
||||
TextSecurePreferences.setHasSeenPNModeSheet(this, true)
|
||||
bottomSheet.dismiss()
|
||||
}
|
||||
bottomSheet.show(supportFragmentManager, bottomSheet.tag)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
@@ -192,6 +184,14 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Updating
|
||||
private fun updateEmptyState() {
|
||||
val threadCount = (recyclerView.adapter as HomeAdapter).itemCount
|
||||
emptyStateContainer.visibility = if (threadCount == 0) View.VISIBLE else View.GONE
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Interaction
|
||||
override fun handleSeedReminderViewContinueButtonTapped() {
|
||||
val intent = Intent(this, SeedActivity::class.java)
|
||||
show(intent)
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.thoughtcrime.securesms.loki.redesign.activities
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.TransitionDrawable
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.support.annotation.DrawableRes
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import kotlinx.android.synthetic.main.activity_display_name.registerButton
|
||||
import kotlinx.android.synthetic.main.activity_home.*
|
||||
import kotlinx.android.synthetic.main.activity_pn_mode.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.redesign.utilities.setUpActionBarSessionLogo
|
||||
import org.thoughtcrime.securesms.loki.redesign.utilities.show
|
||||
import org.thoughtcrime.securesms.util.GroupUtil
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
class PNModeActivity : BaseActionBarActivity() {
|
||||
private var selectedOptionView: LinearLayout? = null
|
||||
|
||||
// region Lifecycle
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setUpActionBarSessionLogo()
|
||||
setContentView(R.layout.activity_pn_mode)
|
||||
fcmOptionView.setOnClickListener { toggleFCM() }
|
||||
backgroundPollingOptionView.setOnClickListener { toggleBackgroundPolling() }
|
||||
registerButton.setOnClickListener { register() }
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Animation
|
||||
private fun performTransition(@DrawableRes transitionID: Int, subject: View) {
|
||||
val drawable = resources.getDrawable(transitionID, theme) as TransitionDrawable
|
||||
subject.background = drawable
|
||||
drawable.startTransition(250)
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Interaction
|
||||
private fun toggleFCM() {
|
||||
when (selectedOptionView) {
|
||||
null -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
|
||||
selectedOptionView = fcmOptionView
|
||||
}
|
||||
fcmOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
|
||||
selectedOptionView = null
|
||||
}
|
||||
backgroundPollingOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
|
||||
selectedOptionView = fcmOptionView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleBackgroundPolling() {
|
||||
when (selectedOptionView) {
|
||||
null -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
|
||||
selectedOptionView = backgroundPollingOptionView
|
||||
}
|
||||
backgroundPollingOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
|
||||
selectedOptionView = null
|
||||
}
|
||||
fcmOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
|
||||
selectedOptionView = backgroundPollingOptionView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun register() {
|
||||
if (selectedOptionView == null) {
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
dialog.setTitle(R.string.activity_pn_mode_no_option_picked_dialog_title)
|
||||
dialog.setPositiveButton(R.string.ok) { _, _ -> }
|
||||
dialog.create().show()
|
||||
return
|
||||
}
|
||||
val displayName = TextSecurePreferences.getProfileName(this)
|
||||
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
|
||||
TextSecurePreferences.setPromptedPushRegistration(this, true)
|
||||
TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == fcmOptionView))
|
||||
TextSecurePreferences.setHasSeenPNModeSheet(this, true) // Shouldn't be shown to users who've done the new onboarding
|
||||
val application = ApplicationContext.getInstance(this)
|
||||
application.setUpStorageAPIIfNeeded()
|
||||
application.setUpP2PAPI()
|
||||
val publicChatAPI = ApplicationContext.getInstance(this).lokiPublicChatAPI
|
||||
if (publicChatAPI != null) {
|
||||
// TODO: This won't be necessary anymore when we don't auto-join the Loki Public Chat anymore
|
||||
application.createDefaultPublicChatsIfNeeded()
|
||||
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
|
||||
servers.forEach { publicChatAPI.setDisplayName(displayName, it) }
|
||||
}
|
||||
application.registerForFCMIfNeeded(true)
|
||||
val intent = Intent(this, HomeActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
show(intent)
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import android.view.ViewGroup
|
||||
import kotlinx.android.synthetic.main.fragment_open_group_suggestion_bottom_sheet.*
|
||||
import network.loki.messenger.R
|
||||
|
||||
public class OpenGroupSuggestionBottomSheet : BottomSheetDialogFragment() {
|
||||
class OpenGroupSuggestionBottomSheet : BottomSheetDialogFragment() {
|
||||
var onJoinTapped: (() -> Unit)? = null
|
||||
var onDismissTapped: (() -> Unit)? = null
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.thoughtcrime.securesms.loki.redesign.dialogs
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.drawable.TransitionDrawable
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.DrawableRes
|
||||
import android.support.design.widget.BottomSheetDialogFragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import kotlinx.android.synthetic.main.fragment_pn_mode_bottom_sheet.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
|
||||
class PNModeBottomSheet : BottomSheetDialogFragment() {
|
||||
private var selectedOptionView: LinearLayout? = null
|
||||
var onConfirmTapped: ((Boolean) -> Unit)? = null
|
||||
var onSkipTapped: (() -> Unit)? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NORMAL, R.style.SessionBottomSheetDialogTheme)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_pn_mode_bottom_sheet, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
fcmOptionView.setOnClickListener { toggleFCM() }
|
||||
backgroundPollingOptionView.setOnClickListener { toggleBackgroundPolling() }
|
||||
confirmButton.setOnClickListener { confirm() }
|
||||
skipButton.setOnClickListener { onSkipTapped?.invoke() }
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface?) {
|
||||
TextSecurePreferences.setHasSeenPNModeSheet(context, true)
|
||||
super.onDismiss(dialog)
|
||||
}
|
||||
|
||||
// region Animation
|
||||
private fun performTransition(@DrawableRes transitionID: Int, subject: View) {
|
||||
val drawable = resources.getDrawable(transitionID, context!!.theme) as TransitionDrawable
|
||||
subject.background = drawable
|
||||
drawable.startTransition(250)
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Interaction
|
||||
private fun toggleFCM() {
|
||||
when (selectedOptionView) {
|
||||
null -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
|
||||
selectedOptionView = fcmOptionView
|
||||
}
|
||||
fcmOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
|
||||
selectedOptionView = null
|
||||
}
|
||||
backgroundPollingOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
|
||||
selectedOptionView = fcmOptionView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleBackgroundPolling() {
|
||||
when (selectedOptionView) {
|
||||
null -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
|
||||
selectedOptionView = backgroundPollingOptionView
|
||||
}
|
||||
backgroundPollingOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
|
||||
selectedOptionView = null
|
||||
}
|
||||
fcmOptionView -> {
|
||||
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
|
||||
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
|
||||
selectedOptionView = backgroundPollingOptionView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun confirm() {
|
||||
if (selectedOptionView == null) {
|
||||
val dialog = AlertDialog.Builder(context)
|
||||
dialog.setTitle(R.string.sheet_pn_mode_no_option_picked_dialog_title)
|
||||
dialog.setPositiveButton(R.string.ok) { _, _ -> }
|
||||
dialog.create().show()
|
||||
return
|
||||
}
|
||||
onConfirmTapped?.invoke(selectedOptionView == fcmOptionView)
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
@@ -28,6 +28,7 @@ class BackgroundPollWorker : PersistentAlarmManagerListener() {
|
||||
}
|
||||
|
||||
override fun onAlarm(context: Context, scheduledTime: Long): Long {
|
||||
if (TextSecurePreferences.isUsingFCM(context)) { return 0L }
|
||||
if (scheduledTime != 0L) {
|
||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val lokiAPIDatabase = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.loki.redesign.messaging
|
||||
package org.thoughtcrime.securesms.loki.redesign.shelved
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
@@ -39,6 +39,7 @@ class NewConversationButtonSetView : RelativeLayout {
|
||||
// endregion
|
||||
|
||||
// region Settings
|
||||
private val minDragDistance by lazy { toPx(40, resources).toFloat() }
|
||||
private val maxDragDistance by lazy { toPx(56, resources).toFloat() }
|
||||
private val dragMargin by lazy { toPx(16, resources).toFloat() }
|
||||
private val bottomMargin by lazy { resources.getDimension(R.dimen.new_conversation_button_bottom_offset) }
|
||||
@@ -252,7 +253,8 @@ class NewConversationButtonSetView : RelativeLayout {
|
||||
expandedButton?.collapse()
|
||||
this.expandedButton = null
|
||||
collapse()
|
||||
if (event.action == MotionEvent.ACTION_UP) {
|
||||
val distanceFromRestPosition = touch.distanceTo(buttonRestPosition)
|
||||
if (event.action == MotionEvent.ACTION_UP && distanceFromRestPosition > (minDragDistance + mainButton.collapsedSize / 2)) {
|
||||
if (sessionButton.contains(touch) || touch.isAbove(sessionButton, dragMargin)) { delegate?.createNewPrivateChat() }
|
||||
else if (closedGroupButton.contains(touch) || touch.isRightOf(closedGroupButton, dragMargin)) { delegate?.createNewClosedGroup() }
|
||||
else if (openGroupButton.contains(touch) || touch.isLeftOf(openGroupButton, dragMargin)) { delegate?.joinOpenGroup() }
|
||||
|
||||
@@ -14,6 +14,7 @@ import android.support.v7.preference.ListPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
@@ -32,6 +33,16 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
|
||||
// Set up FCM toggle
|
||||
String fcmKey = "pref_key_use_fcm";
|
||||
((SwitchPreferenceCompat)findPreference(fcmKey)).setChecked(TextSecurePreferences.isUsingFCM(getContext()));
|
||||
this.findPreference(fcmKey)
|
||||
.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
TextSecurePreferences.setIsUsingFCM(getContext(), (boolean) newValue);
|
||||
ApplicationContext.getInstance(getContext()).registerForFCMIfNeeded(true);
|
||||
return true;
|
||||
});
|
||||
|
||||
Preference ledBlinkPref = this.findPreference(TextSecurePreferences.LED_BLINK_PREF);
|
||||
|
||||
if (NotificationChannels.supported()) {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.thoughtcrime.securesms.service
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import org.thoughtcrime.securesms.jobs.PushContentReceiveJob
|
||||
import org.thoughtcrime.securesms.loki.LokiPushNotificationManager
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.libsignal.logging.Log
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope
|
||||
import org.whispersystems.signalservice.internal.util.Base64
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiMessageWrapper
|
||||
|
||||
class PushNotificationService : FirebaseMessagingService() {
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
super.onNewToken(token)
|
||||
Log.d("Loki", "New FCM token: $token.")
|
||||
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return
|
||||
LokiPushNotificationManager.register(token, userHexEncodedPublicKey, this, false)
|
||||
}
|
||||
|
||||
override fun onMessageReceived(message: RemoteMessage) {
|
||||
val base64EncodedData = message.data["ENCRYPTED_DATA"]
|
||||
val data = base64EncodedData?.let { Base64.decode(it) }
|
||||
if (data != null) {
|
||||
try {
|
||||
val envelope = LokiMessageWrapper.unwrap(data)
|
||||
PushContentReceiveJob(this).processEnvelope(SignalServiceEnvelope(envelope))
|
||||
} catch (e: Exception) {
|
||||
Log.d("Loki", "Failed to unwrap data for message.")
|
||||
}
|
||||
} else {
|
||||
Log.d("Loki", "Failed to decode data for message.")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import com.google.protobuf.ByteString;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -159,7 +158,7 @@ public class GroupUtil {
|
||||
|
||||
@NonNull private final Context context;
|
||||
@Nullable private final GroupContext groupContext;
|
||||
private final List<Recipient> members;
|
||||
private final List<Recipient> newMembers;
|
||||
private final List<Recipient> removedMembers;
|
||||
private boolean ourDeviceWasRemoved;
|
||||
|
||||
@@ -167,35 +166,35 @@ public class GroupUtil {
|
||||
this.context = context.getApplicationContext();
|
||||
this.groupContext = groupContext;
|
||||
|
||||
this.members = new LinkedList<>();
|
||||
this.newMembers = new LinkedList<>();
|
||||
this.removedMembers = new LinkedList<>();
|
||||
this.ourDeviceWasRemoved = false;
|
||||
|
||||
if (groupContext != null && !groupContext.getMembersList().isEmpty()) {
|
||||
List<String> memberList = groupContext.getMembersList();
|
||||
List<Address> currentMembers = getCurrentGroupMembers();
|
||||
if (groupContext != null) {
|
||||
List<String> newMembers = groupContext.getNewMembersList();
|
||||
for (String member : newMembers) {
|
||||
this.newMembers.add(this.toRecipient(member));
|
||||
}
|
||||
|
||||
// Add them to the member or removed members lists
|
||||
for (String member : memberList) {
|
||||
Address address = Address.fromSerialized(member);
|
||||
Recipient recipient = Recipient.from(context, address, true);
|
||||
if (currentMembers == null || currentMembers.contains(address)) {
|
||||
this.members.add(recipient);
|
||||
} else {
|
||||
this.removedMembers.add(recipient);
|
||||
}
|
||||
List<String> removedMembers = groupContext.getRemovedMembersList();
|
||||
for (String member : removedMembers) {
|
||||
this.removedMembers.add(this.toRecipient(member));
|
||||
}
|
||||
|
||||
// Check if our device was removed
|
||||
if (!removedMembers.isEmpty()) {
|
||||
String masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
||||
String hexEncodedPublicKey = masterHexEncodedPublicKey != null ? masterHexEncodedPublicKey : TextSecurePreferences.getLocalNumber(context);
|
||||
Recipient self = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false);
|
||||
ourDeviceWasRemoved = removedMembers.contains(self);
|
||||
ourDeviceWasRemoved = removedMembers.contains(hexEncodedPublicKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Recipient toRecipient(String hexEncodedPublicKey) {
|
||||
Address address = Address.fromSerialized(hexEncodedPublicKey);
|
||||
return Recipient.from(context, address, false);
|
||||
}
|
||||
|
||||
public String toString(Recipient sender) {
|
||||
// Show the local removed message
|
||||
if (ourDeviceWasRemoved) {
|
||||
@@ -211,10 +210,10 @@ public class GroupUtil {
|
||||
|
||||
String title = groupContext.getName();
|
||||
|
||||
if (!members.isEmpty()) {
|
||||
if (!newMembers.isEmpty()) {
|
||||
description.append("\n");
|
||||
description.append(context.getResources().getQuantityString(R.plurals.GroupUtil_joined_the_group,
|
||||
members.size(), toString(members)));
|
||||
newMembers.size(), toString(newMembers)));
|
||||
}
|
||||
|
||||
if (!removedMembers.isEmpty()) {
|
||||
@@ -224,8 +223,8 @@ public class GroupUtil {
|
||||
}
|
||||
|
||||
if (title != null && !title.trim().isEmpty()) {
|
||||
if (!members.isEmpty()) description.append(" ");
|
||||
else description.append("\n");
|
||||
String separator = (!newMembers.isEmpty() || !removedMembers.isEmpty()) ? " " : "\n";
|
||||
description.append(separator);
|
||||
description.append(context.getString(R.string.GroupUtil_group_name_is_now, title));
|
||||
}
|
||||
|
||||
@@ -233,8 +232,8 @@ public class GroupUtil {
|
||||
}
|
||||
|
||||
public void addListener(RecipientModifiedListener listener) {
|
||||
if (!this.members.isEmpty()) {
|
||||
for (Recipient member : this.members) {
|
||||
if (!this.newMembers.isEmpty()) {
|
||||
for (Recipient member : this.newMembers) {
|
||||
member.addListener(listener);
|
||||
}
|
||||
}
|
||||
@@ -252,23 +251,5 @@ public class GroupUtil {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Address> getCurrentGroupMembers() {
|
||||
if (groupContext == null) { return null; }
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
byte[] decodedGroupId = groupContext.getId().toByteArray();
|
||||
String signalGroupId = getEncodedId(decodedGroupId, false);
|
||||
String publicChatId = getEncodedPublicChatId(decodedGroupId);
|
||||
String rssFeedId = getEncodedRSSFeedId(decodedGroupId);
|
||||
GroupRecord groupRecord = null;
|
||||
if (!groupDatabase.isUnknownGroup(signalGroupId)) {
|
||||
groupRecord = groupDatabase.getGroup(signalGroupId).orNull();
|
||||
} else if (!groupDatabase.isUnknownGroup(publicChatId)) {
|
||||
groupRecord = groupDatabase.getGroup(publicChatId).orNull();
|
||||
} else if (!groupDatabase.isUnknownGroup(rssFeedId)) {
|
||||
groupRecord = groupDatabase.getGroup(rssFeedId).orNull();
|
||||
}
|
||||
return (groupRecord != null) ? groupRecord.getMembers() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,6 +185,45 @@ public class TextSecurePreferences {
|
||||
|
||||
private static final String MEDIA_KEYBOARD_MODE = "pref_media_keyboard_mode";
|
||||
|
||||
// region FCM
|
||||
private static final String IS_USING_FCM = "pref_is_using_fcm";
|
||||
private static final String FCM_TOKEN = "pref_fcm_token";
|
||||
private static final String LAST_FCM_TOKEN_UPLOAD_TIME = "pref_last_fcm_token_upload_time";
|
||||
private static final String HAS_SEEN_PN_MODE_SHEET = "pref_has_seen_pn_mode_sheet";
|
||||
|
||||
public static boolean isUsingFCM(Context context) {
|
||||
return getBooleanPreference(context, IS_USING_FCM, false);
|
||||
}
|
||||
|
||||
public static void setIsUsingFCM(Context context, boolean value) {
|
||||
setBooleanPreference(context, IS_USING_FCM, value);
|
||||
}
|
||||
|
||||
public static String getFCMToken(Context context) {
|
||||
return getStringPreference(context, FCM_TOKEN, "");
|
||||
}
|
||||
|
||||
public static void setFCMToken(Context context, String value) {
|
||||
setStringPreference(context, FCM_TOKEN, value);
|
||||
}
|
||||
|
||||
public static long getLastFCMUploadTime(Context context) {
|
||||
return getLongPreference(context, LAST_FCM_TOKEN_UPLOAD_TIME, 0);
|
||||
}
|
||||
|
||||
public static void setLastFCMUploadTime(Context context, long value) {
|
||||
setLongPreference(context, LAST_FCM_TOKEN_UPLOAD_TIME, value);
|
||||
}
|
||||
|
||||
public static boolean hasSeenPNModeSheet(Context context) {
|
||||
return getBooleanPreference(context, HAS_SEEN_PN_MODE_SHEET, false);
|
||||
}
|
||||
|
||||
public static void setHasSeenPNModeSheet(Context context, boolean value) {
|
||||
setBooleanPreference(context, HAS_SEEN_PN_MODE_SHEET, value);
|
||||
}
|
||||
// endregion
|
||||
|
||||
public static boolean isScreenLockEnabled(@NonNull Context context) {
|
||||
return getBooleanPreference(context, SCREEN_LOCK, false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user