1 /* 2 * Copyright (c) 2017 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.wearable.wear.messaging.mock; 18 19 import android.content.Context; 20 import android.support.annotation.NonNull; 21 import android.support.annotation.Nullable; 22 import android.util.Log; 23 import com.example.android.wearable.wear.messaging.model.Chat; 24 import com.example.android.wearable.wear.messaging.model.Message; 25 import com.example.android.wearable.wear.messaging.model.Profile; 26 import com.example.android.wearable.wear.messaging.util.SharedPreferencesHelper; 27 import java.io.IOException; 28 import java.util.ArrayList; 29 import java.util.Collection; 30 import java.util.Collections; 31 import java.util.HashMap; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Set; 35 import java.util.TreeSet; 36 37 /** Mock database stores data in {@link android.content.SharedPreferences} */ 38 public class MockDatabase { 39 40 private static final String TAG = "MockDatabase"; 41 42 /** Callback for retrieving a user asynchronously. */ 43 public interface RetrieveUserCallback { onUserRetrieved(Profile user)44 void onUserRetrieved(Profile user); 45 error(Exception e)46 void error(Exception e); 47 } 48 49 /** Callback for creating a user. */ 50 public interface CreateUserCallback { onSuccess()51 void onSuccess(); 52 onError(Exception e)53 void onError(Exception e); 54 } 55 56 /** 57 * Creates a chat and stores it in the mock database 58 * 59 * @param participants of the chat 60 * @param user that has started the chat 61 * @return a chat with information attached to it 62 */ createChat(Context context, Collection<Profile> participants, Profile user)63 public static Chat createChat(Context context, Collection<Profile> participants, Profile user) { 64 int size = participants.size(); 65 Log.d(TAG, String.format("Creating a new chat with %d participant(s)", size)); 66 67 Chat chat = new Chat(); 68 69 // Initializes chat's last message to a blank String. 70 Message message = new Message.Builder().senderId(user.getId()).text("").build(); 71 72 chat.setLastMessage(message); 73 74 Map<String, Profile> participantMap = new HashMap<>(); 75 for (Profile profile : participants) { 76 participantMap.put(profile.getId(), profile); 77 } 78 chat.setParticipantsAndAlias(participantMap); 79 80 // Create an id for the chat based on the aggregate of the participants' ids 81 chat.setId(concat(participantMap.keySet())); 82 83 // If you start a new chat with someone you already have a chat with, reuse that chat 84 Collection<Chat> allChats = getAllChats(context); 85 Chat exists = findChat(allChats, chat.getId()); 86 if (exists != null) { 87 chat = exists; 88 } else { 89 allChats.add(chat); 90 } 91 92 persistsChats(context, allChats); 93 94 return chat; 95 } 96 persistsChats(Context context, Collection<Chat> chats)97 private static void persistsChats(Context context, Collection<Chat> chats) { 98 SharedPreferencesHelper.writeChatsToJsonPref(context, new ArrayList<>(chats)); 99 } 100 101 @Nullable findChat(Collection<Chat> chats, String chatId)102 private static Chat findChat(Collection<Chat> chats, String chatId) { 103 for (Chat c : chats) { 104 if (c.getId().equals(chatId)) { 105 return c; 106 } 107 } 108 return null; 109 } 110 111 /** 112 * Returns all of the chats stored in {@link android.content.SharedPreferences}. An empty {@link 113 * Collection<Chat>} will be returned if preferences cannot be read from or cannot parse the 114 * json object. 115 * 116 * @return a collection of chats 117 */ getAllChats(Context context)118 public static Collection<Chat> getAllChats(Context context) { 119 try { 120 return SharedPreferencesHelper.readChatsFromJsonPref(context); 121 } catch (IOException e) { 122 Log.e(TAG, "Could not read/unmarshall the list of chats from shared preferences", e); 123 return Collections.emptyList(); 124 } 125 } 126 127 /** 128 * Returns a {@link Chat} object with a given id. 129 * 130 * @param id of the stored chat 131 * @return chat with id or null if no chat has that id 132 */ 133 @Nullable findChatById(Context context, String id)134 public static Chat findChatById(Context context, String id) { 135 return findChat(getAllChats(context), id); 136 } 137 138 /** 139 * Updates the {@link Chat#lastMessage} field in the stored json object. 140 * 141 * @param chat to be updated. 142 * @param lastMessage to be updated on the chat. 143 */ updateLastMessage(Context context, Chat chat, Message lastMessage)144 private static void updateLastMessage(Context context, Chat chat, Message lastMessage) { 145 Collection<Chat> chats = getAllChats(context); 146 // Update reference of chat to what it is the mock database. 147 chat = findChat(chats, chat.getId()); 148 if (chat != null) { 149 chat.setLastMessage(lastMessage); 150 } 151 152 // Save all chats since share prefs are managing them as one entity instead of individually. 153 persistsChats(context, chats); 154 } 155 156 /** 157 * Flattens the collection of strings into a single string. For example, 158 * 159 * <p>Input: ["a", "b", "c"] 160 * 161 * <p>Output: "abc" 162 * 163 * @param collection to be flattened into a string 164 * @return a concatenated string 165 */ 166 @NonNull concat(Collection<String> collection)167 private static String concat(Collection<String> collection) { 168 Set<String> participantIds = new TreeSet<>(collection); 169 StringBuilder sb = new StringBuilder(); 170 for (String id : participantIds) { 171 sb.append(id); 172 } 173 return sb.toString(); 174 } 175 176 /** 177 * Saves the message to the thread of messages for the given chat. The message's sent time will 178 * also be updated to preserve order. 179 * 180 * @param chat that the message should be added to. 181 * @param message that was sent in the chat. 182 * @return message with {@link Message#sentTime} updated 183 */ saveMessage(Context context, Chat chat, Message message)184 public static Message saveMessage(Context context, Chat chat, Message message) { 185 186 message.setSentTime(System.currentTimeMillis()); 187 188 updateLastMessage(context, chat, message); 189 190 Collection<Message> messages = getAllMessagesForChat(context, chat.getId()); 191 messages.add(message); 192 193 SharedPreferencesHelper.writeMessagesForChatToJsonPref( 194 context, chat, new ArrayList<>(messages)); 195 196 return message; 197 } 198 199 /** 200 * Returns all messages related to a given chat. 201 * 202 * @param chatId of the conversation 203 * @return messages in the conversation 204 */ getAllMessagesForChat(Context context, String chatId)205 public static Collection<Message> getAllMessagesForChat(Context context, String chatId) { 206 return SharedPreferencesHelper.readMessagesForChat(context, chatId); 207 } 208 209 /** 210 * Returns message details for a message in a particular chat. 211 * 212 * @param chatId that the message is in 213 * @param messageId of the message to be found in the chat 214 * @return message from a chat 215 */ 216 @Nullable findMessageById(Context context, String chatId, String messageId)217 public static Message findMessageById(Context context, String chatId, String messageId) { 218 for (Message message : getAllMessagesForChat(context, chatId)) { 219 if (message.getId().equals(messageId)) { 220 return message; 221 } 222 } 223 return null; 224 } 225 226 /** 227 * Generates a set of predefined placeholder contacts. You may need to add in extra logic for 228 * timestamp changes between server and local app. 229 * 230 * @return a list of profiles to be used as contacts 231 */ getUserContacts(Context context)232 public static List<Profile> getUserContacts(Context context) { 233 234 List<Profile> contacts = SharedPreferencesHelper.readContactsFromJsonPref(context); 235 if (!contacts.isEmpty()) { 236 return contacts; 237 } 238 239 // Cannot find contacts so we will persist and return a default set of contacts. 240 List<Profile> defaultContacts = MockObjectGenerator.generateDefaultContacts(); 241 SharedPreferencesHelper.writeContactsToJsonPref(context, defaultContacts); 242 return defaultContacts; 243 } 244 245 /** 246 * Returns the user asynchronously to the client via a callback. 247 * 248 * @param id for a user 249 * @param callback used for handling asynchronous responses 250 */ getUser(Context context, String id, RetrieveUserCallback callback)251 public static void getUser(Context context, String id, RetrieveUserCallback callback) { 252 Profile user = SharedPreferencesHelper.readUserFromJsonPref(context); 253 if (user != null && user.getId().equals(id)) { 254 callback.onUserRetrieved(user); 255 } else { 256 // Could not find user with that id. 257 callback.onUserRetrieved(null); 258 } 259 } 260 261 /** 262 * Creates a user asynchronously and notifies the client if a user has been created successfully 263 * or if there were any errors. 264 * 265 * @param user that needs to be created 266 * @param callback used for handling asynchronous responses 267 */ createUser(Context context, Profile user, CreateUserCallback callback)268 public static void createUser(Context context, Profile user, CreateUserCallback callback) { 269 SharedPreferencesHelper.writeUserToJsonPref(context, user); 270 callback.onSuccess(); 271 } 272 } 273