ParseApplication/ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseInstallation.java

322 lines
11 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.PackageInfo;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import bolts.Continuation;
import bolts.Task;
/**
* The {@code ParseInstallation} is a local representation of installation data that can be saved
* and retrieved from the Parse cloud.
*/
@ParseClassName("_Installation")
public class ParseInstallation extends ParseObject {
private static final String TAG = "com.parse.ParseInstallation";
private static final String KEY_OBJECT_ID = "objectId";
private static final String KEY_INSTALLATION_ID = "installationId";
private static final String KEY_DEVICE_TYPE = "deviceType";
private static final String KEY_APP_NAME = "appName";
private static final String KEY_APP_IDENTIFIER = "appIdentifier";
private static final String KEY_PARSE_VERSION = "parseVersion";
private static final String KEY_DEVICE_TOKEN = "deviceToken";
private static final String KEY_PUSH_TYPE = "pushType";
private static final String KEY_TIME_ZONE = "timeZone";
private static final String KEY_LOCALE = "localeIdentifier";
private static final String KEY_APP_VERSION = "appVersion";
/* package */ static final String KEY_CHANNELS = "channels";
private static final List<String> READ_ONLY_FIELDS = Collections.unmodifiableList(
Arrays.asList(KEY_DEVICE_TYPE, KEY_INSTALLATION_ID, KEY_DEVICE_TOKEN, KEY_PUSH_TYPE,
KEY_TIME_ZONE, KEY_LOCALE, KEY_APP_VERSION, KEY_APP_NAME, KEY_PARSE_VERSION,
KEY_APP_IDENTIFIER, KEY_OBJECT_ID));
// TODO(mengyan): Inject into ParseInstallationInstanceController
/* package */ static ParseCurrentInstallationController getCurrentInstallationController() {
return ParseCorePlugins.getInstance().getCurrentInstallationController();
}
public static ParseInstallation getCurrentInstallation() {
try {
return ParseTaskUtils.wait(
getCurrentInstallationController().getAsync());
} catch (ParseException e) {
// In order to have backward compatibility, we swallow the exception silently.
return null;
}
}
/**
* Constructs a query for {@code ParseInstallation}.
* <p/>
* <strong>Note:</strong> We only allow the following types of queries for installations:
* <pre>
* query.get(objectId)
* query.whereEqualTo("installationId", value)
* query.whereMatchesKeyInQuery("installationId", keyInQuery, query)
* </pre>
* <p/>
* You can add additional query clauses, but one of the above must appear as a top-level
* {@code AND} clause in the query.
*
* @see com.parse.ParseQuery#getQuery(Class)
*/
public static ParseQuery<ParseInstallation> getQuery() {
return ParseQuery.getQuery(ParseInstallation.class);
}
public ParseInstallation() {
// do nothing
}
/**
* Returns the unique ID of this installation.
*
* @return A UUID that represents this device.
*/
public String getInstallationId() {
return getString(KEY_INSTALLATION_ID);
}
@Override
public void setObjectId(String newObjectId) {
throw new RuntimeException("Installation's objectId cannot be changed");
}
@Override
/* package */ boolean needsDefaultACL() {
return false;
}
@Override
/* package */ boolean isKeyMutable(String key) {
return !READ_ONLY_FIELDS.contains(key);
}
@Override
/* package */ void updateBeforeSave() {
super.updateBeforeSave();
if (getCurrentInstallationController().isCurrent(ParseInstallation.this)) {
updateTimezone();
updateVersionInfo();
updateDeviceInfo();
updateLocaleIdentifier();
}
}
@Override
/* package */ <T extends ParseObject> Task<T> fetchAsync(
final String sessionToken, final Task<Void> toAwait) {
synchronized (mutex) {
// Because the Service and the global currentInstallation are different objects, we may not
// have the same ObjectID (we never will at bootstrap). The server has a special hack for
// _Installation where save with an existing InstallationID will merge Object IDs
Task<Void> result;
if (getObjectId() == null) {
result = saveAsync(sessionToken, toAwait);
} else {
result = Task.forResult(null);
}
return result.onSuccessTask(new Continuation<Void, Task<T>>() {
@Override
public Task<T> then(Task<Void> task) throws Exception {
return ParseInstallation.super.fetchAsync(sessionToken, toAwait);
}
});
}
}
@Override
/* package */ Task<Void> saveAsync(final String sessionToken, final Task<Void> toAwait) {
return super.saveAsync(sessionToken, toAwait).continueWithTask(new Continuation<Void, Task<Void>>() {
@Override
public Task<Void> then(Task<Void> task) throws Exception {
// Retry the fetch as a save operation because this Installation was deleted on the server.
if (task.getError() != null
&& task.getError() instanceof ParseException) {
int errCode = ((ParseException) task.getError()).getCode();
if (errCode == ParseException.OBJECT_NOT_FOUND
|| (errCode == ParseException.MISSING_REQUIRED_FIELD_ERROR && getObjectId() == null)) {
synchronized (mutex) {
setState(new State.Builder(getState()).objectId(null).build());
markAllFieldsDirty();
return ParseInstallation.super.saveAsync(sessionToken, toAwait);
}
}
}
return task;
}
});
}
@Override
/* package */ Task<Void> handleSaveResultAsync(ParseObject.State result,
ParseOperationSet operationsBeforeSave) {
Task<Void> task = super.handleSaveResultAsync(result, operationsBeforeSave);
if (result == null) { // Failure
return task;
}
return task.onSuccessTask(new Continuation<Void, Task<Void>>() {
@Override
public Task<Void> then(Task<Void> task) throws Exception {
return getCurrentInstallationController().setAsync(ParseInstallation.this);
}
});
}
@Override
/* package */ Task<Void> handleFetchResultAsync(final ParseObject.State newState) {
return super.handleFetchResultAsync(newState).onSuccessTask(new Continuation<Void, Task<Void>>() {
@Override
public Task<Void> then(Task<Void> task) throws Exception {
return getCurrentInstallationController().setAsync(ParseInstallation.this);
}
});
}
// Android documentation states that getID may return one of many forms: America/LosAngeles,
// GMT-<offset>, or code. We only accept the first on the server, so for now we will not upload
// time zones from devices reporting other formats.
private void updateTimezone() {
String zone = TimeZone.getDefault().getID();
if ((zone.indexOf('/') > 0 || zone.equals("GMT")) && !zone.equals(get(KEY_TIME_ZONE))) {
performPut(KEY_TIME_ZONE, zone);
}
}
private void updateVersionInfo() {
synchronized (mutex) {
try {
Context context = Parse.getApplicationContext();
String packageName = context.getPackageName();
PackageManager pm = context.getPackageManager();
PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
String appVersion = pkgInfo.versionName;
String appName = pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)).toString();
if (packageName != null && !packageName.equals(get(KEY_APP_IDENTIFIER))) {
performPut(KEY_APP_IDENTIFIER, packageName);
}
if (appName != null && !appName.equals(get(KEY_APP_NAME))) {
performPut(KEY_APP_NAME, appName);
}
if (appVersion != null && !appVersion.equals(get(KEY_APP_VERSION))) {
performPut(KEY_APP_VERSION, appVersion);
}
} catch (PackageManager.NameNotFoundException e) {
PLog.w(TAG, "Cannot load package info; will not be saved to installation");
}
if (!VERSION_NAME.equals(get(KEY_PARSE_VERSION))) {
performPut(KEY_PARSE_VERSION, VERSION_NAME);
}
}
}
/*
* Save locale in the following format:
* [language code]-[country code]
*
* The language codes are two-letter lowercase ISO language codes (such as "en") as defined by
* <a href="http://en.wikipedia.org/wiki/ISO_639-1">ISO 639-1</a>.
* The country codes are two-letter uppercase ISO country codes (such as "US") as defined by
* <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-3">ISO 3166-1</a>.
*
* Note that Java uses several deprecated two-letter codes. The Hebrew ("he") language
* code is rewritten as "iw", Indonesian ("id") as "in", and Yiddish ("yi") as "ji". This
* rewriting happens even if you construct your own {@code Locale} object, not just for
* instances returned by the various lookup methods.
*/
private void updateLocaleIdentifier() {
final Locale locale = Locale.getDefault();
String language = locale.getLanguage();
String country = locale.getCountry();
if (TextUtils.isEmpty(language)) {
return;
}
// rewrite depreciated two-letter codes
if (language.equals("iw")) language = "he"; // Hebrew
if (language.equals("in")) language = "id"; // Indonesian
if (language.equals("ji")) language = "yi"; // Yiddish
String localeString = language;
if (!TextUtils.isEmpty(country)) {
localeString = String.format(Locale.US, "%s-%s", language, country);
}
if (!localeString.equals(get(KEY_LOCALE))) {
performPut(KEY_LOCALE, localeString);
}
}
// TODO(mengyan): Move to ParseInstallationInstanceController
/* package */ void updateDeviceInfo() {
updateDeviceInfo(ParsePlugins.get().installationId());
}
/* package */ void updateDeviceInfo(InstallationId installationId) {
/*
* If we don't have an installationId, use the one that comes from the installationId file on
* disk. This should be impossible since we set the installationId in setDefaultValues.
*/
if (!has(KEY_INSTALLATION_ID)) {
performPut(KEY_INSTALLATION_ID, installationId.get());
}
String deviceType = "android";
if (!deviceType.equals(get(KEY_DEVICE_TYPE))) {
performPut(KEY_DEVICE_TYPE, deviceType);
}
}
/* package */ PushType getPushType() {
return PushType.fromString(super.getString(KEY_PUSH_TYPE));
}
/* package */ void setPushType(PushType pushType) {
if (pushType != null) {
performPut(KEY_PUSH_TYPE, pushType.toString());
}
}
/* package */ void removePushType() {
performRemove(KEY_PUSH_TYPE);
}
/* package */ String getDeviceToken() {
return super.getString(KEY_DEVICE_TOKEN);
}
/* package */ void setDeviceToken(String deviceToken) {
if (deviceToken != null && deviceToken.length() > 0) {
performPut(KEY_DEVICE_TOKEN, deviceToken);
}
}
/* package */ void removeDeviceToken() {
performRemove(KEY_DEVICE_TOKEN);
}
}