Repository: azuread/microsoft-authentication-library-for-android
Issue: Cache Migration not performed in below scenarios. See Description.

- Single Account
- Multiple Account - getAccount(id)
- Multiple Account - getAccounts - if an interactive call was already made post adoption of MSAL

Changed files:
msal/src/main/java/com/microsoft/identity/client/MultipleAccountPublicClientApplication.java
msal/src/main/java/com/microsoft/identity/client/SingleAccountPublicClientApplication.java
msal/src/main/java/com/microsoft/identity/client/PublicClientApplication.java

diff --git a/msal/src/main/java/com/microsoft/identity/client/MultipleAccountPublicClientApplication.java b/msal/src/main/java/com/microsoft/identity/client/MultipleAccountPublicClientApplication.java
index 49dd50a2..58d11be9 100644
--- a/msal/src/main/java/com/microsoft/identity/client/MultipleAccountPublicClientApplication.java
+++ b/msal/src/main/java/com/microsoft/identity/client/MultipleAccountPublicClientApplication.java
@@ -35,30 +35,19 @@ import com.microsoft.identity.client.internal.AsyncResult;
 import com.microsoft.identity.client.internal.controllers.MSALControllerFactory;
 import com.microsoft.identity.client.internal.controllers.MsalExceptionAdapter;
 import com.microsoft.identity.client.internal.controllers.OperationParametersAdapter;
-import com.microsoft.identity.common.adal.internal.cache.IStorageHelper;
-import com.microsoft.identity.common.adal.internal.cache.StorageHelper;
 import com.microsoft.identity.common.exception.BaseException;
 import com.microsoft.identity.common.internal.cache.ICacheRecord;
-import com.microsoft.identity.common.internal.cache.IShareSingleSignOnState;
-import com.microsoft.identity.common.internal.cache.ISharedPreferencesFileManager;
-import com.microsoft.identity.common.internal.cache.SharedPreferencesFileManager;
 import com.microsoft.identity.common.internal.controllers.CommandCallback;
 import com.microsoft.identity.common.internal.controllers.CommandDispatcher;
 import com.microsoft.identity.common.internal.controllers.LoadAccountCommand;
 import com.microsoft.identity.common.internal.controllers.RemoveAccountCommand;
 import com.microsoft.identity.common.internal.dto.AccountRecord;
 import com.microsoft.identity.common.internal.eststelemetry.PublicApiId;
-import com.microsoft.identity.common.internal.migration.AdalMigrationAdapter;
 import com.microsoft.identity.common.internal.migration.TokenMigrationCallback;
-import com.microsoft.identity.common.internal.migration.TokenMigrationUtility;
-import com.microsoft.identity.common.internal.providers.microsoft.MicrosoftAccount;
-import com.microsoft.identity.common.internal.providers.microsoft.MicrosoftRefreshToken;
 import com.microsoft.identity.common.internal.request.OperationParameters;
 import com.microsoft.identity.common.internal.result.ResultFuture;
 
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import static com.microsoft.identity.client.internal.MsalUtils.throwOnMainThread;
 
@@ -110,92 +99,43 @@ public class MultipleAccountPublicClientApplication extends PublicClientApplicat
      */
     private void getAccountsInternal(@NonNull final LoadAccountsCallback callback,
                                      @NonNull final String publicApiId) {
-        final String methodName = ":getAccounts";
-        final List<ICacheRecord> accounts =
-                mPublicClientConfiguration
-                        .getOAuth2TokenCache()
-                        .getAccountsWithAggregatedAccountData(
-                                null, // * wildcard
-                                mPublicClientConfiguration.getClientId()
-                        );
-
-        final Handler handler;
-
-        if (null != Looper.myLooper() && Looper.getMainLooper() != Looper.myLooper()) {
-            handler = new Handler(Looper.myLooper());
-        } else {
-            handler = new Handler(Looper.getMainLooper());
-        }
-
-        if (accounts.isEmpty()) {
-            // Create the SharedPreferencesFileManager for the legacy accounts/credentials
-            final IStorageHelper storageHelper = new StorageHelper(mPublicClientConfiguration.getAppContext());
-            final ISharedPreferencesFileManager sharedPreferencesFileManager =
-                    new SharedPreferencesFileManager(
-                            mPublicClientConfiguration.getAppContext(),
-                            "com.microsoft.aad.adal.cache",
-                            storageHelper
-                    );
+        TokenMigrationCallback migrationCallback = new TokenMigrationCallback() {
+            @Override
+            public void onMigrationFinished(int numberOfAccountsMigrated) {
+                final Handler handler;
 
-            // Load the old TokenCacheItems as key/value JSON
-            final Map<String, String> credentials = sharedPreferencesFileManager.getAll();
+                if (null != Looper.myLooper() && Looper.getMainLooper() != Looper.myLooper()) {
+                    handler = new Handler(Looper.myLooper());
+                } else {
+                    handler = new Handler(Looper.getMainLooper());
+                }
 
-            final Map<String, String> redirects = new HashMap<>();
-            redirects.put(
-                    mPublicClientConfiguration.getClientId(), // Our client id
-                    mPublicClientConfiguration.getRedirectUri() // Our redirect uri
-            );
+                try {
+                    final OperationParameters params = OperationParametersAdapter.createOperationParameters(mPublicClientConfiguration, mPublicClientConfiguration.getOAuth2TokenCache());
+                    final LoadAccountCommand loadAccountCommand = new LoadAccountCommand(
+                            params,
+                            MSALControllerFactory.getAllControllers(
+                                    mPublicClientConfiguration.getAppContext(),
+                                    params.getAuthority(),
+                                    mPublicClientConfiguration
+                            ),
+                            getLoadAccountsCallback(callback)
+                    );
 
-            new TokenMigrationUtility<MicrosoftAccount, MicrosoftRefreshToken>()._import(
-                    new AdalMigrationAdapter(
-                            mPublicClientConfiguration.getAppContext(),
-                            redirects,
-                            false
-                    ),
-                    credentials,
-                    (IShareSingleSignOnState<MicrosoftAccount, MicrosoftRefreshToken>) mPublicClientConfiguration.getOAuth2TokenCache(),
-                    new TokenMigrationCallback() {
+                    loadAccountCommand.setPublicApiId(publicApiId);
+                    CommandDispatcher.submitSilent(loadAccountCommand);
+                } catch (final MsalClientException e) {
+                    handler.post(new Runnable() {
                         @Override
-                        public void onMigrationFinished(int numberOfAccountsMigrated) {
-                            final String extendedMethodName = ":onMigrationFinished";
-                            com.microsoft.identity.common.internal.logging.Logger.info(
-                                    TAG + methodName + extendedMethodName,
-                                    "Migrated [" + numberOfAccountsMigrated + "] accounts"
-                            );
+                        public void run() {
+                            callback.onError(e);
                         }
-                    }
-            );
-        } else {
-            // The cache contains items - mark migration as complete
-            new AdalMigrationAdapter(
-                    mPublicClientConfiguration.getAppContext(),
-                    null, // unused for this path
-                    false
-            ).setMigrationStatus(true);
-        }
-
-        try {
-            final OperationParameters params = OperationParametersAdapter.createOperationParameters(mPublicClientConfiguration, mPublicClientConfiguration.getOAuth2TokenCache());
-            final LoadAccountCommand loadAccountCommand = new LoadAccountCommand(
-                    params,
-                    MSALControllerFactory.getAllControllers(
-                            mPublicClientConfiguration.getAppContext(),
-                            params.getAuthority(),
-                            mPublicClientConfiguration
-                    ),
-                    getLoadAccountsCallback(callback)
-            );
-
-            loadAccountCommand.setPublicApiId(publicApiId);
-            CommandDispatcher.submitSilent(loadAccountCommand);
-        } catch (final MsalClientException e) {
-            handler.post(new Runnable() {
-                @Override
-                public void run() {
-                    callback.onError(e);
+                    });
                 }
-            });
-        }
+            }
+        };
+
+        performMigration(migrationCallback);
     }
 
     @Override
@@ -249,87 +189,94 @@ public class MultipleAccountPublicClientApplication extends PublicClientApplicat
     private void getAccountInternal(@NonNull final String identifier,
                                     @NonNull final GetAccountCallback callback,
                                     @NonNull final String publicApiId) {
-        final String methodName = ":getAccount";
-
-        com.microsoft.identity.common.internal.logging.Logger.verbose(
-                TAG + methodName,
-                "Get account with the identifier."
-        );
-
-        try {
-            final OperationParameters params = OperationParametersAdapter.createOperationParameters(mPublicClientConfiguration, mPublicClientConfiguration.getOAuth2TokenCache());
-            final LoadAccountCommand loadAccountCommand = new LoadAccountCommand(
-                    params,
-                    MSALControllerFactory.getAllControllers(
-                            mPublicClientConfiguration.getAppContext(),
-                            params.getAuthority(),
-                            mPublicClientConfiguration
-                    ),
-                    new CommandCallback<List<ICacheRecord>, BaseException>() {
-                        @Override
-                        public void onTaskCompleted(final List<ICacheRecord> result) {
-                            if (null == result || result.size() == 0) {
-                                com.microsoft.identity.common.internal.logging.Logger.verbose(
-                                        TAG + methodName,
-                                        "No account found.");
-                                callback.onTaskCompleted(null);
-                            } else {
-                                // First, transform the result into IAccount + TenantProfile form
-                                final List<IAccount>
-                                        accounts = AccountAdapter.adapt(result);
-
-                                final String trimmedIdentifier = identifier.trim();
-
-                                // Evaluation precedence...
-                                //     1. home_account_id
-                                //     2. local_account_id
-                                //     3. username
-                                //     4. Give up.
-
-                                final AccountMatcher accountMatcher = new AccountMatcher(
-                                        homeAccountMatcher,
-                                        localAccountMatcher,
-                                        usernameMatcher
-                                );
-
-                                for (final IAccount account : accounts) {
-                                    if (accountMatcher.matches(trimmedIdentifier, account)) {
-                                        callback.onTaskCompleted(account);
-                                        return;
+        TokenMigrationCallback migrationCallback = new TokenMigrationCallback() {
+            @Override
+            public void onMigrationFinished(int numberOfAccountsMigrated) {
+                final String methodName = ":getAccount";
+
+                com.microsoft.identity.common.internal.logging.Logger.verbose(
+                        TAG + methodName,
+                        "Get account with the identifier."
+                );
+
+                try {
+                    final OperationParameters params = OperationParametersAdapter.createOperationParameters(mPublicClientConfiguration, mPublicClientConfiguration.getOAuth2TokenCache());
+                    final LoadAccountCommand loadAccountCommand = new LoadAccountCommand(
+                            params,
+                            MSALControllerFactory.getAllControllers(
+                                    mPublicClientConfiguration.getAppContext(),
+                                    params.getAuthority(),
+                                    mPublicClientConfiguration
+                            ),
+                            new CommandCallback<List<ICacheRecord>, BaseException>() {
+                                @Override
+                                public void onTaskCompleted(final List<ICacheRecord> result) {
+                                    if (null == result || result.size() == 0) {
+                                        com.microsoft.identity.common.internal.logging.Logger.verbose(
+                                                TAG + methodName,
+                                                "No account found.");
+                                        callback.onTaskCompleted(null);
+                                    } else {
+                                        // First, transform the result into IAccount + TenantProfile form
+                                        final List<IAccount>
+                                                accounts = AccountAdapter.adapt(result);
+
+                                        final String trimmedIdentifier = identifier.trim();
+
+                                        // Evaluation precedence...
+                                        //     1. home_account_id
+                                        //     2. local_account_id
+                                        //     3. username
+                                        //     4. Give up.
+
+                                        final AccountMatcher accountMatcher = new AccountMatcher(
+                                                homeAccountMatcher,
+                                                localAccountMatcher,
+                                                usernameMatcher
+                                        );
+
+                                        for (final IAccount account : accounts) {
+                                            if (accountMatcher.matches(trimmedIdentifier, account)) {
+                                                callback.onTaskCompleted(account);
+                                                return;
+                                            }
+                                        }
+
+                                        callback.onTaskCompleted(null);
                                     }
                                 }
 
-                                callback.onTaskCompleted(null);
-                            }
-                        }
+                                @Override
+                                public void onError(final BaseException exception) {
+                                    com.microsoft.identity.common.internal.logging.Logger.error(
+                                            TAG + methodName,
+                                            exception.getMessage(),
+                                            exception
+                                    );
+                                    callback.onError(MsalExceptionAdapter.msalExceptionFromBaseException(exception));
+                                }
 
-                        @Override
-                        public void onError(final BaseException exception) {
-                            com.microsoft.identity.common.internal.logging.Logger.error(
-                                    TAG + methodName,
-                                    exception.getMessage(),
-                                    exception
-                            );
-                            callback.onError(MsalExceptionAdapter.msalExceptionFromBaseException(exception));
-                        }
+                                @Override
+                                public void onCancel() {
 
-                        @Override
-                        public void onCancel() {
+                                }
+                            }
+                    );
 
-                        }
-                    }
-            );
+                    loadAccountCommand.setPublicApiId(publicApiId);
+                    CommandDispatcher.submitSilent(loadAccountCommand);
+                } catch (final MsalClientException e) {
+                    com.microsoft.identity.common.internal.logging.Logger.error(
+                            TAG + methodName,
+                            e.getMessage(),
+                            e
+                    );
+                    callback.onError(e);
+                }
+            }
+        };
 
-            loadAccountCommand.setPublicApiId(publicApiId);
-            CommandDispatcher.submitSilent(loadAccountCommand);
-        } catch (final MsalClientException e) {
-            com.microsoft.identity.common.internal.logging.Logger.error(
-                    TAG + methodName,
-                    e.getMessage(),
-                    e
-            );
-            callback.onError(e);
-        }
+        performMigration(migrationCallback);
     }
 
     @Override
diff --git a/msal/src/main/java/com/microsoft/identity/client/PublicClientApplication.java b/msal/src/main/java/com/microsoft/identity/client/PublicClientApplication.java
index 35510d41..97f32a4b 100644
--- a/msal/src/main/java/com/microsoft/identity/client/PublicClientApplication.java
+++ b/msal/src/main/java/com/microsoft/identity/client/PublicClientApplication.java
@@ -48,6 +48,8 @@ import com.microsoft.identity.client.internal.AsyncResult;
 import com.microsoft.identity.client.internal.controllers.MSALControllerFactory;
 import com.microsoft.identity.client.internal.controllers.MsalExceptionAdapter;
 import com.microsoft.identity.client.internal.controllers.OperationParametersAdapter;
+import com.microsoft.identity.common.adal.internal.cache.IStorageHelper;
+import com.microsoft.identity.common.adal.internal.cache.StorageHelper;
 import com.microsoft.identity.common.adal.internal.tokensharing.TokenShareUtility;
 import com.microsoft.identity.common.exception.BaseException;
 import com.microsoft.identity.common.exception.ClientException;
@@ -57,8 +59,11 @@ import com.microsoft.identity.common.internal.authorities.Authority;
 import com.microsoft.identity.common.internal.authorities.AzureActiveDirectoryAuthority;
 import com.microsoft.identity.common.internal.authorities.AzureActiveDirectoryB2CAuthority;
 import com.microsoft.identity.common.internal.cache.ICacheRecord;
+import com.microsoft.identity.common.internal.cache.IShareSingleSignOnState;
+import com.microsoft.identity.common.internal.cache.ISharedPreferencesFileManager;
 import com.microsoft.identity.common.internal.cache.MsalOAuth2TokenCache;
 import com.microsoft.identity.common.internal.cache.SchemaUtil;
+import com.microsoft.identity.common.internal.cache.SharedPreferencesFileManager;
 import com.microsoft.identity.common.internal.controllers.BaseController;
 import com.microsoft.identity.common.internal.controllers.CommandCallback;
 import com.microsoft.identity.common.internal.controllers.CommandDispatcher;
@@ -70,8 +75,13 @@ import com.microsoft.identity.common.internal.dto.AccountRecord;
 import com.microsoft.identity.common.internal.eststelemetry.EstsTelemetry;
 import com.microsoft.identity.common.internal.eststelemetry.PublicApiId;
 import com.microsoft.identity.common.internal.logging.Logger;
+import com.microsoft.identity.common.internal.migration.AdalMigrationAdapter;
+import com.microsoft.identity.common.internal.migration.TokenMigrationCallback;
+import com.microsoft.identity.common.internal.migration.TokenMigrationUtility;
 import com.microsoft.identity.common.internal.net.HttpRequest;
 import com.microsoft.identity.common.internal.net.cache.HttpCache;
+import com.microsoft.identity.common.internal.providers.microsoft.MicrosoftAccount;
+import com.microsoft.identity.common.internal.providers.microsoft.MicrosoftRefreshToken;
 import com.microsoft.identity.common.internal.providers.microsoft.azureactivedirectory.AzureActiveDirectory;
 import com.microsoft.identity.common.internal.providers.oauth2.OAuth2TokenCache;
 import com.microsoft.identity.common.internal.request.AcquireTokenOperationParameters;
@@ -84,6 +94,7 @@ import com.microsoft.identity.msal.BuildConfig;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
@@ -1821,4 +1832,41 @@ public class PublicClientApplication implements IPublicClientApplication, IToken
             throw result.getException();
         }
     }
+
+    void performMigration(@NonNull final TokenMigrationCallback callback) {
+        final Map<String, String> redirects = new HashMap<>();
+        redirects.put(
+                mPublicClientConfiguration.getClientId(), // Our client id
+                mPublicClientConfiguration.getRedirectUri() // Our redirect uri
+        );
+
+        final AdalMigrationAdapter adalMigrationAdapter = new AdalMigrationAdapter(
+                mPublicClientConfiguration.getAppContext(),
+                redirects,
+                false
+        );
+
+        if (adalMigrationAdapter.getMigrationStatus()) {
+            callback.onMigrationFinished(0);
+        } else {
+            // Create the SharedPreferencesFileManager for the legacy accounts/credentials
+            final IStorageHelper storageHelper = new StorageHelper(mPublicClientConfiguration.getAppContext());
+            final ISharedPreferencesFileManager sharedPreferencesFileManager =
+                    new SharedPreferencesFileManager(
+                            mPublicClientConfiguration.getAppContext(),
+                            "com.microsoft.aad.adal.cache",
+                            storageHelper
+                    );
+
+            // Load the old TokenCacheItems as key/value JSON
+            final Map<String, String> credentials = sharedPreferencesFileManager.getAll();
+
+            new TokenMigrationUtility<MicrosoftAccount, MicrosoftRefreshToken>()._import(
+                    adalMigrationAdapter,
+                    credentials,
+                    (IShareSingleSignOnState<MicrosoftAccount, MicrosoftRefreshToken>) mPublicClientConfiguration.getOAuth2TokenCache(),
+                    callback
+            );
+        }
+    }
 }
diff --git a/msal/src/main/java/com/microsoft/identity/client/SingleAccountPublicClientApplication.java b/msal/src/main/java/com/microsoft/identity/client/SingleAccountPublicClientApplication.java
index c967c603..b7242626 100644
--- a/msal/src/main/java/com/microsoft/identity/client/SingleAccountPublicClientApplication.java
+++ b/msal/src/main/java/com/microsoft/identity/client/SingleAccountPublicClientApplication.java
@@ -47,6 +47,7 @@ import com.microsoft.identity.common.internal.controllers.GetCurrentAccountComma
 import com.microsoft.identity.common.internal.controllers.RemoveCurrentAccountCommand;
 import com.microsoft.identity.common.internal.dto.AccountRecord;
 import com.microsoft.identity.common.internal.eststelemetry.PublicApiId;
+import com.microsoft.identity.common.internal.migration.TokenMigrationCallback;
 import com.microsoft.identity.common.internal.request.OperationParameters;
 import com.microsoft.identity.common.internal.result.ILocalAuthenticationResult;
 import com.microsoft.identity.common.internal.result.ResultFuture;
@@ -92,43 +93,50 @@ public class SingleAccountPublicClientApplication extends PublicClientApplicatio
 
     private void getCurrentAccountAsyncInternal(@NonNull final CurrentAccountCallback callback,
                                                 @NonNull final String publicApiId) {
-        final OperationParameters params = OperationParametersAdapter.createOperationParameters(mPublicClientConfiguration, mPublicClientConfiguration.getOAuth2TokenCache());
-        final BaseController controller;
-        try {
-            controller = MSALControllerFactory.getDefaultController(
-                    mPublicClientConfiguration.getAppContext(),
-                    params.getAuthority(),
-                    mPublicClientConfiguration);
-        } catch (MsalClientException e) {
-            callback.onError(e);
-            return;
-        }
-
-        final GetCurrentAccountCommand command = new GetCurrentAccountCommand(
-                params,
-                controller,
-                new CommandCallback<List<ICacheRecord>, BaseException>() {
-                    @Override
-                    public void onTaskCompleted(final List<ICacheRecord> result) {
-                        // To simplify the logic, if more than one account is returned, the first account will be picked.
-                        // We do not support switching from MULTIPLE to SINGLE.
-                        // See getAccountFromICacheRecordList() for more details.
-                        checkCurrentAccountNotifyCallback(callback, result);
-                    }
-
-                    @Override
-                    public void onError(final BaseException exception) {
-                        callback.onError(MsalExceptionAdapter.msalExceptionFromBaseException(exception));
-                    }
+        TokenMigrationCallback migrationCallback = new TokenMigrationCallback() {
+            @Override
+            public void onMigrationFinished(int numberOfAccountsMigrated) {
+                final OperationParameters params = OperationParametersAdapter.createOperationParameters(mPublicClientConfiguration, mPublicClientConfiguration.getOAuth2TokenCache());
+                final BaseController controller;
+                try {
+                    controller = MSALControllerFactory.getDefaultController(
+                            mPublicClientConfiguration.getAppContext(),
+                            params.getAuthority(),
+                            mPublicClientConfiguration);
+                } catch (MsalClientException e) {
+                    callback.onError(e);
+                    return;
+                }
 
-                    @Override
-                    public void onCancel() {
-                        //Do nothing
-                    }
-                });
+                final GetCurrentAccountCommand command = new GetCurrentAccountCommand(
+                        params,
+                        controller,
+                        new CommandCallback<List<ICacheRecord>, BaseException>() {
+                            @Override
+                            public void onTaskCompleted(final List<ICacheRecord> result) {
+                                // To simplify the logic, if more than one account is returned, the first account will be picked.
+                                // We do not support switching from MULTIPLE to SINGLE.
+                                // See getAccountFromICacheRecordList() for more details.
+                                checkCurrentAccountNotifyCallback(callback, result);
+                            }
+
+                            @Override
+                            public void onError(final BaseException exception) {
+                                callback.onError(MsalExceptionAdapter.msalExceptionFromBaseException(exception));
+                            }
+
+                            @Override
+                            public void onCancel() {
+                                //Do nothing
+                            }
+                        });
+
+                command.setPublicApiId(publicApiId);
+                CommandDispatcher.submitSilent(command);
+            }
+        };
 
-        command.setPublicApiId(publicApiId);
-        CommandDispatcher.submitSilent(command);
+        performMigration(migrationCallback);
     }
 
     @Override