diff options
author | Benjamin Baxter <benbaxter@google.com> | 2017-03-22 15:56:48 -0700 |
---|---|---|
committer | Benjamin Baxter <benbaxter@google.com> | 2017-04-14 15:50:11 -0700 |
commit | 2264fb2a775cb1a5ba84d1550dfe5f1ac388cbce (patch) | |
tree | 86b214f784050d86baefb410228130fc32802c0a /wearable/wear/WearMessagingApp | |
parent | 06feed786f96cb11cb374645efadcec5b8a900d3 (diff) | |
download | android-2264fb2a775cb1a5ba84d1550dfe5f1ac388cbce.tar.gz |
Adding model, repository and shared preference helper
Bug: 34841755
Change-Id: Ibe7b2908b98aa24b32a6f6d5f44f0533fe545bf3
Diffstat (limited to 'wearable/wear/WearMessagingApp')
18 files changed, 1287 insertions, 10 deletions
diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/mock/MockDatabase.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/mock/MockDatabase.java new file mode 100644 index 00000000..1b0d91a8 --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/mock/MockDatabase.java @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.wearable.wear.messaging.mock; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; +import com.example.android.wearable.wear.messaging.model.Chat; +import com.example.android.wearable.wear.messaging.model.Message; +import com.example.android.wearable.wear.messaging.model.Profile; +import com.example.android.wearable.wear.messaging.util.SharedPreferencesHelper; +import com.fasterxml.jackson.core.JsonProcessingException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +/** Mock database stores data in {@link android.content.SharedPreferences} */ +public class MockDatabase { + + private static final String TAG = "MockDatabase"; + + private static Context mContext; + + /** A callback for events when retrieving a user asynchronously */ + interface RetrieveUserCallback { + void retrieved(Profile user); + + void error(Exception e); + } + + /** A callback for getting events during the creation of a user */ + interface CreateUserCallback { + void onSuccess(); + + void onError(Exception e); + } + + /** + * Initializes the mock database with a context for use with {@link + * android.content.SharedPreferences} + */ + public static void init(Context context) { + mContext = context; + } + + /** + * Creates a chat and stores it in the mock database + * + * @param participants of the chat + * @param user that has started the chat + * @return a chat with information attached to it + */ + public static Chat createChat(Collection<Profile> participants, Profile user) { + int size = participants.size(); + Log.d(TAG, String.format("Creating a new chat with %d participant(s)", size)); + + Chat chat = new Chat(); + + // Initializes chat's last message to a blank String. + Message message = new Message.Builder().senderId(user.getId()).text("").build(); + + chat.setLastMessage(message); + + Map<String, Profile> participantMap = new HashMap<>(); + for (Profile profile : participants) { + participantMap.put(profile.getId(), profile); + } + chat.setParticipantsAndAlias(participantMap); + + // Create an id for the chat based on the aggregate of the participants' ids + chat.setId(concat(participantMap.keySet())); + + // If you start a new chat with someone you already have a chat with, reuse that chat + Collection<Chat> allChats = getAllChats(); + Chat exists = findChat(allChats, chat.getId()); + if (exists != null) { + chat = exists; + } else { + allChats.add(chat); + } + + persistsChats(allChats); + + return chat; + } + + private static void persistsChats(Collection<Chat> chats) { + try { + SharedPreferencesHelper.writeChatsToJsonPref(mContext, new ArrayList<>(chats)); + } catch (JsonProcessingException e) { + Log.e(TAG, "Could not write chats to json", e); + } + } + + @Nullable + private static Chat findChat(Collection<Chat> chats, String chatId) { + for (Chat c : chats) { + if (c.getId().equals(chatId)) { + return c; + } + } + return null; + } + + /** + * Returns all of the chats stored in {@link android.content.SharedPreferences}. An empty {@link + * Collection<Chat>} will be returned if preferences cannot be read from or cannot parse the + * json object. + * + * @return a collection of chats + */ + public static Collection<Chat> getAllChats() { + + try { + return SharedPreferencesHelper.readChatsFromJsonPref(mContext); + } catch (IOException e) { + Log.e(TAG, "Could not read/unmarshall the list of chats from shared preferences", e); + return Collections.emptyList(); + } + } + + /** + * Returns a {@link Chat} object with a given id. + * + * @param id of the stored chat + * @return chat with id or null if no chat has that id + */ + @Nullable + public static Chat findChatById(String id) { + return findChat(getAllChats(), id); + } + + /** + * Updates the {@link Chat#lastMessage} field in the stored json object. + * + * @param chat to be updated. + * @param lastMessage to be updated on the chat. + */ + public static void updateLastMessage(Chat chat, Message lastMessage) { + Collection<Chat> chats = getAllChats(); + // Update reference of chat to what it is the mock database. + chat = findChat(chats, chat.getId()); + if (chat != null) { + chat.setLastMessage(lastMessage); + } + + // Save all chats since share prefs are managing them as one entity instead of individually. + persistsChats(chats); + } + + /** + * Flattens the collection of strings into a single string. For example, + * + * <p>Input: ["a", "b", "c"] + * + * <p>Output: "abc" + * + * @param collection to be flattened into a string + * @return a concatenated string + */ + @NonNull + private static String concat(Collection<String> collection) { + Set<String> participantIds = new TreeSet<>(collection); + StringBuilder sb = new StringBuilder(); + for (String id : participantIds) { + sb.append(id); + } + return sb.toString(); + } + + /** + * Saves the message to the thread of messages for the given chat. The message's sent time will + * also be updated to preserve order. + * + * @param chat that the message should be added to. + * @param message that was sent in the chat. + * @return message with {@link Message#sentTime} updated + */ + public static Message saveMessage(Chat chat, Message message) { + + message.setSentTime(System.currentTimeMillis()); + + updateLastMessage(chat, message); + + Collection<Message> messages = getAllMessagesForChat(chat.getId()); + messages.add(message); + + try { + SharedPreferencesHelper.writeMessagesForChatToJsonPref( + mContext, chat, new ArrayList<>(messages)); + } catch (JsonProcessingException e) { + Log.e(TAG, "Could not write the list of messages to shared preferences", e); + } + + return message; + } + + /** + * Returns all messages related to a given chat. + * + * @param chatId of the conversation + * @return messages in the conversation + */ + public static Collection<Message> getAllMessagesForChat(String chatId) { + try { + return SharedPreferencesHelper.readMessagesForChat(mContext, chatId); + } catch (IOException e) { + Log.e(TAG, "Could not read/unmarshall the list of messages from shared preferences", e); + return Collections.emptyList(); + } + } + + /** + * Returns message details for a message in a particular chat. + * + * @param chatId that the message is in + * @param messageId of the message to be found in the chat + * @return message from a chat + */ + @Nullable + public static Message findMessageById(String chatId, String messageId) { + for (Message message : getAllMessagesForChat(chatId)) { + if (message.getId().equals(messageId)) { + return message; + } + } + return null; + } + + /** + * Generates a set of predefined dummy contacts. You may need to add in extra logic for + * timestamp changes between server and local app. + * + * @return a list of profiles to be used as contacts + */ + public static List<Profile> getUserContacts() { + + try { + List<Profile> contacts = SharedPreferencesHelper.readContactsFromJsonPref(mContext); + if (!contacts.isEmpty()) { + return contacts; + } + } catch (IOException e) { + String logMessage = + "Could not read/unmarshall the list of contacts from shared preferences. " + + "Returning mock contacts."; + Log.e(TAG, logMessage, e); + } + + // Cannot find contacts so we will persist and return a default set of contacts. + List<Profile> defaultContacts = MockObjectGenerator.generateDefaultContacts(); + try { + Log.d(TAG, "saving default contacts"); + SharedPreferencesHelper.writeContactsToJsonPref(mContext, defaultContacts); + } catch (JsonProcessingException e) { + Log.e(TAG, "Could not write the list of contacts to shared preferences", e); + } + return defaultContacts; + } + + /** + * Returns the user asynchronously to the client via a callback. + * + * @param id for a user + * @param callback used for handling asynchronous responses + */ + public static void getUser(String id, RetrieveUserCallback callback) { + Profile user = null; + try { + user = SharedPreferencesHelper.readUserFromJsonPref(mContext); + } catch (IOException e) { + Log.e(TAG, "Could not read/unmarshall the user from shared preferences.", e); + callback.error(e); + } + if (user != null && user.getId().equals(id)) { + callback.retrieved(user); + } else { + // Could not find user with that id. + callback.retrieved(null); + } + } + + /** + * Creates a user asynchronously and notifies the client if a user has been created successfully + * or if there were any errors. + * + * @param user that needs to be created + * @param callback used for handling asynchronous responses + */ + public static void createUser(Profile user, CreateUserCallback callback) { + try { + SharedPreferencesHelper.writeUserToJsonPref(mContext, user); + callback.onSuccess(); + } catch (JsonProcessingException e) { + Log.e(TAG, "Could not write the user to shared preferences", e); + //Let the client know that the user cannot be saved + callback.onError(e); + } + } +} diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/mock/MockObjectGenerator.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/mock/MockObjectGenerator.java new file mode 100644 index 00000000..b99871df --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/mock/MockObjectGenerator.java @@ -0,0 +1,67 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.wearable.wear.messaging.mock; + +import android.support.annotation.NonNull; +import com.example.android.wearable.wear.messaging.R; +import com.example.android.wearable.wear.messaging.model.Profile; +import java.util.Arrays; +import java.util.List; + +/** Helper methods to generate mock objects. */ +public class MockObjectGenerator { + + /** + * Returns a list of mocked contacts. + * + * @return a {@link List<Profile>} that can be used to mock out a user's contact list. + */ + public static List<Profile> generateDefaultContacts() { + + Profile paul = buildProfile("1234", "Paul Saxman", "PaulSaxman@email.com", R.drawable.paul); + + Profile ben = + buildProfile("2345", "Benjamin Baxter", "benjaminbaxter@email.com", R.drawable.ben); + + Profile jeremy = + buildProfile("3456", "Jeremy Walker", "jeremywalker@email.com", R.drawable.jeremy); + + Profile jennifer = + buildProfile( + "4567", "Jennifer Smith", "jennifersmith@email.com", R.drawable.jennifer); + + Profile android = + buildProfile("5678", "Android", "android@email.com", R.drawable.android_logo); + + Profile lisa = + buildProfile("6789", "Lisa Williams", "lisawilliams@email.com", R.drawable.lisa); + + Profile jane = buildProfile("7890", "Jane Doe", "janedoe@email.com", R.drawable.jane); + + return Arrays.asList(paul, jennifer, ben, lisa, jane, jeremy, android); + } + + @NonNull + private static Profile buildProfile(String id, String name, String email, int profileResource) { + return new Profile.Builder() + .id(id) + .name(name) + .email(email) + .profileImageResource(profileResource) + .lastUpdatedTime(System.currentTimeMillis()) + .build(); + } +} diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/model/Chat.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/model/Chat.java new file mode 100644 index 00000000..7138e638 --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/model/Chat.java @@ -0,0 +1,235 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.wearable.wear.messaging.model; + +import android.os.Parcel; +import android.os.Parcelable; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** Data representation of a chat. */ +public class Chat implements Parcelable { + private String id; + private String alias; + + // Map using User's id as a hash for profile details for everyone in the conversation. + private Map<String, Profile> participants; + //TODO: remove me if not being used + private Map<String, String> lastReadMessages; + + private Message lastMessage; + + public Chat() { + id = UUID.randomUUID().toString(); + } + + public Chat(String id) { + this.id = id; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public Map<String, Profile> getParticipants() { + return participants; + } + + public void setParticipants(Map<String, Profile> participants) { + this.participants = participants; + } + + private void generateAliasFromParticipants() { + StringBuilder participantNames = new StringBuilder(); + + // Iterates through each participants then generates a comma separated string as the alias. + for (Profile profile : participants.values()) { + participantNames.append(profile.getName()).append(","); + } + participantNames.setLength(participantNames.length() - 1); // Remove last comma (,) + + this.alias = participantNames.toString(); + } + + /** + * Sets the participants and adds an alias. The alias is displayed in the chat list. + * + * @param participants chat participants + */ + public void setParticipantsAndAlias(Map<String, Profile> participants) { + setParticipants(participants); + generateAliasFromParticipants(); + } + + /** + * Sets the participants and specified alias + * + * @param participants chat participants + * @param alias chat alias + */ + public void setParticipantsAndAlias(Map<String, Profile> participants, String alias) { + setParticipants(participants); + this.alias = alias; + } + + public Map<String, String> getLastReadMessages() { + return lastReadMessages; + } + + public void setLastReadMessages(Map<String, String> lastReadMessages) { + this.lastReadMessages = lastReadMessages; + } + + public void addParticipant(Profile participant) { + participants.put(participant.getId(), participant); + } + + public Message getLastMessage() { + return lastMessage; + } + + public void setLastMessage(Message lastMessage) { + this.lastMessage = lastMessage; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.id); + dest.writeString(this.alias); + dest.writeParcelable(this.lastMessage, 0); + dest.writeInt(this.participants != null ? this.participants.size() : -1); + for (Map.Entry<String, Profile> entry : this.participants.entrySet()) { + dest.writeString(entry.getKey()); + dest.writeParcelable(entry.getValue(), flags); + } + + dest.writeInt(this.lastReadMessages != null ? this.lastReadMessages.size() : -1); + if (this.lastReadMessages != null) { + for (Map.Entry<String, String> entry : this.lastReadMessages.entrySet()) { + dest.writeString(entry.getKey()); + dest.writeString(entry.getValue()); + } + } + } + + protected Chat(Parcel in) { + this.id = in.readString(); + this.alias = in.readString(); + this.lastMessage = in.readParcelable(Message.class.getClassLoader()); + + int participantsSize = in.readInt(); + if (participantsSize != -1) { + this.participants = new HashMap<>(participantsSize); + for (int i = 0; i < participantsSize; i++) { + String key = in.readString(); + Profile value = in.readParcelable(Profile.class.getClassLoader()); + this.participants.put(key, value); + } + } + + int lastReadMessagesSize = in.readInt(); + if (lastReadMessagesSize != -1) { + this.lastReadMessages = new HashMap<>(lastReadMessagesSize); + for (int i = 0; i < lastReadMessagesSize; i++) { + String key = in.readString(); + String value = in.readString(); + this.lastReadMessages.put(key, value); + } + } + } + + public static final Parcelable.Creator<Chat> CREATOR = + new Parcelable.Creator<Chat>() { + @Override + public Chat createFromParcel(Parcel source) { + return new Chat(source); + } + + @Override + public Chat[] newArray(int size) { + return new Chat[size]; + } + }; + + /** + * Indicates whether some other object is "equal to" this one. + * + * @param other - the reference object with which to compare. + * @return true/false based on all fields being equal or equally null + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + Chat chat = (Chat) other; + + if (!id.equals(chat.id)) { + return false; + } + if (alias != null ? !alias.equals(chat.alias) : chat.alias != null) { + return false; + } + if (!participants.equals(chat.participants)) { + return false; + } + if (lastReadMessages != null + ? !lastReadMessages.equals(chat.lastReadMessages) + : chat.lastReadMessages != null) { + return false; + } + return lastMessage != null + ? lastMessage.equals(chat.lastMessage) + : chat.lastMessage == null; + } + + /** + * Returns a hash code value for the object. + * + * @return a hash code value for this object. + */ + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + (alias != null ? alias.hashCode() : 0); + result = 31 * result + participants.hashCode(); + result = 31 * result + (lastReadMessages != null ? lastReadMessages.hashCode() : 0); + result = 31 * result + (lastMessage != null ? lastMessage.hashCode() : 0); + return result; + } +} diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/model/Message.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/model/Message.java new file mode 100644 index 00000000..95f7b834 --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/model/Message.java @@ -0,0 +1,182 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.wearable.wear.messaging.model; + +import android.os.Parcel; +import android.os.Parcelable; + +/** Data representation of each message. Stores the sender, text content, and the sent time. */ +public class Message implements Parcelable { + private String id; + private String senderId; + private String name; + private String text; + private long sentTime; + + public Message() {} + + public Message(Message message, String name) { + senderId = message.getSenderId(); + this.name = name; + text = message.getText(); + } + + private Message(Builder builder) { + id = builder.id; + senderId = builder.senderId; + text = builder.text; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getSenderId() { + return senderId; + } + + public String getText() { + return text; + } + + public long getSentTime() { + return sentTime; + } + + public void setSentTime(long sentTime) { + this.sentTime = sentTime; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** Message Builder */ + public static final class Builder { + private String id; + private String senderId; + private String text; + + public Builder() {} + + public Builder id(String val) { + id = val; + return this; + } + + public Builder senderId(String val) { + senderId = val; + return this; + } + + public Builder text(String val) { + text = val; + return this; + } + + public Message build() { + return new Message(this); + } + } + + /** + * Indicates whether some other object is "equal to" this one. + * + * @param other - the reference object with which to compare. + * @return true/false based on all fields being equal or equally null + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + Message message = (Message) other; + + if (sentTime != message.sentTime) { + return false; + } + if (id != null ? !id.equals(message.id) : message.id != null) { + return false; + } + if (!senderId.equals(message.senderId)) { + return false; + } + if (name != null ? !name.equals(message.name) : message.name != null) { + return false; + } + return text != null ? text.equals(message.text) : message.text == null; + } + + /** + * Returns a hash code value for the object. + * + * @return a hash code value for this object. + */ + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + senderId.hashCode(); + result = 31 * result + (name != null ? name.hashCode() : 0); + result = 31 * result + (text != null ? text.hashCode() : 0); + result = 31 * result + (int) (sentTime ^ (sentTime >>> 32)); + return result; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.senderId); + dest.writeString(this.name); + dest.writeString(this.text); + dest.writeLong(this.sentTime); + } + + protected Message(Parcel in) { + this.senderId = in.readString(); + this.name = in.readString(); + this.text = in.readString(); + this.sentTime = in.readLong(); + } + + public static final Creator<Message> CREATOR = + new Creator<Message>() { + @Override + public Message createFromParcel(Parcel source) { + return new Message(source); + } + + @Override + public Message[] newArray(int size) { + return new Message[size]; + } + }; +} diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/model/Profile.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/model/Profile.java new file mode 100644 index 00000000..7a687496 --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/model/Profile.java @@ -0,0 +1,230 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.wearable.wear.messaging.model; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import com.google.android.gms.auth.api.signin.GoogleSignInAccount; + +/** Represents a user profile. Parcelable to pass between activities. */ +public class Profile implements Parcelable { + + private String id; + private String email; + private String name; + private String profileImageUri; + private int profileImageResource; + private Long lastUpdatedTime; + + public Profile() {} + + public Profile(@NonNull GoogleSignInAccount account) { + this.id = account.getId(); + this.email = account.getEmail(); + this.name = account.getDisplayName(); + this.profileImageUri = + (account.getPhotoUrl() != null) ? account.getPhotoUrl().toString() : null; + this.lastUpdatedTime = System.currentTimeMillis(); + } + + public Profile(String id, String name, String imageUri) { + this.id = id; + this.name = name; + this.profileImageUri = imageUri; + this.lastUpdatedTime = System.currentTimeMillis(); + } + + private Profile(Builder builder) { + setId(builder.id); + setEmail(builder.email); + setName(builder.name); + setProfileImageResource(builder.profileImageResource); + setLastUpdatedTime(builder.lastUpdatedTime); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public int getProfileImageResource() { + return profileImageResource; + } + + public void setProfileImageResource(int profileImageResource) { + this.profileImageResource = profileImageResource; + } + + public void setProfileImageUri(String profileImageUri) { + this.profileImageUri = profileImageUri; + } + + public String getProfileImageUri() { + return profileImageUri; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Long getLastUpdatedTime() { + return lastUpdatedTime; + } + + public void setLastUpdatedTime(Long lastUpdatedTime) { + this.lastUpdatedTime = lastUpdatedTime; + } + + /** + * Indicates whether some other object is "equal to" this one. + * + * @param other - the reference object with which to compare. + * @return true/false based on all fields being equal or equally null + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + Profile profile = (Profile) other; + + if (!id.equals(profile.id)) { + return false; + } + if (email != null ? !email.equals(profile.email) : profile.email != null) { + return false; + } + if (!name.equals(profile.name)) { + return false; + } + if (!profileImageUri.equals(profile.profileImageUri)) { + return false; + } + return lastUpdatedTime != null + ? lastUpdatedTime.equals(profile.lastUpdatedTime) + : profile.lastUpdatedTime == null; + } + + /** + * Returns a hash code value for the object. + * + * @return a hash code value for this object. + */ + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + (email != null ? email.hashCode() : 0); + result = 31 * result + name.hashCode(); + result = 31 * result + profileImageUri.hashCode(); + result = 31 * result + (lastUpdatedTime != null ? lastUpdatedTime.hashCode() : 0); + return result; + } + + /** Builder crates profiles. */ + public static final class Builder { + private String id; + private String email; + private String name; + private int profileImageResource; + private Long lastUpdatedTime; + + public Builder() {} + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder email(String email) { + this.email = email; + return this; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder profileImageResource(int profileImageResource) { + this.profileImageResource = profileImageResource; + return this; + } + + public Builder lastUpdatedTime(Long lastUpdatedTime) { + this.lastUpdatedTime = lastUpdatedTime; + return this; + } + + public Profile build() { + return new Profile(this); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.id); + dest.writeString(this.email); + dest.writeString(this.name); + dest.writeString(this.profileImageUri); + dest.writeValue(this.lastUpdatedTime); + } + + protected Profile(Parcel in) { + this.id = in.readString(); + this.email = in.readString(); + this.name = in.readString(); + this.profileImageUri = in.readString(); + this.lastUpdatedTime = (Long) in.readValue(Long.class.getClassLoader()); + } + + public static final Creator<Profile> CREATOR = + new Creator<Profile>() { + @Override + public Profile createFromParcel(Parcel source) { + return new Profile(source); + } + + @Override + public Profile[] newArray(int size) { + return new Profile[size]; + } + }; +} diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/Constants.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/Constants.java new file mode 100644 index 00000000..e99414a4 --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/Constants.java @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.wearable.wear.messaging.util; + +/** + * All Constants for interaction with the Mock Database, extras between activities, and actions for + * intents. + */ +public class Constants { + + /* Constants for Extras to be passed along through bundles */ + public static final String EXTRA_CHAT = + "com.example.android.wearable.wear.messaging.extra.CHAT"; + public static final String EXTRA_MESSAGE = + "com.example.android.wearable.wear.messaging.extra.MESSAGE"; + public static final String EXTRA_REPLY = + "com.example.android.wearable.wear.messaging.extra.REPLY"; + + /* Shared Preference keys for SharedPreferencesHelper */ + static final String PREFS_NAME = "com.example.android.wearable.wear.messaging"; + static final String PREFS_CHAT_ID_KEY = + "com.example.android.wearable.wear.messaging.prefs.CHAT_ID"; + static final String PREFS_USER_KEY = "com.example.android.wearable.wear.messaging.prefs.USER"; + static final String PREFS_CONTACTS_KEY = + "com.example.android.wearable.wear.messaging.prefs.CONTACTS"; + static final String PREFS_CHATS_KEY = "com.example.android.wearable.wear.messaging.prefs.CHATS"; + static final String PREFS_MESSAGE_PREFIX = "chat_message_"; + + public static final String RESULT_CONTACTS_KEY = + "com.example.android.wear.samples.sup.result.CONTACTS_RESULT"; + + /* Action constants for services/broadcast receiver intents */ + static final String ACTION_RECEIVE_MESSAGE = + "com.example.android.wearable.wear.messaging.action.RECEIVE_MESSAGE"; + public static final String ACTION_REPLY = + "com.example.android.wearable.wear.messaging.action.REPLY_MESSAGE"; +} diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/DividerItemDecoration.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/DividerItemDecoration.java index 7ea10de0..6927216c 100644 --- a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/DividerItemDecoration.java +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/DividerItemDecoration.java @@ -22,9 +22,7 @@ import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; import android.view.View; -/** - * Recycler view divider. - */ +/** Recycler view divider. */ public class DividerItemDecoration extends RecyclerView.ItemDecoration { private Drawable mDrawable; diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/MenuTinter.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/MenuTinter.java index bad1df3a..08f23cdf 100644 --- a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/MenuTinter.java +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/MenuTinter.java @@ -22,9 +22,7 @@ import android.support.v4.graphics.drawable.DrawableCompat; import android.view.Menu; import android.view.MenuItem; -/** - * Helper method to tint menu item icons. - */ +/** Helper method to tint menu item icons. */ public class MenuTinter { private MenuTinter() {} diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/PrescrollToBottom.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/PrescrollToBottom.java index 3b98c5bc..91b7693f 100644 --- a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/PrescrollToBottom.java +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/PrescrollToBottom.java @@ -19,9 +19,7 @@ import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.ViewTreeObserver; -/** - * Prescrolls to the last element in the adapter before draw and removes itself. - */ +/** Prescrolls to the last element in the adapter before draw and removes itself. */ public class PrescrollToBottom implements ViewTreeObserver.OnPreDrawListener { private static final String TAG = "PrescrollToBottom"; @@ -46,4 +44,4 @@ public class PrescrollToBottom implements ViewTreeObserver.OnPreDrawListener { mRecyclerView.getViewTreeObserver().removeOnPreDrawListener(this); return !shouldScroll; } -}
\ No newline at end of file +} diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/SharedPreferencesHelper.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/SharedPreferencesHelper.java new file mode 100644 index 00000000..3caa5edb --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/SharedPreferencesHelper.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.wearable.wear.messaging.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; +import com.example.android.wearable.wear.messaging.model.Chat; +import com.example.android.wearable.wear.messaging.model.Message; +import com.example.android.wearable.wear.messaging.model.Profile; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * SharedPreferencesHelper provides static methods to set, get, delete these objects. + * + * <p>The user's profile details and chat details are persisted in SharedPreferences to access + * across the app. + */ +public class SharedPreferencesHelper { + + private static final String TAG = "SharedPreferencesHelper"; + + private static ObjectMapper mMapper = new ObjectMapper(); + + /** + * Returns logged in user or null if no user is logged in. + * + * @param context shared preferences context + * @return user profile + * @throws IOException + */ + public static Profile readUserFromJsonPref(Context context) throws IOException { + SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.PREFS_NAME, 0); + String profileString = sharedPreferences.getString(Constants.PREFS_USER_KEY, null); + return profileString != null ? mMapper.readValue(profileString, Profile.class) : null; + } + + /** + * Writes a {@link Profile} to json and stores it in preferences. + * + * @param context used to access {@link SharedPreferences} + * @param user to be stored in preferences + * @throws JsonProcessingException if object cannot be written to preferences + */ + public static void writeUserToJsonPref(Context context, Profile user) + throws JsonProcessingException { + SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.PREFS_NAME, 0); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(Constants.PREFS_USER_KEY, mMapper.writeValueAsString(user)); + editor.apply(); + } + + /** + * Reads contacts from preferences. + * + * @param context used to access {@link SharedPreferences} + * @return contacts from preferences + * @throws IOException if there is an error parsing the json from preferences + */ + public static List<Profile> readContactsFromJsonPref(Context context) throws IOException { + return getList(context, Profile.class, Constants.PREFS_CONTACTS_KEY); + } + + /** + * Writes a {@link List<Profile>} to json and stores it in preferences. + * + * @param context used to access {@link SharedPreferences} + * @param contacts to be stored in preferences + * @throws JsonProcessingException if objects cannot be marshalled to json + */ + public static void writeContactsToJsonPref(Context context, List<Profile> contacts) + throws JsonProcessingException { + setList(context, contacts, Constants.PREFS_CONTACTS_KEY); + } + + /** + * Reads chats from preferences + * + * @param context used to access {@link SharedPreferences} + * @return chats from preferences + * @throws IOException if there is an error parsing the json from preferences + */ + public static List<Chat> readChatsFromJsonPref(Context context) throws IOException { + return getList(context, Chat.class, Constants.PREFS_CHATS_KEY); + } + + /** + * Writes a {@link List<Chat>} to json and stores it in preferences. + * + * @param context used to access {@link SharedPreferences} + * @param chats to be stores in preferences + * @throws JsonProcessingException if objects cannot be marshalled to json + */ + public static void writeChatsToJsonPref(Context context, List<Chat> chats) + throws JsonProcessingException { + Log.d(TAG, String.format("Saving %d chat(s)", chats.size())); + setList(context, chats, Constants.PREFS_CHATS_KEY); + } + + /** + * Reads messages for a chat from preferences. + * + * @param context used to access {@link SharedPreferences} + * @param chatId for the chat the messages are from + * @return messages from preferences + * @throws IOException if there is an error parsing the json from preferences + */ + public static List<Message> readMessagesForChat(Context context, String chatId) + throws IOException { + return getList(context, Message.class, Constants.PREFS_MESSAGE_PREFIX + chatId); + } + + /** + * Writes a {@link List<Message>} to json and stores it in preferences. + * + * @param context used to access {@link SharedPreferences} + * @param chat that the messages are from + * @param messages to be stored into preferences + * @throws JsonProcessingException if objects cannot be marshalled to json + */ + public static void writeMessagesForChatToJsonPref( + Context context, Chat chat, List<Message> messages) throws JsonProcessingException { + setList(context, messages, Constants.PREFS_MESSAGE_PREFIX + chat.getId()); + } + + /** + * Returns List of specified class from SharedPreferences (converts from string in + * SharedPreferences to class) + * + * @param context used for getting an instance of shared preferences + * @param clazz the class that the strings will be unmarshalled into + * @param key the key in shared preferences to access the string set + * @param <T> the type of object that will be in the returned list, should be the same as the + * clazz that was supplied + * @return a list of <T> objects that were stored in shared preferences. Returns an empty list + * if no data is available. + * @throws IOException if the string cannot unmarshall into the object <T> + */ + private static <T> List<T> getList(Context context, Class<T> clazz, String key) + throws IOException { + SharedPreferences sharedPreferences = + context.getSharedPreferences(Constants.PREFS_NAME, Context.MODE_PRIVATE); + Set<String> contactsSet = sharedPreferences.getStringSet(key, new HashSet<String>()); + if (contactsSet.isEmpty()) { + // Favoring mutability of the list over Collections.emptyList(). + return new ArrayList<>(); + } + List<T> list = new ArrayList<>(contactsSet.size()); + for (String contactString : contactsSet) { + list.add(mMapper.readValue(contactString, clazz)); + } + return list; + } + + /** + * Sets a List of specified class in SharedPreferences (converts from List of class to string + * for SharedPreferences) + * + * @param context used for getting an instance of shared preferences + * @param list of <T> object that need to be persisted + * @param key the key in shared preferences which the string set will be stored + * @param <T> type the of object we will be marshalling and persisting + * @throws JsonProcessingException if the object cannot be written to a string + */ + private static <T> void setList(Context context, List<T> list, String key) + throws JsonProcessingException { + SharedPreferences sharedPreferences = + context.getSharedPreferences(Constants.PREFS_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPreferences.edit(); + + Set<String> strings = new LinkedHashSet<>(list.size()); + for (T t : list) { + strings.add(mMapper.writeValueAsString(t)); + } + editor.putStringSet(key, strings); + editor.apply(); + } +} diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/android_logo.png b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/android_logo.png Binary files differnew file mode 100644 index 00000000..53f59c69 --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/android_logo.png diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/ben.png b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/ben.png Binary files differnew file mode 100644 index 00000000..676eb94f --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/ben.png diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/default_profile.png b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/default_profile.png Binary files differnew file mode 100644 index 00000000..a57b044c --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/default_profile.png diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/jane.png b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/jane.png Binary files differnew file mode 100644 index 00000000..cbf4a056 --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/jane.png diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/jennifer.png b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/jennifer.png Binary files differnew file mode 100644 index 00000000..3097ce12 --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/jennifer.png diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/jeremy.png b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/jeremy.png Binary files differnew file mode 100644 index 00000000..92204dbf --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/jeremy.png diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/lisa.png b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/lisa.png Binary files differnew file mode 100644 index 00000000..efbdb5d3 --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/lisa.png diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/paul.png b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/paul.png Binary files differnew file mode 100644 index 00000000..31554da2 --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/drawable/paul.png |