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

co.easimart.Easimart Maven / Gradle / Ivy

package co.easimart;

import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.util.Log;

import co.easimart.http.EasimartNetworkInterceptor;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import bolts.Continuation;
import bolts.Task;

/**
 * The {@code Easimart} class contains static functions that handle global configuration for the Easimart
 * library.
 */
public class Easimart {
  private static final String EASIMART_APPLICATION_ID = "co.easimart.APPLICATION_ID";
  private static final String EASIMART_CLIENT_KEY = "co.easimart.CLIENT_KEY";

  private static final Object MUTEX = new Object();
  /* package */ static EasimartEventuallyQueue eventuallyQueue = null;

  //region LDS

  private static boolean isLocalDatastoreEnabled;
  private static OfflineStore offlineStore;

  /**
   * Enable pinning in your application. This must be called before your application can use
   * pinning. You must invoke {@code enableLocalDatastore(Context)} before
   * {@link #initialize(Context)} :
   * 

*

   * public class MyApplication extends Application {
   *   public void onCreate() {
   *     Easimart.enableLocalDatastore(this);
   *     Easimart.initialize(this);
   *   }
   * }
   * 
* * @param context * The active {@link Context} for your application. */ public static void enableLocalDatastore(Context context) { if (isInitialized()) { throw new IllegalStateException("`Easimart#enableLocalDatastore(Context)` must be invoked " + "before `Easimart#initialize(Context)`"); } isLocalDatastoreEnabled = true; } /* package for tests */ static void disableLocalDatastore() { setLocalDatastore(null); // We need to re-register EasimartCurrentInstallationController otherwise it is still offline // controller EasimartCorePlugins.getInstance().reset(); } /* package */ static OfflineStore getLocalDatastore() { return offlineStore; } /* package for tests */ static void setLocalDatastore(OfflineStore offlineStore) { Easimart.isLocalDatastoreEnabled = offlineStore != null; Easimart.offlineStore = offlineStore; } /* package */ static boolean isLocalDatastoreEnabled() { return isLocalDatastoreEnabled; } //endregion /** * Authenticates this client as belonging to your application. *

* You must define {@code co.easimart.APPLICATION_ID} and {@code co.easimart.CLIENT_KEY} * {@code meta-data} in your {@code AndroidManifest.xml}: *

   * <manifest ...>
   *
   * ...
   *
   *   <application ...>
   *     <meta-data
   *       android:name="co.easimart.APPLICATION_ID"
   *       android:value="@string/parse_app_id" />
   *     <meta-data
   *       android:name="co.easimart.CLIENT_KEY"
   *       android:value="@string/parse_client_key" />
   *
   *       ...
   *
   *   </application>
   * </manifest>
   * 
*

* This must be called before your application can use the Easimart library. * The recommended way is to put a call to {@code Easimart.initialize} * in your {@code Application}'s {@code onCreate} method: *

*

   * public class MyApplication extends Application {
   *   public void onCreate() {
   *     Easimart.initialize(this);
   *   }
   * }
   * 
* * @param context * The active {@link Context} for your application. */ public static void initialize(Context context) { Context applicationContext = context.getApplicationContext(); String applicationId; String clientKey; Bundle metaData = ManifestInfo.getApplicationMetadata(applicationContext); if (metaData != null) { applicationId = metaData.getString(EASIMART_APPLICATION_ID); clientKey = metaData.getString(EASIMART_CLIENT_KEY); if (applicationId == null) { throw new RuntimeException("ApplicationId not defined. " + "You must provide ApplicationId in AndroidManifest.xml.\n" + "\" />"); } if (clientKey == null) { throw new RuntimeException("ClientKey not defined. " + "You must provide ClientKey in AndroidManifest.xml.\n" + "\" />"); } } else { throw new RuntimeException("Can't get Application Metadata"); } initialize(context, applicationId, clientKey); } /** * Authenticates this client as belonging to your application. *

* This method is only required if you intend to use a different {@code applicationId} or * {@code clientKey} than is defined by {@code co.easimart.APPLICATION_ID} or * {@code co.easimart.CLIENT_KEY} in your {@code AndroidManifest.xml}. *

* This must be called before your * application can use the Easimart library. The recommended way is to put a call to * {@code Easimart.initialize} in your {@code Application}'s {@code onCreate} method: *

*

   * public class MyApplication extends Application {
   *   public void onCreate() {
   *     Easimart.initialize(this, "your application id", "your client key");
   *   }
   * }
   * 
* * @param context * The active {@link Context} for your application. * @param applicationId * The application id provided in the Easimart dashboard. * @param clientKey * The client key provided in the Easimart dashboard. */ public static void initialize(Context context, String applicationId, String clientKey) { EasimartPlugins.Android.initialize(context, applicationId, clientKey); Context applicationContext = context.getApplicationContext(); EasimartHttpClient.setKeepAlive(true); EasimartHttpClient.setMaxConnections(20); // If we have interceptors in list, we have to initialize all http clients and add interceptors if (interceptors != null) { initializeEasimartHttpClientsWithEasimartNetworkInterceptors(); } EasimartObject.registerEasimartSubclasses(); if (isLocalDatastoreEnabled()) { offlineStore = new OfflineStore(context); } else { EasimartKeyValueCache.initialize(context); } // Make sure the data on disk for Easimart is for the current // application. checkCacheApplicationId(); new Thread("Easimart.initialize Disk Check & Starting Command Cache") { @Override public void run() { // Trigger the command cache to flush its contents. getEventuallyQueue(); } }.start(); EasimartFieldOperations.registerDefaultDecoders(); if (!allEasimartPushIntentReceiversInternal()) { throw new SecurityException("To prevent external tampering to your app's notifications, " + "all receivers registered to handle the following actions must have " + "their exported attributes set to false: co.easimart.push.intent.RECEIVE, "+ "co.easimart.push.intent.OPEN, co.easimart.push.intent.DELETE"); } // May need to update GCM registration ID if app version has changed. // This also primes current installation. GcmRegistrar.getInstance().registerAsync().continueWithTask(new Continuation>() { @Override public Task then(Task task) throws Exception { // Prime current user in the background return EasimartUser.getCurrentUserAsync().makeVoid(); } }).continueWith(new Continuation() { @Override public Void then(Task task) throws Exception { // Prime config in the background EasimartConfig.getCurrentConfig(); return null; } }, Task.BACKGROUND_EXECUTOR); if (ManifestInfo.getPushType() == PushType.PPNS) { PushService.startServiceIfRequired(applicationContext); } dispatchOnEasimartInitialized(); // FYI we probably don't want to do this if we ever add other callbacks. synchronized (MUTEX_CALLBACKS) { Easimart.callbacks = null; } } /* package */ static void destroy() { EasimartEventuallyQueue queue; synchronized (MUTEX) { queue = eventuallyQueue; eventuallyQueue = null; } if (queue != null) { queue.onDestroy(); } EasimartCorePlugins.getInstance().reset(); EasimartPlugins.reset(); } /** * @return {@code True} if {@link #initialize} has been called, otherwise {@code false}. */ /* package */ static boolean isInitialized() { return EasimartPlugins.get() != null; } static Context getApplicationContext() { checkContext(); return EasimartPlugins.Android.get().applicationContext(); } /** * Checks that each of the receivers associated with the three actions defined in * EasimartPushBroadcastReceiver (ACTION_PUSH_RECEIVE, ACTION_PUSH_OPEN, ACTION_PUSH_DELETE) has * their exported attributes set to false. If this is the case for each of the receivers * registered in the AndroidManifest.xml or if no receivers are registered (because we will be registering * the default implementation of EasimartPushBroadcastReceiver in PushService) then true is returned. * Note: the reason for iterating through lists, is because you can define different receivers * in the manifest that respond to the same intents and both all of the receivers will be triggered. * So we want to make sure all them have the exported attribute set to false. */ private static boolean allEasimartPushIntentReceiversInternal() { List intentReceivers = ManifestInfo.getIntentReceivers( EasimartPushBroadcastReceiver.ACTION_PUSH_RECEIVE, EasimartPushBroadcastReceiver.ACTION_PUSH_DELETE, EasimartPushBroadcastReceiver.ACTION_PUSH_OPEN); for (ResolveInfo resolveInfo : intentReceivers) { if (resolveInfo.activityInfo.exported) { return false; } } return true; } /** * @deprecated Please use {@link #getEasimartCacheDir(String)} or {@link #getEasimartFilesDir(String)} * instead. */ @Deprecated /* package */ static File getEasimartDir() { return EasimartPlugins.get().getEasimartDir(); } /* package */ static File getEasimartCacheDir() { return EasimartPlugins.get().getCacheDir(); } /* package */ static File getEasimartCacheDir(String subDir) { synchronized (MUTEX) { File dir = new File(getEasimartCacheDir(), subDir); if (!dir.exists()) { dir.mkdirs(); } return dir; } } /* package */ static File getEasimartFilesDir() { return EasimartPlugins.get().getFilesDir(); } /* package */ static File getEasimartFilesDir(String subDir) { synchronized (MUTEX) { File dir = new File(getEasimartFilesDir(), subDir); if (!dir.exists()) { dir.mkdirs(); } return dir; } } /** * Verifies that the data stored on disk for Easimart was generated using the same application that * is running now. */ static void checkCacheApplicationId() { synchronized (MUTEX) { String applicationId = EasimartPlugins.get().applicationId(); if (applicationId != null) { File dir = Easimart.getEasimartCacheDir(); // Make sure the current version of the cache is for this application id. File applicationIdFile = new File(dir, "applicationId"); if (applicationIdFile.exists()) { // Read the file boolean matches = false; try { RandomAccessFile f = new RandomAccessFile(applicationIdFile, "r"); byte[] bytes = new byte[(int) f.length()]; f.readFully(bytes); f.close(); String diskApplicationId = new String(bytes, "UTF-8"); matches = diskApplicationId.equals(applicationId); } catch (FileNotFoundException e) { // Well, it existed a minute ago. Let's assume it doesn't match. } catch (IOException e) { // Hmm, the applicationId file was malformed or something. Assume it // doesn't match. } // The application id has changed, so everything on disk is invalid. if (!matches) { try { EasimartFileUtils.deleteDirectory(dir); } catch (IOException e) { // We're unable to delete the directy... } } } // Create the version file if needed. applicationIdFile = new File(dir, "applicationId"); try { FileOutputStream out = new FileOutputStream(applicationIdFile); out.write(applicationId.getBytes("UTF-8")); out.close(); } catch (FileNotFoundException e) { // Nothing we can really do about it. } catch (UnsupportedEncodingException e) { // Nothing we can really do about it. This would mean Java doesn't // understand UTF-8, which is unlikely. } catch (IOException e) { // Nothing we can really do about it. } } } } /** * Gets the shared command cache object for all EasimartObjects. This command cache is used to * locally store save commands created by the EasimartObject.saveEventually(). When a new * EasimartCommandCache is instantiated, it will begin running its run loop, which will start by * processing any commands already stored in the on-disk queue. */ /* package */ static EasimartEventuallyQueue getEventuallyQueue() { Context context = EasimartPlugins.Android.get().applicationContext(); synchronized (MUTEX) { boolean isLocalDatastoreEnabled = Easimart.isLocalDatastoreEnabled(); if (eventuallyQueue == null || (isLocalDatastoreEnabled && eventuallyQueue instanceof EasimartCommandCache) || (!isLocalDatastoreEnabled && eventuallyQueue instanceof EasimartPinningEventuallyQueue)) { checkContext(); EasimartHttpClient httpClient = EasimartPlugins.get().restClient(); eventuallyQueue = isLocalDatastoreEnabled ? new EasimartPinningEventuallyQueue(context, httpClient) : new EasimartCommandCache(context, httpClient); // We still need to clear out the old command cache even if we're using Pinning in case // anything is left over when the user upgraded. Checking number of pending and then // initializing should be enough. if (isLocalDatastoreEnabled && EasimartCommandCache.getPendingCount() > 0) { new EasimartCommandCache(context, httpClient); } } return eventuallyQueue; } } static void checkInit() { if (EasimartPlugins.get() == null) { throw new RuntimeException("You must call Easimart.initialize(Context)" + " before using the Easimart library."); } if (EasimartPlugins.get().applicationId() == null) { throw new RuntimeException("applicationId is null. " + "You must call Easimart.initialize(Context)" + " before using the Easimart library."); } if (EasimartPlugins.get().clientKey() == null) { throw new RuntimeException("clientKey is null. " + "You must call Easimart.initialize(Context)" + " before using the Easimart library."); } } static void checkContext() { if (EasimartPlugins.Android.get().applicationContext() == null) { throw new RuntimeException("applicationContext is null. " + "You must call Easimart.initialize(Context)" + " before using the Easimart library."); } } static boolean hasPermission(String permission) { return (getApplicationContext().checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED); } static void requirePermission(String permission) { if (!hasPermission(permission)) { throw new IllegalStateException( "To use this functionality, add this to your AndroidManifest.xml:\n" + ""); } } //region EasimartCallbacks private static final Object MUTEX_CALLBACKS = new Object(); private static Set callbacks = new HashSet<>(); /** * Registers a listener to be called at the completion of {@link #initialize}. *

* Throws {@link java.lang.IllegalStateException} if called after {@link #initialize}. * * @param listener the listener to register */ /* package */ static void registerEasimartCallbacks(EasimartCallbacks listener) { if (isInitialized()) { throw new IllegalStateException( "You must register callbacks before Easimart.initialize(Context)"); } synchronized (MUTEX_CALLBACKS) { if (callbacks == null) { return; } callbacks.add(listener); } } /** * Unregisters a listener previously registered with {@link #registerEasimartCallbacks}. * * @param listener the listener to register */ /* package */ static void unregisterEasimartCallbacks(EasimartCallbacks listener) { synchronized (MUTEX_CALLBACKS) { if (callbacks == null) { return; } callbacks.remove(listener); } } private static void dispatchOnEasimartInitialized() { EasimartCallbacks[] callbacks = collectEasimartCallbacks(); if (callbacks != null) { for (EasimartCallbacks callback : callbacks) { callback.onEasimartInitialized(); } } } private static EasimartCallbacks[] collectEasimartCallbacks() { EasimartCallbacks[] callbacks; synchronized (MUTEX_CALLBACKS) { if (Easimart.callbacks == null) { return null; } callbacks = new EasimartCallbacks[Easimart.callbacks.size()]; if (Easimart.callbacks.size() > 0) { callbacks = Easimart.callbacks.toArray(callbacks); } } return callbacks; } /* package */ interface EasimartCallbacks { public void onEasimartInitialized(); } //endregion //region Logging public static final int LOG_LEVEL_VERBOSE = Log.VERBOSE; public static final int LOG_LEVEL_DEBUG = Log.DEBUG; public static final int LOG_LEVEL_INFO = Log.INFO; public static final int LOG_LEVEL_WARNING = Log.WARN; public static final int LOG_LEVEL_ERROR = Log.ERROR; public static final int LOG_LEVEL_NONE = Integer.MAX_VALUE; /** * Sets the level of logging to display, where each level includes all those below it. The default * level is {@link #LOG_LEVEL_NONE}. Please ensure this is set to {@link #LOG_LEVEL_ERROR} * or {@link #LOG_LEVEL_NONE} before deploying your app to ensure no sensitive information is * logged. The levels are: *

    *
  • {@link #LOG_LEVEL_VERBOSE}
  • *
  • {@link #LOG_LEVEL_DEBUG}
  • *
  • {@link #LOG_LEVEL_INFO}
  • *
  • {@link #LOG_LEVEL_WARNING}
  • *
  • {@link #LOG_LEVEL_ERROR}
  • *
  • {@link #LOG_LEVEL_NONE}
  • *
* * @param logLevel * The level of logcat logging that Easimart should do. */ public static void setLogLevel(int logLevel) { EasimartLog.setLogLevel(logLevel); } /** * Returns the level of logging that will be displayed. */ public static int getLogLevel() { return EasimartLog.getLogLevel(); } //endregion // Suppress constructor to prevent subclassing private Easimart() { throw new AssertionError(); } private static List interceptors; // Initialize all necessary http clients and add interceptors to these http clients private static void initializeEasimartHttpClientsWithEasimartNetworkInterceptors() { // This means developers have not called addInterceptor method so we should do nothing. if (interceptors == null) { return; } List clients = new ArrayList<>(); // Rest http client clients.add(EasimartPlugins.get().restClient()); // AWS http client clients.add(EasimartCorePlugins.getInstance().getFileController().awsClient()); // Add interceptors to http clients for (EasimartHttpClient easimartHttpClient : clients) { // We need to add the decompress interceptor before the external interceptors to return // a decompressed response to Easimart. easimartHttpClient.addInternalInterceptor(new EasimartDecompressInterceptor()); for (EasimartNetworkInterceptor interceptor : interceptors) { easimartHttpClient.addExternalInterceptor(interceptor); } } // Remove interceptors reference since we do not need it anymore interceptors = null; } /** * Add a {@link EasimartNetworkInterceptor}. You must invoke * {@code addEasimartNetworkInterceptor(EasimartNetworkInterceptor)} before * {@link #initialize(Context)}. You can add multiple {@link EasimartNetworkInterceptor}. * * @param interceptor * {@link EasimartNetworkInterceptor} to be added. */ public static void addEasimartNetworkInterceptor(EasimartNetworkInterceptor interceptor) { if (isInitialized()) { throw new IllegalStateException("`Easimart#addEasimartNetworkInterceptor(EasimartNetworkInterceptor)`" + " must be invoked before `Easimart#initialize(Context)`"); } if (interceptors == null) { interceptors = new ArrayList<>(); } interceptors.add(interceptor); } /** * Remove a given {@link EasimartNetworkInterceptor}. You must invoke * {@code removeEasimartNetworkInterceptor(EasimartNetworkInterceptor)} before * {@link #initialize(Context)}. * * @param interceptor * {@link EasimartNetworkInterceptor} to be removed. */ public static void removeEasimartNetworkInterceptor(EasimartNetworkInterceptor interceptor) { if (isInitialized()) { throw new IllegalStateException("`Easimart#addEasimartNetworkInterceptor(EasimartNetworkInterceptor)`" + " must be invoked before `Easimart#initialize(Context)`"); } if (interceptors == null) { return; } interceptors.remove(interceptor); } /* package */ static String externalVersionName() { return "a" + EasimartObject.VERSION_NAME; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy