Merged PR 14464: Updating Android GraphNotificationsSample for improved patterns
Updating the GraphNotificationsSample to follow initialization patterns of the sdksample. It is fairly complicated setup, so it is recommended that all app developers try to follow the model laid out by this sample pretty closely. It shows how to synchronize accounts with a token library at startup, perform per account initialization as fast as possible to let incoming notifications be quickly processed and has a reusable manager object that is not tightly coupled to UI. Cherry picked from !14205
This commit is contained in:
Коммит
5ba898f332
|
@ -2,12 +2,13 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.microsoft.connecteddevices.graphnotifications">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
@ -21,7 +22,6 @@
|
|||
android:theme="@style/AppTheme.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
@ -31,6 +31,7 @@
|
|||
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service android:name=".FCMRegistrationIntentService" android:exported="false" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,219 @@
|
|||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
|
||||
package com.microsoft.connecteddevices.graphnotifications;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesNotificationRegistration;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesAccount;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesAddAccountResult;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesAccountAddedStatus;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesPlatform;
|
||||
import com.microsoft.connecteddevices.signinhelpers.SigninHelperAccount;
|
||||
import com.microsoft.connecteddevices.AsyncOperation;
|
||||
|
||||
import java.lang.IllegalStateException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds all state and logic for a single app + sdk account.
|
||||
*/
|
||||
public class Account {
|
||||
// region Member Variables
|
||||
private final String TAG = Account.class.getName();
|
||||
|
||||
public final String DATE_FORMAT = "MM/dd/yyyy HH:mm:ss";
|
||||
public final String TIMESTAMP_KEY = "TIMESTAMP_KEY";
|
||||
public final String PACKAGE_KEY = "PACKAGE_KEY";
|
||||
public final String PACKAGE_VALUE = "com.microsoft.connecteddevices.graphnotifications";
|
||||
|
||||
private SigninHelperAccount mSignInHelper;
|
||||
private ConnectedDevicesAccount mAccount;
|
||||
private AccountRegistrationState mState;
|
||||
private ConnectedDevicesPlatform mPlatform;
|
||||
private UserNotificationsManager mNotificationsManager;
|
||||
// endregion
|
||||
|
||||
// region Constructors
|
||||
/**
|
||||
* This constructor is for when the Account does not exist in the app cache,
|
||||
* but does exist in the SDK cache.
|
||||
* @param account
|
||||
* @param platform
|
||||
*/
|
||||
public Account(ConnectedDevicesAccount account, ConnectedDevicesPlatform platform) {
|
||||
mAccount = account;
|
||||
mState = AccountRegistrationState.IN_SDK_CACHE_ONLY;
|
||||
mPlatform = platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor is for when the Account is signed in.
|
||||
* @param helperAccount
|
||||
* @param state
|
||||
* @param platform
|
||||
*/
|
||||
public Account(SigninHelperAccount helperAccount, AccountRegistrationState state, ConnectedDevicesPlatform platform) {
|
||||
// This account needs to be signed in, else the `Account` cannot be created
|
||||
mSignInHelper = helperAccount;
|
||||
mAccount = helperAccount.getAccount();
|
||||
mState = state;
|
||||
mPlatform = platform;
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region public instance methods
|
||||
/**
|
||||
* Perform all actions required to have this account signed in, added to the
|
||||
* ConnectedDevicesPlatform.AccountManager and registered with the Rome platform.
|
||||
* @param context Application context
|
||||
* @return The async result for this operation
|
||||
*/
|
||||
public AsyncOperation<Boolean> prepareAccountAsync(final Context context) {
|
||||
// Accounts can be in 3 different scenarios:
|
||||
// 1: cached account in good standing (initialized in the SDK and our token cache).
|
||||
// 2: account missing from the SDK but present in our cache: Add and initialize account.
|
||||
// 3: account missing from our cache but present in the SDK. Log the account out async
|
||||
|
||||
// Subcomponents (e.g. UserDataFeed) can only be initialized when an account is in both the app cache
|
||||
// and the SDK cache.
|
||||
// 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.
|
||||
switch (mState) {
|
||||
// Scenario 1
|
||||
case IN_APP_CACHE_AND_SDK_CACHE:
|
||||
mNotificationsManager = new UserNotificationsManager(context, mAccount, mPlatform);
|
||||
return registerAccountWithSdkAsync();
|
||||
// Scenario 2
|
||||
case IN_APP_CACHE_ONLY: {
|
||||
// Add the this account to the ConnectedDevicesPlatform.AccountManager
|
||||
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;
|
||||
mNotificationsManager = new UserNotificationsManager(context, mAccount, mPlatform);
|
||||
return registerAccountWithSdkAsync();
|
||||
});
|
||||
}
|
||||
// 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
|
||||
return AsyncOperation.completedFuture(false);
|
||||
default:
|
||||
// This account could not be prepared
|
||||
Log.e(TAG, "Failed to prepare account " + mAccount.getId() + " due to unknown state!");
|
||||
return AsyncOperation.completedFuture(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs non-blocking registrations for this account, which are
|
||||
* for notifications then for the relay SDK.
|
||||
* @return The async result for this operation
|
||||
*/
|
||||
public AsyncOperation<Boolean> registerAccountWithSdkAsync() {
|
||||
if (mState != AccountRegistrationState.IN_APP_CACHE_AND_SDK_CACHE) {
|
||||
AsyncOperation<Boolean> toReturn = new AsyncOperation<>();
|
||||
toReturn.completeExceptionally(new IllegalStateException("Cannot register this account due to bad state: " + mAccount.getId()));
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
// Grab the shared GCM/FCM notification token from this app's BroadcastReceiver
|
||||
return RomeNotificationReceiver.getNotificationRegistrationAsync().thenComposeAsync((ConnectedDevicesNotificationRegistration notificationRegistration) -> {
|
||||
// Perform the registration using the NotificationRegistration
|
||||
return mPlatform.getNotificationRegistrationManager().registerForAccountAsync(mAccount, notificationRegistration)
|
||||
.thenComposeAsync((success) -> {
|
||||
if (success) {
|
||||
Log.i(TAG, "Successfully registered account " + mAccount.getId() + " for cloud notifications");
|
||||
} else {
|
||||
Log.e(TAG, "Failed to register account " + mAccount.getId() + " for cloud notifications!");
|
||||
}
|
||||
|
||||
return mNotificationsManager.registerForAccountAsync();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an access token for this account which satisfies the given scopes
|
||||
* @param scopes Scopes the access token must have been requested with
|
||||
* @return The async result for this operation
|
||||
*/
|
||||
public AsyncOperation<String> getAccessTokenAsync(final List<String> scopes) {
|
||||
return mSignInHelper.getAccessTokenAsync(scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down and sign this account out
|
||||
* @param activity Application activity
|
||||
* @return The async result for this operation
|
||||
*/
|
||||
public AsyncOperation<ConnectedDevicesAccount> logoutAsync(Activity activity) {
|
||||
return mSignInHelper.signOut(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ConnectedDevicesAccount
|
||||
*/
|
||||
public ConnectedDevicesAccount getAccount() {
|
||||
return mAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the AccountRegistrationState
|
||||
*/
|
||||
public AccountRegistrationState getRegistrationState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the UserNotificationsManager
|
||||
*/
|
||||
public UserNotificationsManager getNotificationsManager() { return mNotificationsManager; }
|
||||
// endregion
|
||||
|
||||
// region private instance methods
|
||||
|
||||
/**
|
||||
* Grab the initial registration date-time if one is found, otherwise generate a new one.
|
||||
* @param context Application context
|
||||
* @return Datetime to insert into the RemoteSystemAppRegistration
|
||||
*/
|
||||
private String getInitialRegistrationDateTime(final Context context) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
|
||||
|
||||
String timestamp;
|
||||
// Check that the SharedPreferences has the timestamp. This should be true after the first clean install -> Platform init.
|
||||
if (preferences.contains(TIMESTAMP_KEY)) {
|
||||
// The `getString` API requires a default value. Since we check that key exists we should never get the default value of empty
|
||||
// string.
|
||||
timestamp = preferences.getString(TIMESTAMP_KEY, "");
|
||||
if (timestamp.isEmpty()) {
|
||||
Log.e(TAG, "getInitialRegistrationDateTime failed to get the TimeStamp although the key exists");
|
||||
throw new RuntimeException("Failed to get TimeStamp after verifying it exists");
|
||||
}
|
||||
} else {
|
||||
// Create the initial timestamp for RemoteSystemApp registration and store it in SharedPreferences
|
||||
timestamp = new SimpleDateFormat(DATE_FORMAT).format(new Date());
|
||||
preferences.edit().putString(TIMESTAMP_KEY, timestamp).apply();
|
||||
}
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
// endregion
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
|
||||
package com.microsoft.connecteddevices.graphnotifications;
|
||||
|
||||
public enum AccountRegistrationState {
|
||||
IN_APP_CACHE_AND_SDK_CACHE,
|
||||
IN_APP_CACHE_ONLY,
|
||||
IN_SDK_CACHE_ONLY
|
||||
}
|
|
@ -0,0 +1,456 @@
|
|||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
|
||||
package com.microsoft.connecteddevices.graphnotifications;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.microsoft.connecteddevices.AsyncOperation;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesAccessTokenRequest;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesAccessTokenRequestedEventArgs;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesAccessTokenInvalidatedEventArgs;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesAccount;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesAccountManager;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesAccountType;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesAddAccountResult;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesNotificationRegistration;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesNotificationRegistrationStateChangedEventArgs;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesNotificationType;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesNotificationRegistrationManager;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesNotificationRegistrationState;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesPlatform;
|
||||
import com.microsoft.connecteddevices.signinhelpers.AADSigninHelperAccount;
|
||||
import com.microsoft.connecteddevices.signinhelpers.MSASigninHelperAccount;
|
||||
import com.microsoft.connecteddevices.signinhelpers.SigninHelperAccount;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This is a singleton object which holds onto the app's ConnectedDevicesPlatform and handles account management.
|
||||
*/
|
||||
public class ConnectedDevicesManager {
|
||||
// region Member Variables
|
||||
private final String TAG = ConnectedDevicesManager.class.getName();
|
||||
|
||||
private List<Account> mAccounts;
|
||||
private RomeNotificationReceiver mNotificationReceiver;
|
||||
private ConnectedDevicesPlatform mPlatform;
|
||||
|
||||
private static ConnectedDevicesManager sConnectedDevicesManager;
|
||||
// endregion
|
||||
|
||||
// region Constructors
|
||||
/**
|
||||
* This is a singleton object which holds onto the app's ConnectedDevicesPlatform and handles account management.
|
||||
* @param context Application context
|
||||
*/
|
||||
private ConnectedDevicesManager(Context context) {
|
||||
// Initialize list of known accounts
|
||||
mAccounts = new ArrayList<Account>();
|
||||
|
||||
// Create the NotificationReceiver
|
||||
mNotificationReceiver = new RomeNotificationReceiver(context);
|
||||
|
||||
// Create Platform
|
||||
mPlatform = new ConnectedDevicesPlatform(context);
|
||||
|
||||
// Create a final reference to the list of accounts
|
||||
final List<Account> accounts = mAccounts;
|
||||
|
||||
// Subscribe to the AccessTokenRequested event
|
||||
mPlatform.getAccountManager().accessTokenRequested().subscribe((accountManager, args) -> onAccessTokenRequested(accountManager, args, accounts));
|
||||
|
||||
// Subscribe to AccessTokenInvalidated event
|
||||
mPlatform.getAccountManager().accessTokenInvalidated().subscribe((accountManager, args) -> onAccessTokenInvalidated(accountManager, args, accounts));
|
||||
|
||||
// Subscribe to NotificationRegistrationStateChanged event
|
||||
mPlatform.getNotificationRegistrationManager().notificationRegistrationStateChanged().subscribe((notificationRegistrationManager, args) -> onNotificationRegistrationStateChanged(notificationRegistrationManager, args, accounts));
|
||||
|
||||
// Start the platform as we have subscribed to the events it can raise
|
||||
mPlatform.start();
|
||||
|
||||
// Pull the accounts from our app's cache and synchronize the list with the apps cached by
|
||||
// ConnectedDevicesPlatform.AccountManager.
|
||||
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. 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(Context context) {
|
||||
if (sConnectedDevicesManager == null) {
|
||||
sConnectedDevicesManager = new ConnectedDevicesManager(context);
|
||||
}
|
||||
return sConnectedDevicesManager;
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region public instance methods
|
||||
|
||||
/**
|
||||
* Get the ConnectedDevicesPlatform owned by this ConnectedDevicesManager.
|
||||
* @return Platform
|
||||
*/
|
||||
public ConnectedDevicesPlatform getPlatform() {
|
||||
return mPlatform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to ensure there is a signed in MSA Account
|
||||
* @param activity Application activity
|
||||
* @return The async result for when this operation completes
|
||||
*/
|
||||
public synchronized AsyncOperation<Boolean> signInMsaAsync(final Activity activity) {
|
||||
// Create a SigninHelperAccount with a client id for msa, a map of requested scopes to override, and the context
|
||||
final Map<String, String[]> msaScopeOverrides = new ArrayMap<>();
|
||||
msaScopeOverrides.put("https://activity.windows.com/UserActivity.ReadWrite.CreatedByApp",
|
||||
new String[] { "https://activity.windows.com/UserActivity.ReadWrite.CreatedByApp",
|
||||
"https://activity.windows.com/Notifications.ReadWrite.CreatedByApp"});
|
||||
SigninHelperAccount signInHelper = new MSASigninHelperAccount(Secrets.MSA_CLIENT_ID, msaScopeOverrides, (Context)activity);
|
||||
|
||||
if (signInHelper.isSignedIn()) {
|
||||
Log.i(TAG, "Already signed in with a MSA account");
|
||||
return AsyncOperation.completedFuture(true);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Signing in with 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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to ensure there is a signed in MSA Account
|
||||
* @param activity Application activity
|
||||
* @return The async result for when this operation completes
|
||||
*/
|
||||
public synchronized AsyncOperation<Boolean> signInAadAsync(final Activity activity) {
|
||||
// Create a SigninHelperAccount with a client id for msa, a map of requested scopes to override, and the context
|
||||
SigninHelperAccount signInHelper = new AADSigninHelperAccount(Secrets.AAD_CLIENT_ID, Secrets.AAD_REDIRECT_URI, (Context)activity);
|
||||
|
||||
if (signInHelper.isSignedIn()) {
|
||||
Log.i(TAG, "Already signed in with a AAD account");
|
||||
return AsyncOperation.completedFuture(true);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Signing in in with a AAD 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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign out and remove the given Account from the ConnectedDevicesManager
|
||||
* @param activity Application activity
|
||||
* @return The async result for when this operation completes
|
||||
*/
|
||||
public synchronized AsyncOperation<Boolean> logout(Activity activity) {
|
||||
// First remove this account from the list of "ready to go" accounts so it cannot be used while logging out
|
||||
Account accountToRemove = getSignedInAccount();
|
||||
mAccounts.remove(accountToRemove);
|
||||
|
||||
// Now log out this account
|
||||
return accountToRemove.logoutAsync(activity).thenComposeAsync((ConnectedDevicesAccount account) -> {
|
||||
Log.i(TAG, "Successfully signed out account: " + account.getId());
|
||||
return AsyncOperation.completedFuture(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of "ready-to-go" accounts owned by this ConnectedDevicesManager.
|
||||
* @return accounts
|
||||
*/
|
||||
public Account getSignedInAccount() {
|
||||
|
||||
// Compare the app cached account to find a match in the sdk cached accounts
|
||||
if (mAccounts.size() > 0) {
|
||||
return mAccounts.get(0);
|
||||
}
|
||||
|
||||
Log.e(TAG, "No signed in account found!");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a NotificationRegistration using the notification token gained from GCM/FCM.
|
||||
* @param token Notification token gained by the BroadcastReceiver
|
||||
*/
|
||||
public synchronized void setNotificationRegistration(final String token) {
|
||||
// Get the NotificationRegistrationManager from the platform
|
||||
ConnectedDevicesNotificationRegistrationManager registrationManager = mPlatform.getNotificationRegistrationManager();
|
||||
|
||||
// Create a NotificationRegistration obect to store all notification information
|
||||
ConnectedDevicesNotificationRegistration registration = new ConnectedDevicesNotificationRegistration();
|
||||
registration.setType(ConnectedDevicesNotificationType.FCM);
|
||||
registration.setToken(token);
|
||||
registration.setAppId(Secrets.FCM_SENDER_ID);
|
||||
registration.setAppDisplayName("GraphNotificationsSample");
|
||||
|
||||
Log.i(TAG, "Completing the RomeNotificationReceiver operation with token: " + token);
|
||||
|
||||
// For each prepared account, register for notifications
|
||||
for (final Account account : mAccounts) {
|
||||
registrationManager.registerForAccountAsync(account.getAccount(), registration)
|
||||
.whenCompleteAsync((Boolean success, Throwable throwable) -> {
|
||||
if (throwable != null) {
|
||||
Log.e(TAG, "Exception encountered in registerForAccountAsync", throwable);
|
||||
} else if (!success) {
|
||||
Log.e(TAG, "Failed to register account " + account.getAccount().getId() + " for cloud notifications!");
|
||||
} else {
|
||||
Log.i(TAG, "Successfully registered account " + account.getAccount().getId() + " for cloud notifications");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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 to be 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 cleanly handle both cases set the new notification information and then trigger a re registration of all accounts
|
||||
// that are in good standing.
|
||||
RomeNotificationReceiver.setNotificationRegistration(registration);
|
||||
|
||||
// For all the accounts which have been prepared successfully, perform SDK registration
|
||||
for (Account account : mAccounts) {
|
||||
if (account.getRegistrationState() == AccountRegistrationState.IN_APP_CACHE_AND_SDK_CACHE) {
|
||||
account.registerAccountWithSdkAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region private instance methods
|
||||
/**
|
||||
* Pull the accounts from our app's cache and synchronize the list with the
|
||||
* apps cached by ConnectedDevicesPlatform.AccountManager.
|
||||
* @param context Application context
|
||||
* @return List of accounts from the app and SDK's cache
|
||||
*/
|
||||
private List<Account> deserializeAccounts(Context context) {
|
||||
// Since our helper lib can only cache 1 app at a time, we create sign-in helper,
|
||||
// which does user account and access token management for us. Takes three parameters:
|
||||
// a client id for msa, a map of requested auto scopes to override, and the context
|
||||
SigninHelperAccount signInHelper = new MSASigninHelperAccount(Secrets.MSA_CLIENT_ID, new ArrayMap<String, String[]>(), context);
|
||||
|
||||
// Get all of the ConnectedDevicesPlatform's added accounts
|
||||
List<ConnectedDevicesAccount> sdkCachedAccounts = mPlatform.getAccountManager().getAccounts();
|
||||
|
||||
List<Account> returnAccounts = new ArrayList<Account>();
|
||||
|
||||
// If there is a signed in account in the app's cache, find it exists in the SDK's cache
|
||||
if (signInHelper.isSignedIn()) {
|
||||
// Check if the account is also present in ConnectedDevicesPlatform.AccountManager.
|
||||
ConnectedDevicesAccount sdkCachedAccount = findFirst(sdkCachedAccounts, account -> accountsMatch(signInHelper.getAccount(), account));
|
||||
|
||||
AccountRegistrationState registrationState;
|
||||
if (sdkCachedAccount != null) {
|
||||
// Account found in the SDK cache, remove it from the list of sdkCachedAccounts. After
|
||||
// all the appCachedAccounts have been processed any accounts remaining in sdkCachedAccounts
|
||||
// are only in the SDK cache, and should be removed.
|
||||
registrationState = AccountRegistrationState.IN_APP_CACHE_AND_SDK_CACHE;
|
||||
sdkCachedAccounts.remove(sdkCachedAccount);
|
||||
} else {
|
||||
// Account not found in the SDK cache. Later when we initialize the Account,
|
||||
// it will be added to the SDK cache and perform registration.
|
||||
registrationState = AccountRegistrationState.IN_APP_CACHE_ONLY;
|
||||
}
|
||||
|
||||
// Add the app's cached account with the correct registration state
|
||||
returnAccounts.add(new Account(signInHelper, registrationState, mPlatform));
|
||||
}
|
||||
|
||||
// Add all the accounts which exist only in the SDK
|
||||
for (ConnectedDevicesAccount account : sdkCachedAccounts) {
|
||||
returnAccounts.add(new Account(account, mPlatform));
|
||||
}
|
||||
|
||||
return returnAccounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for the java.util.function.Predicate to support pre Java 8 / API 24.
|
||||
*/
|
||||
interface Predicate<T> {
|
||||
public boolean test(T t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for stream.filter.findFirst to support pre Java 8 / API 24.
|
||||
* @param list List to search
|
||||
* @param predicate Predicate to use against the given list
|
||||
* @return First item matching the given predicate, null if none found
|
||||
*/
|
||||
private static <T> T findFirst(List<T> list, Predicate<? super T> predicate) {
|
||||
for (T item : list) {
|
||||
if (predicate.test(item)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matcher function to compare ConnectedDevicesAccounts are equal
|
||||
* @param account1 ConnectedDevicesAccount 1
|
||||
* @param account2 ConnectedDevicesAccount 2
|
||||
* @return Boolean of if the given accounts match
|
||||
*/
|
||||
private static boolean accountsMatch(ConnectedDevicesAccount account1, ConnectedDevicesAccount account2) {
|
||||
String accountId1 = account1.getId();
|
||||
ConnectedDevicesAccountType accountType1 = account1.getType();
|
||||
|
||||
String accountId2 = account2.getId();
|
||||
ConnectedDevicesAccountType accountType2 = account2.getType();
|
||||
|
||||
return accountId2.equals(accountId1) && accountType2.equals(accountType1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 AsyncOperation<Void> prepareAccounts(List<Account> accounts, Context context) {
|
||||
List<AsyncOperation<Boolean>> operations = new ArrayList<>();
|
||||
|
||||
// Kick off all the account preparation and store the AsyncOperations
|
||||
for (Account account : accounts) {
|
||||
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 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
|
||||
return account.prepareAccountAsync(context).thenComposeAsync((success) -> {
|
||||
// If an exception is raised or we gracefully fail to prepare the account, remove it
|
||||
if (success) {
|
||||
Log.i(TAG, "Account: " + account.getAccount().getId() + " is ready-to-go");
|
||||
} else {
|
||||
mAccounts.remove(account);
|
||||
Log.w(TAG, "Account: " + account.getAccount().getId() + " is not ready, removed from the list of ready-to-go accounts!");
|
||||
}
|
||||
|
||||
// Return the success of the account preparation
|
||||
return AsyncOperation.completedFuture(success);
|
||||
}).exceptionally((Throwable throwable) -> {
|
||||
mAccounts.remove(account);
|
||||
Log.e(TAG, "Account: " + account.getAccount().getId() + " is not ready, removed from the list of ready-to-go accounts as an exception was encountered", throwable);
|
||||
// Return the account preparation was not successful
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This event is fired when there is a need to request a token. This event should be subscribed and ready to respond before any request is sent out.
|
||||
* @param sender ConnectedDevicesAccountManager which is making the request
|
||||
* @param args Contains arguments for the event
|
||||
* @param accounts List of accounts to search for
|
||||
*/
|
||||
private void onAccessTokenRequested(ConnectedDevicesAccountManager sender, ConnectedDevicesAccessTokenRequestedEventArgs args, List<Account> accounts) {
|
||||
ConnectedDevicesAccessTokenRequest request = args.getRequest();
|
||||
List<String> scopes = request.getScopes();
|
||||
|
||||
// Compare the app cached account to find a match in the sdk cached accounts
|
||||
Account account = findFirst(accounts, acc -> accountsMatch(request.getAccount(), acc.getAccount()));
|
||||
|
||||
// We always need to complete the request, even if a matching account is not found
|
||||
if (account == null) {
|
||||
Log.e(TAG, "Failed to find a SigninHelperAccount matching the given account for the token request");
|
||||
request.completeWithErrorMessage("The app could not find a matching ConnectedDevicesAccount to get a token");
|
||||
return;
|
||||
}
|
||||
|
||||
// Complete the request with a token
|
||||
account.getAccessTokenAsync(scopes)
|
||||
.thenAcceptAsync((String token) -> {
|
||||
request.completeWithAccessToken(token);
|
||||
}).exceptionally(throwable -> {
|
||||
request.completeWithErrorMessage("The Account could not return a token with those scopes");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This event is fired when a token consumer reports a token error. The token provider needs to
|
||||
* either refresh their token cache or request a new user login to fix their account setup.
|
||||
* If access token in invalidated, refresh token and renew access token.
|
||||
* @param sender ConnectedDevicesAccountManager which is making the request
|
||||
* @param args Contains arguments for the event
|
||||
* @param accounts List of accounts to search for
|
||||
*/
|
||||
private void onAccessTokenInvalidated(ConnectedDevicesAccountManager sender, ConnectedDevicesAccessTokenInvalidatedEventArgs args, List<Account> accounts) {
|
||||
Log.i(TAG, "Token invalidated for account: " + args.getAccount().getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Event for when the registration state changes for a given account.
|
||||
* @param sender ConnectedDevicesNotificationRegistrationManager which is making the request
|
||||
* @param args Contains arguments for the event
|
||||
* @param accounts List of accounts to search for
|
||||
*/
|
||||
private void onNotificationRegistrationStateChanged(ConnectedDevicesNotificationRegistrationManager sender, ConnectedDevicesNotificationRegistrationStateChangedEventArgs args, List<Account> accounts) {
|
||||
// If notification registration state is expiring or expired, re-register for account again.
|
||||
ConnectedDevicesNotificationRegistrationState state = args.getState();
|
||||
switch (args.getState()) {
|
||||
case UNREGISTERED:
|
||||
Log.w(TAG, "Notification registration state is unregistered for account: " + args.getAccount().getId());
|
||||
break;
|
||||
case REGISTERED:
|
||||
Log.i(TAG, "Notification registration state is registered for account: " + args.getAccount().getId());
|
||||
break;
|
||||
case EXPIRING: // fallthrough
|
||||
case EXPIRED:
|
||||
{
|
||||
// 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());
|
||||
Account account = findFirst(accounts, acc -> accountsMatch(args.getAccount(), acc.getAccount()));
|
||||
|
||||
// If the account has been prepared for use then re-register the account with SDK
|
||||
if (account != null && account.getRegistrationState() == AccountRegistrationState.IN_APP_CACHE_AND_SDK_CACHE) {
|
||||
account.registerAccountWithSdkAsync();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
// endregion
|
||||
}
|
|
@ -5,17 +5,13 @@
|
|||
package com.microsoft.connecteddevices.graphnotifications;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.tasks.OnCompleteListener;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.firebase.iid.FirebaseInstanceId;
|
||||
import com.google.firebase.iid.InstanceIdResult;
|
||||
import com.google.firebase.messaging.FirebaseMessagingService;
|
||||
import com.google.firebase.messaging.RemoteMessage;
|
||||
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesPlatform;
|
||||
|
||||
import java.util.Map;
|
||||
|
@ -25,15 +21,13 @@ import java.util.Map;
|
|||
*/
|
||||
public class FCMListenerService extends FirebaseMessagingService {
|
||||
private static final String TAG = "FCMListenerService";
|
||||
|
||||
private static final String RegistrationComplete = "registrationComplete";
|
||||
private static final String REGISTRATION_COMPLETE = "registrationComplete";
|
||||
private static final String TOKEN = "TOKEN";
|
||||
|
||||
private static String s_previousToken = null;
|
||||
private static String sPreviousToken = "";
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
PlatformManager.getInstance().createNotificationReceiver(this);
|
||||
FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(task -> {
|
||||
if (task.isSuccessful()) {
|
||||
String token = task.getResult().getToken();
|
||||
|
@ -45,38 +39,30 @@ public class FCMListenerService extends FirebaseMessagingService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check whether it's a rome notification or not.
|
||||
* Check whether it's a Rome notification or not.
|
||||
* If it is a rome notification,
|
||||
* It will notify the apps with the information in the notification.
|
||||
* @param message FCM class for messaging with a from a data field.
|
||||
*/
|
||||
@Override
|
||||
public void onMessageReceived(RemoteMessage message) {
|
||||
Log.d(TAG, "From: " + message.getFrom());
|
||||
Log.d(TAG, "FCM notification received from: " + message.getFrom());
|
||||
Map data = message.getData();
|
||||
ConnectedDevicesPlatform platform = ensurePlatformInitialized();
|
||||
platform.processNotification(data);
|
||||
try {
|
||||
ConnectedDevicesPlatform platform = ConnectedDevicesManager.getConnectedDevicesManager(getApplicationContext()).getPlatform();
|
||||
platform.processNotification(data);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to process FCM notification" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewToken(String token) {
|
||||
if (token != null && !token.equals(s_previousToken)) {
|
||||
s_previousToken = token;
|
||||
Intent registrationComplete = new Intent(RegistrationComplete);
|
||||
if (token != null && !token.equals(sPreviousToken)) {
|
||||
sPreviousToken = token;
|
||||
Intent registrationComplete = new Intent(REGISTRATION_COMPLETE);
|
||||
registrationComplete.putExtra(TOKEN, token);
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized ConnectedDevicesPlatform ensurePlatformInitialized() {
|
||||
// First see if we have an existing platform
|
||||
ConnectedDevicesPlatform platform = PlatformManager.getInstance().getPlatform();
|
||||
if (platform != null) {
|
||||
return platform;
|
||||
}
|
||||
|
||||
// No existing platform, so we have to create our own
|
||||
return PlatformManager.getInstance().createPlatform(getApplicationContext());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
|
||||
package com.microsoft.connecteddevices.graphnotifications;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Intent;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.firebase.iid.FirebaseInstanceId;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class FCMRegistrationIntentService extends IntentService {
|
||||
private static final String TAG = FCMRegistrationIntentService.class.getName();
|
||||
private static final String REGISTRATION_COMPLETE = "registrationComplete";
|
||||
private static final String TOKEN = "TOKEN";
|
||||
private static final String INTENT_NAME = "FCMRegIntentService";
|
||||
private static final String FCM_SENDER_ID = Secrets.FCM_SENDER_ID;
|
||||
|
||||
public FCMRegistrationIntentService() {
|
||||
super(INTENT_NAME);
|
||||
}
|
||||
|
||||
public FCMRegistrationIntentService(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
String token = "";
|
||||
if (FCM_SENDER_ID == null || FCM_SENDER_ID.isEmpty()) {
|
||||
Log.i(TAG, "FCM SenderID is null/empty, skipping FCM registration!");
|
||||
} else {
|
||||
try {
|
||||
token = FirebaseInstanceId.getInstance().getToken(FCM_SENDER_ID, "FCM");
|
||||
Log.i(TAG, "FCM registration token: " + token);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to get FCM registration token " + e.getMessage());
|
||||
}
|
||||
|
||||
Intent registrationComplete = new Intent(REGISTRATION_COMPLETE);
|
||||
registrationComplete.putExtra(TOKEN, token);
|
||||
LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import android.app.NotificationManager;
|
|||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.TabLayout;
|
||||
|
@ -21,7 +22,6 @@ import android.support.v4.app.NotificationManagerCompat;
|
|||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
|
@ -34,21 +34,13 @@ import android.widget.ListView;
|
|||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.microsoft.connecteddevices.AsyncOperation;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesAccount;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesAccountType;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesNotificationRegistration;
|
||||
import com.microsoft.connecteddevices.EventListener;
|
||||
import com.microsoft.connecteddevices.signinhelpers.AADSigninHelperAccount;
|
||||
import com.microsoft.connecteddevices.signinhelpers.MSASigninHelperAccount;
|
||||
import com.microsoft.connecteddevices.signinhelpers.SigninHelperAccount;
|
||||
import com.microsoft.connecteddevices.userdata.UserDataFeed;
|
||||
import com.microsoft.connecteddevices.userdata.UserDataFeedSyncScope;
|
||||
import com.microsoft.connecteddevices.userdata.usernotifications.UserNotification;
|
||||
import com.microsoft.connecteddevices.userdata.usernotifications.UserNotificationChannel;
|
||||
import com.microsoft.connecteddevices.userdata.usernotifications.UserNotificationReadState;
|
||||
import com.microsoft.connecteddevices.userdata.usernotifications.UserNotificationReader;
|
||||
import com.microsoft.connecteddevices.userdata.usernotifications.UserNotificationReaderOptions;
|
||||
import com.microsoft.connecteddevices.userdata.usernotifications.UserNotificationStatus;
|
||||
import com.microsoft.connecteddevices.userdata.usernotifications.UserNotificationUpdateResult;
|
||||
import com.microsoft.connecteddevices.userdata.usernotifications.UserNotificationUserActionState;
|
||||
|
@ -59,8 +51,8 @@ import java.io.FileReader;
|
|||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EventObject;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
@ -82,150 +74,50 @@ public class MainActivity extends AppCompatActivity {
|
|||
*/
|
||||
private ViewPager mViewPager;
|
||||
|
||||
private static SigninHelperAccount sMSAHelperAccount;
|
||||
private static SigninHelperAccount sAADHelperAccount;
|
||||
private static ConnectedDevicesAccount sLoggedInAccount;
|
||||
private static ConnectedDevicesNotificationRegistration sNotificationRegistration;
|
||||
static ConnectedDevicesManager sConnectedDevicesManager;
|
||||
static UserNotificationsManager sNotificationsManager;
|
||||
static final ArrayList<UserNotification> sActiveNotifications = new ArrayList<>();
|
||||
|
||||
private static UserNotificationReader sReader;
|
||||
private static CountDownLatch sLatch;
|
||||
|
||||
private static final ArrayList<UserNotification> sNotifications = new ArrayList<>();
|
||||
|
||||
static final String CHANNEL_NAME = "GraphNotificationsChannel001";
|
||||
private static final String NOTIFICATION_ID = "ID";
|
||||
|
||||
private enum LoginState {
|
||||
LOGGED_IN_MSA,
|
||||
LOGGED_IN_AAD,
|
||||
LOGGED_OUT
|
||||
}
|
||||
|
||||
private static LoginState sState = LoginState.LOGGED_OUT;
|
||||
|
||||
private static synchronized LoginState getAndUpdateLoginState()
|
||||
{
|
||||
if (sMSAHelperAccount == null || sAADHelperAccount == null || sLoggedInAccount == null) {
|
||||
sState = LoginState.LOGGED_OUT;
|
||||
} else if (sMSAHelperAccount.isSignedIn() && (sLoggedInAccount.getType() == ConnectedDevicesAccountType.MSA)) {
|
||||
sState = LoginState.LOGGED_IN_MSA;
|
||||
} else if (sAADHelperAccount.isSignedIn() && (sLoggedInAccount.getType() == ConnectedDevicesAccountType.AAD)) {
|
||||
sState = LoginState.LOGGED_IN_AAD;
|
||||
} else {
|
||||
sState = LoginState.LOGGED_OUT;
|
||||
}
|
||||
|
||||
return sState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
// Set up the tool bar
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
// Create the adapter that will return a fragment for each of the three
|
||||
// primary sections of the activity.
|
||||
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
|
||||
|
||||
// Set up the ViewPager with the sections adapter.
|
||||
// Set up the tabs with adapter to manage the fragments
|
||||
TabLayout tabLayout = findViewById(R.id.tabs);
|
||||
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
|
||||
mViewPager = findViewById(R.id.container);
|
||||
mViewPager.setAdapter(mSectionsPagerAdapter);
|
||||
|
||||
TabLayout tabLayout = findViewById(R.id.tabs);
|
||||
|
||||
mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
|
||||
tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(mViewPager));
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(CHANNEL_NAME, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
|
||||
channel.setDescription("Graph Notifications Channel");
|
||||
getSystemService(NotificationManager.class).createNotificationChannel(channel);
|
||||
}
|
||||
|
||||
if (sMSAHelperAccount == null) {
|
||||
final Map<String, String[]> msaScopeOverrides = new ArrayMap<>();
|
||||
msaScopeOverrides.put("https://activity.windows.com/UserActivity.ReadWrite.CreatedByApp",
|
||||
new String[] { "https://activity.windows.com/UserActivity.ReadWrite.CreatedByApp",
|
||||
"https://activity.windows.com/Notifications.ReadWrite.CreatedByApp"});
|
||||
sMSAHelperAccount = new MSASigninHelperAccount(Secrets.MSA_CLIENT_ID, msaScopeOverrides, getApplicationContext());
|
||||
}
|
||||
|
||||
if (sAADHelperAccount == null) {
|
||||
sAADHelperAccount = new AADSigninHelperAccount(Secrets.AAD_CLIENT_ID, Secrets.AAD_REDIRECT_URI, getApplicationContext());
|
||||
}
|
||||
// Create the ConnectedDevicesManager
|
||||
sConnectedDevicesManager = ConnectedDevicesManager.getConnectedDevicesManager(this);
|
||||
|
||||
sLatch = new CountDownLatch(1);
|
||||
if (PlatformManager.getInstance().getPlatform() == null) {
|
||||
PlatformManager.getInstance().createPlatform(getApplicationContext());
|
||||
}
|
||||
|
||||
PlatformManager.getInstance().getPlatform().getNotificationRegistrationManager().notificationRegistrationStateChanged().subscribe((connectedDevicesNotificationRegistrationManager, connectedDevicesNotificationRegistrationStateChangedEventArgs) -> {
|
||||
Log.i(TAG, "NotificationRegistrationState changed to " + connectedDevicesNotificationRegistrationStateChangedEventArgs.getState().toString());
|
||||
});
|
||||
|
||||
tryGetNotificationRegistration();
|
||||
|
||||
Intent intent = getIntent();
|
||||
if (intent != null) {
|
||||
final String id = intent.getStringExtra(NOTIFICATION_ID);
|
||||
if (id != null && id.equals("")) {
|
||||
final String id = intent.getStringExtra(UserNotificationsManager.NOTIFICATION_ID);
|
||||
if (id != null && !id.isEmpty()) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
sLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
dismissNotification(id);
|
||||
activateNotification(id);
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void tryGetNotificationRegistration() {
|
||||
if (sNotificationRegistration != null) {
|
||||
Log.i(TAG, "Already have notification registration");
|
||||
return;
|
||||
}
|
||||
|
||||
RomeNotificationReceiver receiver = PlatformManager.getInstance().getNotificationReceiver();
|
||||
if (receiver != null) {
|
||||
receiver.getNotificationRegistrationAsync().whenComplete((connectedDevicesNotificationRegistration, throwable) -> {
|
||||
Log.i(TAG, "Got new notification registration");
|
||||
sNotificationRegistration = connectedDevicesNotificationRegistration;
|
||||
});
|
||||
} else {
|
||||
Log.i(TAG, "No notification receiver!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
String id = intent.getStringExtra(NOTIFICATION_ID);
|
||||
dismissNotification(id);
|
||||
}
|
||||
|
||||
private void dismissNotification(String id) {
|
||||
synchronized (sNotifications) {
|
||||
boolean found = false;
|
||||
for (UserNotification notification : sNotifications) {
|
||||
if (notification.getId().equals(id)) {
|
||||
notification.setUserActionState(UserNotificationUserActionState.ACTIVATED);
|
||||
notification.saveAsync();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
Log.w(TAG, "Attempted to dismiss missing notification!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
|
@ -240,133 +132,182 @@ public class MainActivity extends AppCompatActivity {
|
|||
// as you specify a parent activity in AndroidManifest.xml.
|
||||
int id = item.getItemId();
|
||||
|
||||
//noinspection SimplifiableIfStatement
|
||||
// Request a feed sync, all channels will get updated
|
||||
if (id == R.id.action_refresh) {
|
||||
return true;
|
||||
if (sNotificationsManager != null){
|
||||
sNotificationsManager.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
String id = intent.getStringExtra(UserNotificationsManager.NOTIFICATION_ID);
|
||||
activateNotification(id);
|
||||
}
|
||||
|
||||
private static class RunnableManager {
|
||||
private static Runnable sNotificationsUpdated;
|
||||
private void activateNotification(String id) {
|
||||
if (sNotificationsManager != null){
|
||||
boolean found = false;
|
||||
for (UserNotification notification : sActiveNotifications) {
|
||||
if (notification.getId().equals(id)) {
|
||||
sNotificationsManager.activate(notification);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
Log.w(TAG, "Attempted to dismiss notification!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class RunnableManager {
|
||||
private static Runnable sNotificationsUpdated = null;
|
||||
|
||||
static void setNotificationsUpdated(Runnable runnable) {
|
||||
sNotificationsUpdated = runnable;
|
||||
}
|
||||
|
||||
static Runnable getNotificationsUpdated() {
|
||||
return sNotificationsUpdated;
|
||||
static void runNotificationsUpdated() {
|
||||
if (sNotificationsUpdated != null) {
|
||||
sNotificationsUpdated.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void setupNotificationsManager(final Activity activity) {
|
||||
if (sConnectedDevicesManager.getSignedInAccount() != null) {
|
||||
Log.d(TAG, "Setup Notifications manager");
|
||||
sNotificationsManager = sConnectedDevicesManager.getSignedInAccount().getNotificationsManager();
|
||||
sNotificationsManager.addNotificationsUpdatedEventListener(args -> {
|
||||
Log.d(TAG, "Notifications available!");
|
||||
|
||||
if (sNotificationsManager.HasNewNotifications()) {
|
||||
activity.runOnUiThread(() -> {
|
||||
Toast.makeText(activity, "Got new notifications", Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
|
||||
sActiveNotifications.clear();
|
||||
sActiveNotifications.addAll(sNotificationsManager.HistoricalNotifications());
|
||||
|
||||
if (sLatch.getCount() == 1) {
|
||||
sLatch.countDown();
|
||||
}
|
||||
|
||||
RunnableManager.runNotificationsUpdated();
|
||||
});
|
||||
sNotificationsManager.refresh();
|
||||
} else {
|
||||
activity.runOnUiThread(() -> {
|
||||
Toast.makeText(activity, "No signed-in account found!", Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class LoginFragment extends Fragment {
|
||||
private Button mAadButton;
|
||||
private Button mMsaButton;
|
||||
boolean firstCreate = true;
|
||||
|
||||
enum LoginState {
|
||||
LOGGED_IN_MSA,
|
||||
LOGGED_IN_AAD,
|
||||
LOGGED_OUT
|
||||
}
|
||||
|
||||
private LoginState mState = LoginState.LOGGED_OUT;
|
||||
|
||||
public LoginFragment() {
|
||||
}
|
||||
|
||||
public static LoginFragment newInstance() {
|
||||
return new LoginFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_login, container, false);
|
||||
|
||||
mAadButton = rootView.findViewById(R.id.login_aad_button);
|
||||
mAadButton.setOnClickListener(view -> {
|
||||
if (getLoginState() != LoginState.LOGGED_IN_AAD) {
|
||||
sConnectedDevicesManager.signInAadAsync(getActivity()).whenCompleteAsync((success, throwable) -> {
|
||||
if ((throwable == null) && (success)) {
|
||||
getActivity().runOnUiThread(() -> updateView(LoginState.LOGGED_IN_AAD));
|
||||
setupNotificationsManager(getActivity());
|
||||
|
||||
} else {
|
||||
Log.e(TAG, "AAD login failed!");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
getActivity().runOnUiThread(() -> updateView(LoginState.LOGGED_OUT));
|
||||
sConnectedDevicesManager.logout(getActivity());
|
||||
sNotificationsManager = null;
|
||||
sActiveNotifications.clear();
|
||||
RunnableManager.runNotificationsUpdated();
|
||||
}
|
||||
});
|
||||
|
||||
mMsaButton = rootView.findViewById(R.id.login_msa_button);
|
||||
LoginState loginState = getAndUpdateLoginState();
|
||||
setState(loginState);
|
||||
if (firstCreate && (loginState != LoginState.LOGGED_OUT)) {
|
||||
firstCreate = false;
|
||||
MainActivity.setupChannel(getActivity());
|
||||
mMsaButton.setOnClickListener(view -> {
|
||||
if (getLoginState() != LoginState.LOGGED_IN_MSA) {
|
||||
sConnectedDevicesManager.signInMsaAsync(getActivity()).whenCompleteAsync((success, throwable) -> {
|
||||
if ((throwable == null) && (success)) {
|
||||
getActivity().runOnUiThread(() -> updateView(LoginState.LOGGED_IN_MSA));
|
||||
setupNotificationsManager(getActivity());
|
||||
} else {
|
||||
Log.e(TAG, "MSA login failed!");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
getActivity().runOnUiThread(() -> updateView(LoginState.LOGGED_OUT));
|
||||
sConnectedDevicesManager.logout(getActivity());
|
||||
sNotificationsManager = null;
|
||||
sActiveNotifications.clear();
|
||||
RunnableManager.runNotificationsUpdated();
|
||||
}
|
||||
});
|
||||
|
||||
LoginState currentState = LoginState.LOGGED_OUT;
|
||||
if (sConnectedDevicesManager.getSignedInAccount() != null) {
|
||||
currentState = sConnectedDevicesManager.getSignedInAccount().getAccount().getType() == ConnectedDevicesAccountType.AAD ? LoginState.LOGGED_IN_AAD : LoginState.LOGGED_IN_MSA;
|
||||
setupNotificationsManager(getActivity());
|
||||
}
|
||||
updateView(currentState);
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
void setState(LoginState loginState) {
|
||||
switch (loginState) {
|
||||
synchronized LoginState getLoginState()
|
||||
{
|
||||
return mState;
|
||||
}
|
||||
|
||||
synchronized void updateLoginState(LoginState state)
|
||||
{
|
||||
mState = state;
|
||||
}
|
||||
|
||||
void updateView(LoginState state) {
|
||||
updateLoginState(state);
|
||||
|
||||
switch (state) {
|
||||
case LOGGED_OUT:
|
||||
mAadButton.setEnabled(true);
|
||||
mAadButton.setText(R.string.login_aad);
|
||||
mAadButton.setOnClickListener(view -> sAADHelperAccount.signIn(getActivity()).whenCompleteAsync((connectedDevicesAccount, throwable) -> {
|
||||
if ((throwable == null) && (connectedDevicesAccount != null)) {
|
||||
sLoggedInAccount = connectedDevicesAccount;
|
||||
PlatformManager.getInstance().getPlatform().getAccountManager().accessTokenRequested().subscribe((accountManager, args)->
|
||||
{
|
||||
sAADHelperAccount.getAccessTokenAsync(args.getRequest().getScopes()).whenCompleteAsync((token, t) -> args.getRequest().completeWithAccessToken(token));
|
||||
});
|
||||
|
||||
PlatformManager.getInstance().getPlatform().getAccountManager().accessTokenInvalidated().subscribe((connectedDevicesAccountManager, args) -> {
|
||||
// Don't need to do anything here for now
|
||||
});
|
||||
|
||||
PlatformManager.getInstance().getPlatform().start();
|
||||
|
||||
tryGetNotificationRegistration();
|
||||
|
||||
PlatformManager.getInstance().getPlatform().getAccountManager().addAccountAsync(sLoggedInAccount).whenCompleteAsync((connectedDevicesAddAccountResult, throwable12) -> PlatformManager.getInstance().getPlatform().getNotificationRegistrationManager().registerForAccountAsync(sLoggedInAccount, sNotificationRegistration).whenCompleteAsync((aBoolean, throwable1) -> {
|
||||
getActivity().runOnUiThread(()-> setState(getAndUpdateLoginState()));
|
||||
MainActivity.setupChannel(getActivity());
|
||||
}));
|
||||
|
||||
}
|
||||
}));
|
||||
|
||||
mMsaButton.setEnabled(true);
|
||||
mMsaButton.setText(R.string.login_msa);
|
||||
mMsaButton.setOnClickListener(view -> sMSAHelperAccount.signIn(getActivity()).whenCompleteAsync((connectedDevicesAccount, throwable) -> {
|
||||
if (throwable == null && connectedDevicesAccount != null) {
|
||||
sLoggedInAccount = connectedDevicesAccount;
|
||||
PlatformManager.getInstance().getPlatform().getAccountManager().accessTokenRequested().subscribe((accountManager, args)-> {
|
||||
sMSAHelperAccount.getAccessTokenAsync(args.getRequest().getScopes()).whenCompleteAsync((token, t) -> {
|
||||
args.getRequest().completeWithAccessToken(token);
|
||||
});
|
||||
});
|
||||
|
||||
PlatformManager.getInstance().getPlatform().getAccountManager().accessTokenInvalidated().subscribe((connectedDevicesAccountManager, args) -> {
|
||||
// Don't need to do anything here for now
|
||||
});
|
||||
|
||||
PlatformManager.getInstance().getPlatform().start();
|
||||
|
||||
tryGetNotificationRegistration();
|
||||
|
||||
PlatformManager.getInstance().getPlatform().getAccountManager().addAccountAsync(sLoggedInAccount).whenCompleteAsync((connectedDevicesAddAccountResult, throwable12) -> {
|
||||
PlatformManager.getInstance().getPlatform().getNotificationRegistrationManager().registerForAccountAsync(sLoggedInAccount, sNotificationRegistration).whenCompleteAsync((aBoolean, throwable1) -> {
|
||||
getActivity().runOnUiThread(()-> setState(getAndUpdateLoginState()));
|
||||
MainActivity.setupChannel(getActivity());
|
||||
});
|
||||
});
|
||||
}
|
||||
}));
|
||||
break;
|
||||
|
||||
case LOGGED_IN_AAD:
|
||||
mAadButton.setText(R.string.logout);
|
||||
mAadButton.setOnClickListener(view -> PlatformManager.getInstance().getPlatform().getAccountManager().removeAccountAsync(sLoggedInAccount).whenCompleteAsync((connectedDevicesRemoveAccountResult, throwable) -> {
|
||||
sAADHelperAccount.signOut(getActivity()).whenCompleteAsync((connectedDevicesAccount, throwable13) -> {
|
||||
sLoggedInAccount = null;
|
||||
getActivity().runOnUiThread(()-> setState(getAndUpdateLoginState()));
|
||||
});
|
||||
}));
|
||||
mMsaButton.setEnabled(false);
|
||||
break;
|
||||
|
||||
case LOGGED_IN_MSA:
|
||||
mAadButton.setEnabled(false);
|
||||
mMsaButton.setText(R.string.logout);
|
||||
mMsaButton.setOnClickListener(view -> PlatformManager.getInstance().getPlatform().getAccountManager().removeAccountAsync(sLoggedInAccount).whenCompleteAsync((connectedDevicesRemoveAccountResult, throwable) -> {
|
||||
sMSAHelperAccount.signOut(getActivity()).whenCompleteAsync((connectedDevicesAccount, throwable14) -> {
|
||||
sLoggedInAccount = null;
|
||||
getActivity().runOnUiThread(()-> setState(getAndUpdateLoginState()));
|
||||
});
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -374,51 +315,50 @@ public class MainActivity extends AppCompatActivity {
|
|||
|
||||
static class NotificationArrayAdapter extends ArrayAdapter<UserNotification> {
|
||||
private final Activity mActivity;
|
||||
NotificationArrayAdapter(Context context, List<UserNotification> items, Activity activity) {
|
||||
|
||||
public NotificationArrayAdapter(Context context, List<UserNotification> items, Activity activity) {
|
||||
super(context, R.layout.notifications_list_item, items);
|
||||
mActivity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
final UserNotification notification = sNotifications.get(position);
|
||||
final UserNotification notification = sActiveNotifications.get(position);
|
||||
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(getContext()).inflate(R.layout.notifications_list_item, parent, false);
|
||||
}
|
||||
|
||||
TextView idView = convertView.findViewById(R.id.notification_id);
|
||||
final TextView idView = convertView.findViewById(R.id.notification_id);
|
||||
idView.setText(notification.getId());
|
||||
|
||||
TextView textView = convertView.findViewById(R.id.notification_text);
|
||||
String content = notification.getContent();
|
||||
textView.setText(content);
|
||||
final TextView contentView = convertView.findViewById(R.id.notification_content);
|
||||
contentView.setText(notification.getContent());
|
||||
|
||||
TextView userActionStateView = convertView.findViewById(R.id.notification_useractionstate);
|
||||
userActionStateView.setText((notification.getUserActionState() == UserNotificationUserActionState.NO_INTERACTION)
|
||||
? "NO_INTERACTION" : "ACTIVATED");
|
||||
final TextView userActionStateView = convertView.findViewById(R.id.notification_useractionstate);
|
||||
userActionStateView.setText(notification.getUserActionState().toString());
|
||||
|
||||
final Button readButton = convertView.findViewById(R.id.notification_read);
|
||||
if (notification.getReadState() == UserNotificationReadState.UNREAD) {
|
||||
idView.setTextColor(Color.GREEN);
|
||||
readButton.setEnabled(true);
|
||||
readButton.setOnClickListener(view -> {
|
||||
readButton.setEnabled(false);
|
||||
notification.setReadState(UserNotificationReadState.READ);
|
||||
notification.saveAsync().whenCompleteAsync((userNotificationUpdateResult, throwable) -> {
|
||||
if (throwable == null && userNotificationUpdateResult != null && userNotificationUpdateResult.getSucceeded()) {
|
||||
Log.d(TAG, "Successfully marked notification as read");
|
||||
}
|
||||
});
|
||||
sNotificationsManager.markRead(notification);
|
||||
});
|
||||
} else {
|
||||
idView.setTextColor(Color.RED);
|
||||
readButton.setEnabled(false);
|
||||
}
|
||||
|
||||
final Button deleteButton = convertView.findViewById(R.id.notification_delete);
|
||||
deleteButton.setOnClickListener(view -> {
|
||||
sNotificationsManager.delete(notification);
|
||||
});
|
||||
|
||||
if (notification.getUserActionState() == UserNotificationUserActionState.NO_INTERACTION) {
|
||||
convertView.setOnClickListener(view -> {
|
||||
clearNotification(mActivity, notification.getId());
|
||||
notification.setUserActionState(UserNotificationUserActionState.ACTIVATED);
|
||||
notification.saveAsync();
|
||||
sNotificationsManager.activate(notification);
|
||||
});
|
||||
} else {
|
||||
convertView.setOnClickListener(null);
|
||||
|
@ -429,28 +369,20 @@ public class MainActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
public static class NotificationsFragment extends Fragment {
|
||||
NotificationArrayAdapter mNotificationArrayAdapter;
|
||||
public NotificationsFragment() {
|
||||
}
|
||||
private NotificationArrayAdapter mNotificationArrayAdapter;
|
||||
|
||||
/**
|
||||
* Returns a new instance of this fragment for the given section
|
||||
* number.
|
||||
*/
|
||||
public static NotificationsFragment newInstance() {
|
||||
return new NotificationsFragment();
|
||||
public NotificationsFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
mNotificationArrayAdapter = new NotificationArrayAdapter(getContext(), sNotifications, getActivity());
|
||||
RunnableManager.setNotificationsUpdated(() -> {
|
||||
if (getAndUpdateLoginState() != LoginState.LOGGED_OUT) {
|
||||
Toast.makeText(getContext(), "Got a new notification update!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
mNotificationArrayAdapter = new NotificationArrayAdapter(getContext(), sActiveNotifications, getActivity());
|
||||
|
||||
mNotificationArrayAdapter.notifyDataSetChanged();
|
||||
RunnableManager.setNotificationsUpdated(() -> {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
mNotificationArrayAdapter.notifyDataSetChanged();
|
||||
});
|
||||
});
|
||||
|
||||
View rootView = inflater.inflate(R.layout.fragment_notifications, container, false);
|
||||
|
@ -459,6 +391,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
return rootView;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LogFragment extends Fragment {
|
||||
private View mRootView;
|
||||
private TextView mTextView;
|
||||
|
@ -469,10 +402,6 @@ public class MainActivity extends AppCompatActivity {
|
|||
|
||||
public LogFragment() {}
|
||||
|
||||
public static LogFragment newInstance() {
|
||||
return new LogFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
@ -543,12 +472,12 @@ public class MainActivity extends AppCompatActivity {
|
|||
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
|
||||
* one of the sections/tabs/pages.
|
||||
*/
|
||||
private class SectionsPagerAdapter extends FragmentPagerAdapter {
|
||||
LoginFragment mLoginFragment;
|
||||
NotificationsFragment mNotificationFragment;
|
||||
LogFragment mLogFragment;
|
||||
class SectionsPagerAdapter extends FragmentPagerAdapter {
|
||||
private LoginFragment mLoginFragment;
|
||||
private NotificationsFragment mNotificationFragment;
|
||||
private LogFragment mLogFragment;
|
||||
|
||||
SectionsPagerAdapter(FragmentManager fm) {
|
||||
public SectionsPagerAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
}
|
||||
|
||||
|
@ -558,19 +487,19 @@ public class MainActivity extends AppCompatActivity {
|
|||
{
|
||||
case 0:
|
||||
if (mLoginFragment == null) {
|
||||
mLoginFragment = LoginFragment.newInstance();
|
||||
mLoginFragment = new LoginFragment();
|
||||
}
|
||||
|
||||
return mLoginFragment;
|
||||
case 1:
|
||||
if (mNotificationFragment == null) {
|
||||
mNotificationFragment = NotificationsFragment.newInstance();
|
||||
mNotificationFragment = new NotificationsFragment();
|
||||
}
|
||||
|
||||
return mNotificationFragment;
|
||||
case 2:
|
||||
if (mLogFragment == null) {
|
||||
mLogFragment = LogFragment.newInstance();
|
||||
mLogFragment = new LogFragment();
|
||||
}
|
||||
|
||||
return mLogFragment;
|
||||
|
@ -585,84 +514,4 @@ public class MainActivity extends AppCompatActivity {
|
|||
return 3;
|
||||
}
|
||||
}
|
||||
static void handleNotifications(final List<UserNotification> userNotifications, final Activity activity) {
|
||||
activity.runOnUiThread(() -> {
|
||||
synchronized (sNotifications) {
|
||||
for (final UserNotification notification : userNotifications) {
|
||||
for (int i = 0; i < sNotifications.size(); i++) {
|
||||
if (sNotifications.get(i).getId().equals(notification.getId())) {
|
||||
sNotifications.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (notification.getStatus() == UserNotificationStatus.ACTIVE) {
|
||||
sNotifications.add(0, notification);
|
||||
|
||||
if (notification.getUserActionState() == UserNotificationUserActionState.NO_INTERACTION && notification.getReadState() == UserNotificationReadState.UNREAD) {
|
||||
addNotification(activity, notification.getContent(), notification.getId());
|
||||
} else {
|
||||
clearNotification(activity, notification.getId());
|
||||
}
|
||||
} else {
|
||||
clearNotification(activity, notification.getId());
|
||||
}
|
||||
}
|
||||
|
||||
if (RunnableManager.getNotificationsUpdated() != null) {
|
||||
RunnableManager.getNotificationsUpdated().run();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void setupChannel(final Activity activity) {
|
||||
if (getAndUpdateLoginState() == LoginState.LOGGED_OUT) {
|
||||
return;
|
||||
}
|
||||
|
||||
UserDataFeed dataFeed = UserDataFeed.getForAccount(sLoggedInAccount, PlatformManager.getInstance().getPlatform(), Secrets.APP_HOST_NAME);
|
||||
dataFeed.subscribeToSyncScopesAsync(Arrays.asList(UserNotificationChannel.getSyncScope())).whenCompleteAsync((success, throwable) -> {
|
||||
if (success) {
|
||||
dataFeed.startSync();
|
||||
UserNotificationChannel channel = new UserNotificationChannel(dataFeed);
|
||||
UserNotificationReaderOptions options = new UserNotificationReaderOptions();
|
||||
sReader = channel.createReaderWithOptions(options);
|
||||
sReader.readBatchAsync(Long.MAX_VALUE).thenAccept(userNotifications -> {
|
||||
handleNotifications(userNotifications, activity);
|
||||
if (sLatch.getCount() == 1) {
|
||||
sLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
sReader.dataChanged().subscribe((userNotificationReader, aVoid) -> userNotificationReader.readBatchAsync(Long.MAX_VALUE).thenAccept(userNotifications -> {
|
||||
handleNotifications(userNotifications, activity);
|
||||
}));
|
||||
} else {
|
||||
activity.runOnUiThread(() -> Toast.makeText(activity.getApplicationContext(), "Failed to subscribe to sync scopes", Toast.LENGTH_SHORT));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void addNotification(Activity activity, String message, String notificationId) {
|
||||
Intent intent = new Intent(activity, MainActivity.class);
|
||||
intent.putExtra(NOTIFICATION_ID, notificationId);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(activity, 0, intent, PendingIntent.FLAG_ONE_SHOT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(activity, MainActivity.CHANNEL_NAME)
|
||||
.setSmallIcon(R.mipmap.ic_launcher_round)
|
||||
.setContentTitle("New MSGraph Notification!")
|
||||
.setContentText(message)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
NotificationManagerCompat.from(activity).notify(notificationId.hashCode(), builder.build());
|
||||
}
|
||||
|
||||
static void clearNotification(Activity activity, String notificationId) {
|
||||
((NotificationManager)activity.getSystemService(NOTIFICATION_SERVICE)).cancel(notificationId.hashCode());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
|
||||
package com.microsoft.connecteddevices.graphnotifications;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesPlatform;
|
||||
|
||||
final class PlatformManager {
|
||||
private RomeNotificationReceiver mNotificationReceiver;
|
||||
private ConnectedDevicesPlatform mPlatform;
|
||||
private static PlatformManager sInstance;
|
||||
|
||||
private static final String TAG = PlatformManager.class.getName();
|
||||
|
||||
public static synchronized PlatformManager getInstance()
|
||||
{
|
||||
if (sInstance == null) {
|
||||
sInstance = new PlatformManager();
|
||||
}
|
||||
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public synchronized ConnectedDevicesPlatform createPlatform(Context context) {
|
||||
if (mPlatform != null) {
|
||||
return mPlatform;
|
||||
}
|
||||
|
||||
try {
|
||||
mPlatform = new ConnectedDevicesPlatform(context);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Subscribe to ConnectedDevicesNotificationRegistrationManager's event for when the registration state changes for a given account.
|
||||
mPlatform.getNotificationRegistrationManager().notificationRegistrationStateChanged().subscribe(
|
||||
(notificationRegistrationManager, args) -> {
|
||||
// TODO: Future - Identity-V3: give this to the signin helpers? Not exactly sure how to handle this...
|
||||
});
|
||||
|
||||
return mPlatform;
|
||||
}
|
||||
|
||||
public synchronized void startPlatform() {
|
||||
// Ensure we have created the Platform since there will not be an object to call start on otherwise.
|
||||
if (mPlatform == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mPlatform.start();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to start platform with exception: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized ConnectedDevicesPlatform getPlatform() {
|
||||
return mPlatform;
|
||||
}
|
||||
|
||||
public synchronized void createNotificationReceiver(Context context) {
|
||||
mNotificationReceiver = new RomeNotificationReceiver(context);
|
||||
}
|
||||
|
||||
public synchronized RomeNotificationReceiver getNotificationReceiver() {
|
||||
return mNotificationReceiver;
|
||||
}
|
||||
}
|
|
@ -13,59 +13,37 @@ import android.util.Log;
|
|||
|
||||
import com.microsoft.connecteddevices.AsyncOperation;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesNotificationRegistration;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesNotificationType;
|
||||
import com.microsoft.connecteddevices.EventListener;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class RomeNotificationReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = RomeNotificationReceiver.class.getName();
|
||||
private Map<Long, EventListener<RomeNotificationReceiver, ConnectedDevicesNotificationRegistration>> mListenerMap;
|
||||
private Long mNextListenerId = 0L;
|
||||
private ConnectedDevicesNotificationRegistration mNotificationRegistration;
|
||||
private AsyncOperation<ConnectedDevicesNotificationRegistration> mAsync;
|
||||
private Context mContext;
|
||||
private static final String RegistrationComplete = "registrationComplete";
|
||||
|
||||
RomeNotificationReceiver(Context context) {
|
||||
mListenerMap = new HashMap<>();
|
||||
mContext = context;
|
||||
private static AsyncOperation<ConnectedDevicesNotificationRegistration> sNotificationRegistrationOperation;
|
||||
|
||||
private Context mContext;
|
||||
|
||||
RomeNotificationReceiver(Context context) {
|
||||
mContext = context;
|
||||
registerFCMBroadcastReceiver();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns Notification Registration after it completes async operation.
|
||||
* @return Notification Registration.
|
||||
*/
|
||||
public synchronized AsyncOperation<ConnectedDevicesNotificationRegistration> getNotificationRegistrationAsync() {
|
||||
if (mAsync == null) {
|
||||
mAsync = new AsyncOperation<>();
|
||||
public static synchronized void setNotificationRegistration(ConnectedDevicesNotificationRegistration registration) {
|
||||
// Create the registration operation if it has not been requested already
|
||||
if (sNotificationRegistrationOperation == null) {
|
||||
sNotificationRegistrationOperation = new AsyncOperation<>();
|
||||
}
|
||||
if (mNotificationRegistration != null) {
|
||||
mAsync.complete(mNotificationRegistration);
|
||||
}
|
||||
return mAsync;
|
||||
|
||||
// Complete the operation with the registration, to be fetched later
|
||||
sNotificationRegistrationOperation.complete(registration);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function adds new event listener to notification provider.
|
||||
* @param listener the EventListener.
|
||||
* @return id next event listener id.
|
||||
*/
|
||||
public synchronized long addNotificationProviderChangedListener(
|
||||
EventListener<RomeNotificationReceiver, ConnectedDevicesNotificationRegistration> listener) {
|
||||
mListenerMap.put(mNextListenerId, listener);
|
||||
return mNextListenerId++;
|
||||
}
|
||||
public static synchronized AsyncOperation<ConnectedDevicesNotificationRegistration> getNotificationRegistrationAsync() {
|
||||
// Create the registration operation if it the registration has not been received yet
|
||||
if (sNotificationRegistrationOperation == null) {
|
||||
sNotificationRegistrationOperation = new AsyncOperation<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function removes the event listener.
|
||||
* @param id the id corresponds to the event listener that would be removed.
|
||||
*/
|
||||
public synchronized void removeNotificationProviderChangedListener(long id) {
|
||||
mListenerMap.remove(id);
|
||||
return sNotificationRegistrationOperation;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,47 +53,30 @@ public class RomeNotificationReceiver extends BroadcastReceiver {
|
|||
*/
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String token = null;
|
||||
String action = intent.getAction();
|
||||
Log.i(TAG, "Broadcast received: " + action);
|
||||
|
||||
Log.i("Receiver", "Broadcast received: " + action);
|
||||
|
||||
String token = null;
|
||||
if (action.equals(RegistrationComplete)) {
|
||||
token = intent.getExtras().getString("TOKEN");
|
||||
}
|
||||
|
||||
if (token == null) {
|
||||
Log.e("GraphNotifications",
|
||||
"Got notification that FCM had been registered, but token is null. Was app ID set in FCMRegistrationIntentService?");
|
||||
if (token == null || token.isEmpty()) {
|
||||
Log.e(TAG, "RomeNotificationReceiver gained a token however it is null/empty, check FCMRegistrationIntentService");
|
||||
} else {
|
||||
Log.i(TAG, "RomeNotificationReceiver gained a token: " + token);
|
||||
ConnectedDevicesManager.getConnectedDevicesManager(context).setNotificationRegistration(token);
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
mNotificationRegistration = new ConnectedDevicesNotificationRegistration();
|
||||
mNotificationRegistration.setType(ConnectedDevicesNotificationType.FCM);
|
||||
mNotificationRegistration.setToken(token);
|
||||
mNotificationRegistration.setAppId(Secrets.FCM_SENDER_ID);
|
||||
mNotificationRegistration.setAppDisplayName("OneRomanApp");
|
||||
|
||||
if (mAsync == null) {
|
||||
mAsync = new AsyncOperation<>();
|
||||
}
|
||||
mAsync.complete(mNotificationRegistration);
|
||||
mAsync = new AsyncOperation<>();
|
||||
|
||||
for (EventListener<RomeNotificationReceiver, ConnectedDevicesNotificationRegistration> event : mListenerMap.values()) {
|
||||
event.onEvent(this, mNotificationRegistration);
|
||||
}
|
||||
|
||||
Log.e(TAG, "Successfully completed FCM registration");
|
||||
}
|
||||
mContext.startService(new Intent(mContext, FCMListenerService.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called to start FCM registration service.
|
||||
* Start FCMRegistrationIntentService to register with FCM.
|
||||
*/
|
||||
private void startService() {
|
||||
Log.e(TAG, "Starting FCMListenerService");
|
||||
Intent registrationIntentService = new Intent(mContext, FCMListenerService.class);
|
||||
private void startFCMRegistrationIntentService() {
|
||||
Intent registrationIntentService = new Intent(mContext, FCMRegistrationIntentService.class);
|
||||
mContext.startService(registrationIntentService);
|
||||
}
|
||||
|
||||
|
@ -124,6 +85,6 @@ public class RomeNotificationReceiver extends BroadcastReceiver {
|
|||
*/
|
||||
private void registerFCMBroadcastReceiver() {
|
||||
LocalBroadcastManager.getInstance(mContext).registerReceiver(this, new IntentFilter(RegistrationComplete));
|
||||
startService();
|
||||
startFCMRegistrationIntentService();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
package com.microsoft.connecteddevices.graphnotifications;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import com.microsoft.connecteddevices.AsyncOperation;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesAccount;
|
||||
import com.microsoft.connecteddevices.ConnectedDevicesPlatform;
|
||||
import com.microsoft.connecteddevices.userdata.UserDataFeed;
|
||||
import com.microsoft.connecteddevices.userdata.usernotifications.UserNotification;
|
||||
import com.microsoft.connecteddevices.userdata.usernotifications.UserNotificationChannel;
|
||||
import com.microsoft.connecteddevices.userdata.usernotifications.UserNotificationReadState;
|
||||
import com.microsoft.connecteddevices.userdata.usernotifications.UserNotificationReader;
|
||||
import com.microsoft.connecteddevices.userdata.usernotifications.UserNotificationStatus;
|
||||
import com.microsoft.connecteddevices.userdata.usernotifications.UserNotificationUserActionState;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EventObject;
|
||||
import java.util.List;
|
||||
|
||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
|
||||
public class UserNotificationsManager {
|
||||
private static final String TAG = UserNotificationsManager.class.getName();
|
||||
|
||||
public static final String CHANNEL_NAME = "GraphNotificationsChannel001";
|
||||
public static final String NOTIFICATION_ID = "ID";
|
||||
|
||||
public interface NotificationsUpdatedEventListener {
|
||||
void onEvent(EventObject args);
|
||||
}
|
||||
|
||||
private ArrayList<NotificationsUpdatedEventListener> mListeners = new ArrayList<>();
|
||||
|
||||
private Context mContext;
|
||||
private UserDataFeed mFeed;
|
||||
private UserNotificationChannel mChannel;
|
||||
private UserNotificationReader mReader;
|
||||
|
||||
private final ArrayList<UserNotification> mHistoricalNotifications = new ArrayList<>();
|
||||
private final ArrayList<UserNotification> mNewNotifications = new ArrayList<>();
|
||||
|
||||
public UserNotificationsManager(@NonNull Context context, @NonNull ConnectedDevicesAccount account, @NonNull ConnectedDevicesPlatform platform)
|
||||
{
|
||||
mContext = context;
|
||||
mFeed = UserDataFeed.getForAccount(account, platform, Secrets.APP_HOST_NAME);
|
||||
mChannel = new UserNotificationChannel(mFeed);
|
||||
mReader = mChannel.createReader();
|
||||
mReader.dataChanged().subscribe((reader, aVoid) -> readFromCache(reader));
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(CHANNEL_NAME, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
|
||||
channel.setDescription("GraphNotificationsSample Channel");
|
||||
((NotificationManager)context.getSystemService(NOTIFICATION_SERVICE)).createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
public AsyncOperation<Boolean> registerForAccountAsync()
|
||||
{
|
||||
return mFeed.subscribeToSyncScopesAsync(Arrays.asList(UserNotificationChannel.getSyncScope())).thenComposeAsync((success) -> {
|
||||
mFeed.startSync();
|
||||
readFromCache(mReader);
|
||||
return AsyncOperation.completedFuture(success);
|
||||
});
|
||||
}
|
||||
|
||||
public synchronized void addNotificationsUpdatedEventListener(NotificationsUpdatedEventListener listener) {
|
||||
mListeners.add(listener);
|
||||
}
|
||||
|
||||
public synchronized void removeNotificationsUpdatedEventListener(NotificationsUpdatedEventListener listener) {
|
||||
mListeners.remove(listener);
|
||||
}
|
||||
|
||||
public List<UserNotification> HistoricalNotifications() {
|
||||
return mHistoricalNotifications;
|
||||
}
|
||||
|
||||
public boolean HasNewNotifications() {
|
||||
return !mNewNotifications.isEmpty();
|
||||
}
|
||||
|
||||
public void refresh()
|
||||
{
|
||||
mFeed.startSync();
|
||||
readFromCache(mReader);
|
||||
}
|
||||
|
||||
public void activate(UserNotification notification)
|
||||
{
|
||||
notification.setUserActionState(UserNotificationUserActionState.ACTIVATED);
|
||||
notification.saveAsync().whenCompleteAsync((userNotificationUpdateResult, throwable) -> {
|
||||
if (throwable == null && userNotificationUpdateResult != null && userNotificationUpdateResult.getSucceeded()) {
|
||||
Log.d(TAG, "Successfully activated the notification");
|
||||
}
|
||||
});
|
||||
clearNotification(mContext.getApplicationContext(), notification.getId());
|
||||
}
|
||||
|
||||
public void dismiss(UserNotification notification)
|
||||
{
|
||||
notification.setUserActionState(UserNotificationUserActionState.DISMISSED);
|
||||
notification.saveAsync().whenCompleteAsync((userNotificationUpdateResult, throwable) -> {
|
||||
if (throwable == null && userNotificationUpdateResult != null && userNotificationUpdateResult.getSucceeded()) {
|
||||
Log.d(TAG, "Successfully dismissed the notification");
|
||||
}
|
||||
});
|
||||
clearNotification(mContext.getApplicationContext(), notification.getId());
|
||||
}
|
||||
|
||||
public void markRead(UserNotification notification)
|
||||
{
|
||||
notification.setReadState(UserNotificationReadState.READ);
|
||||
notification.saveAsync().whenCompleteAsync((userNotificationUpdateResult, throwable) -> {
|
||||
if (throwable == null && userNotificationUpdateResult != null && userNotificationUpdateResult.getSucceeded()) {
|
||||
Log.d(TAG, "Successfully marked the notification as read");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void delete(UserNotification notification)
|
||||
{
|
||||
mChannel.deleteUserNotificationAsync(notification.getId()).whenCompleteAsync((userNotificationUpdateResult, throwable) -> {
|
||||
if (throwable == null && userNotificationUpdateResult != null && userNotificationUpdateResult.getSucceeded()) {
|
||||
Log.d(TAG, "Successfully deleted the notification");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void NotifyNotificationsUpdated() {
|
||||
Log.d(TAG, "Notifying listeners");
|
||||
List<NotificationsUpdatedEventListener> listeners = new ArrayList<>();
|
||||
synchronized (this) {
|
||||
listeners.addAll(mListeners);
|
||||
}
|
||||
for (NotificationsUpdatedEventListener listener : listeners) {
|
||||
listener.onEvent(new EventObject(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for the java.util.function.Predicate to support pre Java 8 / API 24.
|
||||
*/
|
||||
interface Predicate<T> {
|
||||
public boolean test(T t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for list.removeIf to support pre Java 8 / API 24.
|
||||
* @param list List to search
|
||||
* @param predicate Predicate to use against the given list
|
||||
* @return True if removed item matching the given predicate, false if none found
|
||||
*/
|
||||
private static <T> boolean removeIf(List<T> list, Predicate<? super T> predicate) {
|
||||
for (T item : list) {
|
||||
if (predicate.test(item)) {
|
||||
list.remove(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void readFromCache(final UserNotificationReader reader)
|
||||
{
|
||||
Log.d(TAG, "Read notifications from cache");
|
||||
reader.readBatchAsync(Long.MAX_VALUE).thenAccept(notifications -> {
|
||||
synchronized (this) {
|
||||
for (final UserNotification notification : notifications) {
|
||||
if (notification.getStatus() == UserNotificationStatus.ACTIVE) {
|
||||
removeIf(mNewNotifications, item -> notification.getId().equals(item.getId()));
|
||||
|
||||
if (notification.getUserActionState() == UserNotificationUserActionState.NO_INTERACTION) {
|
||||
mNewNotifications.add(notification);
|
||||
if (notification.getReadState() != UserNotificationReadState.READ) {
|
||||
clearNotification(mContext.getApplicationContext(), notification.getId());
|
||||
addNotification(mContext.getApplicationContext(), notification.getContent(), notification.getId());
|
||||
}
|
||||
} else {
|
||||
clearNotification(mContext.getApplicationContext(), notification.getId());
|
||||
}
|
||||
|
||||
removeIf(mHistoricalNotifications, item -> notification.getId().equals(item.getId()));
|
||||
mHistoricalNotifications.add(0, notification);
|
||||
} else {
|
||||
removeIf(mNewNotifications, item -> notification.getId().equals(item.getId()));
|
||||
removeIf(mHistoricalNotifications, item -> notification.getId().equals(item.getId()));
|
||||
clearNotification(mContext.getApplicationContext(), notification.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NotifyNotificationsUpdated();
|
||||
});
|
||||
}
|
||||
|
||||
static void addNotification(Context ctx, String message, String notificationId) {
|
||||
Intent intent = new Intent(ctx, MainActivity.class);
|
||||
intent.putExtra(NOTIFICATION_ID, notificationId);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, intent, PendingIntent.FLAG_ONE_SHOT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, CHANNEL_NAME)
|
||||
.setSmallIcon(R.mipmap.ic_launcher_round)
|
||||
.setContentTitle("New UserNotification!")
|
||||
.setContentText(message)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
NotificationManagerCompat.from(ctx).notify(notificationId.hashCode(), builder.build());
|
||||
}
|
||||
|
||||
static void clearNotification(Context ctx, String notificationId) {
|
||||
((NotificationManager)ctx.getSystemService(NOTIFICATION_SERVICE)).cancel(notificationId.hashCode());
|
||||
}
|
||||
}
|
|
@ -1,23 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/fragment_log"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
<TextView
|
||||
android:id="@+id/log_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/log_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="" />
|
||||
</LinearLayout>
|
||||
android:text="" />
|
||||
</ScrollView>
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,23 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/fragment_login"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/login_msa_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/login_msa" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/login_aad_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/login_aad" />
|
||||
|
||||
</LinearLayout>
|
|
@ -1,33 +0,0 @@
|
|||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/constraintLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="com.microsoft.connecteddevices.graphnotifications.MainActivity$LoginFragment">
|
||||
|
||||
<Button
|
||||
android:id="@+id/login_aad_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_aad"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.323" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/login_msa_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="160dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/login_msa"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.502"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/login_aad_button"
|
||||
app:layout_constraintVertical_bias="1.0" />
|
||||
</android.support.constraint.ConstraintLayout>
|
|
@ -1,8 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/fragment_usernotification"
|
||||
android:orientation="vertical" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:id="@+id/fragment_notifications"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
<ListView
|
||||
android:id="@+id/notificationListView"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -1,44 +1,47 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="30dp" >
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notification_id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:text="" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notification_text"
|
||||
android:id="@+id/notification_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/notification_id"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:text="" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notification_useractionstate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/notification_text"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:text="" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/notification_read"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_toEndOf="@id/notification_useractionstate"
|
||||
android:layout_alignTop="@+id/notification_useractionstate"
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="10dp"
|
||||
android:text="@string/read" />
|
||||
android:orientation="horizontal">
|
||||
<Button
|
||||
android:id="@+id/notification_read"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/read"
|
||||
android:enabled="false"/>
|
||||
<Button
|
||||
android:id="@+id/notification_delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/delete" />
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
|
@ -8,5 +8,6 @@
|
|||
<string name="login_aad">Login with AAD</string>
|
||||
<string name="login_msa">Login with MSA</string>
|
||||
<string name="logout">Log Out</string>
|
||||
<string name="read">Read</string>
|
||||
<string name="read">Mark Read</string>
|
||||
<string name="delete">Delete</string>
|
||||
</resources>
|
||||
|
|
Загрузка…
Ссылка в новой задаче