PageRenderTime 66ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/Parse/src/main/java/com/parse/ParseUser.java

https://gitlab.com/yousafsyed/Parse-SDK-Android
Java | 1401 lines | 861 code | 150 blank | 390 comment | 126 complexity | 57cf5bd6d60540f4236e226707894250 MD5 | raw file
  1. /*
  2. * Copyright (c) 2015-present, Parse, LLC.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. */
  9. package com.parse;
  10. import org.json.JSONObject;
  11. import java.util.ArrayList;
  12. import java.util.Arrays;
  13. import java.util.Collections;
  14. import java.util.HashMap;
  15. import java.util.Iterator;
  16. import java.util.LinkedList;
  17. import java.util.List;
  18. import java.util.Map;
  19. import bolts.Continuation;
  20. import bolts.Task;
  21. /**
  22. * The {@code ParseUser} is a local representation of user data that can be saved and retrieved from
  23. * the Parse cloud.
  24. */
  25. @ParseClassName("_User")
  26. public class ParseUser extends ParseObject {
  27. private static final String KEY_SESSION_TOKEN = "sessionToken";
  28. private static final String KEY_AUTH_DATA = "authData";
  29. private static final String KEY_USERNAME = "username";
  30. private static final String KEY_PASSWORD = "password";
  31. private static final String KEY_EMAIL = "email";
  32. private static final List<String> READ_ONLY_KEYS = Collections.unmodifiableList(
  33. Arrays.asList(KEY_SESSION_TOKEN, KEY_AUTH_DATA));
  34. /**
  35. * Constructs a query for {@code ParseUser}.
  36. *
  37. * @see com.parse.ParseQuery#getQuery(Class)
  38. */
  39. public static ParseQuery<ParseUser> getQuery() {
  40. return ParseQuery.getQuery(ParseUser.class);
  41. }
  42. /* package for tests */ static ParseUserController getUserController() {
  43. return ParseCorePlugins.getInstance().getUserController();
  44. }
  45. /* package for tests */ static ParseCurrentUserController getCurrentUserController() {
  46. return ParseCorePlugins.getInstance().getCurrentUserController();
  47. }
  48. /* package for tests */ static ParseAuthenticationManager getAuthenticationManager() {
  49. return ParseCorePlugins.getInstance().getAuthenticationManager();
  50. }
  51. /** package */ static class State extends ParseObject.State {
  52. /** package */ static class Builder extends Init<Builder> {
  53. private boolean isNew;
  54. public Builder() {
  55. super("_User");
  56. }
  57. /* package */ Builder(State state) {
  58. super(state);
  59. isNew = state.isNew();
  60. }
  61. @Override
  62. /* package */ Builder self() {
  63. return this;
  64. }
  65. @SuppressWarnings("unchecked")
  66. @Override
  67. public State build() {
  68. return new State(this);
  69. }
  70. @Override
  71. public Builder apply(ParseObject.State other) {
  72. isNew(((State) other).isNew());
  73. return super.apply(other);
  74. }
  75. public Builder sessionToken(String sessionToken) {
  76. return put(KEY_SESSION_TOKEN, sessionToken);
  77. }
  78. public Builder authData(Map<String, Map<String, String>> authData) {
  79. return put(KEY_AUTH_DATA, authData);
  80. }
  81. @SuppressWarnings("unchecked")
  82. public Builder putAuthData(String authType, Map<String, String> authData) {
  83. Map<String, Map<String, String>> newAuthData =
  84. (Map<String, Map<String, String>>) serverData.get(KEY_AUTH_DATA);
  85. if (newAuthData == null) {
  86. newAuthData = new HashMap<>();
  87. }
  88. newAuthData.put(authType, authData);
  89. serverData.put(KEY_AUTH_DATA, newAuthData);
  90. return this;
  91. }
  92. public Builder isNew(boolean isNew) {
  93. this.isNew = isNew;
  94. return this;
  95. }
  96. }
  97. private final boolean isNew;
  98. private State(Builder builder) {
  99. super(builder);
  100. isNew = builder.isNew;
  101. }
  102. @SuppressWarnings("unchecked")
  103. @Override
  104. public Builder newBuilder() {
  105. return new Builder(this);
  106. }
  107. public String sessionToken() {
  108. return (String) get(KEY_SESSION_TOKEN);
  109. }
  110. @SuppressWarnings("unchecked")
  111. public Map<String, Map<String, String>> authData() {
  112. Map<String, Map<String, String>> authData =
  113. (Map<String, Map<String, String>>) get(KEY_AUTH_DATA);
  114. if (authData == null) {
  115. // We'll always return non-null for now since we don't have any null checking in place.
  116. // Be aware not to get and set without checking size or else we'll be adding a value that
  117. // wasn't there in the first place.
  118. authData = new HashMap<>();
  119. }
  120. return authData;
  121. }
  122. public boolean isNew() {
  123. return isNew;
  124. }
  125. }
  126. // Whether the object is a currentUser. If so, it will always be persisted to disk on updates.
  127. private boolean isCurrentUser;
  128. /**
  129. * Constructs a new ParseUser with no data in it. A ParseUser constructed in this way will not
  130. * have an objectId and will not persist to the database until {@link #signUp} is called.
  131. */
  132. public ParseUser() {
  133. isCurrentUser = false;
  134. }
  135. @Override
  136. /* package */ boolean needsDefaultACL() {
  137. return false;
  138. }
  139. @Override
  140. boolean isKeyMutable(String key) {
  141. return !READ_ONLY_KEYS.contains(key);
  142. }
  143. @Override
  144. /* package */ State.Builder newStateBuilder(String className) {
  145. return new State.Builder();
  146. }
  147. @Override
  148. /* package */ State getState() {
  149. return (State) super.getState();
  150. }
  151. /**
  152. * @return {@code true} if this user was created with {@link #getCurrentUser()} when no current
  153. * user previously existed and {@link #enableAutomaticUser()} is set, false if was created by any
  154. * other means or if a previously "lazy" user was saved remotely.
  155. */
  156. /* package */ boolean isLazy() {
  157. synchronized (mutex) {
  158. return getObjectId() == null && ParseAnonymousUtils.isLinked(this);
  159. }
  160. }
  161. /**
  162. * Whether the ParseUser has been authenticated on this device. This will be true if the ParseUser
  163. * was obtained via a logIn or signUp method. Only an authenticated ParseUser can be saved (with
  164. * altered attributes) and deleted.
  165. */
  166. public boolean isAuthenticated() {
  167. synchronized (mutex) {
  168. ParseUser current = ParseUser.getCurrentUser();
  169. return isLazy() ||
  170. (getState().sessionToken() != null
  171. && current != null
  172. && getObjectId().equals(current.getObjectId()));
  173. }
  174. }
  175. @Override
  176. public void remove(String key) {
  177. if (KEY_USERNAME.equals(key)) {
  178. throw new IllegalArgumentException("Can't remove the username key.");
  179. }
  180. super.remove(key);
  181. }
  182. @Override
  183. /* package */ JSONObject toRest(
  184. ParseObject.State state,
  185. List<ParseOperationSet> operationSetQueue,
  186. ParseEncoder objectEncoder) {
  187. // Create a sanitized copy of operationSetQueue with `password` removed if necessary
  188. List<ParseOperationSet> cleanOperationSetQueue = operationSetQueue;
  189. for (int i = 0; i < operationSetQueue.size(); i++) {
  190. ParseOperationSet operations = operationSetQueue.get(i);
  191. if (operations.containsKey(KEY_PASSWORD)) {
  192. if (cleanOperationSetQueue == operationSetQueue) {
  193. cleanOperationSetQueue = new LinkedList<>(operationSetQueue);
  194. }
  195. ParseOperationSet cleanOperations = new ParseOperationSet(operations);
  196. cleanOperations.remove(KEY_PASSWORD);
  197. cleanOperationSetQueue.set(i, cleanOperations);
  198. }
  199. }
  200. return super.toRest(state, cleanOperationSetQueue, objectEncoder);
  201. }
  202. /* package for tests */ Task<Void> cleanUpAuthDataAsync() {
  203. ParseAuthenticationManager controller = getAuthenticationManager();
  204. Map<String, Map<String, String>> authData;
  205. synchronized (mutex) {
  206. authData = getState().authData();
  207. if (authData.size() == 0) {
  208. return Task.forResult(null); // Nothing to see or do here...
  209. }
  210. }
  211. List<Task<Void>> tasks = new ArrayList<>();
  212. Iterator<Map.Entry<String, Map<String, String>>> i = authData.entrySet().iterator();
  213. while (i.hasNext()) {
  214. Map.Entry<String, Map<String, String>> entry = i.next();
  215. if (entry.getValue() == null) {
  216. i.remove();
  217. tasks.add(controller.restoreAuthenticationAsync(entry.getKey(), null).makeVoid());
  218. }
  219. }
  220. State newState = getState().newBuilder()
  221. .authData(authData)
  222. .build();
  223. setState(newState);
  224. return Task.whenAll(tasks);
  225. }
  226. @Override
  227. /* package */ Task<Void> handleSaveResultAsync(
  228. ParseObject.State result, ParseOperationSet operationsBeforeSave) {
  229. boolean success = result != null;
  230. if (success) {
  231. operationsBeforeSave.remove(KEY_PASSWORD);
  232. }
  233. return super.handleSaveResultAsync(result, operationsBeforeSave);
  234. }
  235. @Override
  236. /* package */ void validateSaveEventually() throws ParseException {
  237. if (isDirty(KEY_PASSWORD)) {
  238. // TODO(mengyan): Unify the exception we throw when validate fails
  239. throw new ParseException(
  240. ParseException.OTHER_CAUSE,
  241. "Unable to saveEventually on a ParseUser with dirty password");
  242. }
  243. }
  244. //region Getter/Setter helper methods
  245. /* package */ boolean isCurrentUser() {
  246. synchronized (mutex) {
  247. return isCurrentUser;
  248. }
  249. }
  250. /* package */ void setIsCurrentUser(boolean isCurrentUser) {
  251. synchronized (mutex) {
  252. this.isCurrentUser = isCurrentUser;
  253. }
  254. }
  255. /**
  256. * @return the session token for a user, if they are logged in.
  257. */
  258. public String getSessionToken() {
  259. return getState().sessionToken();
  260. }
  261. // This is only used when upgrading to revocable session
  262. private Task<Void> setSessionTokenInBackground(String newSessionToken) {
  263. synchronized (mutex) {
  264. State state = getState();
  265. if (newSessionToken.equals(state.sessionToken())) {
  266. return Task.forResult(null);
  267. }
  268. State.Builder builder = state.newBuilder()
  269. .sessionToken(newSessionToken);
  270. setState(builder.build());
  271. return saveCurrentUserAsync(this);
  272. }
  273. }
  274. /* package for testes */ Map<String, Map<String, String>> getAuthData() {
  275. synchronized (mutex) {
  276. Map<String, Map<String, String>> authData = getMap(KEY_AUTH_DATA);
  277. if (authData == null) {
  278. // We'll always return non-null for now since we don't have any null checking in place.
  279. // Be aware not to get and set without checking size or else we'll be adding a value that
  280. // wasn't there in the first place.
  281. authData = new HashMap<>();
  282. }
  283. return authData;
  284. }
  285. }
  286. private Map<String, String> getAuthData(String authType) {
  287. return getAuthData().get(authType);
  288. }
  289. /* package */ void putAuthData(String authType, Map<String, String> authData) {
  290. synchronized (mutex) {
  291. Map<String, Map<String, String>> newAuthData = getAuthData();
  292. newAuthData.put(authType, authData);
  293. performPut(KEY_AUTH_DATA, newAuthData);
  294. }
  295. }
  296. private void removeAuthData(String authType) {
  297. synchronized (mutex) {
  298. Map<String, Map<String, String>> newAuthData = getAuthData();
  299. newAuthData.remove(authType);
  300. performPut(KEY_AUTH_DATA, newAuthData);
  301. }
  302. }
  303. /**
  304. * Sets the username. Usernames cannot be null or blank.
  305. *
  306. * @param username
  307. * The username to set.
  308. */
  309. public void setUsername(String username) {
  310. put(KEY_USERNAME, username);
  311. }
  312. /**
  313. * Retrieves the username.
  314. */
  315. public String getUsername() {
  316. return getString(KEY_USERNAME);
  317. }
  318. /**
  319. * Sets the password.
  320. *
  321. * @param password
  322. * The password to set.
  323. */
  324. public void setPassword(String password) {
  325. put(KEY_PASSWORD, password);
  326. }
  327. /* package for tests */ String getPassword() {
  328. return getString(KEY_PASSWORD);
  329. }
  330. /**
  331. * Sets the email address.
  332. *
  333. * @param email
  334. * The email address to set.
  335. */
  336. public void setEmail(String email) {
  337. put(KEY_EMAIL, email);
  338. }
  339. /**
  340. * Retrieves the email address.
  341. */
  342. public String getEmail() {
  343. return getString(KEY_EMAIL);
  344. }
  345. /**
  346. * Indicates whether this {@code ParseUser} was created during this session through a call to
  347. * {@link #signUp()} or by logging in with a linked service such as Facebook.
  348. */
  349. public boolean isNew() {
  350. return getState().isNew();
  351. }
  352. //endregion
  353. @Override
  354. public void put(String key, Object value) {
  355. synchronized (mutex) {
  356. if (KEY_USERNAME.equals(key)) {
  357. // When the username is set, remove any semblance of anonymity.
  358. stripAnonymity();
  359. }
  360. super.put(key, value);
  361. }
  362. }
  363. private void stripAnonymity() {
  364. synchronized (mutex) {
  365. if (ParseAnonymousUtils.isLinked(this)) {
  366. if (getObjectId() != null) {
  367. putAuthData(ParseAnonymousUtils.AUTH_TYPE, null);
  368. } else {
  369. removeAuthData(ParseAnonymousUtils.AUTH_TYPE);
  370. }
  371. }
  372. }
  373. }
  374. // TODO(grantland): Can we replace this with #revert(String)?
  375. private void restoreAnonymity(Map<String, String> anonymousData) {
  376. synchronized (mutex) {
  377. if (anonymousData != null) {
  378. putAuthData(ParseAnonymousUtils.AUTH_TYPE, anonymousData);
  379. }
  380. }
  381. }
  382. @Override
  383. /* package */ void validateSave() {
  384. synchronized (mutex) {
  385. if (getObjectId() == null) {
  386. throw new IllegalArgumentException(
  387. "Cannot save a ParseUser until it has been signed up. Call signUp first.");
  388. }
  389. if (isAuthenticated() || !isDirty() || isCurrentUser()) {
  390. return;
  391. }
  392. }
  393. if (!Parse.isLocalDatastoreEnabled()) {
  394. // This might be a different of instance of the currentUser, so we need to check objectIds
  395. ParseUser current = ParseUser.getCurrentUser(); //TODO (grantland): possible blocking disk i/o
  396. if (current != null && getObjectId().equals(current.getObjectId())) {
  397. return;
  398. }
  399. }
  400. throw new IllegalArgumentException("Cannot save a ParseUser that is not authenticated.");
  401. }
  402. @Override
  403. /* package */ Task<Void> saveAsync(String sessionToken, Task<Void> toAwait) {
  404. return saveAsync(sessionToken, isLazy(), toAwait);
  405. }
  406. /* package for tests */ Task<Void> saveAsync(String sessionToken, boolean isLazy, Task<Void> toAwait) {
  407. Task<Void> task;
  408. if (isLazy) {
  409. task = resolveLazinessAsync(toAwait);
  410. } else {
  411. task = super.saveAsync(sessionToken, toAwait);
  412. }
  413. if (isCurrentUser()) {
  414. // If the user is the currently logged in user, we persist all data to disk
  415. return task.onSuccessTask(new Continuation<Void, Task<Void>>() {
  416. @Override
  417. public Task<Void> then(Task<Void> task) throws Exception {
  418. return cleanUpAuthDataAsync();
  419. }
  420. }).onSuccessTask(new Continuation<Void, Task<Void>>() {
  421. @Override
  422. public Task<Void> then(Task<Void> task) throws Exception {
  423. return saveCurrentUserAsync(ParseUser.this);
  424. }
  425. });
  426. }
  427. return task;
  428. }
  429. @Override
  430. /* package */ void setState(ParseObject.State newState) {
  431. if (isCurrentUser()) {
  432. State.Builder newStateBuilder = newState.newBuilder();
  433. // Avoid clearing sessionToken when updating the current user's State via ParseQuery result
  434. if (getSessionToken() != null && newState.get(KEY_SESSION_TOKEN) == null) {
  435. newStateBuilder.put(KEY_SESSION_TOKEN, getSessionToken());
  436. }
  437. // Avoid clearing authData when updating the current user's State via ParseQuery result
  438. if (getAuthData().size() > 0 && newState.get(KEY_AUTH_DATA) == null) {
  439. newStateBuilder.put(KEY_AUTH_DATA, getAuthData());
  440. }
  441. newState = newStateBuilder.build();
  442. }
  443. super.setState(newState);
  444. }
  445. @Override
  446. /* package */ void validateDelete() {
  447. synchronized (mutex) {
  448. super.validateDelete();
  449. if (!isAuthenticated() && isDirty()) {
  450. throw new IllegalArgumentException("Cannot delete a ParseUser that is not authenticated.");
  451. }
  452. }
  453. }
  454. @SuppressWarnings("unchecked")
  455. @Override
  456. public ParseUser fetch() throws ParseException {
  457. return (ParseUser) super.fetch();
  458. }
  459. @SuppressWarnings("unchecked")
  460. @Override
  461. /* package */ <T extends ParseObject> Task<T> fetchAsync(
  462. String sessionToken, Task<Void> toAwait) {
  463. //TODO (grantland): It doesn't seem like we should do this.. Why don't we error like we do
  464. // when fetching an unsaved ParseObject?
  465. if (isLazy()) {
  466. return Task.forResult((T) this);
  467. }
  468. Task<T> task = super.fetchAsync(sessionToken, toAwait);
  469. if (isCurrentUser()) {
  470. return task.onSuccessTask(new Continuation<T, Task<Void>>() {
  471. @Override
  472. public Task<Void> then(final Task<T> fetchAsyncTask) throws Exception {
  473. return cleanUpAuthDataAsync();
  474. }
  475. }).onSuccessTask(new Continuation<Void, Task<Void>>() {
  476. @Override
  477. public Task<Void> then(Task<Void> task) throws Exception {
  478. return saveCurrentUserAsync(ParseUser.this);
  479. }
  480. }).onSuccess(new Continuation<Void, T>() {
  481. @Override
  482. public T then(Task<Void> task) throws Exception {
  483. return (T) ParseUser.this;
  484. }
  485. });
  486. }
  487. return task;
  488. }
  489. /**
  490. * Signs up a new user. You should call this instead of {@link #save} for new ParseUsers. This
  491. * will create a new ParseUser on the server, and also persist the session on disk so that you can
  492. * access the user using {@link #getCurrentUser}.
  493. * <p/>
  494. * A username and password must be set before calling signUp.
  495. * <p/>
  496. * This is preferable to using {@link #signUp}, unless your code is already running from a
  497. * background thread.
  498. *
  499. * @return A Task that is resolved when sign up completes.
  500. */
  501. public Task<Void> signUpInBackground() {
  502. return taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
  503. @Override
  504. public Task<Void> then(Task<Void> task) throws Exception {
  505. return signUpAsync(task);
  506. }
  507. });
  508. }
  509. /* package for tests */ Task<Void> signUpAsync(Task<Void> toAwait) {
  510. final ParseUser user = getCurrentUser(); //TODO (grantland): convert to async
  511. synchronized (mutex) {
  512. final String sessionToken = user != null ? user.getSessionToken() : null;
  513. if (ParseTextUtils.isEmpty(getUsername())) {
  514. return Task.forError(new IllegalArgumentException("Username cannot be missing or blank"));
  515. }
  516. if (ParseTextUtils.isEmpty(getPassword())) {
  517. return Task.forError(new IllegalArgumentException("Password cannot be missing or blank"));
  518. }
  519. if (getObjectId() != null) {
  520. // For anonymous users, there may be an objectId. Setting the
  521. // userName will have removed the anonymous link and set the
  522. // value in the authData object to JSONObject.NULL, so we can
  523. // just treat it like a save operation.
  524. Map<String, Map<String, String>> authData = getAuthData();
  525. if (authData.containsKey(ParseAnonymousUtils.AUTH_TYPE)
  526. && authData.get(ParseAnonymousUtils.AUTH_TYPE) == null) {
  527. return saveAsync(sessionToken, toAwait);
  528. }
  529. // Otherwise, throw.
  530. return Task.forError(
  531. new IllegalArgumentException("Cannot sign up a user that has already signed up."));
  532. }
  533. // If the operationSetQueue is has operation sets in it, then a save or signUp is in progress.
  534. // If there is a signUp or save already in progress, don't allow another one to start.
  535. if (operationSetQueue.size() > 1) {
  536. return Task.forError(
  537. new IllegalArgumentException("Cannot sign up a user that is already signing up."));
  538. }
  539. // If the current user is an anonymous user, merge this object's data into the anonymous user
  540. // and save.
  541. if (user != null && ParseAnonymousUtils.isLinked(user)) {
  542. // this doesn't have any outstanding saves, so we can safely merge its operations into the
  543. // current user.
  544. if (this == user) {
  545. return Task.forError(
  546. new IllegalArgumentException("Attempt to merge currentUser with itself."));
  547. }
  548. boolean isLazy = user.isLazy();
  549. final String oldUsername = user.getUsername();
  550. final String oldPassword = user.getPassword();
  551. final Map<String, String> anonymousData = user.getAuthData(ParseAnonymousUtils.AUTH_TYPE);
  552. user.copyChangesFrom(this);
  553. user.setUsername(getUsername());
  554. user.setPassword(getPassword());
  555. revert();
  556. return user.saveAsync(sessionToken, isLazy, toAwait).continueWithTask(new Continuation<Void, Task<Void>>() {
  557. @Override
  558. public Task<Void> then(Task<Void> task) throws Exception {
  559. if (task.isCancelled() || task.isFaulted()) { // Error
  560. synchronized (user.mutex) {
  561. if (oldUsername != null) {
  562. user.setUsername(oldUsername);
  563. } else {
  564. user.revert(KEY_USERNAME);
  565. }
  566. if (oldPassword != null) {
  567. user.setPassword(oldPassword);
  568. } else {
  569. user.revert(KEY_PASSWORD);
  570. }
  571. user.restoreAnonymity(anonymousData);
  572. }
  573. return task;
  574. } else { // Success
  575. user.revert(KEY_PASSWORD);
  576. revert(KEY_PASSWORD);
  577. }
  578. mergeFromObject(user);
  579. return saveCurrentUserAsync(ParseUser.this);
  580. }
  581. });
  582. }
  583. final ParseOperationSet operations = startSave();
  584. return toAwait.onSuccessTask(new Continuation<Void, Task<Void>>() {
  585. @Override
  586. public Task<Void> then(Task<Void> task) throws Exception {
  587. return getUserController().signUpAsync(
  588. getState(), operations, sessionToken
  589. ).continueWithTask(new Continuation<ParseUser.State, Task<Void>>() {
  590. @Override
  591. public Task<Void> then(final Task<ParseUser.State> signUpTask) throws Exception {
  592. ParseUser.State result = signUpTask.getResult();
  593. return handleSaveResultAsync(result,
  594. operations).continueWithTask(new Continuation<Void, Task<Void>>() {
  595. @Override
  596. public Task<Void> then(Task<Void> task) throws Exception {
  597. if (!signUpTask.isCancelled() && !signUpTask.isFaulted()) {
  598. return saveCurrentUserAsync(ParseUser.this);
  599. }
  600. return signUpTask.makeVoid();
  601. }
  602. });
  603. }
  604. });
  605. }
  606. });
  607. }
  608. }
  609. /**
  610. * Signs up a new user. You should call this instead of {@link #save} for new ParseUsers. This
  611. * will create a new ParseUser on the server, and also persist the session on disk so that you can
  612. * access the user using {@link #getCurrentUser}.
  613. * <p/>
  614. * A username and password must be set before calling signUp.
  615. * <p/>
  616. * Typically, you should use {@link #signUpInBackground} instead of this, unless you are managing
  617. * your own threading.
  618. *
  619. * @throws ParseException
  620. * Throws an exception if the server is inaccessible, or if the username has already
  621. * been taken.
  622. */
  623. public void signUp() throws ParseException {
  624. ParseTaskUtils.wait(signUpInBackground());
  625. }
  626. /**
  627. * Signs up a new user. You should call this instead of {@link #save} for new ParseUsers. This
  628. * will create a new ParseUser on the server, and also persist the session on disk so that you can
  629. * access the user using {@link #getCurrentUser}.
  630. * <p/>
  631. * A username and password must be set before calling signUp.
  632. * <p/>
  633. * This is preferable to using {@link #signUp}, unless your code is already running from a
  634. * background thread.
  635. *
  636. * @param callback
  637. * callback.done(user, e) is called when the signUp completes.
  638. */
  639. public void signUpInBackground(SignUpCallback callback) {
  640. ParseTaskUtils.callbackOnMainThreadAsync(signUpInBackground(), callback);
  641. }
  642. /**
  643. * Logs in a user with a username and password. On success, this saves the session to disk, so you
  644. * can retrieve the currently logged in user using {@link #getCurrentUser}.
  645. * <p/>
  646. * This is preferable to using {@link #logIn}, unless your code is already running from a
  647. * background thread.
  648. *
  649. * @param username
  650. * The username to log in with.
  651. * @param password
  652. * The password to log in with.
  653. *
  654. * @return A Task that is resolved when logging in completes.
  655. */
  656. public static Task<ParseUser> logInInBackground(String username, String password) {
  657. if (username == null) {
  658. throw new IllegalArgumentException("Must specify a username for the user to log in with");
  659. }
  660. if (password == null) {
  661. throw new IllegalArgumentException("Must specify a password for the user to log in with");
  662. }
  663. return getUserController().logInAsync(username, password).onSuccessTask(new Continuation<State, Task<ParseUser>>() {
  664. @Override
  665. public Task<ParseUser> then(Task<State> task) throws Exception {
  666. State result = task.getResult();
  667. final ParseUser newCurrent = ParseObject.from(result);
  668. return saveCurrentUserAsync(newCurrent).onSuccess(new Continuation<Void, ParseUser>() {
  669. @Override
  670. public ParseUser then(Task<Void> task) throws Exception {
  671. return newCurrent;
  672. }
  673. });
  674. }
  675. });
  676. }
  677. /**
  678. * Logs in a user with a username and password. On success, this saves the session to disk, so you
  679. * can retrieve the currently logged in user using {@link #getCurrentUser}.
  680. * <p/>
  681. * Typically, you should use {@link #logInInBackground} instead of this, unless you are managing
  682. * your own threading.
  683. *
  684. * @param username
  685. * The username to log in with.
  686. * @param password
  687. * The password to log in with.
  688. * @throws ParseException
  689. * Throws an exception if the login was unsuccessful.
  690. * @return The user if the login was successful.
  691. */
  692. public static ParseUser logIn(String username, String password) throws ParseException {
  693. return ParseTaskUtils.wait(logInInBackground(username, password));
  694. }
  695. /**
  696. * Logs in a user with a username and password. On success, this saves the session to disk, so you
  697. * can retrieve the currently logged in user using {@link #getCurrentUser}.
  698. * <p/>
  699. * This is preferable to using {@link #logIn}, unless your code is already running from a
  700. * background thread.
  701. *
  702. * @param username
  703. * The username to log in with.
  704. * @param password
  705. * The password to log in with.
  706. * @param callback
  707. * callback.done(user, e) is called when the login completes.
  708. */
  709. public static void logInInBackground(final String username, final String password,
  710. LogInCallback callback) {
  711. ParseTaskUtils.callbackOnMainThreadAsync(logInInBackground(username, password), callback);
  712. }
  713. /**
  714. * Authorize a user with a session token. On success, this saves the session to disk, so you can
  715. * retrieve the currently logged in user using {@link #getCurrentUser}.
  716. * <p/>
  717. * This is preferable to using {@link #become}, unless your code is already running from a
  718. * background thread.
  719. *
  720. * @param sessionToken
  721. * The session token to authorize with.
  722. *
  723. * @return A Task that is resolved when authorization completes.
  724. */
  725. public static Task<ParseUser> becomeInBackground(String sessionToken) {
  726. if (sessionToken == null) {
  727. throw new IllegalArgumentException("Must specify a sessionToken for the user to log in with");
  728. }
  729. return getUserController().getUserAsync(sessionToken).onSuccessTask(new Continuation<State, Task<ParseUser>>() {
  730. @Override
  731. public Task<ParseUser> then(Task<State> task) throws Exception {
  732. State result = task.getResult();
  733. final ParseUser user = ParseObject.from(result);
  734. return saveCurrentUserAsync(user).onSuccess(new Continuation<Void, ParseUser>() {
  735. @Override
  736. public ParseUser then(Task<Void> task) throws Exception {
  737. return user;
  738. }
  739. });
  740. }
  741. });
  742. }
  743. /**
  744. * Authorize a user with a session token. On success, this saves the session to disk, so you can
  745. * retrieve the currently logged in user using {@link #getCurrentUser}.
  746. * <p/>
  747. * Typically, you should use {@link #becomeInBackground} instead of this, unless you are managing
  748. * your own threading.
  749. *
  750. * @param sessionToken
  751. * The session token to authorize with.
  752. * @throws ParseException
  753. * Throws an exception if the authorization was unsuccessful.
  754. * @return The user if the authorization was successful.
  755. */
  756. public static ParseUser become(String sessionToken) throws ParseException {
  757. return ParseTaskUtils.wait(becomeInBackground(sessionToken));
  758. }
  759. /**
  760. * Authorize a user with a session token. On success, this saves the session to disk, so you can
  761. * retrieve the currently logged in user using {@link #getCurrentUser}.
  762. * <p/>
  763. * This is preferable to using {@link #become}, unless your code is already running from a
  764. * background thread.
  765. *
  766. * @param sessionToken
  767. * The session token to authorize with.
  768. * @param callback
  769. * callback.done(user, e) is called when the authorization completes.
  770. */
  771. public static void becomeInBackground(final String sessionToken, LogInCallback callback) {
  772. ParseTaskUtils.callbackOnMainThreadAsync(becomeInBackground(sessionToken), callback);
  773. }
  774. //TODO (grantland): Publicize
  775. /* package */ static Task<ParseUser> getCurrentUserAsync() {
  776. return getCurrentUserController().getAsync();
  777. }
  778. /**
  779. * This retrieves the currently logged in ParseUser with a valid session, either from memory or
  780. * disk if necessary.
  781. *
  782. * @return The currently logged in ParseUser
  783. */
  784. public static ParseUser getCurrentUser() {
  785. return getCurrentUser(isAutomaticUserEnabled());
  786. }
  787. /**
  788. * This retrieves the currently logged in ParseUser with a valid session, either from memory or
  789. * disk if necessary.
  790. *
  791. * @param shouldAutoCreateUser
  792. * {@code true} to automatically create and set an anonymous user as current.
  793. * @return The currently logged in ParseUser
  794. */
  795. private static ParseUser getCurrentUser(boolean shouldAutoCreateUser) {
  796. try {
  797. return ParseTaskUtils.wait(getCurrentUserController().getAsync(shouldAutoCreateUser));
  798. } catch (ParseException e) {
  799. //TODO (grantland): Publicize this exception
  800. return null;
  801. }
  802. }
  803. //TODO (grantland): Make it throw ParseException and call #getCurrenSessionTokenInBackground()
  804. /* package */ static String getCurrentSessionToken() {
  805. ParseUser current = ParseUser.getCurrentUser();
  806. return current != null ? current.getSessionToken() : null;
  807. }
  808. //TODO (grantland): Make it really async and publicize in v2
  809. /* package */ static Task<String> getCurrentSessionTokenAsync() {
  810. return getCurrentUserController().getCurrentSessionTokenAsync();
  811. }
  812. // Persists a user as currentUser to disk, and into the singleton
  813. private static Task<Void> saveCurrentUserAsync(ParseUser user) {
  814. return getCurrentUserController().setAsync(user);
  815. }
  816. /**
  817. * Used by {@link ParseObject#pin} to persist lazy users to LDS that haven't been pinned yet.
  818. */
  819. /* package */ static Task<Void> pinCurrentUserIfNeededAsync(ParseUser user) {
  820. if (!Parse.isLocalDatastoreEnabled()) {
  821. throw new IllegalStateException("Method requires Local Datastore. " +
  822. "Please refer to `Parse#enableLocalDatastore(Context)`.");
  823. }
  824. return getCurrentUserController().setIfNeededAsync(user);
  825. }
  826. /**
  827. * Logs out the currently logged in user session. This will remove the session from disk, log out
  828. * of linked services, and future calls to {@link #getCurrentUser()} will return {@code null}.
  829. * <p/>
  830. * This is preferable to using {@link #logOut}, unless your code is already running from a
  831. * background thread.
  832. *
  833. * @return A Task that is resolved when logging out completes.
  834. */
  835. public static Task<Void> logOutInBackground() {
  836. return getCurrentUserController().logOutAsync();
  837. }
  838. /**
  839. * Logs out the currently logged in user session. This will remove the session from disk, log out
  840. * of linked services, and future calls to {@link #getCurrentUser()} will return {@code null}.
  841. * <p/>
  842. * This is preferable to using {@link #logOut}, unless your code is already running from a
  843. * background thread.
  844. */
  845. public static void logOutInBackground(LogOutCallback callback) {
  846. ParseTaskUtils.callbackOnMainThreadAsync(logOutInBackground(), callback);
  847. }
  848. /**
  849. * Logs out the currently logged in user session. This will remove the session from disk, log out
  850. * of linked services, and future calls to {@link #getCurrentUser()} will return {@code null}.
  851. * <p/>
  852. * Typically, you should use {@link #logOutInBackground()} instead of this, unless you are
  853. * managing your own threading.
  854. * <p/>
  855. * <strong>Note:</strong>: Any errors in the log out flow will be swallowed due to
  856. * backward-compatibility reasons. Please use {@link #logOutInBackground()} if you'd wish to
  857. * handle them.
  858. */
  859. public static void logOut() {
  860. try {
  861. ParseTaskUtils.wait(logOutInBackground());
  862. } catch (ParseException e) {
  863. //TODO (grantland): We shouldn't swallow errors, but we need to for backwards compatibility.
  864. // Change this in v2.
  865. }
  866. }
  867. //TODO (grantland): Add to taskQueue
  868. /* package */ Task<Void> logOutAsync() {
  869. return logOutAsync(true);
  870. }
  871. /* package */ Task<Void> logOutAsync(boolean revoke) {
  872. ParseAuthenticationManager controller = getAuthenticationManager();
  873. List<Task<Void>> tasks = new ArrayList<>();
  874. final String oldSessionToken;
  875. synchronized (mutex) {
  876. oldSessionToken = getState().sessionToken();
  877. for (Map.Entry<String, Map<String, String>> entry : getAuthData().entrySet()) {
  878. tasks.add(controller.deauthenticateAsync(entry.getKey()));
  879. }
  880. State newState = getState().newBuilder()
  881. .sessionToken(null)
  882. .isNew(false)
  883. .build();
  884. isCurrentUser = false;
  885. setState(newState);
  886. }
  887. if (revoke) {
  888. tasks.add(ParseSession.revokeAsync(oldSessionToken));
  889. }
  890. return Task.whenAll(tasks);
  891. }
  892. /**
  893. * Requests a password reset email to be sent in a background thread to the specified email
  894. * address associated with the user account. This email allows the user to securely reset their
  895. * password on the Parse site.
  896. * <p/>
  897. * This is preferable to using {@link #requestPasswordReset(String)}, unless your code is already
  898. * running from a background thread.
  899. *
  900. * @param email
  901. * The email address associated with the user that forgot their password.
  902. *
  903. * @return A Task that is resolved when the command completes.
  904. */
  905. public static Task<Void> requestPasswordResetInBackground(String email) {
  906. return getUserController().requestPasswordResetAsync(email);
  907. }
  908. /**
  909. * Requests a password reset email to be sent to the specified email address associated with the
  910. * user account. This email allows the user to securely reset their password on the Parse site.
  911. * <p/>
  912. * Typically, you should use {@link #requestPasswordResetInBackground} instead of this, unless you
  913. * are managing your own threading.
  914. *
  915. * @param email
  916. * The email address associated with the user that forgot their password.
  917. * @throws ParseException
  918. * Throws an exception if the server is inaccessible, or if an account with that email
  919. * doesn't exist.
  920. */
  921. public static void requestPasswordReset(String email) throws ParseException {
  922. ParseTaskUtils.wait(requestPasswordResetInBackground(email));
  923. }
  924. /**
  925. * Requests a password reset email to be sent in a background thread to the specified email
  926. * address associated with the user account. This email allows the user to securely reset their
  927. * password on the Parse site.
  928. * <p/>
  929. * This is preferable to using {@link #requestPasswordReset(String)}, unless your code is already
  930. * running from a background thread.
  931. *
  932. * @param email
  933. * The email address associated with the user that forgot their password.
  934. * @param callback
  935. * callback.done(e) is called when the request completes.
  936. */
  937. public static void requestPasswordResetInBackground(final String email,
  938. RequestPasswordResetCallback callback) {
  939. ParseTaskUtils.callbackOnMainThreadAsync(requestPasswordResetInBackground(email), callback);
  940. }
  941. @SuppressWarnings("unchecked")
  942. @Override
  943. public ParseUser fetchIfNeeded() throws ParseException {
  944. return super.fetchIfNeeded();
  945. }
  946. //region Third party authentication
  947. /**
  948. * Registers a third party authentication callback.
  949. * <p />
  950. * <strong>Note:</strong> This shouldn't be called directly unless developing a third party authentication
  951. * library.
  952. *
  953. * @param authType The name of the third party authentication source.
  954. * @param callback The third party authentication callback to be registered.
  955. *
  956. * @see AuthenticationCallback
  957. */
  958. public static void registerAuthenticationCallback(
  959. String authType, AuthenticationCallback callback) {
  960. getAuthenticationManager().register(authType, callback);
  961. }
  962. /**
  963. * Logs in a user with third party authentication credentials.
  964. * <p />
  965. * <strong>Note:</strong> This shouldn't be called directly unless developing a third party authentication
  966. * library.
  967. *
  968. * @param authType The name of the third party authentication source.
  969. * @param authData The user credentials of the third party authentication source.
  970. * @return A {@code Task} is resolved when logging in completes.
  971. *
  972. * @see AuthenticationCallback
  973. */
  974. public static Task<ParseUser> logInWithInBackground(
  975. final String authType, final Map<String, String> authData) {
  976. if (authType == null) {
  977. throw new IllegalArgumentException("Invalid authType: " + null);
  978. }
  979. final Continuation<Void, Task<ParseUser>> logInWithTask = new Continuation<Void, Task<ParseUser>>() {
  980. @Override
  981. public Task<ParseUser> then(Task<Void> task) throws Exception {
  982. return getUserController().logInAsync(authType, authData).onSuccessTask(new Continuation<ParseUser.State, Task<ParseUser>>() {
  983. @Override
  984. public Task<ParseUser> then(Task<ParseUser.State> task) throws Exception {
  985. ParseUser.State result = task.getResult();
  986. final ParseUser user = ParseObject.from(result);
  987. return saveCurrentUserAsync(user).onSuccess(new Continuation<Void, ParseUser>() {
  988. @Override
  989. public ParseUser then(Task<Void> task) throws Exception {
  990. return user;
  991. }
  992. });
  993. }
  994. });
  995. }
  996. };
  997. // Handle claiming of user.
  998. return getCurrentUserController().getAsync(false).onSuccessTask(new Continuation<ParseUser, Task<ParseUser>>() {
  999. @Override
  1000. public Task<ParseUser> then(Task<ParseUser> task) throws Exception {
  1001. final ParseUser user = task.getResult();
  1002. if (user != null) {
  1003. synchronized (user.mutex) {
  1004. if (ParseAnonymousUtils.isLinked(user)) {
  1005. if (user.isLazy()) {
  1006. final Map<String, String> oldAnonymousData =
  1007. user.getAuthData(ParseAnonymousUtils.AUTH_TYPE);
  1008. return user.taskQueue.enqueue(new Continuation<Void, Task<ParseUser>>() {
  1009. @Override
  1010. public Task<ParseUser> then(final Task<Void> toAwait) throws Exception {
  1011. return toAwait.continueWithTask(new Continuation<Void, Task<Void>>() {
  1012. @Override
  1013. public Task<Void> then(Task<Void> task) throws Exception {
  1014. synchronized (user.mutex) {
  1015. // Replace any anonymity with the new linked authData.
  1016. user.stripAnonymity();
  1017. user.putAuthData(authType, authData);
  1018. return user.resolveLazinessAsync(task);
  1019. }
  1020. }
  1021. }).continueWithTask(new Continuation<Void, Task<ParseUser>>() {
  1022. @Override
  1023. public Task<ParseUser> then(Task<Void> task) throws Exception {
  1024. synchronized (user.mutex) {
  1025. if (task.isFaulted()) {
  1026. user.removeAuthData(authType);
  1027. user.restoreAnonymity(oldAnonymousData);
  1028. return Task.forError(task.getError());
  1029. }
  1030. if (task.isCancelled()) {
  1031. return Task.cancelled();
  1032. }
  1033. return Task.forResult(user);
  1034. }
  1035. }
  1036. });
  1037. }
  1038. });
  1039. } else {
  1040. // Try to link the current user with third party user, unless a user is already linked
  1041. // to that third party user, then we'll just create a new user and link it with the
  1042. // third party user. New users will not be linked to the previous user's data.
  1043. return user.linkWithInBackground(authType, authData)
  1044. .continueWithTask(new Continuation<Void, Task<ParseUser>>() {
  1045. @Override
  1046. public Task<ParseUser> then(Task<Void> task) throws Exception {
  1047. if (task.isFaulted()) {
  1048. Exception error = task.getError();
  1049. if (error instanceof ParseException
  1050. && ((ParseException) error).getCode() == ParseException.ACCOUNT_ALREADY_LINKED) {
  1051. // An account that's linked to the given authData already exists, so log in
  1052. // instead of trying to claim.
  1053. return Task.<Void>forResult(null).continueWithTask(logInWithTask);
  1054. }
  1055. }
  1056. if (task.isCancelled()) {
  1057. return Task.cancelled();
  1058. }
  1059. return Task.forResult(user);
  1060. }
  1061. });
  1062. }
  1063. }
  1064. }
  1065. }
  1066. return Task.<Void>forResult(null).continueWithTask(logInWithTask);
  1067. }
  1068. });
  1069. }
  1070. /**
  1071. * Indicates whether this user is linked with a third party authentication source.
  1072. * <p />
  1073. * <strong>Note:</strong> This shouldn't be called directly unless developing a third party authentication
  1074. * library.
  1075. *
  1076. * @param authType The name of the third party authentication source.
  1077. * @return {@code true} if linked, otherwise {@code false}.
  1078. *
  1079. * @see AuthenticationCallback
  1080. */
  1081. public boolean isLinked(String authType) {
  1082. Map<String, Map<String, String>> authData = getAuthData();
  1083. return authData.containsKey(authType) && authData.get(authType) != null;
  1084. }
  1085. /**
  1086. * Ensures that all auth sources have auth data (e.g. access tokens, etc.) that matches this
  1087. * user.
  1088. */
  1089. /* package */ Task<Void> synchronizeAllAuthDataAsync() {
  1090. Map<String, Map<String, String>> authData;
  1091. synchronized (mutex) {
  1092. if (!isCurrentUser()) {
  1093. return Task.forResult(null);
  1094. }
  1095. authData = getAuthData();
  1096. }
  1097. List<Task<Void>> tasks = new ArrayList<>(authData.size());
  1098. for (String authType : authData.keySet()) {
  1099. tasks.add(synchronizeAuthDataAsync(authType));
  1100. }
  1101. return Task.whenAll(tasks);
  1102. }
  1103. /* package */ Task<Void> synchronizeAuthDataAsync(String authType) {
  1104. Map<String, String> authData;
  1105. synchronized (mutex) {
  1106. if (!isCurrentUser()) {
  1107. return Task.forResult(null);
  1108. }
  1109. authData = getAuthData(authType);
  1110. }
  1111. return synchronizeAuthDataAsync(getAuthenticationManager(), authType, authData);
  1112. }
  1113. private Task<Void> synchronizeAuthDataAsync(
  1114. ParseAuthenticationManager manager, final String authType, Map<String, String> authData) {
  1115. return manager.restoreAuthenticationAsync(authType, authData).continueWithTask(new Continuation<Boolean, Task<Void>>() {
  1116. @Override
  1117. public Task<Void> then(Task<Boolean> task) throws Exception {
  1118. boolean success = !task.isFaulted() && task.getResult();
  1119. if (!success) {
  1120. return unlinkFromInBackground(authType);
  1121. }
  1122. return task.makeVoid();
  1123. }
  1124. });
  1125. }
  1126. private Task<Void> linkWithAsync(
  1127. final String authType,
  1128. final Map<String, String> authData,
  1129. final Task<Void> toAwait,
  1130. final String sessionToken) {
  1131. synchronized (mutex) {
  1132. final boolean isLazy = isLazy();
  1133. final Map<String, String> oldAnonymousData = getAuthData(ParseAnonymousUtils.AUTH_TYPE);
  1134. stripAnonymity();
  1135. putAuthData(authType, authData);
  1136. return saveAsync(sessionToken, isLazy, toAwait).continueWithTask(new Continuation<Void, Task<Void>>() {
  1137. @Override
  1138. public Task<Void> then(Task<Void> task) throws Exception {
  1139. synchronized (mutex) {
  1140. if (task.isFaulted() || task.isCancelled()) {
  1141. restoreAnonymity(oldAnonymousData);
  1142. return task;
  1143. }
  1144. return synchronizeAuthDataAsync(authType);
  1145. }
  1146. }
  1147. });
  1148. }
  1149. }
  1150. private Task<Void> linkWithAsync(
  1151. final String authType,
  1152. final Map<String, String> authData,
  1153. final String sessionToken) {
  1154. return taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
  1155. @Override
  1156. public Task<Void> then(Task<Void> task) throws Exception {
  1157. return linkWithAsync(authType, authData, task, sessionToken);
  1158. }
  1159. });
  1160. }
  1161. /**
  1162. * Links this user to a third party authentication source.
  1163. * <p />
  1164. * <strong>Note:</strong> This shouldn't be called directly unless developing a third party authentication
  1165. * library.
  1166. *
  1167. * @param authType The name of the third party authentication source.
  1168. * @param authData The user credentials of the third party authentication source.
  1169. * @return A {@code Task} is resolved when linking completes.
  1170. *
  1171. * @see AuthenticationCallback
  1172. */
  1173. public Task<Void> linkWithInBackground(
  1174. String authType, Map<String, String> authData) {
  1175. if (authType == null) {
  1176. throw new IllegalArgumentException("Invalid authType: " + null);
  1177. }
  1178. return linkWithAsync(authType, authData, getSessionToken());
  1179. }
  1180. /**
  1181. * Unlinks this user from a third party authentication source.
  1182. * <p />
  1183. * <strong>Note:</strong> This shouldn't be called directly unless developing a third party authentication
  1184. * library.
  1185. *
  1186. * @param authType The name of the third party authentication source.
  1187. * @return A {@code Task} is resolved when unlinking completes.
  1188. *
  1189. * @see AuthenticationCallback
  1190. */
  1191. public Task<Void> unlinkFromInBackground(final String authType) {
  1192. if (authType == null) {
  1193. return Task.forResult(null);
  1194. }
  1195. synchronized (mutex) {
  1196. if (!getAuthData().containsKey(authType)) {
  1197. return Task.forResult(null);
  1198. }
  1199. putAuthData(authType, null);
  1200. }
  1201. return saveInBackground();
  1202. }
  1203. //endregion
  1204. /**
  1205. * Try to resolve a lazy user.
  1206. *
  1207. * If {@code authData} is empty, we'll treat this just as a SignUp. Otherwise, we'll
  1208. * treat this as a SignUpOrLogIn. We'll merge the server result with this user, only if LDS is not
  1209. * enabled.
  1210. *
  1211. * @param toAwait {@code Task} to wait for completion before running.
  1212. * @return A {@code Task} that will resolve to the current user. If this is a SignUp it'll be this
  1213. * {@code ParseUser} instance, otherwise it'll be a new {@code ParseUser} instance.
  1214. */
  1215. /* package for tests */ Task<Void> resolveLazinessAsync(Task<Void> toAwait) {
  1216. synchronized (mutex) {
  1217. if (getAuthData().size() == 0) { // TODO(grantland): Could we just check isDirty(KEY_AUTH_DATA)?
  1218. // If there are no linked services, treat this as a SignUp.
  1219. return signUpAsync(toAwait);
  1220. }
  1221. final ParseOperationSet operations = startSave();
  1222. // Otherwise, treat this as a SignUpOrLogIn
  1223. return toAwait.onSuccessTask(new Continuation<Void, Task<Void>>() {
  1224. @Override
  1225. public Task<Void> then(Task<Void> task) throws Exception {
  1226. return getUserController().logInAsync(getState(), operations).onSuccessTask(new Continuation<ParseUser.State, Task<Void>>() {
  1227. @Override
  1228. public Task<Void> then(Task<ParseUser.State> task) throws Exception {
  1229. final ParseUser.State result = task.getResult();
  1230. Task<ParseUser.State> resultTask;
  1231. // We can't merge this user with the server if this is a LogIn because LDS might
  1232. // already be keeping track of the servers objectId.
  1233. if (Parse.isLocalDatastoreEnabled() && !result.isNew()) {
  1234. resultTask = Task.forResult(result);
  1235. } else {
  1236. resultTask = handleSaveResultAsync(result,
  1237. operations).onSuccess(new Continuation<Void, ParseUser.State>() {
  1238. @Override
  1239. public ParseUser.State then(Task<Void> task) throws Exception {
  1240. return result;
  1241. }
  1242. });
  1243. }
  1244. return resultTask.onSuccessTask(new Continuation<ParseUser.State, Task<Void>>() {
  1245. @Override
  1246. public Task<Void> then(Task<ParseUser.State> task) throws Exception {
  1247. ParseUser.State result = task.getResult();
  1248. if (!result.isNew()) {
  1249. // If the result is not a new user, treat this as a fresh logIn with complete
  1250. // serverData, and switch the current user to the new user.
  1251. final ParseUser newUser = ParseObjec