com.leanplum.LeanplumInbox Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of leanplum-core Show documentation
Show all versions of leanplum-core Show documentation
The Leanplum SDK messaging platform
The newest version!
/*
* Copyright 2017, Leanplum, Inc. All rights reserved.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.leanplum;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.Nullable;
import com.leanplum.callbacks.InboxChangedCallback;
import com.leanplum.callbacks.InboxSyncedCallback;
import com.leanplum.callbacks.VariablesChangedCallback;
import com.leanplum.internal.AESCrypt;
import com.leanplum.internal.APIConfig;
import com.leanplum.internal.CollectionUtil;
import com.leanplum.internal.Constants;
import com.leanplum.internal.JsonConverter;
import com.leanplum.internal.Log;
import com.leanplum.internal.OperationQueue;
import com.leanplum.internal.Request.RequestType;
import com.leanplum.internal.RequestBuilder;
import com.leanplum.internal.Request;
import com.leanplum.internal.RequestSender;
import com.leanplum.utils.SharedPreferencesUtil;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Inbox class.
*
* @author Aleksandar Gyorev, Anna Orlova
*/
public class LeanplumInbox {
private static LeanplumInbox instance = new LeanplumInbox();
static Set downloadedImageUrls;
static boolean isInboxImagePrefetchingEnabled = true;
private volatile int unreadCount;
private volatile Map messages;
private volatile boolean didLoad = false;
private final List changedCallbacks;
private final List syncedCallbacks;
private final Object updatingLock = new Object();
private LeanplumInbox() {
this.unreadCount = 0;
this.messages = new HashMap<>();
this.didLoad = false;
this.changedCallbacks = new ArrayList<>();
this.syncedCallbacks = new ArrayList<>();
downloadedImageUrls = new HashSet<>();
}
/**
* Disable prefetching images.
*/
public static void disableImagePrefetching() {
isInboxImagePrefetchingEnabled = false;
}
/**
* Returns the number of all inbox messages on the device.
*/
public int count() {
return messages.size();
}
/**
* Returns the number of the unread inbox messages on the device.
*/
public int unreadCount() {
return unreadCount;
}
/**
* Returns the identifiers of all inbox messages on the device sorted in ascending
* chronological order, i.e. the id of the oldest message is the first one, and the most recent
* one is the last one in the array.
*/
public List messagesIds() {
List messageIds = new ArrayList<>(messages.keySet());
try {
Collections.sort(messageIds, new Comparator() {
@Override
public int compare(String firstMessageId, String secondMessageId) {
// Message that is null will be moved to the back of the list.
LeanplumInboxMessage firstMessage = messageForId(firstMessageId);
if (firstMessage == null) {
return -1;
}
LeanplumInboxMessage secondMessage = messageForId(secondMessageId);
if (secondMessage == null) {
return 1;
}
// Message with null date will be moved to the back of the list.
Date firstDate = firstMessage.getDeliveryTimestamp();
if (firstDate == null) {
return -1;
}
Date secondDate = secondMessage.getDeliveryTimestamp();
if (secondDate == null) {
return 1;
}
return firstDate.compareTo(secondDate);
}
});
} catch (Throwable t) {
Log.exception(t);
}
return messageIds;
}
/**
* Returns a List containing all of the inbox messages sorted chronologically ascending (i.e.
* the oldest first and the newest last).
*/
public List allMessages() {
return allMessages(new ArrayList());
}
/**
* Returns a List containing all of the unread inbox messages sorted chronologically ascending
* (i.e. the oldest first and the newest last).
*/
public List unreadMessages() {
return unreadMessages(new ArrayList());
}
/**
* Returns the inbox messages associated with the given getMessageId identifier.
*/
public LeanplumInboxMessage messageForId(String messageId) {
return messages.get(messageId);
}
/**
* Add a callback for when the inbox receives new values from the server.
*/
public void addChangedHandler(InboxChangedCallback handler) {
synchronized (changedCallbacks) {
changedCallbacks.add(handler);
}
if (this.didLoad) {
handler.inboxChanged();
}
}
/**
* Removes a inbox changed callback.
*/
public void removeChangedHandler(InboxChangedCallback handler) {
synchronized (changedCallbacks) {
changedCallbacks.remove(handler);
}
}
/**
* Add a callback for when the forceContentUpdate was called.
*
* @param handler InboxSyncedCallback callback that need to be added.
*/
public void addSyncedHandler(InboxSyncedCallback handler) {
synchronized (syncedCallbacks) {
syncedCallbacks.add(handler);
}
}
/**
* Removes a inbox synced callback.
*
* @param handler InboxSyncedCallback callback that need to be removed.
*/
public void removeSyncedHandler(InboxSyncedCallback handler) {
synchronized (syncedCallbacks) {
syncedCallbacks.remove(handler);
}
}
/**
* Static 'getInstance' method.
*/
static LeanplumInbox getInstance() {
return instance;
}
boolean isInboxImagePrefetchingEnabled() {
return isInboxImagePrefetchingEnabled;
}
void updateUnreadCount(int unreadCount) {
this.unreadCount = unreadCount;
save();
triggerChanged();
}
void update(Map messages, int unreadCount, boolean shouldSave) {
try {
synchronized (updatingLock) {
this.unreadCount = unreadCount;
if (messages != null) {
this.messages = messages;
}
}
this.didLoad = true;
if (shouldSave) {
save();
}
triggerChanged();
} catch (Throwable t) {
Log.exception(t);
}
}
void removeMessage(String messageId) {
int unreadCount = this.unreadCount;
LeanplumInboxMessage message = messageForId(messageId);
if (message != null && !message.isRead()) {
unreadCount--;
}
messages.remove(messageId);
update(messages, unreadCount, true);
if (Constants.isNoop()) {
return;
}
Request req = RequestBuilder
.withDeleteInboxMessageAction()
.andParam(Constants.Params.INBOX_MESSAGE_ID, messageId)
.create();
RequestSender.getInstance().send(req);
}
void triggerChanged() {
synchronized (changedCallbacks) {
for (InboxChangedCallback callback : changedCallbacks) {
OperationQueue.sharedInstance().addUiOperation(callback);
}
}
}
/**
* Trigger InboxSyncedCallback with status of forceContentUpdate.
* @param success True if forceContentUpdate was successful.
*/
void triggerInboxSyncedWithStatus(boolean success) {
synchronized (syncedCallbacks) {
for (InboxSyncedCallback callback : syncedCallbacks) {
callback.setSuccess(success);
OperationQueue.sharedInstance().addUiOperation(callback);
}
}
}
private void triggerInboxSyncedWithStatus(
boolean success,
@Nullable InboxSyncedCallback oneTimeCallback) {
if (oneTimeCallback != null) {
addSyncedHandler(oneTimeCallback);
triggerInboxSyncedWithStatus(success);
removeSyncedHandler(oneTimeCallback);
} else {
triggerInboxSyncedWithStatus(success);
}
}
void load() {
if (Constants.isNoop()) {
return;
}
Context context = Leanplum.getContext();
SharedPreferences defaults = context.getSharedPreferences(
"__leanplum__", Context.MODE_PRIVATE);
if (APIConfig.getInstance().token() == null) {
update(new HashMap(), 0, false);
return;
}
int unreadCount = 0;
AESCrypt aesContext = new AESCrypt(APIConfig.getInstance().appId(), APIConfig.getInstance().token());
String newsfeedString = aesContext.decodePreference(
defaults, Constants.Defaults.INBOX_KEY, "{}");
Map newsfeed = JsonConverter.fromJson(newsfeedString);
Map messages = new HashMap<>();
if (newsfeed == null) {
Log.e("Could not parse newsfeed string: " + newsfeedString);
} else {
for (Map.Entry entry : newsfeed.entrySet()) {
String messageId = entry.getKey();
Map data = CollectionUtil.uncheckedCast(entry.getValue());
LeanplumInboxMessage message = LeanplumInboxMessage.createFromJsonMap(messageId, data);
if (message != null && message.isActive()) {
messages.put(messageId, message);
if (!message.isRead()) {
unreadCount++;
}
}
}
}
update(messages, unreadCount, false);
}
void save() {
if (Constants.isNoop()) {
return;
}
if (APIConfig.getInstance().token() == null) {
return;
}
Context context = Leanplum.getContext();
SharedPreferences defaults = context.getSharedPreferences(
"__leanplum__", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = defaults.edit();
Map messages = new HashMap<>();
for (Map.Entry entry : this.messages.entrySet()) {
String messageId = entry.getKey();
LeanplumInboxMessage inboxMessage = entry.getValue();
Map data = inboxMessage.toJsonMap();
messages.put(messageId, data);
}
String messagesJson = JsonConverter.toJson(messages);
AESCrypt aesContext = new AESCrypt(APIConfig.getInstance().appId(), APIConfig.getInstance().token());
editor.putString(Constants.Defaults.INBOX_KEY, aesContext.encrypt(messagesJson));
SharedPreferencesUtil.commitChanges(editor);
}
/**
* Forces downloading of inbox messages from the server. After messages are downloaded the
* appropriate callbacks will fire.
*/
public void downloadMessages() {
downloadMessages(null);
}
/**
* Forces downloading of inbox messages from the server. After messages are downloaded the
* appropriate callbacks will fire.
*
* @param callback The callback to invoke when messages are downloaded.
*/
public void downloadMessages(@Nullable InboxSyncedCallback callback) {
if (Constants.isNoop()) {
return;
}
Request req = RequestBuilder
.withGetInboxMessagesAction()
.andType(RequestType.IMMEDIATE)
.create();
req.onResponse(new Request.ResponseCallback() {
@Override
public void response(JSONObject response) {
try {
if (response == null) {
Log.e("No inbox response received from the server.");
return;
}
JSONObject messagesDict = response.optJSONObject(Constants.Keys.INBOX_MESSAGES);
if (messagesDict == null) {
Log.e("No inbox messages found in the response from the server. %s", response);
return;
}
int unreadCount = 0;
final Map messages = new HashMap<>();
Boolean willDownladImages = false;
for (Iterator iterator = messagesDict.keys(); iterator.hasNext(); ) {
String messageId = (String) iterator.next();
JSONObject messageDict = messagesDict.getJSONObject(messageId);
Map actionArgs = JsonConverter.mapFromJson(
messageDict.getJSONObject(Constants.Keys.MESSAGE_DATA).getJSONObject(Constants.Keys.VARS)
);
Long deliveryTimestamp = messageDict.getLong(Constants.Keys.DELIVERY_TIMESTAMP);
Long expirationTimestamp = null;
if (messageDict.opt(Constants.Keys.EXPIRATION_TIMESTAMP) != null) {
expirationTimestamp = messageDict.getLong(Constants.Keys.EXPIRATION_TIMESTAMP);
}
boolean isRead = messageDict.getBoolean(Constants.Keys.IS_READ);
LeanplumInboxMessage message = LeanplumInboxMessage.constructMessage(messageId,
deliveryTimestamp, expirationTimestamp, isRead, actionArgs);
if (message != null) {
willDownladImages |= message.downloadImageIfPrefetchingEnabled();
if (!isRead) {
unreadCount++;
}
messages.put(messageId, message);
}
}
if (!willDownladImages) {
update(messages, unreadCount, true);
triggerInboxSyncedWithStatus(true, callback);
return;
}
final int totalUnreadCount = unreadCount;
Leanplum.addOnceVariablesChangedAndNoDownloadsPendingHandler(
new VariablesChangedCallback() {
@Override
public void variablesChanged() {
update(messages, totalUnreadCount, true);
triggerInboxSyncedWithStatus(true, callback);
}
});
} catch (Throwable t) {
triggerInboxSyncedWithStatus(false, callback);
Log.exception(t);
}
}
});
req.onError(new Request.ErrorCallback() {
@Override
public void error(Exception e) {
triggerInboxSyncedWithStatus(false, callback);
}
});
RequestSender.getInstance().send(req);
}
/**
* Returns a List containing all of the inbox messages sorted chronologically ascending (i.e.
* the oldest first and the newest last).
*/
private List allMessages(List messages) {
if (messages == null) {
messages = new ArrayList<>();
}
try {
for (String messageId : messagesIds()) {
LeanplumInboxMessage message = messageForId(messageId);
if (message != null) {
messages.add(message);
}
}
} catch (Throwable t) {
Log.exception(t);
}
return messages;
}
/**
* Returns a List containing all of the unread inbox messages sorted chronologically ascending
* (i.e. the oldest first and the newest last).
*/
private List unreadMessages(List unreadMessages) {
if (unreadMessages == null) {
unreadMessages = new ArrayList<>();
}
List messages = allMessages(null);
for (LeanplumInboxMessage message : messages) {
if (!message.isRead()) {
unreadMessages.add(message);
}
}
return unreadMessages;
}
}