Getting Started with Android
- Introduction
- Authentication - Authentication Flow Overview
- Authentication - ADAuthentication Context
- Authentication - Acquire a token
- Authentication - Authenticating silently
- Authentication - Re-entry and API Calls
- Mail - Introduction
- Mail - Dependency resolver
- Mail - Entity client
- Mail - Execute query
- Mail - Getting folders
- Mail - Getting Contacts
- Mail - Getting messages
- Mail - Getting calendars
- Mail - Getting events
- Mail - CRUD Operations
- Mail - Other Operations
- Mail - Sending messages
- Lists - Introduction
- Lists - Dependency resolver
- Lists - Entity client
- Lists - Getting Lists
- Lists - Getting List Items
- Lists - Advanced Filtering
- Lists - Paging and Sorting
- Lists - Selecting Specific Fields
- Lists - Creating new list items
- Files - Dependency Resolver
- Files - Create Client
- Files - Execute Query
Introduction
Welcome to the Getting Started experience for integrating your Android application with Office 365. This getting started experience is meant as a starting point to help you dive into hooking your Android app with Office 365 APIs and capabilities.
Ready to get going?
Click on the AD Authentication Context tab to learn how to integrate Azure AD Authentication with your app.
Authentication Flow Overview
Full authentication is only done on application startup. The Splash Screen and Login screen are the gateway to your app – the user must authenticate to continue.
This is the general flow of authentication that must occur at the launch of the app in order to authenticate the user and give them access to their data:
1. The app launches and starts the default activity or splash screen.
2. Immediately invoke the tryAuthSilent() method, which attempts to authenticate silently with the ADAL using any cached tokens – this allows the user to use the app without constantly re-authenticating.
3. If the silent authentication succeeds, the access token is stored and the user is forwarded to the Main screen of the app, and the finish() method is called to remove the splash screen from the activity stack.
4. If the silent authentication does not succeed, then we show the user the login screen – the user must then authenticate to continue.
5. The user clicks “sign in” button, which invokes acquireToken() – this function calls into the ADAL.
6. The ADAL launches it’s Authentication screen and the OAuth flow takes place: the user authenticates with Azure AD. If they cancel, we return them to the login screen.
7. The authentication result (an Access Token and some user information) is returned and cached in memory. This will be appended to all further API calls automatically.
8. Finally we launch the next Activity. The login screen calls the finish() method to remove itself from the activity stack.
Here is the diagram that depicts the flow described above.
Authentication Flow - App Start
This pattern can be adjusted to meet the requirements of your own app. For example, you might merge the behaviour of the Splash and Login screens into one screen. Or you might eliminate the “auto-login” behaviour all together, and require the user to always authenticate.
ADAuthentication context
Authentication with O365 SharePoint is handled by Azure AD. Azure AD supports OAuth 2.0 – Access tokens and refresh tokens. The Active Directory Authentication Library (ADAL) provides UI and services for implementing Azure AD OAuth within an Android app.
The Azure AD Library for Android can be downloaded from GitHub.
This SDK supports both Eclipse and Android Studio, and can be included as source, or as a binary AAR package from Maven, or as a jar in your libs folder.
To add the ADAL to an Android Studio project, add this to your build.gradle file:
compile 'com.microsoft.aad:adal:1.0.2'
Android studio will automatically download the binaries and add them to your project.
Before continuing, you must register your Android app in Azure AD as a “native client” app, and obtain a client ID. This client ID is passed to the ADAL along with other information about your AD/O365 instance.
The AuthenticationContext class is our hook into the ADAL. It provides a number of methods for:
- Acquiring an access token
- Acquiring an access token using a refresh token
The acquireToken function may launch an authentication activity, where the user is asked to sign in. Consequently, we must pass any activity results through to the AuthenticationContext class so that it can complete the authentication process.
Create an instance of AuthenticationContext
//authority is in the form of "https://login.windows.net/yourtenant.onmicrosoft.com"
//context is of the type android.content.Context
mContext = new AuthenticationContext(MyActivity.this, authority, false);
Handle authentication completion
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (mContext != null) {
mContext.onActivityResult(requestCode, resultCode, data);
}
}
Acquire a token
The next step in authenticating against Azure AD is to acquire a token. This will prompt the user to sign in and then once the correct credentials are passed to Azure AD, it will return a token that can be used by the app to authenticate as the user.
This operation results in both an Access Token and a Refresh Token, as well as other user metadata.
Acquire a token
mAuthContext.acquireToken(MyActivity.this,
resourceId, //e.g. http://mydomain.sharepoint.com
clientId, //an Azure AD client id (guid)
redirectUrl, //e.g. "http://android/callback" (configured with AD app)
loginHint, //e.g. "[email protected]" (optional)
PromptBehavior.Auto,
extraQueryArgs,
callback //e.g. new AuthenticationCallback() {...}
);
With PromptBehaviour.Auto, acquireToken will attempt to retrieve an access token from the cache, or will use a cached refresh token. Only if neither of those options is available will it launch the authentication UI.
With PromptBehaviour.Always, acquireToken will always prompt the user for their credentials.
Authenticating silently
If we want to authenticate a returning user, we can cache their User Id, and later use that to attempt to authenticate silently.
This function uses the User Id to attempt to retrieve the cached and encrypted access token and refresh token from a previous successful authentication.
If there is no refresh token available, or something else goes wrong, then this function fails silently (no user prompt).
Authenticating the user silently (without a sign in prompt)
mAuthContext.acquireTokenSilent(
resourceId, //e.g. http://mydomain.sharepoint.com
clientId, //an Azure AD client id (guid)
cachedUserId, //a cached user Id from a previous successful authentication
callback //e.g. new AuthenticationCallback() {...}
);
Securely storing tokens
While there are ADAL methods which accept passing refresh tokens, you do not need to use them since the ADAL automatically caches access/refresh tokens securely. Tokens are encrypted using a private key obtained from the Android Keystore.
The recommended practice is to avoid caching refresh tokens yourself since the ADAL takes care of this for you. Cached access/refresh tokens are encrypted using a key obtained from the Android Key Store: https://developer.android.com/training/articles/keystore.html.
“The Android Keystore system lets you store private keys in a container to make it more difficult to extract from the device. Once keys are in the keystore, they can be used for cryptographic operations with the private key material remaining non-exportable.”
Authentication - Re-entry and API Calls
Next code needs to be written to support re-entrancy into potentially any location within the app. The user may at any time dismiss the app, do some work in another app, and then potentially return to the app after their token has expired.
In this situation the Access Token may need to be refreshed or re-authenticate (or we may not).
1. The user initiates some action (e.g. Refresh the current view)
2. First call the ensureToken() method to verify that there is an Access Token and that it is valid. A callback needs to be passes to this funciton because this it may asynchronously refresh the token.
3. If the token is still valid then continue with the API call immediately (this may happen completely synchronously).
4. Otherwise, refresh the token and store the new tokens. This happens asynchronously and requires an internet connection.
5. If successful, continue with the API call.
6. If the refresh operation fails, then prompt the user to let them know, and restart the app so that the user may authenticate again.
Here is a diagram depicting this re-entry or API call authentication flow that was described above.
Authentication Flow - App Re-entry and API Calls
All of this behavior can be wrapped into a few helper methods and classes.
Mail - Introduction
The Office 365 SDK for Android provides programmatic access to your Mail, Calendar and Contacts in O365 Exchange Online. Authentication is handled by Azure Active Directory.
The Office 365 SDK for Android is available on GitHub.
Mail - Dependency Resolver
Classes in the Office 365 SDK for Android use an Inversion of Control pattern, which allows you to override certain internal behaviors by providing a dependency resolver.
In order to use the DefaultDependencyResolver class, we must provide a CredentialsFactory instance, which ultimately be used to configure all HTTP requests with Authorization headers.
Here we have obtained an access token using the Active Directory Authentication Library. We then create a DependencyResolver instance and provide it with an anonymously-implemented CredentialsFactory, which will automatically add an Authorization header to each request.
Create and configure a dependency resolver
final String accessToken = ...; // obtained using ADAL
OAuthCredentials credentials = new OAuthCredentials(accessToken);
CredentialsFactoryImpl credFactory = new CredentialsFactoryImpl();
credFactory.setCredentials(credentials);
DefaultDependencyResolver dependencyResolver = new DefaultDependencyResolver();
dependencyResolver.setCredentialsFactory(credFactory);
Mail - Entity Client
Create an instance of EntityClientContainer. This object is used as the basis of all your interactions with the Office 365 Mail API.
All data functions on the EntityClientContainer return strongly-typed Fetcher objects, which are used to build queries against the API.
“Singular” functions like getById and getMe all return a strongly-typed subclass of ODataEntityFetcher, like UserFetcher and FolderFetcher.
“Plural” functions like getFolders and getContacts return an instance of ODataCollectionFetcher, which has additional methods for building collection queries.
Create an entity client
import com.microsoft.outlookservices.odata.EntityContainerClient;
final String url = "https://outlook.office365.com/ews/odata";
EntityContainerClient client = new EntityContainerClient(url, dependencyResolver);
Build a query
UserFetcher meFetcher = client.getMe();
ODataCollectionFetcher foldersFetcher = client.getMe().getFolders();
FolderFetcher inboxFolderFetcher = client.getMe().getFolders().getById("Inbox");
Mail - Executing a Query
Invoke the read function to start the query. Queries in the Office 365 SDK for Android are asynchronous – they return a ListenableFuture
The Futures helper class allows for attaching callback handlers to the ListenableFuture. If the request was successful, then the onSuccess handler is invoked, and the result of the call is passed in argument.
The ListenableFuture class is part of the Google Guava class library: https://code.google.com/p/guava-libraries/
Execute a query and await the result (asynch)
// Execute the query...
ListenableFuture folderFuture = inboxFolderFetcher.read();
// And await the result
Futures.addCallback(folderFuture, new FutureCallback() {
public void onSuccess(final Folder result) {
// Invoked when the request completes successfully
}
public void onFailure(final Throwable t) {
// Invoked when something goes wrong
}
});
An alternative to registering callbacks is to use the synchronous get() method on ListenableFuture
This function blocks the current thread until the underlying asynchronous task has completed, and then returns the result.
This can greatly simplify code by eliminating “callback syndrome”, but does not allow for parallel operations.
Warning: get() and other synchronous functions MUST be executed on a background thread! For example, using the AsyncTask type. This should NOT be done on the UI thread.
Execute a query and get immediate result(s) (synch)
ListenableFuture folderFuture = ...;
Folder result = folderFuture.get();
Mail - Getting Folders
“Fetcher” objects can be used to refine your query further.
Here we demonstrate the Folders ODataCollectionFetcher returned by getFolders(). We can chain calls to query modifiers until we call read(), when the actual query will be executed.
In this query we’re using the select() query modifier. This allows us to explicitly select the set of fields to be returned to us by the server.
This function can be useful in reducing the amount of data sent across the wire.
Getting folders
// retrieve a folder by its Id
future = client.getMe()
.getFolders()
.getById("Inbox")
.read(); // executes the query - returns ListenableFuture
// compose a query for folders
future = client.getMe()
.getFolders()
.select("Id,DisplayName") // odata select
.read(); // executes the query - returns ListenableFuture<List<Folder>>
Mail - Getting Contacts
We can query for the current user’s contacts and contacts folders.
Here we’re also using the filter() query modifier. The string is an ODATA FILTER expression, which in this case aims to filter results to all Contacts born after 1990.
All of these query modifiers (filter, top, skip, select and expand) can be combined.
For more information on the filter syntax, see http://www.odata.org/documentation/odata-version-2-0/uri-conventions#FilterSystemQueryOption
Getting contacts
// retrieve a contact by Id
future = client.getMe()
.getContacts()
.getById("")
.read(); // executes the query – returns ListenableFuture
// compose a query for contacts
future = client.getMe()
.getContacts()
.filter("Birthday ge datetime'2000-01-01T00:00'") // odata filter
.read(); // executes the query - returns ListenableFuture<List>
Mail - Getting Messages
We can query for the current user’s email messages.
First we grab a reference to the user’s Inbox, which is just another folder. Note that this line does not execute a request to the server (yet).
The user might have a lot of email messages in their Inbox, so we want to do some simple paging. These variables pageSize and pageIndex would be kept as state on UI object.
We pass the pageSize and pageIndex variables to the top() and skip() query modifiers, and call read() to execute the query.
We can use the same pattern for child folders – indeed this pattern will work for most entities in the Mail API (e.g. Folders, Contacts, Calendar Appointments).
Getting messages
// get a reference to the Inbox. Note: this does not execute a query
FolderFetcher folder = client.getMe().getFolders().getById("Inbox");
// retrieve the first page of results
int pageSize = 15, pageIndex = 0;
future = folder.getMessages()
.top(pageSize).skip(pageSize * pageIndex) // Simple paging
.read(); // execute query - returns ListenableFuture<List>
// use the same pattern for any other folders
FolderFetcher childFolder = folder.getChildFolders().getById("Project");
// retrieves the first page of messages in the "Project" folder
future = childFolder.getMessages().top(pageSize).skip(pageSize * pageIndex).read();
Mail - Getting Calendars
We can query the current user’s calendar.
As with the other querying functions in the Mail SDK, we can apply additional filtering operations to our query before executing it.
Getting calendars
// retrieve the user's primary calendar
future = client.getMe()
.getCalendar()
.read(); // returns ListenableFuture
// query all of the user's calendars which start with the string 'My'
future = client.getMe()
.getCalendars()
.filter("startswith(Name, 'My')")
.read(); // returns ListenableFuture<List>
Mail - Getting Events
We can query the current user’s calendar for events.
This query finds all events on the user’s primary calendar which started on Jan 1st, 2014 UTC.
As with the other querying functions in the Mail SDK, we can apply additional filtering operations to our query before executing it.
Getting events
// query the user's primary calendar for events which started Jan 1st, 2014 UTC
final String filterExpr = "Start ge datetime'2014-01-01T00:00Z' and " +
"Start lt datetime'2014-01-02T00:00Z'";
future = client.getMe()
.getCalendar()
.getEvents()
.filter(filterExpr)
.read();
Mail - CRUD Operations
CRUD operations can be accessed via their related collection fetchers. E.g. to create a new Event on the user’s Calendar:
CRUD Operations Sample
// Create a new event to add to the calendar
Event event = createEvent();
future = client.getMe().getCalendar().getEvents().add(event); //ListenableFuture
Mail - Other Operations
Other operations are accessed via the getOperations() function. These include things like accepting appointments:
Accepting appointments sample
// mark the event as Accepted
future = client.getMe().getCalendar().getEvents().getById("")
.getOperations()
.accept("Accepted");
Mail - Sending Messages
Sending a message is as simple as building the email message, and posting it through the sendMail() function. Other Fetchers will expose strongly-typed Operations, e.g. for Creating a folder, Moving a message from one folder to another. These are exposed by the getOperations function on each Fetcher class.
Sending messages
// Create an email message...
Message message = createEmailMessage();
// And send it. Like the query functions, sendMail returns a Future
boolean saveToSentItems = true;
future = client.getMe().getOperations().sendMail(message, saveToSentItems);
Lists - Introduction
The Office 365 Lists SDK for Android provides programmatic access to your Lists and List Items in Office 365 SharePoint Online.
Authentication is handled by Azure Active Directory
Lists - Dependency Resolver
Classes in the Office 365 SDK for Android use an Inversion of Control pattern to allow you to override certain internal behaviors by providing a dependency resolver.
In order to use the DefaultDependencyResolver class, we must provide a CredentialsFactory instance, which ultimately be used to configure all HTTP requests with Authorization headers.
Here we have obtained an access token using the Active Directory Authentication Library. We then create a DependencyResolver instance and provide it with an anonymously-implemented CredentialsFactory, which will automatically add an Authorization header to each request.
Create and configure a Dependency Resolver
final String accessToken = ...; // obtained using ADAL
OAuthCredentials credentials = new OAuthCredentials(accessToken);
CredentialsFactoryImpl credFactory = new CredentialsFactoryImpl();
credFactory.setCredentials(credentials);
DefaultDependencyResolver dependencyResolver = new DefaultDependencyResolver();
dependencyResolver.setCredentialsFactory(credFactory);
Lists - Entity Client
Create an instance of SharepointListsClient. The all data functions on the SharepointListsClient class are asynchronous. They return a ListenableFuture
The ListenableFuture class is part of the Google Guava class library: https://code.google.com/p/guava-libraries/
Create a Lists client and execute a query asynchronously
final String serverUrl = "https://mydomain.sharepoint.com/";
final String siteRelativeUrl = "sites/ExampleTeamSite";
SharepointListsClient client = new SharepointListsClient(
serverUrl,
siteRelativeUrl,
dependencyResolver
);
ListenableFuture<List> listsFuture = client.getLists(null);
//attach callbacks to listsFuture
An alternative to registering callbacks is to use the synchronous get() method on ListenableFuture
This function blocks the current thread until the underlying asynchronous task has completed, and then returns the result.
This can greatly simplify code by eliminating “callback syndrome”, but does not allow for parallel operations.
Warning: get() and other synchronous functions MUST be executed on a background thread! E.g. using the AsyncTask type.
Execute a query synchronously
ListenableFuture<List> listsFuture = ...;
List lists = listsFuture.get();
ListenableFuture
Lists - Getting Lists
Find Lists by title
SPList myList = client.getList("My List").get();
Filter results by using a Query object
//Find top ten lists sorted by Title
Query query = new Query().orderBy("Title", QueryOrder.Ascending)
.top(10);
List lists = client.getLists(query).get();
Lists - Getting List Items
Getting all items in a list
SPListItem myListItems = client.getListItems("My List", null).get();
Filter results by using a Query object
//Find List items created in 2008
Query query = QueryOperations.year("Created").eq().val(2008);
SPListItem myListItems = client.getListItems("My List", query).get();
Lists - Advanced Filtering
Build complex filters using the Query class
// year(Created) eq 2008
Query query = QueryOperations.year("Created").eq().val(2008);
// startsWith(Custom, 'Prefix-')
Query query = QueryOperations.startsWith("Custom", "Prefix-");
// (Custom eq 'Some Value') and (100 not gt Count)
Query query = QueryOperations.query(
QueryOperations.field("Custom").eq().val("Some Value")
).and(
QueryOperations.val(100).not().gt().field("Count")
);
Lists - Paging and Sorting
We can implement server-side paging using the top() and skip() query modifiers.
When paging we should also provide a sort column.
Implement paging using the Query class
String sortColumn = ...;
// Retrieve page 4
int pageSize = 15, pageIndex = 3;
// $top=100 $orderby=Title desc
Query pagingQuery = new Query().skip(pageSize * pageIndex)
.top(pageSize)
.orderby(sortColumn, QueryOrder.Ascending);
Lists - Specific Fields
You may want to filter the fields you request from SharePoint to reduce the amount of data sent over the wire.
Some specific fields are resource intensive to retrieve, and are excluded by default. Use the select() function to retrieve these columns.
Select the fields to be returned
// Use * to select all fields
final static String[] MY_FIELDS = { "Id", "Title", "Description", "Count" };
// $select=Id,Title,Description,Count
Query query = QueryOperations.select(MY_FIELDS);
Note that some fields must be explicitly selected, as they are filtered out of results by default.
Lists - Creating new list items
Create a list item use insertListItem
// a reference to the List we're adding to.
SPList list = ...;
// a new List Item to add to the List
SPListItem newListItem = createSPListItem();
// returns ListenableFuture
future = client.insertListItem(newListItem, list);
Updating and Deleting list items
SPList list = ...;
// update a list item...
SPListItem modifiedListItem = ...;
future = client.updateListItem(modifiedListItem, list);
// delete a list item...
SPListItem listItemToDelete = ...;
future = client.deleteListItem(listItemToDelete, list);
Files - Dependency Resolver
The Office 365 SDK for Android provides programmatic access to the Files in in your Office 365 OneDrive account.
Authentication is handled by Azure Active Directory
Classes in the Office 365 SDK for Android use an Inversion of Control pattern to allow you to override certain internal behaviors by providing a dependency resolver.
In order to use the DefaultDependencyResolver class, we must provide a CredentialsFactory instance, which ultimately be used to configure all HTTP requests with Authorization headers.
Here we have obtained an access token using the Active Directory Authentication Library. We then create a DependencyResolver instance and provide it with an anonymously-implemented CredentialsFactory, which will automatically add an Authorization header to each request.
Create and configure a dependency resolver
final String accessToken = ...; // obtained using ADAL
OAuthCredentials credentials = new OAuthCredentials(accessToken);
CredentialsFactoryImpl credFactory = new CredentialsFactoryImpl();
credFactory.setCredentials(credentials);
DefaultDependencyResolver dependencyResolver = new DefaultDependencyResolver();
dependencyResolver.setCredentialsFactory(credFactory);
Files - Create Client
Create an instance of SharepointListsClient.
The all data functions on the SharepointListsClient class are asynchronous. They return a ListenableFuture
The ListenableFuture class is part of the Google Guava class library: https://code.google.com/p/guava-libraries/
Create a client and execute a query
import com.microsoft.coreservices.odata.EntityContainerClient;
final String url = "https://mydomain.sharepoint.com/_api/v1.0";
EntityContainerClient client = new EntityContainerClient(url, dependencyResolver);
Files - Execute Query
Get all of the user's files
// retrieve a list of all files on the user's account
future = client.getme().getfiles().read();
