├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── NOTICES.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── microsoft │ │ └── office365 │ │ └── connect │ │ ├── AuthenticationManager.java │ │ ├── ConnectActivity.java │ │ ├── Constants.java │ │ ├── DiscoveryManager.java │ │ ├── MailManager.java │ │ ├── OperationCallback.java │ │ └── SendMailActivity.java │ └── res │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── layout │ ├── activity_connect.xml │ └── activity_send_mail.xml │ ├── menu │ └── send_mail.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── loc └── README-ja.md ├── readme-images ├── O365-Android-Connect-Constants.png ├── O365-Android-Connect-video_play_icon.png └── o365-exchange-permissions.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.ap_ 2 | *.apk 3 | *.class 4 | *.dex 5 | *.iml 6 | *.ipr 7 | *.iws 8 | .DS_Store 9 | .classpath 10 | .gradle 11 | .idea 12 | .project 13 | /.idea/libraries 14 | /.idea/workspace.xml 15 | /build 16 | /captures 17 | /gradle 18 | /gradlew 19 | /gradlew.bat 20 | /local.properties 21 | Thumbs.db 22 | bin/ 23 | build/ 24 | gen/ 25 | gradle/ 26 | out/ 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: android 3 | android: 4 | components: 5 | - tools 6 | - platform-tools 7 | - extra 8 | - android-23 9 | - build-tools-23.0.3 10 | script: 11 | - gradle clean build 12 | notifications: 13 | slack: 14 | secure: JIndOGoBOL+4P1Rmty0GZ2M655HuZTwnWQJA6swCm+h4D4LVQIRqo4kgCdmteMZLVOPraIStFw6A0piKmhiwfA3SP+resO2rCoQQfJkLEfq5aJWph1bUEaoG+LMZwfS5pwHf5TSXk6WpwQ4imxYNiT3hiCA4us1rE6R4SpphupY= 15 | email: 16 | recipients: 17 | - jak@microsoft.com 18 | on_success: never 19 | on_failure: always 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Microsoft. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NOTICES.md: -------------------------------------------------------------------------------- 1 | This project includes the following third-party components: 2 | 3 | MS Open Tech Office 365 SDK for Android, which is Copyright (c) Microsoft Open Technologies, Inc. and is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 4 | 5 | Android SDK, which is provided by the Android Open Source Project and is used according to terms described in the [Creative Commons 2.5 Attribution License](http://creativecommons.org/licenses/by/2.5/). The Android SDK is available at [http://developer.android.com/sdk/index.html](http://developer.android.com/sdk/index.html). 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Office 365 Connect Sample for Android 2 | [![Build Status](https://travis-ci.org/OfficeDev/O365-Android-Connect.svg?branch=master)](https://travis-ci.org/OfficeDev/O365-Android-Connect) 3 | 4 | [日本 (日本語)](/loc/README-ja.md) (Japanese) 5 | 6 | [![Office 365 Connect sample](/readme-images/O365-Android-Connect-video_play_icon.png)](https://www.youtube.com/watch?v=3IQIDFrqhY4 "Click to see the sample in action") 7 | 8 | Connecting to Office 365 is the first step every Android app must take to start working with Office 365 services and data. This sample shows how to connect and then call one API. 9 | 10 | ## Device requirements 11 | 12 | To run the Connect sample, your device needs to meet the following requirements: 13 | 14 | * A screen size of 800 x 480 or larger. 15 | * Android API level 15 or later. 16 | 17 | ## Prerequisites 18 | 19 | To use the Office 365 Connect sample for Android you need the following: 20 | 21 | * [Android Studio](http://developer.android.com/sdk/index.html) version 1.0 or later. 22 | * [Java Development Kit (JDK) 7](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html). 23 | * An Office 365 account. You can [join the Office 365 Developer Program and get a free 1 year subscription to Office 365](https://aka.ms/devprogramsignup) that includes the resources that you need to start building Office 365 apps. 24 | 25 | > Note: If you already have a subscription, the previous link sends you to a page that says *Sorry, you can’t add that to your current account*. In that case use an account from your current Office 365 subscription.

26 | If you are already signed-in to Office 365, the Sign-in button in the previous link shows the message *Sorry, we can't process your request*. In that case sign-out from Office 365 in that same page and sign-in again. 27 | 28 | * A Microsoft Azure tenant to register your application. Azure Active Directory provides identity services that applications use for authentication and authorization. A trial subscription can be acquired here: [Microsoft Azure](https://account.windowsazure.com/SignUp). 29 | 30 | > Important: You will also need to ensure your Azure subscription is bound to your Office 365 tenant. To do this see the Active Directory team's blog post, [Creating and Managing Multiple Windows Azure Active Directories](http://blogs.technet.com/b/ad/archive/2013/11/08/creating-and-managing-multiple-windows-azure-active-directories.aspx). The section **Adding a new directory** will explain how to do this. You can also see [Set up your Office 365 development environment](https://msdn.microsoft.com/office/office365/howto/setup-development-environment#bk_CreateAzureSubscription) and the section **Associate your Office 365 account with Azure AD to create and manage apps** for more information. 31 | 32 | * A client id and redirect uri values of an application registered in Azure. The application must be granted the **Send mail as a user** permission. [Add a native client application in Azure](https://msdn.microsoft.com/office/office365/HowTo/add-common-consent-manually#bk_RegisterNativeApp) and [grant proper permissions](https://github.com/OfficeDev/O365-Android-Connect/wiki/Grant-permissions-to-the-Connect-application-in-Azure) to it. 33 | 34 | ## Open the sample using Android Studio 35 | 36 | 1. Install [Android Studio](http://developer.android.com/tools/studio/index.html#install-updates) and add the Android SDK packages according to the [instructions](http://developer.android.com/sdk/installing/adding-packages.html) on developer.android.com. 37 | 2. Download or clone this sample. 38 | 3. Start Android Studio. 39 | 1. Close any projects that you might have open, then select **Open an existing Android Studio project**. 40 | 2. Browse to your local repository and select the O365-Android-Connect project. Click **OK**. 41 | 42 | > Note: Android Studio might display a dialog asking if you want to use Gradle wrapper. Click **OK**. 43 | > 44 | > Additionally, Android Studio shows a **Frameworks detected** notification if you don't have the **Android Support Repository** installed. Open the SDK manager and add the Android Support Repository to avoid the Frameworks detected notification. 45 | 4. Open the [```Constants.java```](app/src/main/java/com/microsoft/office365/connect/Constants.java) file. 46 | 1. Find the [```CLIENT_ID```](app/src/main/java/com/microsoft/office365/connect/Constants.java#L12) constant and set its String value equal to the client id you registered in Azure Active Directory. 47 | 2. Find the [```REDIRECT_URI```](/app/src/main/java/com/microsoft/office365/connect/Constants.java#L13) constant and set its String value equal to the redirect URI you registered in Azure Active Directory. 48 | ![Office 365 Connect sample](/readme-images/O365-Android-Connect-Constants.png "Client ID and Redirect URI values in Constants file") 49 | 50 | > Note: If you have don't have CLIENT\_ID and REDIRECT\_URI values, [add a native client application in Azure](https://msdn.microsoft.com/library/azure/dn132599.aspx#BKMK_Adding) and take note of the CLIENT\_ID and REDIRECT_URI. 51 | 52 | Once you've built the Connect sample, you can run it on an emulator or device. Pick a device with API level 15 or higher from the **Choose device** dialog. 53 | 54 | To learn more about the sample, visit our [understanding the code](https://github.com/OfficeDev/O365-Android-Connect/wiki/Understanding-the-Connect-sample-code) wiki page. If you just want to use this code sample in your app, visit the [Using the O365 Android Connect sample code in your app](https://github.com/OfficeDev/O365-Android-Connect/wiki/Using-the-O365-Android-Connect-sample-code-in-your-app). 55 | 56 | ## Questions and comments 57 | 58 | We'd love to get your feedback on the O365 Android Connect project. You can send your questions and suggestions to us in the [Issues](https://github.com/OfficeDev/O365-Android-Connect/issues) section of this repository. 59 | 60 | Questions about Office 365 development in general should be posted to [Stack Overflow](http://stackoverflow.com/questions/tagged/Office365+API). Make sure that your questions or comments are tagged with [Office365] and [API]. 61 | 62 | ## Next steps 63 | 64 | This sample just shows the essentials that your apps need to work with Office 365. There is so much more that your apps can do using the Office 365 APIs, like helping your users to manage their work day with calendar, find just the information they need in all the files they store in OneDrive, or find the exact person they need from their list of contacts. We have more to share with you in the [Office 365 APIs Starter Project for Android](https://github.com/officedev/O365-Android-Start/). We think it can help you fuel your ideas. 65 | 66 | ## Additional resources 67 | 68 | * [Office 365 APIs platform overview](https://msdn.microsoft.com/office/office365/howto/platform-development-overview) 69 | * [Office 365 SDK for Android](https://github.com/OfficeDev/Office-365-SDK-for-Android) 70 | * [Get started with Office 365 APIs in apps](https://msdn.microsoft.com/office/office365/howto/getting-started-Office-365-APIs) 71 | * [Office 365 APIs starter projects and code samples](https://msdn.microsoft.com/office/office365/howto/starter-projects-and-code-samples) 72 | * [Office 365 Code Snippets for Android](https://github.com/OfficeDev/O365-Android-Snippets) 73 | * [Office 365 APIs Starter Project for Android](https://github.com/OfficeDev/O365-Android-Start) 74 | * [Office 365 Profile sample for Android](https://github.com/OfficeDev/O365-Android-Profile) 75 | 76 | ## Copyright 77 | Copyright (c) 2015 Microsoft. All rights reserved. 78 | 79 | 80 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 81 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.microsoft.office365.connect" 9 | minSdkVersion 15 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:23.3.0' 25 | 26 | // Discovery and Outlook services 27 | compile(group: 'com.microsoft.services', name: 'discovery-services', version: '1.0.0', ext: 'aar'){ 28 | transitive = true 29 | } 30 | compile(group: 'com.microsoft.services', name: 'outlook-services', version: '1.0.0', ext: 'aar'){ 31 | transitive = true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Program Files (x86)\Android\android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 36 | 39 | 40 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/microsoft/office365/connect/AuthenticationManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | package com.microsoft.office365.connect; 6 | 7 | import android.app.Activity; 8 | import android.content.Context; 9 | import android.content.SharedPreferences; 10 | import android.os.Build; 11 | import android.provider.Settings; 12 | import android.util.Log; 13 | 14 | import com.microsoft.aad.adal.ADALError; 15 | import com.microsoft.aad.adal.AuthenticationCallback; 16 | import com.microsoft.aad.adal.AuthenticationContext; 17 | import com.microsoft.aad.adal.AuthenticationException; 18 | import com.microsoft.aad.adal.AuthenticationResult; 19 | import com.microsoft.aad.adal.AuthenticationResult.AuthenticationStatus; 20 | import com.microsoft.aad.adal.AuthenticationSettings; 21 | import com.microsoft.aad.adal.PromptBehavior; 22 | import com.microsoft.services.orc.core.DependencyResolver; 23 | import com.microsoft.services.orc.log.LogLevel; 24 | import com.microsoft.services.orc.resolvers.ADALDependencyResolver; 25 | 26 | import java.io.UnsupportedEncodingException; 27 | 28 | /** 29 | * Handles setup of ADAL Dependency Resolver for use in API clients. 30 | * Check the {@link AuthenticationManager#connect(AuthenticationCallback)} method to learn how to 31 | * get Azure AD tokens for your app. 32 | * You can also check {@link AuthenticationManager#authenticatePrompt(AuthenticationCallback)} to 33 | * learn how to get tokens by prompting the user for credentials, or 34 | * {@link AuthenticationManager#authenticateSilent(AuthenticationCallback)} to learn how to get 35 | * tokens silently. 36 | * To learn how to dispose the tokens, see {@link AuthenticationManager#disconnect()}. 37 | */ 38 | 39 | public class AuthenticationManager { 40 | private static final String TAG = "AuthenticationManager"; 41 | private static final String PREFERENCES_FILENAME = "ConnectFile"; 42 | private static final String USER_ID_VAR_NAME = "userId"; 43 | private AuthenticationContext mAuthenticationContext; 44 | private ADALDependencyResolver mDependencyResolver; 45 | private Activity mContextActivity; 46 | private String mResourceId; 47 | 48 | static{ 49 | // Devices with API level lower than 18 must setup an encryption key. 50 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2 && 51 | AuthenticationSettings.INSTANCE.getSecretKeyData() == null) { 52 | AuthenticationSettings.INSTANCE.setSecretKey(generateSecretKey()); 53 | } 54 | 55 | // We're not using Microsoft Intune Company portal app, 56 | // skip the broker check so we don't get warnings about the following permissions 57 | // in manifest: 58 | // GET_ACCOUNTS 59 | // USE_CREDENTIALS 60 | // MANAGE_ACCOUNTS 61 | AuthenticationSettings.INSTANCE.setSkipBroker(true); 62 | } 63 | 64 | /** 65 | * Calls {@link AuthenticationManager#authenticatePrompt(AuthenticationCallback)} if no user id is stored in the shared preferences. 66 | * Calls {@link AuthenticationManager#authenticateSilent(AuthenticationCallback)} otherwise. 67 | * @param authenticationCallback The callback to notify when the processing is finished. 68 | */ 69 | public void connect(final AuthenticationCallback authenticationCallback) { 70 | // Since we're doing considerable work, let's get out of the main thread 71 | new Thread(new Runnable() { 72 | @Override 73 | public void run() { 74 | if (verifyAuthenticationContext()) { 75 | if (isConnected()) { 76 | authenticateSilent(authenticationCallback); 77 | } else { 78 | authenticatePrompt(authenticationCallback); 79 | } 80 | } else { 81 | Log.e(TAG, "connect - Auth context verification failed. Did you set a context activity?"); 82 | throw new AuthenticationException( 83 | ADALError.ACTIVITY_REQUEST_INTENT_DATA_IS_NULL, 84 | "Auth context verification failed. Did you set a context activity?"); 85 | } 86 | } 87 | }).start(); 88 | } 89 | 90 | /** 91 | * Calls acquireTokenSilent with the user id stored in shared preferences. 92 | * In case of an error, it falls back to {@link AuthenticationManager#authenticatePrompt(AuthenticationCallback)}. 93 | * @param authenticationCallback The callback to notify when the processing is finished. 94 | */ 95 | private void authenticateSilent(final AuthenticationCallback authenticationCallback) { 96 | getAuthenticationContext().acquireTokenSilent( 97 | this.mResourceId, 98 | Constants.CLIENT_ID, 99 | getUserId(), 100 | new AuthenticationCallback() { 101 | @Override 102 | public void onSuccess(final AuthenticationResult authenticationResult) { 103 | if (authenticationResult != null && authenticationResult.getStatus() == AuthenticationStatus.Succeeded) { 104 | mDependencyResolver = new ADALDependencyResolver( 105 | getAuthenticationContext(), 106 | mResourceId, 107 | Constants.CLIENT_ID); 108 | authenticationCallback.onSuccess(authenticationResult); 109 | } else if (authenticationResult != null) { 110 | // I could not authenticate the user silently, 111 | // falling back to prompt the user for credentials. 112 | authenticatePrompt(authenticationCallback); 113 | } 114 | } 115 | 116 | @Override 117 | public void onError(Exception e) { 118 | // I could not authenticate the user silently, 119 | // falling back to prompt the user for credentials. 120 | authenticatePrompt(authenticationCallback); 121 | } 122 | } 123 | ); 124 | } 125 | 126 | /** 127 | * Calls acquireToken to prompt the user for credentials. 128 | * @param authenticationCallback The callback to notify when the processing is finished. 129 | */ 130 | private void authenticatePrompt(final AuthenticationCallback authenticationCallback) { 131 | getAuthenticationContext().acquireToken( 132 | this.mContextActivity, 133 | this.mResourceId, 134 | Constants.CLIENT_ID, 135 | Constants.REDIRECT_URI, 136 | PromptBehavior.Always, 137 | new AuthenticationCallback() { 138 | @Override 139 | public void onSuccess(final AuthenticationResult authenticationResult) { 140 | if (authenticationResult != null && authenticationResult.getStatus() == AuthenticationStatus.Succeeded) { 141 | setUserId(authenticationResult.getUserInfo().getUserId()); 142 | mDependencyResolver = new ADALDependencyResolver( 143 | getAuthenticationContext(), 144 | mResourceId, 145 | Constants.CLIENT_ID); 146 | authenticationCallback.onSuccess(authenticationResult); 147 | } else if (authenticationResult != null) { 148 | // We need to make sure that there is no data stored with the failed auth 149 | AuthenticationManager.getInstance().disconnect(); 150 | // This condition can happen if user signs in with an MSA account 151 | // instead of an Office 365 account 152 | authenticationCallback.onError( 153 | new AuthenticationException( 154 | ADALError.AUTH_FAILED, 155 | authenticationResult.getErrorDescription() 156 | ) 157 | ); 158 | } 159 | } 160 | 161 | @Override 162 | public void onError(Exception e) { 163 | // We need to make sure that there is no data stored with the failed auth 164 | AuthenticationManager.getInstance().disconnect(); 165 | authenticationCallback.onError(e); 166 | } 167 | } 168 | ); 169 | } 170 | 171 | /** 172 | * Disconnects the app from Office 365 by clearing the token cache, setting the client objects 173 | * to null, and removing the user id from shred preferences. 174 | */ 175 | public void disconnect(){ 176 | // Clear tokens. 177 | if(getAuthenticationContext().getCache() != null) { 178 | getAuthenticationContext().getCache().removeAll(); 179 | } 180 | 181 | // Reset the AuthenticationManager object 182 | AuthenticationManager.resetInstance(); 183 | 184 | // Forget the user 185 | removeUserId(); 186 | } 187 | 188 | public static synchronized AuthenticationManager getInstance() { 189 | if (INSTANCE == null) { 190 | INSTANCE = new AuthenticationManager(); 191 | } 192 | return INSTANCE; 193 | } 194 | 195 | private static synchronized void resetInstance() { 196 | INSTANCE = null; 197 | } 198 | 199 | private static AuthenticationManager INSTANCE; 200 | 201 | private AuthenticationManager() { 202 | mResourceId = Constants.DISCOVERY_RESOURCE_ID; 203 | } 204 | 205 | /** 206 | * Set the context activity before connecting to the currently active activity. 207 | * @param contextActivity Currently active activity which can be utilized for interactive 208 | * prompt. 209 | */ 210 | public void setContextActivity(final Activity contextActivity) { 211 | this.mContextActivity = contextActivity; 212 | } 213 | 214 | /** 215 | * Change from the default Resource ID set in ServiceConstants to a different 216 | * resource ID. 217 | * This can be called at anytime without requiring another interactive prompt. 218 | * @param resourceId URL of resource ID to be accessed on behalf of user. 219 | */ 220 | public void setResourceId(final String resourceId) { 221 | this.mResourceId = resourceId; 222 | this.mDependencyResolver.setResourceId(resourceId); 223 | } 224 | 225 | /** 226 | * Gets authentication context for Azure Active Directory. 227 | * @return an authentication context, if successful. 228 | */ 229 | public AuthenticationContext getAuthenticationContext() { 230 | if (mAuthenticationContext == null) { 231 | try { 232 | mAuthenticationContext = new AuthenticationContext(this.mContextActivity, Constants.AUTHORITY_URL, false); 233 | } catch (Throwable t) { 234 | Log.e(TAG, t.toString()); 235 | } 236 | } 237 | return mAuthenticationContext; 238 | } 239 | 240 | /** 241 | * Dependency resolver that can be used to create client objects. 242 | * The {@link DiscoveryManager#getServiceInfo} method uses it to create a DiscoveryClient object. 243 | * The {@link MailManager#sendMail(String, String, String, OperationCallback)} uses it to create an OutlookClient object. 244 | * @return The dependency resolver object. 245 | */ 246 | public DependencyResolver getDependencyResolver() { 247 | return getInstance().mDependencyResolver; 248 | } 249 | 250 | private boolean verifyAuthenticationContext() { 251 | if (this.mContextActivity == null) { 252 | Log.e(TAG, "Must set context activity"); 253 | return false; 254 | } 255 | return true; 256 | } 257 | 258 | private boolean isConnected(){ 259 | SharedPreferences settings = this 260 | .mContextActivity 261 | .getSharedPreferences(PREFERENCES_FILENAME, Context.MODE_PRIVATE); 262 | 263 | return settings.contains(USER_ID_VAR_NAME); 264 | } 265 | 266 | private String getUserId(){ 267 | SharedPreferences settings = this 268 | .mContextActivity 269 | .getSharedPreferences(PREFERENCES_FILENAME, Context.MODE_PRIVATE); 270 | 271 | return settings.getString(USER_ID_VAR_NAME, ""); 272 | } 273 | 274 | private void setUserId(String value){ 275 | SharedPreferences settings = this 276 | .mContextActivity 277 | .getSharedPreferences(PREFERENCES_FILENAME, Context.MODE_PRIVATE); 278 | 279 | SharedPreferences.Editor editor = settings.edit(); 280 | editor.putString(USER_ID_VAR_NAME, value); 281 | editor.apply(); 282 | } 283 | 284 | private void removeUserId(){ 285 | SharedPreferences settings = this 286 | .mContextActivity 287 | .getSharedPreferences(PREFERENCES_FILENAME, Context.MODE_PRIVATE); 288 | 289 | SharedPreferences.Editor editor = settings.edit(); 290 | editor.remove(USER_ID_VAR_NAME); 291 | editor.apply(); 292 | } 293 | 294 | /** 295 | * Generates an encryption key for devices with API level lower than 18 using the 296 | * ANDROID_ID value as a seed. 297 | * In production scenarios, you should come up with your own implementation of this method. 298 | * Consider that your algorithm must return the same key so it can encrypt/decrypt values 299 | * successfully. 300 | * @return The encryption key in a 32 byte long array. 301 | */ 302 | private static byte[] generateSecretKey() { 303 | byte[] key = new byte[32]; 304 | byte[] android_id; 305 | 306 | try{ 307 | android_id = Settings.Secure.ANDROID_ID.getBytes("UTF-8"); 308 | } catch (UnsupportedEncodingException e){ 309 | Log.e(TAG, "generateSecretKey - " + e.getMessage()); 310 | throw new RuntimeException(e); 311 | } 312 | 313 | for(int i = 0; i < key.length; i++){ 314 | key[i] = android_id[i % android_id.length]; 315 | } 316 | 317 | return key; 318 | } 319 | 320 | /** 321 | * Turn logging on. 322 | * @param level LogLevel to set. 323 | */ 324 | public void enableLogging(LogLevel level) { 325 | this.mDependencyResolver.getLogger().setEnabled(true); 326 | this.mDependencyResolver.getLogger().setLogLevel(level); 327 | } 328 | 329 | /** 330 | * Turn logging off. 331 | */ 332 | public void disableLogging() { 333 | this.mDependencyResolver.getLogger().setEnabled(false); 334 | } 335 | } -------------------------------------------------------------------------------- /app/src/main/java/com/microsoft/office365/connect/ConnectActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | package com.microsoft.office365.connect; 6 | 7 | import android.content.Intent; 8 | import android.os.Bundle; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.util.Log; 11 | import android.view.View; 12 | import android.widget.Button; 13 | import android.widget.ProgressBar; 14 | import android.widget.TextView; 15 | import android.widget.Toast; 16 | 17 | import com.microsoft.aad.adal.AuthenticationCallback; 18 | import com.microsoft.aad.adal.AuthenticationResult; 19 | 20 | import java.net.URI; 21 | import java.util.UUID; 22 | 23 | /** 24 | * Starting activity of the app. Handles the connection to Office 365. 25 | * When it first starts it only displays a button to Connect to Office 365. 26 | * If there are no cached tokens, the user is required to sign in to Office 365. 27 | * If there are cached tokens, the app tries to reuse them. 28 | * The activity redirects the user to the SendMailActivity upon successful connection. 29 | */ 30 | public class ConnectActivity extends AppCompatActivity { 31 | 32 | private static final String TAG = "ConnectActivity"; 33 | 34 | private Button mConnectButton; 35 | private TextView mTitleTextView; 36 | private ProgressBar mConnectProgressBar; 37 | private TextView mDescriptionTextView; 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_connect); 43 | 44 | initializeViews(); 45 | } 46 | 47 | /** 48 | * Event handler for the onclick event of the button. 49 | * @param v The view that sent the event. 50 | */ 51 | public void onConnectButtonClick(View v) { 52 | showConnectingInProgressUI(); 53 | 54 | //check that client id and redirect have been set correctly 55 | try { 56 | UUID.fromString(Constants.CLIENT_ID); 57 | URI.create(Constants.REDIRECT_URI); 58 | } 59 | catch (IllegalArgumentException e) { 60 | Toast.makeText( 61 | this 62 | , getString(R.string.warning_client_id_redirect_uri_incorrect) 63 | , Toast.LENGTH_LONG).show(); 64 | 65 | resetUIForConnect(); 66 | return; 67 | } 68 | 69 | final Intent sendMailIntent = new Intent(this, SendMailActivity.class); 70 | AuthenticationManager.getInstance().setContextActivity(this); 71 | 72 | AuthenticationManager.getInstance().connect( 73 | new AuthenticationCallback() { 74 | /** 75 | * If the connection is successful, the activity extracts the username and 76 | * displayableId values from the authentication result object and sends them 77 | * to the SendMail activity. 78 | * @param result The authentication result object that contains information about 79 | * the user and the tokens. 80 | */ 81 | @Override 82 | public void onSuccess(AuthenticationResult result) { 83 | Log.i(TAG, "onConnectButtonClick - Successfully connected to Office 365"); 84 | 85 | sendMailIntent.putExtra("givenName", result 86 | .getUserInfo() 87 | .getGivenName()); 88 | sendMailIntent.putExtra("displayableId", result 89 | .getUserInfo() 90 | .getDisplayableId()); 91 | startActivity(sendMailIntent); 92 | 93 | resetUIForConnect(); 94 | } 95 | 96 | @Override 97 | public void onError(final Exception e) { 98 | Log.e(TAG, "onCreate - " + e.getMessage()); 99 | showConnectErrorUI(); 100 | } 101 | }); 102 | } 103 | 104 | /** 105 | * This activity gets notified about the completion of the ADAL activity through this method. 106 | * @param requestCode The integer request code originally supplied to startActivityForResult(), 107 | * allowing you to identify who this result came from. 108 | * @param resultCode The integer result code returned by the child activity through its 109 | * setResult(). 110 | * @param data An Intent, which can return result data to the caller (various data 111 | * can be attached to Intent "extras"). 112 | */ 113 | @Override 114 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 115 | Log.i(TAG, "onActivityResult - AuthenticationActivity has come back with results"); 116 | super.onActivityResult(requestCode, resultCode, data); 117 | AuthenticationManager 118 | .getInstance() 119 | .getAuthenticationContext() 120 | .onActivityResult(requestCode, resultCode, data); 121 | } 122 | 123 | private void initializeViews(){ 124 | mConnectButton = (Button)findViewById(R.id.connectButton); 125 | mConnectProgressBar = (ProgressBar)findViewById(R.id.connectProgressBar); 126 | mTitleTextView = (TextView)findViewById(R.id.titleTextView); 127 | mDescriptionTextView = (TextView)findViewById(R.id.descriptionTextView); 128 | } 129 | 130 | private void resetUIForConnect(){ 131 | mConnectButton.setVisibility(View.VISIBLE); 132 | mTitleTextView.setVisibility(View.GONE); 133 | mDescriptionTextView.setVisibility(View.GONE); 134 | mConnectProgressBar.setVisibility(View.GONE); 135 | } 136 | 137 | private void showConnectingInProgressUI(){ 138 | mConnectButton.setVisibility(View.GONE); 139 | mTitleTextView.setVisibility(View.GONE); 140 | mDescriptionTextView.setVisibility(View.GONE); 141 | mConnectProgressBar.setVisibility(View.VISIBLE); 142 | } 143 | 144 | private void showConnectErrorUI(){ 145 | mConnectButton.setVisibility(View.VISIBLE); 146 | mConnectProgressBar.setVisibility(View.GONE); 147 | mTitleTextView.setText(R.string.title_text_error); 148 | mTitleTextView.setVisibility(View.VISIBLE); 149 | mDescriptionTextView.setText(R.string.connect_text_error); 150 | mDescriptionTextView.setVisibility(View.VISIBLE); 151 | Toast.makeText( 152 | ConnectActivity.this, 153 | R.string.connect_toast_text_error, 154 | Toast.LENGTH_LONG).show(); 155 | } 156 | } -------------------------------------------------------------------------------- /app/src/main/java/com/microsoft/office365/connect/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | package com.microsoft.office365.connect; 6 | 7 | interface Constants { 8 | String AUTHORITY_URL = "https://login.microsoftonline.com/common"; 9 | String DISCOVERY_RESOURCE_URL = "https://api.office.com/discovery/v1.0/me/"; 10 | String DISCOVERY_RESOURCE_ID = "https://api.office.com/discovery/"; 11 | String MAIL_CAPABILITY = "Mail"; 12 | // Update these two constants with the values for your application: 13 | String CLIENT_ID = ""; 14 | String REDIRECT_URI = ""; 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/microsoft/office365/connect/DiscoveryManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | package com.microsoft.office365.connect; 6 | 7 | import android.util.Log; 8 | 9 | import com.microsoft.services.discovery.ServiceInfo; 10 | import com.microsoft.services.discovery.fetchers.DiscoveryClient; 11 | import com.microsoft.services.orc.resolvers.ADALDependencyResolver; 12 | 13 | import java.util.List; 14 | import java.util.NoSuchElementException; 15 | import java.util.concurrent.ExecutionException; 16 | 17 | /** 18 | * Handles the discovery of the service endpoints 19 | * for the capabilities that the user has access to 20 | * in Office 365. 21 | */ 22 | public class DiscoveryManager { 23 | 24 | private static final String TAG = "DiscoveryManager"; 25 | 26 | private List mServices; 27 | 28 | public static synchronized DiscoveryManager getInstance() { 29 | if (INSTANCE == null) { 30 | INSTANCE = new DiscoveryManager(); 31 | } 32 | return INSTANCE; 33 | } 34 | 35 | private static DiscoveryManager INSTANCE; 36 | 37 | /** 38 | * Provides information about the service that corresponds to the provided capability. 39 | * Gets the info from a local cache. 40 | * Calls {@link DiscoveryManager#getServiceInfoFromDiscoveryService(String, OperationCallback)} 41 | * if the service info was not found in cache. 42 | * @param capability A string that contains the capability of the service that 43 | * is going to be discovered. 44 | * @param operationCallback The callback to which return the result or error. 45 | */ 46 | public void getServiceInfo(final String capability, final OperationCallback operationCallback) { 47 | // Since we're doing considerable work, let's get out of the main thread 48 | new Thread(new Runnable() { 49 | @Override 50 | public void run() { 51 | // First, look in the locally cached services. 52 | if(mServices != null) { 53 | for (ServiceInfo serviceInfo : mServices) { 54 | if (serviceInfo.getCapability().equals(capability)) { 55 | Log.i(TAG, "getServiceInfo - " + serviceInfo.getServiceName() + " service for " + capability + " was found in local cached services"); 56 | operationCallback.onSuccess(serviceInfo); 57 | return; 58 | } 59 | } 60 | 61 | // We already cached the services but couldn't find the requested service in local cache 62 | Log.e(TAG, "getServiceInfo - The " + capability + " capability was not found in the local cached services. " 63 | + "Falling back to the discovery service"); 64 | getServiceInfoFromDiscoveryService(capability, operationCallback); 65 | } else { 66 | // The services have not been cached yet. Go ask the discovery service. 67 | getServiceInfoFromDiscoveryService(capability, operationCallback); 68 | } 69 | } 70 | }).start(); 71 | } 72 | 73 | /** 74 | * Provides information about the service that corresponds to the provided capability. 75 | * Gets the info from the discovery service. 76 | * @param capability A string that contains the capability of the service that 77 | * is going to be discovered. 78 | * @param operationCallback The callback to which return the result or error. 79 | */ 80 | private void getServiceInfoFromDiscoveryService(final String capability, final OperationCallback operationCallback) { 81 | try { 82 | AuthenticationManager.getInstance().setResourceId(Constants.DISCOVERY_RESOURCE_ID); 83 | ADALDependencyResolver dependencyResolver = (ADALDependencyResolver) AuthenticationManager 84 | .getInstance() 85 | .getDependencyResolver(); 86 | 87 | DiscoveryClient discoveryClient = new DiscoveryClient(Constants.DISCOVERY_RESOURCE_URL, dependencyResolver); 88 | 89 | List services = 90 | discoveryClient 91 | .getServices() 92 | .select("serviceResourceId,serviceEndpointUri,serviceName,capability") 93 | .read().get(); 94 | 95 | Log.i(TAG, "getServiceInfoFromDiscoveryService - Services discovered\n"); 96 | // Save the discovered services to serve further requests from the local cache. 97 | mServices = services; 98 | 99 | for (ServiceInfo serviceInfo : services) { 100 | if (serviceInfo.getCapability().equals(capability)) { 101 | // We found the service, send the info to the caller and end this method call 102 | Log.i(TAG, "getServiceInfoFromDiscoveryService - " + serviceInfo.getServiceName() + " service for " + capability + " was found in services retrieved from discovery"); 103 | operationCallback.onSuccess(serviceInfo); 104 | return; 105 | } 106 | } 107 | 108 | // We haven't cached the services but couldn't find the requested service in discovery service 109 | NoSuchElementException noSuchElementException = new NoSuchElementException("The " + capability + " capability was not found in the user services."); 110 | Log.e(TAG, "getServiceInfoFromDiscoveryService - " + noSuchElementException.getMessage()); 111 | operationCallback.onError(noSuchElementException); 112 | } catch (InterruptedException | ExecutionException e) { 113 | Log.e(TAG, "getServiceInfoFromDiscoveryService - " + e.getMessage()); 114 | operationCallback.onError(e); 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /app/src/main/java/com/microsoft/office365/connect/MailManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | package com.microsoft.office365.connect; 6 | 7 | import android.util.Log; 8 | 9 | import com.microsoft.services.orc.resolvers.ADALDependencyResolver; 10 | import com.microsoft.services.outlook.BodyType; 11 | import com.microsoft.services.outlook.EmailAddress; 12 | import com.microsoft.services.outlook.ItemBody; 13 | import com.microsoft.services.outlook.Message; 14 | import com.microsoft.services.outlook.Recipient; 15 | import com.microsoft.services.outlook.fetchers.OutlookClient; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.MissingResourceException; 20 | import java.util.concurrent.ExecutionException; 21 | 22 | /** 23 | * Handles the creation of the message and contacting the mail service to send the message. 24 | * The app must have connected to Office 365 and discovered the mail service endpoints before 25 | * sending an email. 26 | */ 27 | public class MailManager { 28 | 29 | private static final String TAG = "MailManager"; 30 | 31 | private String mServiceResourceId; 32 | private String mServiceEndpointUri; 33 | 34 | /** 35 | * Sends an email message using the Office 365 mail capability from the address of the 36 | * signed in user. You need to initialize the MailManager by calling 37 | * - {@link MailManager#setServiceResourceId(String)} 38 | * - {@link MailManager#setServiceEndpointUri(String)} 39 | * @param emailAddress The recipient email address. 40 | * @param subject The subject to use in the mail message. 41 | * @param body The body of the message. 42 | * @param operationCallback The callback to which return the result or error. 43 | */ 44 | public void sendMail(final String emailAddress, final String subject, final String body, final OperationCallback operationCallback) { 45 | 46 | if(!isReady()){ 47 | throw new MissingResourceException( 48 | "You must set the ServiceResourceId and ServiceEndPointUri before using sendMail", 49 | "MailManager", 50 | "ServiceResourceId, ServiceEndPointUri" 51 | ); 52 | } 53 | 54 | // Since we're doing considerable work, let's get out of the main thread 55 | new Thread(new Runnable() { 56 | @Override 57 | public void run() { 58 | try { 59 | AuthenticationManager.getInstance().setResourceId(mServiceResourceId); 60 | ADALDependencyResolver dependencyResolver = (ADALDependencyResolver) AuthenticationManager 61 | .getInstance() 62 | .getDependencyResolver(); 63 | 64 | OutlookClient mailClient = new OutlookClient(mServiceEndpointUri, dependencyResolver); 65 | 66 | // Prepare the message. 67 | List recipientList = new ArrayList<>(); 68 | 69 | Recipient recipient = new Recipient(); 70 | EmailAddress email = new EmailAddress(); 71 | email.setAddress(emailAddress); 72 | recipient.setEmailAddress(email); 73 | recipientList.add(recipient); 74 | 75 | Message messageToSend = new Message(); 76 | messageToSend.setToRecipients(recipientList); 77 | 78 | ItemBody bodyItem = new ItemBody(); 79 | bodyItem.setContentType(BodyType.HTML); 80 | bodyItem.setContent(body); 81 | messageToSend.setBody(bodyItem); 82 | messageToSend.setSubject(subject); 83 | 84 | // Contact the Office 365 service and deliver the message. 85 | Integer mailId = mailClient 86 | .getMe() 87 | .getOperations() 88 | .sendMail(messageToSend, true).get(); 89 | 90 | Log.i(TAG, "sendMail - Email with ID: " + mailId + "sent"); 91 | operationCallback.onSuccess(mailId); 92 | } catch (InterruptedException | ExecutionException e) { 93 | Log.e(TAG, "sendMail - " + e.getMessage()); 94 | operationCallback.onError(e); 95 | } 96 | } 97 | }).start(); 98 | } 99 | 100 | public static synchronized MailManager getInstance() { 101 | if (INSTANCE == null) { 102 | INSTANCE = new MailManager(); 103 | } 104 | return INSTANCE; 105 | } 106 | 107 | private static MailManager INSTANCE; 108 | 109 | /** 110 | * Store the service resource id from the discovered service. 111 | * @param serviceResourceId The service resource id obtained from the discovery service. 112 | */ 113 | public void setServiceResourceId(final String serviceResourceId) { 114 | this.mServiceResourceId = serviceResourceId; 115 | } 116 | 117 | /** 118 | * Store the service endpoint uri from the discovered service. 119 | * @param serviceEndpointUri The service endpoint uri obtained from the discovery service. 120 | */ 121 | public void setServiceEndpointUri(final String serviceEndpointUri) { 122 | this.mServiceEndpointUri = serviceEndpointUri; 123 | } 124 | 125 | /** 126 | * Check to see if the service resource id and service endpoint uri values have been set. 127 | * @return True if service resource id and service endpoint uri have been set, false otherwise. 128 | */ 129 | private boolean isReady(){ 130 | return mServiceEndpointUri != null && mServiceResourceId != null; 131 | } 132 | } -------------------------------------------------------------------------------- /app/src/main/java/com/microsoft/office365/connect/OperationCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | package com.microsoft.office365.connect; 6 | 7 | /** 8 | * Callback interface for Office 365 operations 9 | * such as discovering a service or sending email. 10 | * @param The result of the operation in case of success. 11 | */ 12 | interface OperationCallback { 13 | /** 14 | * The method to call in case of success. 15 | * @param result The result of the operation. 16 | */ 17 | void onSuccess(T result); 18 | 19 | /** 20 | * The method to call in case of failure. 21 | * @param e The exception or reason of failure. 22 | */ 23 | void onError(Exception e); 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/microsoft/office365/connect/SendMailActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | package com.microsoft.office365.connect; 6 | 7 | import android.content.Intent; 8 | import android.os.Bundle; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.util.Log; 11 | import android.view.Menu; 12 | import android.view.MenuItem; 13 | import android.view.View; 14 | import android.widget.Button; 15 | import android.widget.EditText; 16 | import android.widget.ProgressBar; 17 | import android.widget.TextView; 18 | import android.widget.Toast; 19 | 20 | import com.microsoft.services.discovery.ServiceInfo; 21 | 22 | import java.text.MessageFormat; 23 | 24 | /** 25 | * This activity handles the send mail operation of the app. 26 | * The app must be connected to Office 365 before this activity can send an email. 27 | * The activity uses the DiscoveryManager class to get the service endpoint. It also 28 | * uses the MailManager to send the message. 29 | */ 30 | public class SendMailActivity extends AppCompatActivity { 31 | 32 | private static final String TAG = "SendMailActivity"; 33 | 34 | private TextView mTitleTextView; 35 | private TextView mDescriptionTextView; 36 | private EditText mEmailEditText; 37 | private Button mSendMailButton; 38 | private ProgressBar mSendMailProgressBar; 39 | private TextView mConclusionTextView; 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setContentView(R.layout.activity_send_mail); 45 | 46 | initializeViews(); 47 | 48 | // Extract the givenName and displayableId and use it in the UI. 49 | mTitleTextView.append(getIntent() 50 | .getStringExtra("givenName") + "!"); 51 | mEmailEditText.setText(getIntent() 52 | .getStringExtra("displayableId")); 53 | 54 | // We don't need to wait for user input to discover the mail service, 55 | // so we just do it 56 | discoverMailService(); 57 | } 58 | 59 | /** 60 | * Locates the service endpoints for the mail service using the DiscoveryManager class. 61 | */ 62 | public void discoverMailService(){ 63 | resetUIForDiscoverMailService(); 64 | 65 | // DiscoveryManager does its job in a worker thread 66 | // we can just call getServiceInfo 67 | DiscoveryManager 68 | .getInstance() 69 | .getServiceInfo(Constants.MAIL_CAPABILITY, 70 | new OperationCallback() { 71 | @Override 72 | public void onSuccess(final ServiceInfo serviceInfo) { 73 | Log.i(TAG, "discoverMailService - Mail service discovered"); 74 | 75 | // Initialize MailManager with ResourceID and ServiceEndpointURI 76 | MailManager 77 | .getInstance() 78 | .setServiceResourceId( 79 | serviceInfo.getServiceResourceId() 80 | ); 81 | MailManager 82 | .getInstance() 83 | .setServiceEndpointUri( 84 | serviceInfo.getServiceEndpointUri() 85 | ); 86 | 87 | showDiscoverSuccessUI(); 88 | } 89 | 90 | @Override 91 | public void onError(Exception e) { 92 | Log.e(TAG, "discoverMailService - " + e.getMessage()); 93 | showDiscoverErrorUI(); 94 | } 95 | } 96 | ); 97 | } 98 | 99 | /** 100 | * Handler for the onclick event of the send mail button. It uses the MailManager 101 | * class to send an email to the address stored in the mEmailEditText view. 102 | * The subject and body of the message is stored in the strings.xml file. 103 | * @param v The view that sent the event. 104 | */ 105 | public void onSendMailButtonClick(View v){ 106 | resetUIForSendMail(); 107 | 108 | // MailManager does its job in a worker thread 109 | // we can just call sendMail 110 | MailManager.getInstance().sendMail( 111 | mEmailEditText.getText().toString(), 112 | getResources().getString(R.string.mail_subject_text), 113 | MessageFormat.format( 114 | getResources().getString(R.string.mail_body_text), 115 | getIntent().getStringExtra("givenName") 116 | ), 117 | new OperationCallback() { 118 | @Override 119 | public void onSuccess(Integer result) { 120 | Log.i(TAG, "onSendMailButtonClick - Mail sent"); 121 | showSendMailSuccessUI(); 122 | } 123 | 124 | @Override 125 | public void onError(Exception e) { 126 | Log.e(TAG, "onSendMailButtonClick - " + e.getMessage()); 127 | showSendMailErrorUI(); 128 | } 129 | } 130 | ); 131 | } 132 | 133 | @Override 134 | public boolean onCreateOptionsMenu(Menu menu) { 135 | // Inflate the menu; this adds items to the action bar if it is present. 136 | getMenuInflater().inflate(R.menu.send_mail, menu); 137 | return true; 138 | } 139 | 140 | @Override 141 | public boolean onOptionsItemSelected(MenuItem item) { 142 | try { 143 | switch (item.getItemId()) { 144 | case R.id.disconnectMenuItem: 145 | AuthenticationManager.getInstance().disconnect(); 146 | showDisconnectSuccessUI(); 147 | Intent connectIntent = new Intent(this, ConnectActivity.class); 148 | startActivity(connectIntent); 149 | return true; 150 | default: 151 | return super.onOptionsItemSelected(item); 152 | } 153 | 154 | } catch (Throwable t) { 155 | if (t.getMessage() == null) 156 | Log.e(TAG, " "); 157 | else 158 | Log.e(TAG, t.getMessage()); 159 | } 160 | return true; 161 | } 162 | 163 | private void initializeViews(){ 164 | mTitleTextView = (TextView)findViewById(R.id.titleTextView); 165 | mDescriptionTextView = (TextView)findViewById(R.id.descriptionTextView); 166 | mEmailEditText = (EditText)findViewById(R.id.emailEditText); 167 | mSendMailButton = (Button)findViewById(R.id.sendMailButton); 168 | mSendMailProgressBar = (ProgressBar)findViewById(R.id.sendMailProgressBar); 169 | mConclusionTextView = (TextView)findViewById(R.id.conclusionTextView); 170 | } 171 | 172 | private void resetUIForDiscoverMailService(){ 173 | mSendMailButton.setVisibility(View.GONE); 174 | mConclusionTextView.setVisibility(View.GONE); 175 | mSendMailProgressBar.setVisibility(View.VISIBLE); 176 | } 177 | 178 | private void resetUIForSendMail(){ 179 | mSendMailButton.setVisibility(View.GONE); 180 | mConclusionTextView.setVisibility(View.GONE); 181 | mSendMailProgressBar.setVisibility(View.VISIBLE); 182 | } 183 | 184 | private void showDiscoverSuccessUI(){ 185 | runOnUiThread(new Runnable() { 186 | @Override 187 | public void run() { 188 | // Now that we have discovered the mail service, show the send mail button 189 | mSendMailButton.setVisibility(View.VISIBLE); 190 | mSendMailProgressBar.setVisibility(View.GONE); 191 | 192 | Toast.makeText( 193 | SendMailActivity.this, 194 | R.string.discover_toast_text, 195 | Toast.LENGTH_SHORT).show(); 196 | } 197 | }); 198 | } 199 | 200 | private void showDiscoverErrorUI(){ 201 | runOnUiThread(new Runnable() { 202 | @Override 203 | public void run(){ 204 | mSendMailProgressBar.setVisibility(View.GONE); 205 | mSendMailButton.setVisibility(View.VISIBLE); 206 | mConclusionTextView.setText(R.string.discover_text_error); 207 | mConclusionTextView.setVisibility(View.VISIBLE); 208 | Toast.makeText( 209 | SendMailActivity.this, 210 | R.string.discover_toast_text_error, 211 | Toast.LENGTH_LONG).show(); 212 | } 213 | }); 214 | } 215 | 216 | private void showSendMailSuccessUI(){ 217 | runOnUiThread(new Runnable() { 218 | @Override 219 | public void run(){ 220 | mSendMailProgressBar.setVisibility(View.GONE); 221 | mSendMailButton.setVisibility(View.VISIBLE); 222 | mConclusionTextView.setText(R.string.conclusion_text); 223 | mConclusionTextView.setVisibility(View.VISIBLE); 224 | Toast.makeText( 225 | SendMailActivity.this, 226 | R.string.send_mail_toast_text, 227 | Toast.LENGTH_SHORT).show(); 228 | } 229 | }); 230 | } 231 | 232 | private void showSendMailErrorUI(){ 233 | runOnUiThread(new Runnable() { 234 | @Override 235 | public void run(){ 236 | mSendMailProgressBar.setVisibility(View.GONE); 237 | mSendMailButton.setVisibility(View.VISIBLE); 238 | mConclusionTextView.setText(R.string.send_mail_text_error); 239 | mConclusionTextView.setVisibility(View.VISIBLE); 240 | Toast.makeText( 241 | SendMailActivity.this, 242 | R.string.send_mail_toast_text_error, 243 | Toast.LENGTH_LONG).show(); 244 | } 245 | }); 246 | } 247 | 248 | private void showDisconnectSuccessUI(){ 249 | mTitleTextView.setVisibility(View.GONE); 250 | mDescriptionTextView.setVisibility(View.GONE); 251 | mEmailEditText.setVisibility(View.GONE); 252 | mSendMailButton.setVisibility(View.GONE); 253 | mConclusionTextView.setVisibility(View.GONE); 254 | 255 | Toast.makeText( 256 | SendMailActivity.this, 257 | R.string.disconnect_toast_text, 258 | Toast.LENGTH_SHORT).show(); 259 | } 260 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/O365-Android-Connect/fa87d9c355bcea1e9b7c75ab09b4f01fbce05659/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_connect.xml: -------------------------------------------------------------------------------- 1 | 5 | 13 | 14 |