All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.leanplum.LeanplumInbox Maven / Gradle / Ivy

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;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy