Was this page helpful?
Your feedback about this content is important. Let us know what you think.
Additional feedback?
1500 characters remaining
Create an Android OneNote API app

Tutorial: create an Android OneNote API app

Last modified: February 15, 2015

Applies to: OneNote service

In this article
Create a "hello world" OneNote app for Android
Build and run the app
Additional resources

Use this tutorial to create a simple "hello world" mobile Android app that uses the OneNote API to save HTML documents to a OneNote notebook.

You can also download a complete OneNote API Android sample (Eclipse) from GitHub.

If you're new to Android development, visit the Android developer site for help getting started and developing Android apps. If you're an experienced Android developer, you’ll probably already have some of the resources listed below.

Here’s what you’ll need to use this tutorial:

  • Live SDK for Android. Lets you use Live Connect web services to access OneNote with a Microsoft account.

  • Microsoft accounts for your users, and a client ID for your app. Follow the instructions in Get a client ID for use with the OneNote API to get a Microsoft account and a client ID for your app. In your app's API settings, choose Yes for Mobile or desktop client app.

  • JDK. The Java SE Development Kit used by Android apps and tools to run in the Java environment.

  • Android Studio and Android SDK. The IDE, developer tools, and libraries to build, test, and debug apps for Android. Follow the instructions in Installing Android Studio to install the IDE, SDK, and recommended packages.

Note Note

You aren't required to have a developer account to download the tools and develop apps, but you'll need a developer account to publish and sell them. See Android developer site for more information.

Create and set up the project

The steps below describe how to import the Live SDK into a new Android project. If you're new to Android Studio, see Creating an Android project to get familiar with the development environment and tools. (For Eclipse instructions, see the Live SDK readme.)

To import the Live SDK for Android library into a new project

  1. Extract the contents of the LiveSDK-for-Android-master.zip file.

  2. In Android Studio, create a new project that uses a blank activity. Use OneNoteServiceHelloWorld as the application name and example.com as the company domain, and then accept all of the default selections.
    Note: The code in this sample was built using the Android 4.0.3 minimum SDK. If you choose a different version, your project may not work correctly.

  3. Open File > Import Module and browse to the folder where you extracted the Live SDK.

  4. Expand the LiveSDK-for-Android-master folder, choose the src folder, and then choose OK.

  5. Change the module name from :src to :livesdk, and then choose Finish.

  6. Open File > Project Structure, choose the app module, and then open the Dependencies tab.

  7. Open + > Module dependency, choose :livesdk, and then choose OK.

  8. In the Project Structure window, choose OK.

After the Gradle projects sync, you can use the Live SDK in your Android project. Next, you'll create the basic resources and files for your app.

Create the app skeleton

  1. Add the following four classes. In the project pane, expand the app folder, expand java, right-click com.example.onenoteservicehelloworld, and then choose New > Java Class.

    • ApiResponse: Defines the class that contains the content of the OneNote API response.

    • AsyncResponse: Defines the interface for handling the asynchronous response from the OneNote API.

    • Constants: Contains the constant values used in the app, such as your app’s client ID.

    • SendPageCreateAsyncTask: Creates and handles the asynchronous call to the OneNote API and the response.

  2. In the project pane, right-click the app folder, and then choose New > Activity > Blank Activity. Name the activity ResultsActivity.

    This creates a ResultsActivity.java file in the com.example.onenoteservicehelloworld package, activity_results.xml in the res/layouts folder, and menu_results.xml in the res/menu folder.

  3. Add the following code to the AndroidManifest.xml file, inside the <manifest> tags.

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
    
    
  4. Replace the contents of the <resources> tags in the strings.xml file (in the res/values folder) with the following code.

    <string name="app_name">OneNote Service Hello World</string>
    <string name="action_settings">Settings</string>
    
    <!-- Auth Related Strings -->
    <string name="auth_yes">Authenticated</string>
    <string name="auth_no">Not Authenticated</string>
    <string name="auth_err">Error signing in:</string>
    
    <!-- Button Strings -->
    <string name="btn_auth">Authenticate</string>
    <string name="btn_sendRequest">Create Page with Screenshots</string>
    <string name="title_activity_results">ResultsActivity</string>
    
    <!-- Response Page Strings -->
    <string name="lbl_Response_Title">RESPONSE:</string>
    <string name="lbl_clientLink_Title">ONENOTE CLIENT LINK:</string>
    <string name="lbl_webLink_Title">ONENOTE WEB LINK:</string>
    <string name="title_activity_async_response">AsyncResponse</string>
    <string name="title_activity_api_response">ApiResponse</string>
    <string name="title_activity_constants">Constants</string>
    <string name="title_activity_sample">SampleActivity</string>
    
    

Create the layout for your app

Replace the contents of activity_main.xml with the following code. This creates a simple interface with a button used for authorizing the user and a button that saves a block of HTML to a notebook on the user’s OneDrive.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <Button
            android:id="@+id/btn_auth"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="callOAuth"
            android:text="@string/btn_auth" />
   </LinearLayout>

   <LinearLayout
         android:layout_width="fill_parent"
        android:layout_height="wrap_content"
         android:layout_marginBottom="35dp"
         android:gravity="center_horizontal"
         android:orientation="horizontal" >
        <TextView
            android:id="@+id/txtView_Auth"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_weight="1"
            android:text="@string/auth_no"
            android:textAlignment="center"
            android:textAppearance="?android:attr/textAppearanceMedium" />
    </LinearLayout>
    
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="15dp"
        android:orientation="horizontal" >
        <Button
            android:id="@+id/btn_sendRequest"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="btn_sendRequest_onClick"
            android:text="@string/btn_sendRequest" />
   </LinearLayout>
   
   <TextView
        android:id="@+id/tvStatus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Status: " />
    
</LinearLayout>

Replace the contents of activity_results.xml with the following code. This layout displays some information about the results of your call to the OneNote API.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".ResultsActivity" >

    <TextView
        android:id="@+id/lbl_Response_Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/lbl_Response_Title"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:layout_marginBottom="10dp" />
    
     <!-- NO TEXT SINCE THIS IS SET PROGRAMMATICALLY -->
    <TextView
        android:id="@+id/lbl_Response"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="25dp" />
    
        <TextView
        android:id="@+id/lbl_clientLink_Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/lbl_clientLink_Title"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:layout_marginBottom="10dp" />

    <!-- NO TEXT SINCE THIS IS SET PROGRAMMATICALLY -->
    <TextView
        android:id="@+id/lbl_clientLink"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="25dp"/>

    <TextView
        android:id="@+id/lbl_webLink_Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/lbl_webLink_Title"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:layout_marginBottom="10dp" />

    <!-- NO TEXT SINCE THIS IS SET PROGRAMMATICALLY -->
    <TextView
        android:id="@+id/lbl_webLink"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="25dp"/>

</LinearLayout>

Define MainActivity

The following code changes the default MainActivity class so that it implements the LiveAuthListener interface from the Live SDK. This interface requires implementations for the onAuthComplete and onAuthError methods that handle both the success and failure of your user’s authorization attempt. The btn_auth button in activity_main.xml triggers the onOauth method which kicks off the authorization process that is handled by the LiveAuthClient.

It also overrides the default OnCreate method so that it creates a new LiveAuthClient object that handles your user’s authorization. The other methods handle the tasks of sending a call to the service and handling the response.

Replace the contents of the MainActivity.java file with the following code. You’ll see build errors, but they will go away as you fill out your project.

package com.example.onenoteservicehelloworld;

import java.util.Arrays;

import android.content.Intent;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.TextView;

import com.microsoft.live.LiveAuthClient;
import com.microsoft.live.LiveAuthException;
import com.microsoft.live.LiveAuthListener;
import com.microsoft.live.LiveConnectClient;
import com.microsoft.live.LiveConnectSession;
import com.microsoft.live.LiveStatus;

public class MainActivity extends Activity implements LiveAuthListener, AsyncResponse {
    private LiveAuthClient mAuthClient;
    private LiveConnectClient mLiveConnectClient;
    private TextView resultTextView;
    private String mAccessToken = null; 
               
    @Override
    protected void onCreate(Bundle savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        resultTextView = (TextView) findViewById(R.id.txtView_Auth);
        mAuthClient = new LiveAuthClient(this, Constants.CLIENTID);
    }
    
    public void onAuthComplete(LiveStatus status, LiveConnectSession session, Object userState)  {              
        if (status == LiveStatus.CONNECTED)  {
            resultTextView.setText(R.string.auth_yes);
            mAccessToken = session.getAccessToken();
            mLiveConnectClient = new LiveConnectClient(session);
         }
        else {
            resultTextView.setText(R.string.auth_no);
            mLiveConnectClient = null;
        }
    }
               
    public void onAuthError(LiveAuthException exception, Object userState)  {
        resultTextView.setText(getResources().getString(R.string.auth_err) + exception.getMessage());
        mLiveConnectClient = null;
    }
                              
    /** Called when the user clicks the Authenticate button. Requests the office.onenote_create scope.  */
    public void callOAuth(View view)  {
        super.onStart();
        Iterable<String> scopes = Arrays.asList("office.onenote_create");
        mAuthClient.login(this, scopes, this);
     }
               
    private void StartRequest()  {
        TextView tvStatus = (TextView)findViewById(R.id.tvStatus);
        tvStatus.setText("Status: Sending request...");
    }

    /** Called when the user clicks the Create Page button. */
    public void btn_sendRequest_onClick(View view)  {
        StartRequest();
        SendPageCreateAsyncTask task = new SendPageCreateAsyncTask();
        task.delegate = this;
        task.execute(mAccessToken);
    }
               
    @Override
    public void processFinish(ApiResponse response)  {
        showResponse(response);
    }
               
    public void showResponse(ApiResponse response)  {
        TextView tvStatus = (TextView)findViewById(R.id.tvStatus);
        if (response.getResponseCode() == 201)  {
            tvStatus.setText("Status: Page created successfully.");
                                             
            Intent intent = new Intent(this, ResultsActivity.class);
                                             
            String message = response.getResponseMessage();
            String clientUrl = response.getOneNoteClientUrl();
            String webUrl = response.getOneNoteWebUrl();
                                             
            intent.putExtra(Constants.RESPONSE, message);
            intent.putExtra(Constants.CLIENT_URL, clientUrl);
            intent.putExtra(Constants.WEB_URL, webUrl);
                                             
            startActivity(intent);
        }
        else tvStatus.setText("Status: Page creation failed with error code " + response.getResponseCode());
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)  {

        // Inflate the menu. This adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }  
}

Define SendPageCreateAsyncTask

The SendPageCreateAsyncTask class extends the android.os.AsyncTask class. It handles the work associated with making the asynchronous call to the OneNote API and handling the response. This class also contains the createPageWithScreenshots method that constructs the request that is sent to the service.

Replace the contents of the SendPageCreateAsyncTask.java file with the following code.

package com.example.onenoteservicehelloworld;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.net.ssl.HttpsURLConnection;

import org.json.JSONException;
import org.json.JSONObject;

import android.os.AsyncTask;


public class SendPageCreateAsyncTask extends
AsyncTask<String, String, ApiResponse> {
    private final String PAGES_ENDPOINT = "https://www.onenote.com/api/v1.0/pages";
    private OutputStream mOutputStream = null;
    private final String DELIMITER = "--";
    private final String BOUNDARY = "Asdfs"+Long.toString(System.currentTimeMillis()) + "aBc";
    private HttpsURLConnection mUrlConnection = null;
    private String mAccessToken = null;
    public AsyncResponse delegate = null;
               
    @Override
    protected ApiResponse doInBackground(String... params)  {
        // http://www.softwarepassion.com/android-series-get-post-and-multipart-post-requests/
        // http://www.javacodegeeks.com/2013/06/android-http-client-get-post-download-upload-multipart-request.html

        mAccessToken = params[0];
        return createPageWithScreenshots();
    }
               
    private ApiResponse createPageWithScreenshots()  {
        try {
            this.connectForMultipart(PAGES_ENDPOINT);
            String htmlPartName = "embedded1";
            String date = getDate();
            String presentationHtml = "<html>" +
                "<head>" +
                "<title>A Page With HTML and Webpage Screenshots (Android Sample)</title>" +
                "<meta name=\"created\" content=\"" + date + "\" />" +
                "</head>" +
                "<body>" +
                "<h1>This page has HTML captured as a screenshot and a webpage screenshot.</h1>" +
                "<img data-render-src=\"name:" + htmlPartName + "\" alt=\"Webpage HTML\"/>"  +
                "<img data-render-src=\"http://dev.onenote.com\" alt=\"Webpage screenshot\"/>" +
                "</body>" +
                "</html>";
            String embeddedHtml = "<h1>This is content from a webpage, rendered as an image.</h1>" +
                "<p>Lorem ipsum dolor sit amet. Nullam vehicula magna quis mauris accumsan, nec imperdiet " +
                "nisi tempus. Suspendisse potenti, ornare sed orci. Nulla id felis quis sem blandit dapibus. Ut viverra " +
                "auctor nisi ac egestas. Quisque ac neque nec velit fringilla sagittis porttitor sit amet quam.</p>" ;  
            this.addFormPart("presentation", "application/xhtml+xml", presentationHtml);
            this.addFormPart(htmlPartName, "text/html", embeddedHtml);
            this.finishMultipart();
            ApiResponse response = this.getResponse();
            return response;
        } catch (Exception ex)  {
            String errorMessage = ex.getMessage();
        }                             
        return null;
    }
               
    private void connectForMultipart(String endpoint) throws Exception {
        mUrlConnection = (HttpsURLConnection) ( new URL(endpoint)).openConnection();
        mUrlConnection.setDoOutput(true);
        mUrlConnection.setRequestMethod("POST");
        mUrlConnection.setDoInput(true);
        mUrlConnection.setRequestProperty("Connection", "Keep-Alive");
        mUrlConnection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
        mUrlConnection.setRequestProperty("Authorization", "Bearer " + mAccessToken);
        mUrlConnection.connect();
        mOutputStream = mUrlConnection.getOutputStream();
    }
               
    private void addFormPart(String paramName, String contentType, String value) throws Exception {
        writeParamData(paramName, contentType, value);
    }
               
    private void writeParamData(String paramName, String contentType, String value) throws Exception {
        mOutputStream.write( (DELIMITER + BOUNDARY + "\r\n").getBytes());
        mOutputStream.write( ("Content-Type: " + contentType + "\r\n").getBytes());
        mOutputStream.write( ("Content-Disposition: form-data; name=\"" + paramName + "\"\r\n").getBytes());;
        mOutputStream.write( ("\r\n" + value + "\r\n").getBytes());
    }

    private void finishMultipart() throws Exception {
        mOutputStream.write( (DELIMITER + BOUNDARY + DELIMITER + "\r\n").getBytes());
        mOutputStream.close();
    }
               
    public ApiResponse getResponse() throws Exception {
        int responseCode = mUrlConnection.getResponseCode();
        String responseMessage = mUrlConnection.getResponseMessage();
        String responseBody = null;
        if ( responseCode == 201)  {
            InputStream is = mUrlConnection.getInputStream();
            byte[] b1 = new byte[1024];
            StringBuffer buffer = new StringBuffer();
            while ( is.read(b1) != -1)
                buffer.append(new String(b1));
            mUrlConnection.disconnect();
            responseBody =  buffer.toString();
        }

        JSONObject responseObject = null;
        ApiResponse response = new ApiResponse();
        try {
            response.setResponseCode(responseCode);
            response.setResponseMessage(responseMessage);                                             
            if ( responseCode == 201)  {
                responseObject = new JSONObject(responseBody);
                String clientUrl = responseObject.getJSONObject("links").getJSONObject("oneNoteClientUrl").getString("href");                              
                String webUrl = responseObject.getJSONObject("links").getJSONObject("oneNoteWebUrl").getString("href");
                response.setOneNoteClientUrl(clientUrl);
                response.setOneNoteWebUrl(webUrl);
            }
        } catch (JSONException ex)  {
            String msg = ex.getMessage();
            msg = msg;
        }
        return response;  
    }
               
    @Override
    protected void onPostExecute(ApiResponse response)  {
        super.onPostExecute(response);                               
        delegate.processFinish(response);
    }
               
    /**
    * Get the CreatedTime in ISO-8601 in UTC format with timezone.
    */
    private String getDate()  {
        Date d = new Date();
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZ");
        return df.format(d);
    }
}

Define ResultsActivity

If the call to the OneNote API is successful, the ResultsActivity class handles the display of the results. Replace the contents of the ResultsActivity.java file with the following code.

package com.example.onenoteservicehelloworld;

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;
import android.view.MenuItem;
import android.support.v4.app.NavUtils;
import android.annotation.TargetApi;
import android.os.Build;
import android.widget.TextView;

public class ResultsActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_results);

        // Show the Up button in the action bar.
        setupActionBar();
                              
        // Get messages from the intent.
        Intent intent = getIntent();
                              
        // Set the messages.
        ((TextView) findViewById(R.id.lbl_Response)).setText(intent.getStringExtra(Constants.RESPONSE));
        ((TextView) findViewById(R.id.lbl_clientLink)).setText(intent.getStringExtra(Constants.CLIENT_URL));
        ((TextView) findViewById(R.id.lbl_webLink)).setText(intent.getStringExtra(Constants.WEB_URL));
    }

    /**
    * Set up the {@link android.app.ActionBar}, if the API is available.
    */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void setupActionBar()  {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)  {
            getActionBar().setDisplayHomeAsUpEnabled(true);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)  {

        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_results, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)  {
        switch (item.getItemId())  {
            case android.R.id.home:

                // This ID represents the Home or Up button. In the case of this
                // activity, the Up button is shown. Use NavUtils to allow users
                // to navigate up one level in the application structure. For
                // more details, see the Navigation pattern on Android Design:
                //
                // http://developer.android.com/design/patterns/navigation.html#up-vs-back
                NavUtils.navigateUpFromSameTask(this);
                return true;
            }
        return super.onOptionsItemSelected(item);
        }
}

Define ApiResponse

The ApiResponse class contains the values that the app displays on the results page if the call to the OneNote API is successful. Replace the contents of the ApiResponse.java file with the following code.

package com.example.onenoteservicehelloworld;

public class ApiResponse {
      private String mOneNoteClientUrl;
      private String mOneNoteWebUrl;
      private String mResponseMessage;
      private int mResponseCode;
      
      public String getOneNoteClientUrl()  {
            return mOneNoteClientUrl;
      }
      
      public String getOneNoteWebUrl()  {
            return mOneNoteWebUrl;
      }
      
      public String getResponseMessage()  {
            return mResponseMessage;
      }
      
      public int getResponseCode()  {
            return mResponseCode;
      }
      
      public void setOneNoteClientUrl(String url)  {
            mOneNoteClientUrl = url;
      }
      
      public void setOneNoteWebUrl(String url)  {
            mOneNoteWebUrl = url;
      }
      
      public void setResponseCode(int code)  {
            mResponseCode= code;
      }
      
      public void setResponseMessage(String message)  {
            mResponseMessage = message;
      }
}

Define AsyncResponse

The following code defines this interface for handling the responses from the OneNote API. Replace the contents of the AsyncResponse.java file with the following code.

package com.example.onenoteservicehelloworld;

public interface AsyncResponse {
      void processFinish(ApiResponse response);
}

Add constants

The Constants.java file contains the constant values used by the app, including the client id. The value for the CLIENTID string is the client id that you got when you registered your app by following the instructions in Get a client ID for use with the OneNote API. Replace the contents of the Constants.java file with the following code.

package com.example.onenoteservicehelloworld;

public class Constants {

     // Add the client ID for your application from the Microsoft account Developer Center.
     // Example: "0000000012345678"
      public static final String CLIENTID = "your client id here";

      // Set up values for the ResultsActivity intent.
      public static final String RESPONSE = "com.example.onenoteservicehelloworld.RESPONSE";
      public static final String CLIENT_URL = "com.example.onenoteservicehelloworld.CLIENT_LINK";
      public static final String WEB_URL = "com.example.onenoteservicehelloworld.WEB_LINK";
}

After you finish setting up the app, choose Run > Run 'app'. You can test the app on an unlocked Android device or in an emulator that you created in Android Virtual Device Manager.

Figure 1 shows how the app will appear after you’ve started it.

Figure 1. App start page

Android OneNote hello world launch page

Figure 2 shows the authentication page that the user sees after choosing Authenticate.

Figure 2. Authenticating with the user’s Microsoft account

Android OneNote Hello World app authenticate page

After authenticating, the app asks the user for permission to create new pages in OneNote, as shown in Figure 3.

Figure 3. App request for permissions

Android OneNote Hello World app approve page

Figure 4 shows the results page that appears when the call to OneNote API is successful.

Figure 4. App results page

Android OneNote Hello World app results page
Show:
© 2015 Microsoft