This commit is contained in:
Andrew Peacock 2019-01-25 15:49:16 -08:00
Родитель d0dbeeba4d
Коммит dfeff9818e
6 изменённых файлов: 143 добавлений и 138 удалений

Просмотреть файл

@ -51,7 +51,6 @@ public class Account {
private SigninHelperAccount mSignInHelper;
private ConnectedDevicesAccount mAccount;
private AccountRegistrationState mState;
private GcmNotificationReceiver mNotificationReceiver;
private ConnectedDevicesPlatform mPlatform;
private RemoteSystemAppRegistration mRegistration;
// endregion
@ -101,86 +100,68 @@ public class Account {
// For scenario 1, initialize our subcomponents.
// For scenario 2, subcomponents will be initialized after InitializeAccountAsync registers the account with the SDK.
// For scenario 3, InitializeAccountAsync will unregister the account and subcomponents will never be initialized.
AsyncOperation<Boolean> returnOperation = new AsyncOperation<Boolean>();
switch (mState) {
// Scenario 1
case IN_APP_CACHE_AND_SDK_CACHE:
initializeSubcomponents(context);
// This account has been prepared
Log.e(TAG, "[CDP_TRACES] IN_APP_CACHE_AND_SDK_CACHE");
returnOperation.complete(true);
break;
return registerAccountWithSdkAsync();
// Scenario 2
case IN_APP_CACHE_ONLY: {
// Add the this account to the ConnectedDevicesPlatform.AccountManager
mPlatform.getAccountManager().addAccountAsync(mAccount).whenComplete((ConnectedDevicesAddAccountResult result, Throwable throwable) -> {
// Note: If add account fails, retry again at a later time. Any operations that require
// the account cannot be performed until the account has been added successfully.
if (throwable != null) {
Log.e(TAG, "[CDP_TRACES] AddAccount encountered " + throwable);
returnOperation.complete(false);
} else if (result.getStatus() != ConnectedDevicesAccountAddedStatus.SUCCESS) {
Log.e(TAG, "[CDP_TRACES] Failed to add account " + mAccount.getId() + " to the AccountManager due to " + result.getStatus());
returnOperation.complete(false);
} else {
// Set the registration state of this account as in both app and sdk cache
mState = AccountRegistrationState.IN_APP_CACHE_AND_SDK_CACHE;
initializeSubcomponents(context);
// This account has been prepared
Log.e(TAG, "[CDP_TRACES] IN_APP_CACHE_ONLY");
returnOperation.complete(true);
return mPlatform.getAccountManager().addAccountAsync(mAccount).thenComposeAsync((ConnectedDevicesAddAccountResult result) -> {
// We failed to add the account, so exit with a failure to prepare bool
if (result.getStatus() != ConnectedDevicesAccountAddedStatus.SUCCESS) {
Log.e(TAG, "Failed to add account " + mAccount.getId() + " to the AccountManager due to " + result.getStatus());
return AsyncOperation.completedFuture(false);
}
// Set the registration state of this account as in both app and sdk cache
mState = AccountRegistrationState.IN_APP_CACHE_AND_SDK_CACHE;
initializeSubcomponents(context);
return registerAccountWithSdkAsync();
});
break;
}
// Scenario 3
case IN_SDK_CACHE_ONLY:
// Remove the account from the SDK since the app has no knowledge of it
mPlatform.getAccountManager().removeAccountAsync(mAccount);
// This account could not be prepared
Log.e(TAG, "[CDP_TRACES] IN_SDK_CACHE_ONLY");
returnOperation.complete(false);
break;
return AsyncOperation.completedFuture(false);
default:
// This account could not be prepared
return AsyncOperation.completedFuture(false);
}
return returnOperation;
}
/**
* Performs non-blocking registrations for this account, which are
* for notifications then for the relay SDK.
*/
public void registerAccountWithSdkAsync() {
// This needs to return a AsyncOperation that prepareAccountAsync returns
public AsyncOperation<Boolean> registerAccountWithSdkAsync() {
if (mState != AccountRegistrationState.IN_APP_CACHE_AND_SDK_CACHE) {
throw new IllegalStateException("Cannot register this account due to bad state: " + mAccount.getId());
AsyncOperation<Boolean> toReturn = new AsyncOperation<>();
toReturn.completeExceptionally(new IllegalStateException("Cannot register this account due to bad state: " + mAccount.getId()));
return toReturn;
}
mNotificationReceiver.getNotificationRegistrationAsync().whenCompleteAsync((ConnectedDevicesNotificationRegistration notificationRegistration, Throwable notificationThowable) -> {
if (notificationThowable != null) {
Log.e(TAG, "NotificationReceiver.getNotificationRegistrationAsync for account " + mAccount.getId() + " encountered " + notificationThowable);
return;
}
// Grab the shared GCM/FCM notification token from this app's BroadcastReceiver
return GcmNotificationReceiver.getNotificationRegistrationAsync().thenComposeAsync((ConnectedDevicesNotificationRegistration notificationRegistration) -> {
// Perform the registration using the NotificationRegistration
mPlatform.getNotificationRegistrationManager().registerForAccountAsync(mAccount, notificationRegistration)
.whenCompleteAsync((Boolean success, Throwable throwable) -> {
if (throwable != null) {
Log.e(TAG, "NotificationRegistrationManager encountered " + throwable);
} else if (!success) {
return mPlatform.getNotificationRegistrationManager().registerForAccountAsync(mAccount, notificationRegistration)
.thenComposeAsync((Boolean success) -> {
if (!success) {
Log.e(TAG, "Failed to perform notification registration for account: " + mAccount.getId());
} else {
Log.i(TAG, "Successfully performed notification registration for account:" + mAccount.getId());
return AsyncOperation.completedFuture(success);
}
});
});
// Perform the relay SDK registration by saving the RemoteSystemAppRegistration object
mRegistration.saveAsync().whenCompleteAsync((Boolean success, Throwable throwable) -> {
if (throwable != null) {
Log.e(TAG, "Registration saveAsync completed with throwable: " + throwable);
} else {
Log.v(TAG, "RemoteSystemHostRegistration was saved with success: " + success);
}
Log.i(TAG, "Successfully performed notification registration for account:" + mAccount.getId());
// Perform the relay SDK registration by saving the RemoteSystemAppRegistration object
return mRegistration.saveAsync().thenComposeAsync((Boolean saveSuccess) -> {
Log.v(TAG, "RemoteSystemHostRegistration was saved with success: " + saveSuccess);
return AsyncOperation.completedFuture(saveSuccess);
});
});
});
}
@ -193,9 +174,6 @@ public class Account {
throw new IllegalStateException("Cannot initialize subcomponents of this account due to bad state: " + mAccount.getId());
}
// Create the NotificationReceiver
mNotificationReceiver = new GcmNotificationReceiver(context);
// Create our attributes, a timestamp for registration and package,
// used to identity the RemoteSystemApp and sort by newest
Map<String, String> attributes = new TreeMap<String, String>() {

Просмотреть файл

@ -16,7 +16,9 @@ import com.microsoft.connecteddevices.ConnectedDevicesNotificationRegistrationSt
import com.microsoft.connecteddevices.ConnectedDevicesAccessTokenInvalidatedEventArgs;
import com.microsoft.connecteddevices.ConnectedDevicesAccount;
import com.microsoft.connecteddevices.ConnectedDevicesAccountManager;
import com.microsoft.connecteddevices.ConnectedDevicesNotificationRegistration;
import com.microsoft.connecteddevices.ConnectedDevicesAccountType;
import com.microsoft.connecteddevices.ConnectedDevicesNotificationType;
import com.microsoft.connecteddevices.ConnectedDevicesAddAccountResult;
import com.microsoft.connecteddevices.ConnectedDevicesNotificationRegistrationManager;
import com.microsoft.connecteddevices.ConnectedDevicesNotificationRegistrationState;
@ -47,6 +49,7 @@ public class ConnectedDevicesManager {
private String currentAccountId;
private List<Account> mAccounts;
private GcmNotificationReceiver mNotificationReceiver;
private ConnectedDevicesPlatform mPlatform;
private static ConnectedDevicesManager sConnectedDevicesManager;
// endregion
@ -60,6 +63,9 @@ public class ConnectedDevicesManager {
// Initialize list of known accounts
mAccounts = new ArrayList<Account>();
// Create the NotificationReceiver
mNotificationReceiver = new GcmNotificationReceiver(context);
// Create Platform
mPlatform = new ConnectedDevicesPlatform(context);
@ -80,20 +86,17 @@ public class ConnectedDevicesManager {
List<Account> deserializedAccounts = deserializeAccounts(context);
// Finally initialize the accounts. This will refresh registrations when needed, add missing accounts,
// and remove stale accounts from the ConnectedDevicesPlatform.AccountManager.
// and remove stale accounts from the ConnectedDevicesPlatform AccountManager. The AsyncOperation associated
// with all of this asynchronous work need not be waited on as any sub component work will be accomplished
// in the synchronous portion of the call. If your app needs to sequence when other apps can see this app's registration
// (i.e. when RemoteSystemAppRegistration SaveAsync completes) then it would be useful to use the AsyncOperation returned by
// prepareAccountsAsync
prepareAccounts(deserializedAccounts, context);
}
// endregion
// region public static methods
public static synchronized ConnectedDevicesManager getConnectedDevicesManager() {
return sConnectedDevicesManager;
}
public static synchronized ConnectedDevicesManager getOrInitializeConnectedDevicesManager(Context context) {
if (sConnectedDevicesManager == null) {
sConnectedDevicesManager = new ConnectedDevicesManager(context);
}
public static synchronized ConnectedDevicesManager getConnectedDevicesManager(Context context) {
return sConnectedDevicesManager;
}
// endregion
@ -104,27 +107,22 @@ public class ConnectedDevicesManager {
* @param activity Application activity
* @return The async result for when this operation completes
*/
public synchronized AsyncOperation<Void> signInMsa(final Activity activity) {
AsyncOperation<Void> returnOperation = new AsyncOperation<Void>();
public synchronized AsyncOperation<Boolean> signInMsaAsync(final Activity activity) {
// Create a Signin helper Account with a client id for msa, a map of requested scopes to override, and the context
SigninHelperAccount signInHelper = new MSASigninHelperAccount(Secrets.MSA_CLIENT_ID, new ArrayMap<String, String[]>(), (Context)activity);
if (!signInHelper.isSignedIn()) {
Log.i(TAG, "Signin in a MSA account");
// Call signin, which may prompt the user to enter credentials or just retreive a cached token if they exist and are valid
signInHelper.signIn(activity).thenAcceptAsync((ConnectedDevicesAccount account) -> {
// Prepare the account, adding it to the list of app's cached accounts is prepared successfully
prepareAccount(new Account(signInHelper, AccountRegistrationState.IN_APP_CACHE_ONLY, mPlatform), (Context)activity);
returnOperation.complete(null);
});
} else {
if (signInHelper.isSignedIn()) {
Log.i(TAG, "Already signed in with a MSA account");
returnOperation.complete(null);
return AsyncOperation.completedFuture(true);
}
return returnOperation;
Log.i(TAG, "Signin in a MSA account");
// Call signin, which may prompt the user to enter credentials or just retreive a cached token if they exist and are valid
return signInHelper.signIn(activity).thenComposeAsync((ConnectedDevicesAccount account) -> {
// Prepare the account, adding it to the list of app's cached accounts is prepared successfully
return prepareAccountAsync(new Account(signInHelper, AccountRegistrationState.IN_APP_CACHE_ONLY, mPlatform), (Context)activity);
});
}
/**
@ -146,60 +144,42 @@ public class ConnectedDevicesManager {
// Create a NotificationRegistration obect to store all notification information
ConnectedDevicesNotificationRegistration registration = new ConnectedDevicesNotificationRegistration();
notification.setType(ConnectedDevicesNotificationType.GCM);
notification.setToken(token);
notification.setAppId(Secrets.GCM_SENDER_ID);
notification.setAppDisplayName("OneSDK Sample");
registration.setType(ConnectedDevicesNotificationType.GCM);
registration.setToken(token);
registration.setAppId(Secrets.GCM_SENDER_ID);
registration.setAppDisplayName("OneSDK Sample");
Log.i(TAG, "Completing the GcmNotificationReceiver operation with token: " + token);
// For each prepared account, register for notifications
for (Account account : mAccounts) {
registrationManager.registerForAccountAsync(account, notification)
.whenCompleteAsync((Boolean result, Throwable throwable) -> {
registrationManager.registerForAccountAsync(account.getAccount(), registration)
.whenCompleteAsync((Boolean success, Throwable throwable) -> {
if (throwable != null) {
Log.e(TAG, "RegistrationManager registration encountered " + throwable);
} else if (result) {
Log.i(TAG, "Successfully performed notification registration for given account");
} else if (!success) {
Log.e(TAG, "Failed to perform notification registration for given account.");
} else {
Log.e(TAG, "Failed to perform notification registration for given account." + throwable);
Log.i(TAG, "Successfully performed notification registration for given account");
}
notifyNotificationRegistered(throwable == null && result);
});
}
// Because the notificaiton registration is expiring, the per account registration work needs to be kicked off again.
// This means registering with the NotificationRegistrationManager as well as any sub component work like RemoteSystemAppRegistration.
Log.i(TAG, "Notification " + args.getState() + " for account: " + args.getAccount().getId());
Optional<Account> account = mAccounts
.stream()
.filter(acc -> accountsMatch(args.getAccount(), acc.getAccount()))
.findFirst();
// The two cases of receiving a new notification token are:
// 1. A notification registration is asked for and now it is available. In this case there is a pending promise that was made
// at the time of requesting the information. It now needs completed.
// 2. The account is already registered but for whatever reason the registration changes (GCM/FCM gives the app a new token)
//
// In order to most cleany handle both cases set the new notification information and then trigger a re registration of all accounts
// that are in good standing.
GcmNotificationReceiver.setNotificationRegistration(registration);
// If the account has been prepared for use then re-register the account with SDK
if (account.isPresent() && account.get().getRegistrationState() == AccountRegistrationState.IN_APP_CACHE_AND_SDK_CACHE) {
account.get().registerAccountWithSdkAsync();
// For all the accounts which have been prepared successfully, perform Relay SDK registration
for (Account account : mAccounts) {
if (account.getRegistrationState() == AccountRegistrationState.IN_APP_CACHE_AND_SDK_CACHE) {
account.registerAccountWithSdkAsync();
}
}
break;
}
/**
* Give the GCM notification to Rome to process.
* @param data The bundle of data in a GCM notification
* @return The async result for this operation
*/
public AsyncOperation<Void> receiveNotificationAsync(Bundle data) {
return mPlatform.processNotification(data).waitForCompletionAsync();
}
/**
* Give the FCM notification to Rome to process.
* @param data The map of data in a FCM notification
* @return The async result for this operation
*/
public AsyncOperation<Void> receiveNotificationAsync(Map data) {
return mPlatform.processNotification(data).waitForCompletionAsync();
}
public ConnectedDevicesPlatform getPlatform() {
@ -280,31 +260,48 @@ public class ConnectedDevicesManager {
/**
* Prepare the accounts; refresh registrations when needed, add missing accounts and remove stale accounts from the ConnectedDevicesPlatform.AccountManager.
* @param context Application context
* @return A async operation that will complete when all accounts are prepared
*/
private void prepareAccounts(List<Account> accounts, Context context) {
private AsyncOperation<Void> prepareAccounts(List<Account> accounts, Context context) {
List<AsyncOperation<Boolean>> operations = new ArrayList<>();
// Kick off all the account preperation and store the AsyncOperations
for (Account account : accounts) {
prepareAccount(account, context);
operations.add(prepareAccountAsync(account, context));
}
// Return an operation that will complete when all of the operations complete.
return AsyncOperation.allOf(operations.toArray(new AsyncOperation[operations.size()]));
}
/**
* Attempt to prepare the account. If the account was prepared successfully, add it to the list of "ready to use" accounts.
* @param context Application context
* @return AsyncOperation with the exception captured
*/
private void prepareAccount(Account account, Context context) {
private AsyncOperation<Boolean> prepareAccountAsync(Account account, Context context) {
Log.v(TAG, "Preparing account: " + account.getAccount().getId());
// Add the account to the list of available accounts
mAccounts.add(account);
// Prepare the account, removing it from the list of accounts if it failed
account.prepareAccountAsync(context).thenAcceptAsync((success) -> {
if (success) {
Log.i(TAG, "Account: " + account.getAccount().getId() + " is prepared!.");
} else {
return account.prepareAccountAsync(context).thenComposeAsync((Boolean success) -> {
// If an exception is raised or we gracefully fail to prepare the account, remove it
if (!success) {
mAccounts.remove(account);
Log.w(TAG, "Removed account: " + account.getAccount().getId() + " from the list of ready-to-go accounts as it failed to be prepared.");
} else {
Log.i(TAG, "Account: " + account.getAccount().getId() + " is prepared!.");
}
// Return the success of the account preparation
return AsyncOperation.completedFuture(success);
}).exceptionally((Throwable throwable) -> {
mAccounts.remove(account);
Log.e(TAG, "Removed account: " + account.getAccount().getId() + " from the list of ready-to-go accounts as an exception was encountered");
// Return the account preparation was not successful
return false;
});
}

Просмотреть файл

@ -21,6 +21,7 @@ public class GcmNotificationReceiver extends BroadcastReceiver {
private static final String RegistrationComplete = "registrationComplete";
private Context mContext;
private static AsyncOperation<ConnectedDevicesNotificationRegistration> sNotificationRegistrationOperation;
// endregion
GcmNotificationReceiver(Context context) {
@ -29,10 +30,31 @@ public class GcmNotificationReceiver extends BroadcastReceiver {
registerGcmBroadcastReceiver();
}
/**
* TODO: Comment
* @param registration
*/
public static synchronized void setNotificationRegistration(ConnectedDevicesNotificationRegistration registration) {
// Create the registration operation if it has not been requested already
if (sNotificationRegistrationOperation == null) {
sNotificationRegistrationOperation = new AsyncOperation<>();
}
// Complete the operation with the registration, to be fetched later
sNotificationRegistrationOperation.complete(registration);
}
/**
* This function returns Notification Registration after it completes async operation.
* @return Notification Registration.
*/
public synchronized static AsyncOperation<ConnectedDevicesNotificationRegistration> getNotificationRegistrationAsync() {
// Create the registration operation if it the registration has not been received yet
if (sNotificationRegistrationOperation == null) {
sNotificationRegistrationOperation = new AsyncOperation<>();
}
return sNotificationRegistrationOperation;
}
/**
* When GCM has been registered, this will get fired.
@ -52,10 +74,13 @@ public class GcmNotificationReceiver extends BroadcastReceiver {
if (token == null) {
Log.e(TAG, "Got notification that GCM had been registered, but token is null. Was app ID set in GcmRegistrationIntentService?");
}
} else if (token.isEmpty()) {
Log.e(TAG, "GcmNotificationReceiver gained the a token however it was empty");
} else {
Log.i(TAG, "GcmNotificationReceiver gained the token: " + token);
// Get an existing ConnectedDevicesManager or initialize one and give it the notification token
ConnectedDevicesManager.getOrInitializeConnectedDevicesManager(context).setNotificationRegistration(token);
ConnectedDevicesManager.getConnectedDevicesManager(context).setNotificationRegistration(token);
}
mContext.startService(new Intent(mContext, SampleGcmListenerService.class));
}

Просмотреть файл

@ -120,13 +120,16 @@ public class MainActivity extends AppCompatActivity {
mNavigationDrawer.addDrawerListener(actionBarDrawerToggle);
// Create the ConnectedDevicesManager
mConnectedDevicesManager = ConnectedDevicesManager.getOrInitializeConnectedDevicesManager((Context)this);
mConnectedDevicesManager = ConnectedDevicesManager.getConnectedDevicesManager((Context)this);
// Sign the user in, which may or may not require UI interaction
mConnectedDevicesManager.signInMsa(this).thenAcceptAsync((Void v) -> {
// Initialize the UserActivity Feed
// TODO: Enable this once race is solved
// getUserActivityFragment().initializeUserActivityFeed();
mConnectedDevicesManager.signInMsaAsync(this).thenAcceptAsync((success) -> {
if (success) {
// Initialize the UserActivity Feed
getUserActivityFragment().initializeUserActivityFeed();
} else {
Log.e(TAG, "ConnectedDevicesManager failed to sign in an MSA account");
}
});
}

Просмотреть файл

@ -30,7 +30,9 @@ public class SampleGcmListenerService extends GcmListenerService {
public void onMessageReceived(String from, Bundle data) {
Log.d(TAG, "GCM listener received data from: " + from);
// Get an existing ConnectedDevicesManager or initialize one and give it the notification
ConnectedDevicesManager.getOrInitializeConnectedDevicesManager(getApplicationContext()).receiveNotificationAsync(data);
// Get a ConnectedDevicesPlatform to give the notification to
ConnectedDevicesPlatform platform = ConnectedDevicesManager.getConnectedDevicesManager(getApplicationContext()).getPlatform();
platform.processNotification(data).waitForCompletionAsync();
}
}

Просмотреть файл

@ -85,7 +85,7 @@ public class UserActivityFragment extends BaseFragment implements View.OnClickLi
private String mStatusText;
private UserDataFeed getUserDataFeed(ConnectedDevicesAccount account, List<UserDataFeedSyncScope> scopes, EventListener<UserDataFeed, UserDataFeedSyncStatusChangedEventArgs> listener) {
UserDataFeed feed = UserDataFeed.getForAccount(account, ConnectedDevicesManager.getConnectedDevicesManager().getPlatform(), Secrets.APP_HOST_NAME);
UserDataFeed feed = UserDataFeed.getForAccount(account, ConnectedDevicesManager.getConnectedDevicesManager(getActivity()).getPlatform(), Secrets.APP_HOST_NAME);
feed.syncStatusChanged().subscribe(listener);
// TODO: Subscribe to sync scopes async
feed.startSync();
@ -99,7 +99,7 @@ public class UserActivityFragment extends BaseFragment implements View.OnClickLi
try {
// Step #1
// get the UserDataFeed for the signed in account
ConnectedDevicesManager manager = ConnectedDevicesManager.getConnectedDevicesManager();
ConnectedDevicesManager manager = ConnectedDevicesManager.getConnectedDevicesManager(getActivity());
List<Account> accounts = manager.getAccounts();
// Ensure we have an account to use