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