Made creating chat system. Added KeyPairManager

This commit is contained in:
ChronosX88 2019-03-21 19:02:34 +04:00
parent a65b871eab
commit 2680a86ba8
12 changed files with 139 additions and 86 deletions

View File

@ -3,9 +3,6 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<compositeConfiguration>
<compositeBuild compositeDefinitionSource="SCRIPT" />
</compositeConfiguration>
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules"> <option name="modules">

View File

@ -44,4 +44,5 @@ dependencies {
implementation 'com.google.android.material:material:1.1.0-alpha04' implementation 'com.google.android.material:material:1.1.0-alpha04'
implementation 'androidx.preference:preference:1.1.0-alpha03' implementation 'androidx.preference:preference:1.1.0-alpha03'
implementation 'com.google.code.gson:gson:2.8.5' implementation 'com.google.code.gson:gson:2.8.5'
//implementation group: 'com.github.demidenko05', name: 'a-javabeans', version: '1.0.4'
} }

View File

@ -6,5 +6,4 @@ import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
public interface ChatListLogicContract { public interface ChatListLogicContract {
List<ChatEntity> loadAllChats(); List<ChatEntity> loadAllChats();
void createChatBySender(ChatEntity entity);
} }

View File

@ -0,0 +1,73 @@
package io.github.chronosx88.influence.helpers;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
public class KeyPairManager {
private File keyPairDir;
public KeyPairManager() {
this.keyPairDir = new File(AppHelper.getContext().getFilesDir().getAbsoluteFile(), "keyPairs");
}
public KeyPair openMainKeyPair() {
return getKeyPair("mainKeyPair");
}
public KeyPair getKeyPair(String keyPairName) {
KeyPair keyPair = null;
keyPairName = keyPairName + ".kp";
File keyPairFile = new File(keyPairDir, keyPairName);
if (!keyPairFile.exists()) {
return createKeyPairFile(keyPairFile);
}
return openKeyPairFile(keyPairFile);
}
private synchronized KeyPair openKeyPairFile(File keyPairFile) {
KeyPair keyPair = null;
try {
FileInputStream inputStream = new FileInputStream(keyPairFile);
byte[] serializedKeyPair = new byte[(int) keyPairFile.length()];
inputStream.read(serializedKeyPair);
inputStream.close();
keyPair = Serializer.deserializeObject(new String(serializedKeyPair, StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
return keyPair;
}
private synchronized KeyPair createKeyPairFile(File keyPairFile) {
KeyPair keyPair = null;
try {
keyPairFile.createNewFile();
keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
FileOutputStream outputStream = new FileOutputStream(keyPairFile);
outputStream.write(Serializer.serializeObject(keyPair).getBytes(StandardCharsets.UTF_8));
outputStream.close();
} catch (IOException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
return keyPair;
}
public synchronized void saveKeyPair(String keyPairID, KeyPair keyPair) {
File keyPairFile = new File(keyPairDir, keyPairID + ".kp");
if(!keyPairFile.exists()) {
try {
FileOutputStream outputStream = new FileOutputStream(keyPairFile);
outputStream.write(Serializer.serializeObject(keyPair).getBytes(StandardCharsets.UTF_8));
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

View File

@ -9,6 +9,7 @@ import net.tomp2p.peers.PeerAddress;
import io.github.chronosx88.influence.contracts.observer.Observer; import io.github.chronosx88.influence.contracts.observer.Observer;
import io.github.chronosx88.influence.helpers.actions.NetworkActions; import io.github.chronosx88.influence.helpers.actions.NetworkActions;
import io.github.chronosx88.influence.helpers.actions.UIActions; import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
import io.github.chronosx88.influence.observable.MainObservable; import io.github.chronosx88.influence.observable.MainObservable;
public class NetworkHandler implements Observer { public class NetworkHandler implements Observer {
@ -27,7 +28,7 @@ public class NetworkHandler implements Observer {
switch (object.get("action").getAsInt()) { switch (object.get("action").getAsInt()) {
case NetworkActions.START_CHAT: { case NetworkActions.START_CHAT: {
String chatStarterPlainAddress = object.get("senderAddress").getAsString(); String chatStarterPlainAddress = object.get("senderAddress").getAsString();
createChatEntry(object.get("chatID").getAsString(), chatStarterPlainAddress); createChatEntry(object.get("chatID").getAsString(), object.get("senderID").getAsString(), chatStarterPlainAddress);
handleIncomingChat(object.get("chatID").getAsString(), PrepareData.prepareFromStore(chatStarterPlainAddress)); handleIncomingChat(object.get("chatID").getAsString(), PrepareData.prepareFromStore(chatStarterPlainAddress));
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.NEW_CHAT); jsonObject.addProperty("action", UIActions.NEW_CHAT);
@ -36,9 +37,9 @@ public class NetworkHandler implements Observer {
} }
case NetworkActions.SUCCESSFULL_CREATE_CHAT: { case NetworkActions.SUCCESSFULL_CREATE_CHAT: {
createChatEntry(object.get("chatID").getAsString(), object.get("senderAddress").getAsString()); createChatEntry(object.get("chatID").getAsString(), object.get("senderID").getAsString(), object.get("senderAddress").getAsString());
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.SUCCESSFULL_CREATE_CHAT); jsonObject.addProperty("action", UIActions.NEW_CHAT);
AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL); AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL);
break; break;
} }
@ -46,14 +47,15 @@ public class NetworkHandler implements Observer {
}).start(); }).start();
} }
private void createChatEntry(String chatID, String peerAddress) { private void createChatEntry(String chatID, String name, String peerAddress) {
// TODO: make saving chat in db AppHelper.getChatDB().chatDao().addChat(new ChatEntity(chatID, name, peerAddress, ""));
} }
private void handleIncomingChat(String chatID, PeerAddress chatStarterAddress) { private void handleIncomingChat(String chatID, PeerAddress chatStarterAddress) {
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", NetworkActions.SUCCESSFULL_CREATE_CHAT); jsonObject.addProperty("action", NetworkActions.SUCCESSFULL_CREATE_CHAT);
jsonObject.addProperty("chatID", chatID); jsonObject.addProperty("chatID", chatID);
jsonObject.addProperty("senderID", AppHelper.getPeerID());
jsonObject.addProperty("senderAddress", PrepareData.prepareToStore(peerDHT.peerAddress())); jsonObject.addProperty("senderAddress", PrepareData.prepareToStore(peerDHT.peerAddress()));
AppHelper.getPeerDHT().peer().sendDirect(chatStarterAddress).object(gson.toJson(jsonObject)).start().awaitUninterruptibly(); AppHelper.getPeerDHT().peer().sendDirect(chatStarterAddress).object(gson.toJson(jsonObject)).start().awaitUninterruptibly();
} }

View File

@ -1,42 +1,15 @@
package io.github.chronosx88.influence.logic; package io.github.chronosx88.influence.logic;
import com.google.gson.JsonObject;
import java.util.List; import java.util.List;
import io.github.chronosx88.influence.contracts.chatlist.ChatListLogicContract; import io.github.chronosx88.influence.contracts.chatlist.ChatListLogicContract;
import io.github.chronosx88.influence.contracts.observer.Observer;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.actions.NetworkActions;
import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.models.roomEntities.ChatEntity; import io.github.chronosx88.influence.models.roomEntities.ChatEntity;
import io.github.chronosx88.influence.observable.MainObservable;
public class ChatListLogic implements ChatListLogicContract, Observer { public class ChatListLogic implements ChatListLogicContract {
public ChatListLogic() {
AppHelper.getObservable().register(this, MainObservable.OTHER_ACTIONS_CHANNEL);
}
@Override @Override
public List<ChatEntity> loadAllChats() { public List<ChatEntity> loadAllChats() {
return AppHelper.getChatDB().chatDao().getAllChats(); return AppHelper.getChatDB().chatDao().getAllChats();
} }
@Override
public void handleEvent(JsonObject object) {
switch (object.get("action").getAsInt()) {
case NetworkActions.START_CHAT: {
createChatBySender(new ChatEntity(object.get("chatID").getAsString(), object.get("name").getAsString(), ""));
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", UIActions.NEW_CHAT);
AppHelper.getObservable().notifyObservers(jsonObject, MainObservable.UI_ACTIONS_CHANNEL);
break;
}
}
}
@Override
public void createChatBySender(ChatEntity entity) {
AppHelper.getChatDB().chatDao().addChat(entity);
}
} }

View File

@ -25,21 +25,16 @@ import net.tomp2p.storage.Data;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.util.UUID; import java.util.UUID;
import io.github.chronosx88.influence.contracts.main.MainLogicContract; import io.github.chronosx88.influence.contracts.main.MainLogicContract;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.KeyPairManager;
import io.github.chronosx88.influence.helpers.Serializer; import io.github.chronosx88.influence.helpers.Serializer;
import io.github.chronosx88.influence.helpers.StorageMVStore; import io.github.chronosx88.influence.helpers.StorageMVStore;
import io.github.chronosx88.influence.helpers.actions.NetworkActions; import io.github.chronosx88.influence.helpers.actions.NetworkActions;
@ -57,11 +52,13 @@ public class MainLogic implements MainLogicContract {
private PeerAddress bootstrapPeerAddress = null; private PeerAddress bootstrapPeerAddress = null;
private Gson gson; private Gson gson;
private AutoReplication replication; private AutoReplication replication;
private KeyPairManager keyPairManager;
public MainLogic() { public MainLogic() {
this.context = AppHelper.getContext(); this.context = AppHelper.getContext();
this.preferences = context.getSharedPreferences("io.github.chronosx88.influence_preferences", context.MODE_PRIVATE); this.preferences = context.getSharedPreferences("io.github.chronosx88.influence_preferences", context.MODE_PRIVATE);
gson = new Gson(); gson = new Gson();
keyPairManager = new KeyPairManager();
} }
@Override @Override
@ -135,7 +132,7 @@ public class MainLogic implements MainLogicContract {
Gson gson = new Gson(); Gson gson = new Gson();
JsonObject publicProfile = new JsonObject(); JsonObject publicProfile = new JsonObject();
publicProfile.addProperty("peerAddress", Base64.encodeToString(Serializer.serializeObject(peerDHT.peerAddress()).getBytes(StandardCharsets.UTF_8), Base64.URL_SAFE)); publicProfile.addProperty("peerAddress", Base64.encodeToString(Serializer.serializeObject(peerDHT.peerAddress()).getBytes(StandardCharsets.UTF_8), Base64.URL_SAFE));
peerDHT.put(Number160.createHash(preferences.getString("peerID", null) + "_profile")).data(new Data(gson.toJson(publicProfile)).protectEntry(createMainKeyPair())).start().awaitUninterruptibly(); peerDHT.put(Number160.createHash(preferences.getString("peerID", null) + "_profile")).data(new Data(gson.toJson(publicProfile)).protectEntry(keyPairManager.openMainKeyPair())).start().awaitUninterruptibly();
replication = new AutoReplication(peerDHT.peer()).start(); replication = new AutoReplication(peerDHT.peer()).start();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@ -214,34 +211,4 @@ public class MainLogic implements MainLogicContract {
} }
return false; return false;
} }
private KeyPair createMainKeyPair() {
KeyPair kp = null;
try {
File keyPairDir = new File(AppHelper.getContext().getFilesDir().getAbsoluteFile(), "keyPairs");
if (!keyPairDir.exists())
keyPairDir.mkdir();
File mainKeyPairFile = new File(keyPairDir, "mainKeyPair.kp");
if (!mainKeyPairFile.exists()) {
mainKeyPairFile.createNewFile();
try {
kp = KeyPairGenerator.getInstance("RSA").generateKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
FileOutputStream outputStream = new FileOutputStream(mainKeyPairFile);
outputStream.write(Serializer.serializeObject(kp).getBytes(StandardCharsets.UTF_8));
outputStream.close();
return kp;
}
FileInputStream inputStream = new FileInputStream(mainKeyPairFile);
byte[] serializedKeyPair = new byte[(int) mainKeyPairFile.length()];
inputStream.read(serializedKeyPair);
inputStream.close();
kp = Serializer.deserializeObject(new String(serializedKeyPair, StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
return kp;
}
} }

View File

@ -1,16 +1,19 @@
package io.github.chronosx88.influence.logic; package io.github.chronosx88.influence.logic;
import android.util.Base64; import android.util.Base64;
import android.util.Log;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import net.tomp2p.dht.FutureGet; import net.tomp2p.dht.FutureGet;
import net.tomp2p.dht.FuturePut;
import net.tomp2p.dht.PeerDHT; import net.tomp2p.dht.PeerDHT;
import net.tomp2p.futures.FuturePing; import net.tomp2p.futures.FuturePing;
import net.tomp2p.peers.Number160; import net.tomp2p.peers.Number160;
import net.tomp2p.peers.PeerAddress; import net.tomp2p.peers.PeerAddress;
import net.tomp2p.storage.Data;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -18,6 +21,7 @@ import java.util.UUID;
import io.github.chronosx88.influence.contracts.startchat.StartChatLogicContract; import io.github.chronosx88.influence.contracts.startchat.StartChatLogicContract;
import io.github.chronosx88.influence.helpers.AppHelper; import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.KeyPairManager;
import io.github.chronosx88.influence.helpers.PrepareData; import io.github.chronosx88.influence.helpers.PrepareData;
import io.github.chronosx88.influence.helpers.Serializer; import io.github.chronosx88.influence.helpers.Serializer;
import io.github.chronosx88.influence.helpers.actions.NetworkActions; import io.github.chronosx88.influence.helpers.actions.NetworkActions;
@ -27,10 +31,13 @@ import io.github.chronosx88.influence.observable.MainObservable;
public class StartChatLogic implements StartChatLogicContract { public class StartChatLogic implements StartChatLogicContract {
private PeerDHT peerDHT; private PeerDHT peerDHT;
private Gson gson; private Gson gson;
private KeyPairManager keyPairManager;
private final static String LOG_TAG = "StartChatLogic";
public StartChatLogic() { public StartChatLogic() {
peerDHT = AppHelper.getPeerDHT(); peerDHT = AppHelper.getPeerDHT();
gson = new Gson(); gson = new Gson();
keyPairManager = new KeyPairManager();
} }
@Override @Override
@ -45,16 +52,32 @@ public class StartChatLogic implements StartChatLogicContract {
} }
String peerAddressString = recipientPublicProfile.get("peerAddress").getAsString(); String peerAddressString = recipientPublicProfile.get("peerAddress").getAsString();
PeerAddress peerAddress = Serializer.deserializeObject(new String(Base64.decode(peerAddressString, Base64.URL_SAFE), StandardCharsets.UTF_8)); PeerAddress peerAddress = Serializer.deserializeObject(new String(Base64.decode(peerAddressString, Base64.URL_SAFE), StandardCharsets.UTF_8));
FuturePing ping = peerDHT.peer().ping().peerAddress(peerAddress).start().awaitUninterruptibly(); String chatID = UUID.randomUUID().toString();
if(ping.isSuccess()) {
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("action", NetworkActions.START_CHAT); jsonObject.addProperty("action", NetworkActions.START_CHAT);
jsonObject.addProperty("chatID", UUID.randomUUID().toString()); jsonObject.addProperty("chatID", chatID);
jsonObject.addProperty("senderID", AppHelper.getPeerID());
// TODO: Append public key to new chat request (for encryption) // TODO: Append public key to new chat request (for encryption)
jsonObject.addProperty("senderAddress", PrepareData.prepareToStore(peerDHT.peerAddress())); jsonObject.addProperty("senderAddress", PrepareData.prepareToStore(peerDHT.peerAddress()));
FuturePing ping = peerDHT.peer().ping().peerAddress(peerAddress).start().awaitUninterruptibly();
if(ping.isSuccess()) {
peerDHT.peer().sendDirect(peerAddress).object(gson.toJson(jsonObject)).start(); peerDHT.peer().sendDirect(peerAddress).object(gson.toJson(jsonObject)).start();
} else { } else {
// TODO: put chat entry to "*peerID*_newChats". That peer later will fetch this new chats try {
FuturePut futurePut = peerDHT
.put(Number160.createHash(peerID))
.data(Number160.createHash(UUID.randomUUID().toString()), new Data(gson.toJson(jsonObject))
.protectEntry(keyPairManager.openMainKeyPair())).start().awaitUninterruptibly();
if(futurePut.isSuccess()) {
Log.i(LOG_TAG, "# Create new offline chat request is successful! ChatID: " + chatID);
} else {
Log.e(LOG_TAG, "# Failed to create chat: " + futurePut.failedReason());
}
} catch (IOException e) {
e.printStackTrace();
}
} }
}).start(); }).start();
} }

View File

@ -8,9 +8,9 @@ import androidx.room.PrimaryKey;
@Entity(tableName = "chats") @Entity(tableName = "chats")
public class ChatEntity { public class ChatEntity {
@NonNull @PrimaryKey String id; @NonNull @PrimaryKey String id;
@ColumnInfo String name; @ColumnInfo public String name;
@ColumnInfo String peerAddresses; @ColumnInfo public String peerAddresses;
@ColumnInfo String keyPairID; @ColumnInfo public String keyPairID;
public ChatEntity(String id, String name, String peerAddresses, String keyPairID) { public ChatEntity(String id, String name, String peerAddresses, String keyPairID) {
this.id = id; this.id = id;

View File

@ -1,12 +1,18 @@
package io.github.chronosx88.influence.presenters; package io.github.chronosx88.influence.presenters;
import com.google.gson.JsonObject;
import io.github.chronosx88.influence.contracts.chatlist.ChatListLogicContract; import io.github.chronosx88.influence.contracts.chatlist.ChatListLogicContract;
import io.github.chronosx88.influence.contracts.chatlist.ChatListPresenterContract; import io.github.chronosx88.influence.contracts.chatlist.ChatListPresenterContract;
import io.github.chronosx88.influence.contracts.chatlist.ChatListViewContract; import io.github.chronosx88.influence.contracts.chatlist.ChatListViewContract;
import io.github.chronosx88.influence.contracts.observer.Observer;
import io.github.chronosx88.influence.helpers.AppHelper;
import io.github.chronosx88.influence.helpers.ChatListAdapter; import io.github.chronosx88.influence.helpers.ChatListAdapter;
import io.github.chronosx88.influence.helpers.actions.UIActions;
import io.github.chronosx88.influence.logic.ChatListLogic; import io.github.chronosx88.influence.logic.ChatListLogic;
import io.github.chronosx88.influence.observable.MainObservable;
public class ChatListPresenter implements ChatListPresenterContract { public class ChatListPresenter implements ChatListPresenterContract, Observer {
private ChatListViewContract view; private ChatListViewContract view;
private ChatListLogicContract logic; private ChatListLogicContract logic;
private ChatListAdapter chatListAdapter; private ChatListAdapter chatListAdapter;
@ -16,6 +22,7 @@ public class ChatListPresenter implements ChatListPresenterContract {
chatListAdapter = new ChatListAdapter(); chatListAdapter = new ChatListAdapter();
this.logic = new ChatListLogic(); this.logic = new ChatListLogic();
this.view.setRecycleAdapter(chatListAdapter); this.view.setRecycleAdapter(chatListAdapter);
AppHelper.getObservable().register(this, MainObservable.UI_ACTIONS_CHANNEL);
} }
@Override @Override
@ -28,4 +35,13 @@ public class ChatListPresenter implements ChatListPresenterContract {
public void openChat(String chatID) { public void openChat(String chatID) {
// TODO // TODO
} }
@Override
public void handleEvent(JsonObject object) {
switch (object.get("action").getAsInt()) {
case UIActions.NEW_CHAT: {
updateChatList();
}
}
}
} }

View File

@ -23,7 +23,7 @@ public class StartChatPresenter implements StartChatPresenterContract, Observer
@Override @Override
public void startChatWithPeer(String peerID) { public void startChatWithPeer(String peerID) {
// TODO logic.sendStartChatMessage(peerID);
} }
@Override @Override

View File

@ -10,6 +10,7 @@ import com.google.gson.JsonObject;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import io.github.chronosx88.influence.R; import io.github.chronosx88.influence.R;
import io.github.chronosx88.influence.contracts.chatlist.ChatListPresenterContract; import io.github.chronosx88.influence.contracts.chatlist.ChatListPresenterContract;
@ -41,6 +42,7 @@ public class ChatListFragment extends Fragment implements ChatListViewContract,
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
chatList = view.findViewById(R.id.chatlist_container); chatList = view.findViewById(R.id.chatlist_container);
chatList.setLayoutManager(new LinearLayoutManager(getContext()));
presenter = new ChatListPresenter(this); presenter = new ChatListPresenter(this);
presenter.updateChatList(); presenter.updateChatList();
} }