771 lines
25 KiB
Java
771 lines
25 KiB
Java
/*
|
|
* 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.
|
|
* <p>
|
|
* This context will then be passed through to the rest of the Parse SDK for use during
|
|
* initialization.
|
|
* <p>
|
|
* <p/>
|
|
* 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}:
|
|
* <pre>
|
|
* <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>
|
|
* </pre>
|
|
* <p/>
|
|
* <p>
|
|
* 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.
|
|
* <p>
|
|
* 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.
|
|
* <p>
|
|
* 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
|
|
* <p>
|
|
*
|
|
* @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
|
|
* <p>
|
|
*
|
|
* @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)} :
|
|
* <p/>
|
|
* <pre>
|
|
* public class MyApplication extends Application {
|
|
* public void onCreate() {
|
|
* Parse.enableLocalDatastore(this);
|
|
* Parse.initialize(this);
|
|
* }
|
|
* }
|
|
* </pre>
|
|
*
|
|
* @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.
|
|
* <p/>
|
|
* 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}:
|
|
* <pre>
|
|
* <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>
|
|
* </pre>
|
|
* <p/>
|
|
* 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:
|
|
* <p/>
|
|
* <pre>
|
|
* public class MyApplication extends Application {
|
|
* public void onCreate() {
|
|
* Parse.initialize(this);
|
|
* }
|
|
* }
|
|
* </pre>
|
|
*
|
|
* @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" +
|
|
"<meta-data\n" +
|
|
" android:name=\"com.parse.SERVER_URL\"\n" +
|
|
" android:value=\"<Your Server Url>\" />");
|
|
}
|
|
if (builder.applicationId == null) {
|
|
throw new RuntimeException("ApplicationId not defined. " +
|
|
"You must provide ApplicationId in AndroidManifest.xml.\n" +
|
|
"<meta-data\n" +
|
|
" android:name=\"com.parse.APPLICATION_ID\"\n" +
|
|
" android:value=\"<Your Application Id>\" />");
|
|
}
|
|
initialize(builder
|
|
.setLocalDatastoreEnabled(isLocalDatastoreEnabled)
|
|
.build()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Authenticates this client as belonging to your application.
|
|
* <p/>
|
|
* This method is only required if you intend to use a different {@code applicationId} or
|
|
* {@code clientKey} than is defined by {@code com.parse.APPLICATION_ID} or
|
|
* {@code com.parse.CLIENT_KEY} in your {@code AndroidManifest.xml}.
|
|
* <p/>
|
|
* 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:
|
|
* <p/>
|
|
* <pre>
|
|
* public class MyApplication extends Application {
|
|
* public void onCreate() {
|
|
* Parse.initialize(this, "your application id", "your client key");
|
|
* }
|
|
* }
|
|
* </pre>
|
|
*
|
|
* @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<Void>() {
|
|
@Override
|
|
public Void call() throws Exception {
|
|
getEventuallyQueue(context);
|
|
return null;
|
|
}
|
|
});
|
|
|
|
ParseFieldOperations.registerDefaultDecoders();
|
|
|
|
if (!allParsePushIntentReceiversInternal()) {
|
|
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: com.parse.push.intent.RECEIVE, " +
|
|
"com.parse.push.intent.OPEN, com.parse.push.intent.DELETE");
|
|
}
|
|
|
|
// May need to update GCM registration ID if app version has changed.
|
|
// This also primes current installation.
|
|
PushServiceUtils.initialize().continueWithTask(new Continuation<Void, Task<Void>>() {
|
|
@Override
|
|
public Task<Void> then(Task<Void> task) throws Exception {
|
|
// Prime current user in the background
|
|
return ParseUser.getCurrentUserAsync().makeVoid();
|
|
}
|
|
}).continueWith(new Continuation<Void, Void>() {
|
|
@Override
|
|
public Void then(Task<Void> task) throws Exception {
|
|
// Prime config in the background
|
|
ParseConfig.getCurrentConfig();
|
|
return null;
|
|
}
|
|
}, Task.BACKGROUND_EXECUTOR);
|
|
|
|
dispatchOnParseInitialized();
|
|
|
|
// FYI we probably don't want to do this if we ever add other callbacks.
|
|
synchronized (MUTEX_CALLBACKS) {
|
|
Parse.callbacks = null;
|
|
}
|
|
}
|
|
|
|
static void destroy() {
|
|
ParseEventuallyQueue queue;
|
|
synchronized (MUTEX) {
|
|
queue = eventuallyQueue;
|
|
eventuallyQueue = null;
|
|
}
|
|
if (queue != null) {
|
|
queue.onDestroy();
|
|
}
|
|
|
|
ParseCorePlugins.getInstance().reset();
|
|
ParsePlugins.reset();
|
|
}
|
|
|
|
/**
|
|
* @return {@code True} if {@link #initialize} has been called, otherwise {@code false}.
|
|
*/
|
|
static boolean isInitialized() {
|
|
return ParsePlugins.get() != null;
|
|
}
|
|
|
|
static Context getApplicationContext() {
|
|
checkContext();
|
|
return ParsePlugins.get().applicationContext();
|
|
}
|
|
|
|
/**
|
|
* Checks that each of the receivers associated with the three actions defined in
|
|
* ParsePushBroadcastReceiver (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 ParsePushBroadcastReceiver 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 allParsePushIntentReceiversInternal() {
|
|
List<ResolveInfo> intentReceivers = ManifestInfo.getIntentReceivers(
|
|
ParsePushBroadcastReceiver.ACTION_PUSH_RECEIVE,
|
|
ParsePushBroadcastReceiver.ACTION_PUSH_DELETE,
|
|
ParsePushBroadcastReceiver.ACTION_PUSH_OPEN);
|
|
|
|
for (ResolveInfo resolveInfo : intentReceivers) {
|
|
if (resolveInfo.activityInfo.exported) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @deprecated Please use {@link #getParseCacheDir(String)} or {@link #getParseFilesDir(String)}
|
|
* instead.
|
|
*/
|
|
@Deprecated
|
|
static File getParseDir() {
|
|
return ParsePlugins.get().getParseDir();
|
|
}
|
|
|
|
static File getParseCacheDir() {
|
|
return ParsePlugins.get().getCacheDir();
|
|
}
|
|
|
|
static File getParseCacheDir(String subDir) {
|
|
synchronized (MUTEX) {
|
|
File dir = new File(getParseCacheDir(), subDir);
|
|
if (!dir.exists()) {
|
|
dir.mkdirs();
|
|
}
|
|
return dir;
|
|
}
|
|
}
|
|
|
|
static File getParseFilesDir() {
|
|
return ParsePlugins.get().getFilesDir();
|
|
}
|
|
|
|
static File getParseFilesDir(String subDir) {
|
|
synchronized (MUTEX) {
|
|
File dir = new File(getParseFilesDir(), subDir);
|
|
if (!dir.exists()) {
|
|
dir.mkdirs();
|
|
}
|
|
return dir;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifies that the data stored on disk for Parse was generated using the same application that
|
|
* is running now.
|
|
*/
|
|
static void checkCacheApplicationId() {
|
|
synchronized (MUTEX) {
|
|
String applicationId = ParsePlugins.get().applicationId();
|
|
if (applicationId != null) {
|
|
File dir = Parse.getParseCacheDir();
|
|
|
|
// 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 (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 {
|
|
ParseFileUtils.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 (IOException e) {
|
|
// Nothing we can really do about it.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the shared command cache object for all ParseObjects. This command cache is used to
|
|
* locally store save commands created by the ParseObject.saveEventually(). When a new
|
|
* ParseCommandCache is instantiated, it will begin running its run loop, which will start by
|
|
* processing any commands already stored in the on-disk queue.
|
|
*/
|
|
static ParseEventuallyQueue getEventuallyQueue() {
|
|
Context context = ParsePlugins.get().applicationContext();
|
|
return getEventuallyQueue(context);
|
|
}
|
|
|
|
private static ParseEventuallyQueue getEventuallyQueue(Context context) {
|
|
synchronized (MUTEX) {
|
|
boolean isLocalDatastoreEnabled = Parse.isLocalDatastoreEnabled();
|
|
if (eventuallyQueue == null
|
|
|| (isLocalDatastoreEnabled && eventuallyQueue instanceof ParseCommandCache)
|
|
|| (!isLocalDatastoreEnabled && eventuallyQueue instanceof ParsePinningEventuallyQueue)) {
|
|
checkContext();
|
|
ParseHttpClient httpClient = ParsePlugins.get().restClient();
|
|
eventuallyQueue = isLocalDatastoreEnabled
|
|
? new ParsePinningEventuallyQueue(context, httpClient)
|
|
: new ParseCommandCache(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 && ParseCommandCache.getPendingCount() > 0) {
|
|
new ParseCommandCache(context, httpClient);
|
|
}
|
|
}
|
|
return eventuallyQueue;
|
|
}
|
|
}
|
|
|
|
static void checkInit() {
|
|
if (ParsePlugins.get() == null) {
|
|
throw new RuntimeException("You must call Parse.initialize(Context)"
|
|
+ " before using the Parse library.");
|
|
}
|
|
|
|
if (ParsePlugins.get().applicationId() == null) {
|
|
throw new RuntimeException("applicationId is null. "
|
|
+ "You must call Parse.initialize(Context)"
|
|
+ " before using the Parse library.");
|
|
}
|
|
}
|
|
|
|
static void checkContext() {
|
|
if (ParsePlugins.get().applicationContext() == null) {
|
|
throw new RuntimeException("applicationContext is null. "
|
|
+ "You must call Parse.initialize(Context)"
|
|
+ " before using the Parse 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"
|
|
+ "<uses-permission android:name=\"" + permission + "\" />");
|
|
}
|
|
}
|
|
|
|
//region ParseCallbacks
|
|
private static final Object MUTEX_CALLBACKS = new Object();
|
|
private static Set<ParseCallbacks> callbacks = new HashSet<>();
|
|
|
|
/**
|
|
* Registers a listener to be called at the completion of {@link #initialize}.
|
|
* <p>
|
|
* 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:
|
|
* <ul>
|
|
* <li>{@link #LOG_LEVEL_VERBOSE}</li>
|
|
* <li>{@link #LOG_LEVEL_DEBUG}</li>
|
|
* <li>{@link #LOG_LEVEL_INFO}</li>
|
|
* <li>{@link #LOG_LEVEL_WARNING}</li>
|
|
* <li>{@link #LOG_LEVEL_ERROR}</li>
|
|
* <li>{@link #LOG_LEVEL_NONE}</li>
|
|
* </ul>
|
|
*
|
|
* @param logLevel The level of logcat logging that Parse should do.
|
|
*/
|
|
public static void setLogLevel(int logLevel) {
|
|
PLog.setLogLevel(logLevel);
|
|
}
|
|
|
|
/**
|
|
* Returns the level of logging that will be displayed.
|
|
*/
|
|
public static int getLogLevel() {
|
|
return PLog.getLogLevel();
|
|
}
|
|
|
|
//endregion
|
|
|
|
// Suppress constructor to prevent subclassing
|
|
private Parse() {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
static String externalVersionName() {
|
|
return "a" + ParseObject.VERSION_NAME;
|
|
}
|
|
}
|