/* * Copyright (c) 2015-present, Parse, LLC. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.parse; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; import android.util.Log; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.net.MalformedURLException; import java.net.URL; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import bolts.Continuation; import bolts.Task; import okhttp3.OkHttpClient; /** * The {@code Parse} class contains static functions that handle global configuration for the Parse * library. */ public class Parse { private static final String TAG = "com.parse.Parse"; private static final int DEFAULT_MAX_RETRIES = ParseRequest.DEFAULT_MAX_RETRIES; /** * Represents an opaque configuration for the {@code Parse} SDK configuration. */ public static final class Configuration { /** * Allows for simple constructing of a {@code Configuration} object. */ public static final class Builder { private Context context; private String applicationId; private String clientKey; private String server; private boolean localDataStoreEnabled; private OkHttpClient.Builder clientBuilder; private int maxRetries = DEFAULT_MAX_RETRIES; /** * Initialize a bulider with a given context. *
* This context will then be passed through to the rest of the Parse SDK for use during * initialization. *
*
* You may define {@code com.parse.SERVER_URL}, {@code com.parse.APPLICATION_ID} and (optional) {@code com.parse.CLIENT_KEY} * {@code meta-data} in your {@code AndroidManifest.xml}: ** <manifest ...> * * ... * * <application ...> * <meta-data * android:name="com.parse.SERVER_URL" * android:value="@string/parse_server_url" /> * <meta-data * android:name="com.parse.APPLICATION_ID" * android:value="@string/parse_app_id" /> * <meta-data * android:name="com.parse.CLIENT_KEY" * android:value="@string/parse_client_key" /> * * ... * * </application> * </manifest> ** *
* This will cause the values for {@code server}, {@code applicationId} and {@code clientKey} to be set to * those defined in your manifest. * * @param context The active {@link Context} for your application. Cannot be null. */ public Builder(Context context) { this.context = context; // Yes, our public API states we cannot be null. But for unit tests, it's easier just to // support null here. if (context != null) { Context applicationContext = context.getApplicationContext(); Bundle metaData = ManifestInfo.getApplicationMetadata(applicationContext); if (metaData != null) { server(metaData.getString(PARSE_SERVER_URL)); applicationId = metaData.getString(PARSE_APPLICATION_ID); clientKey = metaData.getString(PARSE_CLIENT_KEY); } } } /** * Set the application id to be used by Parse. *
* This method is only required if you intend to use a different {@code applicationId} than * is defined by {@code com.parse.APPLICATION_ID} in your {@code AndroidManifest.xml}. * * @param applicationId The application id to set. * @return The same builder, for easy chaining. */ public Builder applicationId(String applicationId) { this.applicationId = applicationId; return this; } /** * Set the client key to be used by Parse. *
* This method is only required if you intend to use a different {@code clientKey} than * is defined by {@code com.parse.CLIENT_KEY} in your {@code AndroidManifest.xml}. * * @param clientKey The client key to set. * @return The same builder, for easy chaining. */ public Builder clientKey(String clientKey) { this.clientKey = clientKey; return this; } /** * Set the server URL to be used by Parse. * * @param server The server URL to set. * @return The same builder, for easy chaining. */ public Builder server(String server) { // Add an extra trailing slash so that Parse REST commands include // the path as part of the server URL (i.e. http://api.myhost.com/parse) if (server != null && !server.endsWith("/")) { server = server + "/"; } this.server = server; return this; } /** * Enable pinning in your application. This must be called before your application can use * pinning. * * @return The same builder, for easy chaining. */ public Builder enableLocalDataStore() { localDataStoreEnabled = true; return this; } private Builder setLocalDatastoreEnabled(boolean enabled) { localDataStoreEnabled = enabled; return this; } /** * Set the {@link okhttp3.OkHttpClient.Builder} to use when communicating with the Parse * REST API *
* * @param builder The client builder, which will be modified for compatibility * @return The same builder, for easy chaining. */ public Builder clientBuilder(OkHttpClient.Builder builder) { clientBuilder = builder; return this; } /** * Set the max number of times to retry Parse operations before deeming them a failure *
* * @param maxRetries The maximum number of times to retry. <=0 to never retry commands * @return The same builder, for easy chaining. */ public Builder maxRetries(int maxRetries) { this.maxRetries = maxRetries; return this; } /** * Construct this builder into a concrete {@code Configuration} instance. * * @return A constructed {@code Configuration} object. */ public Configuration build() { return new Configuration(this); } } final Context context; final String applicationId; final String clientKey; final String server; final boolean localDataStoreEnabled; final OkHttpClient.Builder clientBuilder; final int maxRetries; private Configuration(Builder builder) { this.context = builder.context; this.applicationId = builder.applicationId; this.clientKey = builder.clientKey; this.server = builder.server; this.localDataStoreEnabled = builder.localDataStoreEnabled; this.clientBuilder = builder.clientBuilder; this.maxRetries = builder.maxRetries; } } private static final String PARSE_SERVER_URL = "com.parse.SERVER_URL"; private static final String PARSE_APPLICATION_ID = "com.parse.APPLICATION_ID"; private static final String PARSE_CLIENT_KEY = "com.parse.CLIENT_KEY"; private static final Object MUTEX = new Object(); static ParseEventuallyQueue 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() { * Parse.enableLocalDatastore(this); * Parse.initialize(this); * } * } ** * @param context The active {@link Context} for your application. */ public static void enableLocalDatastore(Context context) { if (isInitialized()) { throw new IllegalStateException("`Parse#enableLocalDatastore(Context)` must be invoked " + "before `Parse#initialize(Context)`"); } isLocalDatastoreEnabled = true; } static void disableLocalDatastore() { setLocalDatastore(null); // We need to re-register ParseCurrentInstallationController otherwise it is still offline // controller ParseCorePlugins.getInstance().reset(); } static OfflineStore getLocalDatastore() { return offlineStore; } static void setLocalDatastore(OfflineStore offlineStore) { Parse.isLocalDatastoreEnabled = offlineStore != null; Parse.offlineStore = offlineStore; } public static boolean isLocalDatastoreEnabled() { return isLocalDatastoreEnabled; } //endregion /** * Authenticates this client as belonging to your application. * * You may define {@code com.parse.SERVER_URL}, {@code com.parse.APPLICATION_ID} and (optional) {@code com.parse.CLIENT_KEY} * {@code meta-data} in your {@code AndroidManifest.xml}: *
* <manifest ...> * * ... * * <application ...> * <meta-data * android:name="com.parse.SERVER_URL" * android:value="@string/parse_server_url" /> * <meta-data * android:name="com.parse.APPLICATION_ID" * android:value="@string/parse_app_id" /> * <meta-data * android:name="com.parse.CLIENT_KEY" * android:value="@string/parse_client_key" /> * * ... * * </application> * </manifest> ** * This must be called before your application can use the Parse library. * The recommended way is to put a call to {@code Parse.initialize} * in your {@code Application}'s {@code onCreate} method: * *
* public class MyApplication extends Application { * public void onCreate() { * Parse.initialize(this); * } * } ** * @param context The active {@link Context} for your application. */ public static void initialize(Context context) { Configuration.Builder builder = new Configuration.Builder(context); if (builder.server == null) { throw new RuntimeException("ServerUrl not defined. " + "You must provide ServerUrl in AndroidManifest.xml.\n" + "
* public class MyApplication extends Application { * public void onCreate() { * Parse.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 Parse dashboard. * @param clientKey The client key provided in the Parse dashboard. */ public static void initialize(Context context, String applicationId, String clientKey) { initialize(new Configuration.Builder(context) .applicationId(applicationId) .clientKey(clientKey) .setLocalDatastoreEnabled(isLocalDatastoreEnabled) .build() ); } public static void initialize(Configuration configuration) { if (isInitialized()) { PLog.w(TAG, "Parse is already initialized"); return; } // NOTE (richardross): We will need this here, as ParsePlugins uses the return value of // isLocalDataStoreEnabled() to perform additional behavior. isLocalDatastoreEnabled = configuration.localDataStoreEnabled; ParsePlugins.initialize(configuration.context, configuration); try { ParseRESTCommand.server = new URL(configuration.server); } catch (MalformedURLException ex) { throw new RuntimeException(ex); } ParseObject.registerParseSubclasses(); if (configuration.localDataStoreEnabled) { offlineStore = new OfflineStore(configuration.context); } else { ParseKeyValueCache.initialize(configuration.context); } // Make sure the data on disk for Parse is for the current // application. checkCacheApplicationId(); final Context context = configuration.context; Task.callInBackground(new Callable
* Throws {@link java.lang.IllegalStateException} if called after {@link #initialize}. * * @param listener the listener to register */ static void registerParseCallbacks(ParseCallbacks listener) { if (isInitialized()) { throw new IllegalStateException( "You must register callbacks before Parse.initialize(Context)"); } synchronized (MUTEX_CALLBACKS) { if (callbacks == null) { return; } callbacks.add(listener); } } /** * Unregisters a listener previously registered with {@link #registerParseCallbacks}. * * @param listener the listener to register */ static void unregisterParseCallbacks(ParseCallbacks listener) { synchronized (MUTEX_CALLBACKS) { if (callbacks == null) { return; } callbacks.remove(listener); } } private static void dispatchOnParseInitialized() { ParseCallbacks[] callbacks = collectParseCallbacks(); if (callbacks != null) { for (ParseCallbacks callback : callbacks) { callback.onParseInitialized(); } } } private static ParseCallbacks[] collectParseCallbacks() { ParseCallbacks[] callbacks; synchronized (MUTEX_CALLBACKS) { if (Parse.callbacks == null) { return null; } callbacks = new ParseCallbacks[Parse.callbacks.size()]; if (Parse.callbacks.size() > 0) { callbacks = Parse.callbacks.toArray(callbacks); } } return callbacks; } interface ParseCallbacks { void onParseInitialized(); } //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: *