Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
SAP How-to Guide Mobility SAP Mobile Platform How To... On-board users with SMP 3.0 OData API (Android) Applicable Releases: SAP Mobile Platform 3.0 SP04 SAP Mobile SDK 3.0 SP05 Version 2.0 © Copyright 2015 SAP AG. All rights reserved. All other product and service names mentioned are the trademarks of No part of this publication may be reproduced or transmitted in any form their respective companies. Data contained in this document serves or for any purpose without the express permission of SAP AG. The informational purposes only. National product specifications may vary. information contained herein may be changed without prior notice. The information in this document is proprietary to SAP. No part of this Some software products marketed by SAP AG and its distributors document may be reproduced, copied, or transmitted in any form or for contain proprietary software components of other software vendors. any purpose without the express prior written permission of SAP AG. Microsoft, Windows, Excel, Outlook, and PowerPoint are registered This document is a preliminary version and not subject to your license trademarks of Microsoft Corporation. agreement or any other agreement with SAP. This document contains IBM, DB2, DB2 Universal Database, System i, System i5, System p, System p5, System x, System z, System z10, System z9, z10, z9, iSeries, pSeries, xSeries, zSeries, eServer, z/VM, z/OS, i5/OS, S/390, OS/390, OS/400, AS/400, S/390 Parallel Enterprise Server, PowerVM, Power Architecture, POWER6+, POWER6, POWER5+, POWER5, POWER, only intended strategies, developments, and functionalities of the SAP® product and is not intended to be binding upon SAP to any particular course of business, product strategy, and/or development. Please note that this document is subject to change and may be changed by SAP at any time without notice. OpenPower, PowerPC, BatchPipes, BladeCenter, System Storage, GPFS, SAP assumes no responsibility for errors or omissions in this document. HACMP, RETAIN, DB2 Connect, RACF, Redbooks, OS/2, Parallel Sysplex, SAP does not warrant the accuracy or completeness of the information, MVS/ESA, AIX, Intelligent Miner, WebSphere, Netfinity, Tivoli and text, graphics, links, or other items contained within this material. This Informix are trademarks or registered trademarks of IBM Corporation. document is provided without a warranty of any kind, either express or Linux is the registered trademark of Linus Torvalds in the U.S. and other countries. Adobe, the Adobe logo, Acrobat, PostScript, and Reader are either trademarks or registered trademarks of Adobe Systems Incorporated in the United States and/or other countries. Oracle is a registered trademark of Oracle Corporation. UNIX, X/Open, OSF/1, and Motif are registered trademarks of the Open Group. Citrix, ICA, Program Neighborhood, MetaFrame, WinFrame, VideoFrame, and MultiWin are trademarks or registered trademarks of Citrix Systems, Inc. HTML, XML, XHTML and W3C are trademarks or registered trademarks of W3C®, World Wide Web Consortium, Massachusetts Institute of Technology. Java is a registered trademark of Sun Microsystems, Inc. implied, including but not limited to the implied warranties of merchantability, fitness for a particular purpose, or non-infringement. SAP shall have no liability for damages of any kind including without limitation direct, special, indirect, or consequential damages that may result from the use of these materials. This limitation shall not apply in cases of intent or gross negligence. The statutory liability for personal injury and defective products is not affected. SAP has no control over the information that you may access through the use of hot links contained in these materials and does not endorse your use of third-party Web pages nor provide any warranty whatsoever relating to third-party Web pages. SAP “How-to” Guides are intended to simplify the product implementtation. While specific product features and procedures typically are explained in a practical business context, it is not implied that those features and procedures are the only approach in solving a specific business problem using SAP NetWeaver. Should you wish to receive JavaScript is a registered trademark of Sun Microsystems, Inc., used additional information, clarification or support, please refer to SAP under license for technology invented and implemented by Netscape. Consulting. SAP, R/3, SAP NetWeaver, Duet, PartnerEdge, ByDesign, SAP Any software coding and/or code lines / strings (“Code”) included in this BusinessObjects Explorer, StreamWork, and other SAP products and documentation are only examples and are not intended to be used in a services mentioned herein as well as their respective logos are productive system environment. The Code is only intended better explain trademarks or registered trademarks of SAP AG in Germany and other and visualize the syntax and phrasing rules of certain coding. SAP does countries. not warrant the correctness and completeness of the Code given herein, Business Objects and the Business Objects logo, BusinessObjects, and SAP shall not be liable for errors or damages caused by the usage of Crystal Reports, Crystal Decisions, Web Intelligence, Xcelsius, and other the Code, except if such damages were caused by SAP intentionally or Business Objects products and services mentioned herein as well as their grossly negligent. respective logos are trademarks or registered trademarks of Business Disclaimer Objects Software Ltd. Business Objects is an SAP company. Some components of this product are based on Java™. Any code change Sybase and Adaptive Server, iAnywhere, Sybase 365, SQL Anywhere, in these components may cause unpredictable and severe malfunctions and other Sybase products and services mentioned herein as well as their and is therefore expressively prohibited, as is any decompilation of these respective logos are trademarks or registered trademarks of Sybase, Inc. components. Sybase is an SAP company. Any Java™ Source Code delivered with this product is only to be used by SAP’s Support Services and may not be modified or altered in any way. Document History Document Version Description 1.00 First official release of this guide 2.00 Based in AndroidStudio with improvements in the code Typographic Conventions Icons Type Style Icon Example Text Description Words or characters quoted from the screen. These include field names, screen titles, pushbuttons labels, menu names, menu paths, and menu options. Cross-references to other documentation Example text Example text Example text <Example text> EXAMPLE TEXT Emphasized words or phrases in body text, graphic titles, and table titles File and directory names and their paths, messages, names of variables and parameters, source text, and names of installation, upgrade and database tools. User entry texts. These are words or characters that you enter in the system exactly as they appear in the documentation. Variable user entry. Angle brackets indicate that you replace these words and characters with appropriate entries to make entries in the system. Keys on the keyboard, for example, F2 or ENTER. Description Caution Note or Important Example Recommendation or Tip Table of Contents 1. Business Scenario ..................................................................................................................1 2. Background Information ........................................................................................................1 3. Prerequisites ..........................................................................................................................1 4. Step-by-Step Procedure ....................................................................................................... 2 4.1 Create Android Project .................................................................................................... 2 4.2 Add required libraries ...................................................................................................... 5 4.3 Create Logon Screen ......................................................................................................12 4.4 Add Logon Screen Texts ...................................................................................12 4.3.2 Create LoginActivity ..........................................................................................13 Onboard users with LogonCore ..................................................................................... 17 4.4.1 Create UIListener ............................................................................................... 17 4.4.2 Create ODataBaseListener .............................................................................. 18 4.4.3 Create MyLogonCoreListener ......................................................................... 20 4.4.4 Implement LoginActivity .................................................................................. 23 4.5 Review AndroidManifest.xml ........................................................................................ 24 4.6 Open OfflineStore and Access Data Offline ................................................................. 24 4.7 5. 4.3.1 4.6.1 Create CredentialsProvider class .................................................................... 24 4.6.2 Create OfflineManager class............................................................................ 25 4.6.3 Complete MainActivity ..................................................................................... 25 Run the Application ........................................................................................................ 26 Appendix.............................................................................................................................. 29 How To... On-board users with SMP 3.0 OData API (Android) 1. Business Scenario Before any communication with the SAP backend systems can take place at all, the app needs to on-board users onto the SAP Mobile Platform. The Mobile Application Framework (MAF) contains the MAF logon component that provides easy integration for applications that use logon UI behavior. However, certain use cases require more control over the look and feel of the logon screen or don’t require the logon UI behavior. In these cases, developers can use the SMP 3.0 OData SDK to onboard users. 2. Background Information The goal of this exercise is to show the key pieces of code and information needed to onboard users with the SMP 3.0 OData SDK. Using the SMP OData 3.0 SDK to onboard users give developers more control about the look and feel of the logon screen. However, if a more sophisticated mechanism of authentication is used (i.e SAML, x.509 certificates) developers require to implement how the app will react to the authentication challenges triggered by the authentication provider. To understand how developers can leverage the MAF Logon component in more complex scenarios, please visit help.sap.com 3. Prerequisites This exercise has the following prerequisites: • Java Standard Edition 7 • Eclipse Kepler • Android SDK with API level 8 and 21 downloaded • Android ATD Eclipse plugin installed • To get the most out of this exercise, some experience with Java is recommended. • SAP Mobile Platform 3.0 SP04 This example assumes you have configured an application in SMP 3.0 called com.sap.flight for more information on how to create an application configuration, please visit Deploying Applications • SAP Mobile SDK 3.0 SP05 1 How To... On-board users with SMP 3.0 OData API (Android) 4. Step-by-Step Procedure The following sections provide a detailed step-by-step procedure on how to code an Android app to on-board/register a user onto the SAP Mobile Platform using the SMP OData SDK. The following section is optional, you can reuse an existing android project for the purpose of this exercise 4.1 Create Android Project ... 1. Open Android Studio and select Start a new Android Studio Project 2. Enter the following information to create the android project and click the Next button 2 How To... On-board users with SMP 3.0 OData API (Android) 3. Keep the default values as shown in the image and click Next. 4. You can choose any template to create an activity, for simplicity purposes we will chose a Blank Activity 3 How To... On-board users with SMP 3.0 OData API (Android) 5. Enter the following information to create the activity and its corresponding layout resources and click Finish 6. Your project should look like the image below. 4 How To... On-board users with SMP 3.0 OData API (Android) 4.2 Add required libraries 1. Locate your SMP 3.0 SDK installation folder. You will find the jar files in the two different folders: • <Client SDK dir>\NativeSDK\ODataFramework\Android\libraries • <Client SDK dir>\NativeSDK\MAFReuse\Android\libraries 2. Using the Project view, copy the following jar libraries from the SDK installation folders and paste it into the libs folder in Android Studio: • AfariaSLL.jar • HttpConvAuthFlows.jar • ODataAPI.jar • ClientHubSLL • HttpConversation.jar • odataoffline.jar (offline store) • ClientLog.jar • maflogger.jar • ODataOnline.jar (online store) • Common.jar • maflogoncore.jar • perflib.jar • Connectivity.jar • maflogonui.jar • Request.jar • CoreServices.jar • mafuicomponents.jar • sap-e2etrace.jar • DataVaultLib.jar • mafsettingscreen.jar • SupportabilityFacade.jar • E2ETrace.jar • MobilePlace.jar • XscriptParser.jar 5 How To... On-board users with SMP 3.0 OData API (Android) 6 How To... On-board users with SMP 3.0 OData API (Android) 3. Go to the Project view 4. Paste the libraries into the libs folder 5. A confirmation window will appear. Click OK Remember all the required jar file are locates in two different folders, make sure you include all the libraries indicated in step 2 7 How To... On-board users with SMP 3.0 OData API (Android) 6. Select the libraries, right click on the selected libraries and select Add as library from the context menu 7. Add it to the app module and click OK 8 How To... On-board users with SMP 3.0 OData API (Android) 8. You can check the libraries were added in the build.gradle file located in the app module 9 How To... On-board users with SMP 3.0 OData API (Android) Mandatory if using Offline Store If you are going to use the Offline store, remember you need to integrate native libraries (.so resources) in your app. The following steps explains how to do it in Android Studio 9. Right-click on main folder and select New -> Directory from the context menu 10. Enter jniLibs in the directory name field and click OK 11. Locate your SMP 3.0 SDK installation folder. You will find libmlcrsa16.so and the libodataofflinejni.so files in the following folder: <Client SDK dir>\NativeSDK\ODataFramework\Android\libraries\armeabi 10 How To... On-board users with SMP 3.0 OData API (Android) 12. Copy the armeabi folder and paste it into jniLibs in Android Studio. For the Offline Store you will need libmlcrsa16.so and libodataofflinejni.so, you can delete the rest. 13. Save your changes 11 How To... On-board users with SMP 3.0 OData API (Android) 4.3 Create Logon Screen We are assuming this app requires more control over the logon screen, which means that developers have to design the logon screen and define the texts included in the screens. 4.3.1 Add Logon Screen Texts Under res -> values folder you will find strings.xml file, which contains all the text that your application uses 1. Select the Android perspective 2. Go to res -> values and open strings.xml 3. strings.xml should look like the image below 4. Add the following labels and messages between the <resources> </resources> tag <resources> <string name="app_name">MyApp</string> <string name="hello_world">Hello world!</string> <string name="action_settings">Settings</string> <!-- === Main texts ====================================== --> <string name="title_welcome">Welcome %1$s</string> <!-- === Logon texts ======================================== --> <string name="lbl_host">Host</string> <string name="lbl_port">Port</string> <string name="lbl_username">Username</string> <string name="lbl_password">Password</string> <string name="lbl_https">HTTPS enabled</string> <string name="btn_logon">Login</string> <string name="msg_registration_progress">Registration is in Progress</string> 12 How To... On-board users with SMP 3.0 OData API (Android) <string <string <string <string name="msg_registration_success">Registration was successful</string> name="msg_registration_fail">Registration failed</string> name="msg_already_registered">Device already registered</string> name="title_activity_login">Login</string> <!-- === default values ======================================== --> <string name="value_host">SMP Host</string> <string name="value_username">Username</string> <string name="value_port">SMP Port</string> <!-- === Main texts ======================================== --> <string name="msg_offline_progress">Opening offline store</string> <string name="msg_offline_success">Offline store is opened. It has %1$d agencies</string> <string name="msg_offline_fail">Offline store could not be opened. Error: %1$s</string> </resources> 4.3.2 1. Create LoginActivity Select File -> New -> Activity -> Blank Activity 2. Enter the following information to create the activity and its corresponding layout resources and click Finish 13 How To... On-board users with SMP 3.0 OData API (Android) 3. You should see the activity_login.xml layout in the editor. At the bottom of the editor, click on Text to add new UI elements to this screen. 4. Remove the hello_world text view 14 How To... On-board users with SMP 3.0 OData API (Android) 5. Copy the following xml code to define the logon screen. <RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context="com.sap.sample.myapp.LoginActivity"> <EditText android:id="@+id/txt_host" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="@string/lbl_host"> <requestFocus /> </EditText> <EditText android:id="@+id/txt_port" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="@string/lbl_port" android:layout_below="@+id/txt_host"> </EditText> <EditText android:id="@+id/txt_username" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="@string/lbl_username" android:layout_below="@+id/txt_port"/> <EditText android:id="@+id/txt_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="@string/lbl_password" android:inputType="text|textPassword" android:layout_below="@+id/txt_username"/> <Button android:id="@+id/logon_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dip" android:text="@string/btn_logon" android:layout_below="@+id/txt_password"/> </RelativeLayout> 15 How To... On-board users with SMP 3.0 OData API (Android) 5. The graphical layout should look like the image below 16 How To... On-board users with SMP 3.0 OData API (Android) 4.4 Onboard users with LogonCore LogonCore is responsible for handling the registration/de-registration. Android is Multi-Threaded, which means that applications responsibilities can be separated. We need to create different classes to run the network operations in the background thread and the changes in the screen in the main thread: • UIListener interface to notify the user and make changes in the UI • ODataBaseListener class to transition from the background thread to the main tread through the UIListener • MyLogonCoreListener: Whether the onboarding process finishes successful or not, is this listener who receives the response from the SMP server. 4.4.1 Create UIListener UIListener is an interface that defines two methods. Activities can implement the UIListener interface to receive responses from the server 1. Go to Java -> com.sap.sample and select New -> Java Class from the context menu to define an interface. 2. Enter UIListener in the name field and click OK 3. Define the UIListener interface with the following source code package com.sap.sample.myapp; /** * Define two methods that activities can * implement to receive responses from the server */ public interface UIListener { void onODataRequestError(Exception e); void onODataRequestSuccess(String info); } 4. Save the changes 17 How To... On-board users with SMP 3.0 OData API (Android) 4.4.2 Create ODataBaseListener The ODataBaseListener will define the methods to notify the user about successes or failures using the main thread. 4. Go to Java -> com.sap.sample and select New -> Package from the context menu 5. Enter services and click OK 6. Select the newly created package and Go to New -> Java Class in the context menu 7. Enter ODataBaseListener and click OK 18 How To... On-board users with SMP 3.0 OData API (Android) 8. Implement the class with the following code package com.sap.sample.myapp.services; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import com.sap.sample.myapp.UIListener; import java.lang.ref.WeakReference; /** * Get the responses from the server in the background treads and * send notifications to the activities through the main thread. */ public class ODataBaseListener { private static final String TAG = ODataBaseListener.class.getSimpleName(); protected UIListener uiListener; protected int operation; private final int SUCCESS = 0; private final int ERROR = -1; private Handler uiHandler = new myHandler(this); private static class myHandler extends Handler { private WeakReference<ODataBaseListener> baseListenerWeakReference; public myHandler(ODataBaseListener oDataBaseListener){ super(Looper.getMainLooper()); baseListenerWeakReference = new WeakReference<ODataBaseListener>(oDataBaseListener); } @Override public void handleMessage(Message msg) { ODataBaseListener baseListener = baseListenerWeakReference.get(); if (msg.what == baseListener.SUCCESS) { // Notify the Activity the is complete String key = (String) msg.obj; baseListener.uiListener.onODataRequestSuccess(key); } else if (msg.what == baseListener.ERROR) { Exception e = (Exception) msg.obj; baseListener.uiListener.onODataRequestError( e); } } } public ODataBaseListener(int operation, UIListener uiListener) { super(); this.operation = operation; this.uiListener = uiListener; } /** * Notify the UIListener that the request was successful. * @param info an Exception that denotes the error that occurred. */ protected void notifySuccessToListener(String info) { Message msg = uiHandler.obtainMessage(); msg.what = SUCCESS; msg.obj = info; uiHandler.sendMessage(msg); Log.d(TAG, "notifySuccessToListener: " + info); } /** * Notify the UIListener that the request has an error. * @param exception an Exception that denotes the error that occurred. */ protected void notifyErrorToListener(Exception exception) { Message msg = uiHandler.obtainMessage(); msg.what = ERROR; msg.obj = exception; uiHandler.sendMessage(msg); Log.e(TAG, "notifyErrorToListener", exception); } } 19 How To... On-board users with SMP 3.0 OData API (Android) 4. Save the changes 4.4.3 Create MyLogonCoreListener Define MyLogonCoreListener that implements the LogonCoreListener interface. This listener implements the methods that are called when the registration finishes 1. Right click on the services package and select New -> Java Class from the context menu 2. Enter MyLogonCoreListener and click OK 3. Make this class extend the ODataBaseListener and implements LogonCoreListener /** * This listener implements the methods that are called when the registration finishes */ public class MyLogonCoreListener extends ODataBaseListener implements LogonCoreListener{ } 20 How To... On-board users with SMP 3.0 OData API (Android) 4. With your cursor in the definition of the class, click on the icon that appears on your left and select Implement methods from the menu 5. Select all methods to implement and click OK 6. You also need to create a constructor matching the super class 21 How To... On-board users with SMP 3.0 OData API (Android) 7. The registrationFinished method has a Boolean b as parameter. It indicates the registration was successful or not. Your class should look like the code below package com.sap.sample.myapp.services; import import import import com.sap.maf.tools.logon.core.LogonCoreListener; com.sap.maf.tools.logon.logonui.api.LogonListener; com.sap.sample.myapp.UIListener; com.sybase.persistence.DataVault; /** * This listener implements the methods that are called when the registration finishes */ public class MyLogonCoreListener extends ODataBaseListener implements LogonCoreListener{ public static final String TAG = MyLogonCoreListener.class.getSimpleName(); public MyLogonCoreListener(int operation, UIListener uiListener) { super(operation, uiListener); } @Override public void registrationFinished(boolean b, String s, int i, DataVault.DVPasswordPolicy dvPasswordPolicy) { Log.d(TAG, "registrationFinished: "+b); if (b){ try { LogonCore lgCore = LogonCore.getInstance(); //For testing purposes we are not using password to create the secure store //Consider enabling the password if the application handles sensitive // information lgCore.createStore(null, false); //Persists the registration information into the secure store // then clears the sensitive information (e.g. password arrays) from the //memory lgCore.persistRegistration(); notifySuccessToListener("successful: " + s); } catch (LogonCoreException e) { notifyErrorToListener(e); } } else { notifyErrorToListener(new GenericException("registration failed: "+ s)); } } @Override public void deregistrationFinished(boolean b) { } @Override public void backendPasswordChanged(boolean b) { } @Override public void applicationSettingsUpdated() { } @Override public void traceUploaded() { } } 4. Save the changes 22 How To... On-board users with SMP 3.0 OData API (Android) 4.4.4 1. Implement LoginActivity Open LoginActivity 2. Copy the code from Appendix A – LoginActivity.java and paste it into your LoginActivity class. The following steps explains in high level the important pieces of code form Login Activity 3. Notice LoginActivity implements the interface UIListener that we defined in step Create UIListener. In consequence it needs to implement the onODataRequestError method to notify the user if an error occurred during the registration and the onODataRequestSuccess method to redirect the user to the main screen. public class LoginActivity extends Activity implements View.OnClickListener, UIListener { … @Override public void onODataRequestError(Exception e) { … } @Override public void onODataRequestSuccess(String info) { … ) 4. Notice LoginActivity uses the private methods initializeLogonCore and registerDevice to initialize the Logon objects, get the SMP information from the screen and register the user. Once the user is registered successful, the application will display the MainActivity. 5. More details can be found in Appendix A – LoginActivity.java 23 How To... On-board users with SMP 3.0 OData API (Android) sa 4.5 Review AndroidManifest.xml 1. Ensure that AndroidManifest.xml contains the INTERNET permission: <!-- allow connections to Internet Services. --> <uses-permission android:name="android.permission.INTERNET" /> 2. Define as main activity the LogonActivity. This is a new activity that represents the logon screen for this application. We are going to create it during this exercise. <activity android:name=".LogonActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> 3. Define the MainActivity as a second screen that will be displayed after the registration finishes. <activity android:name=".MainActivity" android:configChanges="orientation|screenSize" android:label="@string/app_name" android:screenOrientation="portrait" > </activity> 4. Your manifest file should look like Appendix B – AndroidManifest.xml. 4.6 Open OfflineStore and Access Data Offline When the onboarding process finishes, the application will display the main screen. Once the MainActivity is displayed, we are going to open the online store and download the list of agencies from the backend. In order to do it, we need to create two main classes: • • 4.6.1 CredentialsProvider: is the class that provides the username and password to the offline store OfflineManager is the class responsible to open the offline store and access the data Create CredentialsProvider class You can find the source code for the CredentialsProvider class in Appendix C – CredentialsProvider. This class implements UsernamePasswordProvider interface to provide username and password for basic authentication. Credentials can be provided either upfront or in response to a challenge. public class CredentialsProvider implements UsernamePasswordProvider{ @Override public Object onCredentialsNeededUpfront(ISendEvent iSendEvent) { … } @Override public Object onCredentialsNeededForChallenge(IReceiveEvent iReceiveEvent) { … } } 24 How To... On-board users with SMP 3.0 OData API (Android) 4.6.2 Create OfflineManager class You can find the source code for the OfflineManager class in Appendix D – OfflineManager. This class is responsible for opening the offline store and downloading the list of travel agencies (Agency objects) for offline access. Notice the CredentialsProvider class is used to create the HttpConversationManager when opening the offline store CredentialsProvider credProvider = CredentialsProvider .getInstance(lgCtx); HttpConversationManager manager = new CommonAuthFlowsConfigurator( context).supportBasicAuthUsing(credProvider).configure( new HttpConversationManager(context)); 4.6.3 Complete MainActivity You can find the source code for the MainActivity in Appendix G – MainActivity and Appendix F – AsyncResult. This activity define an internal class that extends AsyncTask to execute network operations like opening the offline store and downloading the list of agencies. private class GetTask extends AsyncTask<Void, Void, AsyncResult<List<Agency>>>{ @Override protected AsyncResult<List<Agency>> doInBackground(Void... voids) { try { OfflineManager.openOfflineStore(MainActivity.this); return new AsyncResult<>(OfflineManager.getAgencies()); } catch (Exception e){ return new AsyncResult<>(e); } } @Override protected void onPostExecute(AsyncResult<List<Agency>> listAsyncResult) { if (listAsyncResult.getException() != null || listAsyncResult.getData() == null) { String message = String.format(getString(R.string.msg_offline_fail),listAsyncResult.getException() ); Toast.makeText(MainActivity.this,message, Toast.LENGTH_SHORT).show(); Log.e(TAG, "Error loading agencies", listAsyncResult.getException()); } else { final List<Agency> agencies = listAsyncResult.getData(); String message = String.format(getString(R.string.msg_offline_success), agencies.size()); Toast.makeText(MainActivity.this,message, Toast.LENGTH_SHORT).show(); Log.d(TAG, message); } } } Save your changes 25 How To... On-board users with SMP 3.0 OData API (Android) 4.7 1. Run the Application Check the registrations in the SAP Management Cockpit 2. In AndroidStudio, click on the debug icon 3. Select your testing device and click OK 26 How To... On-board users with SMP 3.0 OData API (Android) 4. Enter SMP host and port, username and password and click Login 5. A progress message will be displayed 6. A successful message will be displayed and the user will be redirected to the main screen 7. Another message indicating the offline store was successfully opened with the number of travel agencies should appear 27 How To... On-board users with SMP 3.0 OData API (Android) 8. To verify that the user was registered successful, you can open the SAP Management Cockpit and check if the number of registrations increased when you register your device Using the SMP OData SDK to onboard users give developers more control about the look and feel of the logon screen. However, if a more sophisticated mechanism of authentication is used (i.e SAML, x.509 certificates) developers require to implement how the app will react to the authentication challenges triggered by the authentication provider. To understand how developers can leverage the MAF Logon component in more complex scenarios, please visit help.sap.com Congratulations!, you completed this exercise. 28 How To... On-board users with SMP 3.0 OData API (Android) 5. Appendix Appendix A – LoginActivity.java package com.sap.sample.myapp; import import import import import import import import import import import import import import import import android.app.Activity; android.app.ProgressDialog; android.content.Intent; android.os.Bundle; android.text.TextUtils; android.util.Log; android.view.Menu; android.view.MenuItem; android.view.View; android.widget.Button; android.widget.EditText; android.widget.Toast; com.sap.maf.tools.logon.core.LogonCore; com.sap.maf.tools.logon.core.LogonCoreContext; com.sap.maf.tools.logon.core.LogonCoreException; com.sap.sample.myapp.services.MyLogonCoreListener; public class LoginActivity extends Activity implements View.OnClickListener, UIListener { private final String TAG = LoginActivity.class.getSimpleName(); private static final String VK_APPCID = "appcid"; private Button logonBtn; private EditText hostEdit, portEdit, usernameEdit, passwordEdit; private ProgressDialog progressDialog; private String appConnId; private LogonCore lgCore; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); //Initialize UI elements in the screen this.initializeViews(); //Get application connection id this.initializeLogonCore(); //If application connection id exists, then display main screen if (!TextUtils.isEmpty(appConnId)){ Intent goToNextActivity = new Intent(this, MainActivity.class); startActivity(goToNextActivity); } } @Override public void onClick(View view) { registerDevice(); } @Override public void onODataRequestError(Exception e) { progressDialog.dismiss(); logonBtn.setEnabled(true); //notify the user the registration fails Toast.makeText(this, R.string.msg_registration_fail, Toast.LENGTH_LONG).show(); } @Override public void onODataRequestSuccess(String info) { progressDialog.dismiss(); //Store application connection Id in the secure store //This way next time the app runs, we know if the user has been //registered before try { appConnId = lgCore.getLogonContext().getConnId(); if (appConnId != null) { // store it if (!lgCore.isStoreOpen()) lgCore.unlockStore(null); lgCore.addObjectToStore(VK_APPCID, appConnId); } //notify the user the registration was complete successfully Toast.makeText(this, R.string.msg_registration_success, Toast.LENGTH_LONG).show(); //Display the main screen Intent goToNextActivity = new Intent(this,MainActivity.class); startActivity(goToNextActivity); 29 How To... On-board users with SMP 3.0 OData API (Android) } catch (LogonCoreException e) { Log.e(TAG, "error getting application connection id", e); //notify the user the registration fails Toast.makeText(this, R.string.msg_registration_fail, Toast.LENGTH_LONG).show(); logonBtn.setEnabled(true); } } /** * Initialize UI elements */ private void initializeViews() { logonBtn = (Button) findViewById(R.id.logon_button); logonBtn.setOnClickListener(this); hostEdit = (EditText) findViewById(R.id.txt_host); portEdit = (EditText) findViewById(R.id.txt_port); usernameEdit = (EditText) findViewById(R.id.txt_username); passwordEdit = (EditText) findViewById(R.id.txt_password); } /** * Initialize LogonCore component */ private void initializeLogonCore(){ //Get LogonCore instance lgCore = LogonCore.getInstance(); //Create a LogonCoreListener for asynchronously registration MyLogonCoreListener listener = new MyLogonCoreListener(Operation.Login.getValue(), this); //Set the listener lgCore.setLogonCoreListener(listener); //Initialize LogonCore with application configuraton name lgCore.init(this, <APPLICATION_NAME>); //Check if application connection exists try { //check if secure store is available if (lgCore.isStoreAvailable()) { //Unlock the store lgCore.unlockStore(null); //Get application connection id appConnId = lgCore.getObjectFromStore(VK_APPCID); } } catch (LogonCoreException e) { Log.e(TAG, "error initializing logon core", e); } } /** * Onboard device with Mobile services */ private void registerDevice() { logonBtn.setEnabled(false); progressDialog = ProgressDialog.show(this, "", getString(R.string.msg_registration_progress), true); //Check if the Application Connection Id already exists if (TextUtils.isEmpty(appConnId)){ //Get LogonCoreContext instance LogonCoreContext lgCtx = lgCore.getLogonContext(); //Set host lgCtx.setHost(hostEdit.getText().toString()); //Set port int port = 8080; try { port = Integer.valueOf(portEdit.getText().toString()); } catch (NumberFormatException e) { Log.e(TAG, "Invalid port value, default (8080) is taken!"); } lgCtx.setPort(port); //Set whether the registration uses secure connection or not lgCtx.setHttps(false); //set user creation policy LogonCore.UserCreationPolicy ucPolicy = LogonCore.UserCreationPolicy.automatic; lgCtx.setUserCreationPolicy(ucPolicy); //Set username and password try { lgCtx.setBackendUser(usernameEdit.getText().toString()); lgCtx.setBackendPassword(passwordEdit.getText().toString()); } catch (LogonCoreException e) { //Notifies the execution finished onODataRequestError(e); } //Register user 30 How To... On-board users with SMP 3.0 OData API (Android) lgCore.register(lgCtx); } else { //This means the user is already registered Log.d(TAG, getString(R.string.msg_already_registered)); //notify the user the device is already regitered Toast.makeText(this, R.string.msg_already_registered, Toast.LENGTH_LONG).show(); } } } Appendix B – AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.sap.sample.myapp" > <!-- allow connections to Internet Services. --> <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > </activity> <activity android:name=".LoginActivity" android:label="@string/title_activity_login" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> 31 How To... On-board users with SMP 3.0 OData API (Android) Appendix C – CredentialsProvider package com.sap.sample.myapp.services; import import import import import import com.sap.maf.tools.logon.core.LogonCoreContext; com.sap.maf.tools.logon.core.LogonCoreException; com.sap.smp.client.httpc.authflows.UsernamePasswordProvider; com.sap.smp.client.httpc.authflows.UsernamePasswordToken; com.sap.smp.client.httpc.events.IReceiveEvent; com.sap.smp.client.httpc.events.ISendEvent; /** * Created by I821448 on 12/22/2015. * */ public class CredentialsProvider implements UsernamePasswordProvider{ private static CredentialsProvider instance; private LogonCoreContext lgCtx; private CredentialsProvider(LogonCoreContext logonContext) { lgCtx = logonContext; } public static CredentialsProvider getInstance(LogonCoreContext logonContext) { if (instance == null) { instance = new CredentialsProvider(logonContext); } return instance; } @Override public Object onCredentialsNeededUpfront(ISendEvent iSendEvent) { try { String username = lgCtx.getBackendUser(); String password = lgCtx.getBackendPassword(); return new UsernamePasswordToken(username, password); } catch (LogonCoreException e) { return null; } } @Override public Object onCredentialsNeededForChallenge(IReceiveEvent iReceiveEvent) { try { String username = lgCtx.getBackendUser(); String password = lgCtx.getBackendPassword(); return new UsernamePasswordToken(username, password); } catch (LogonCoreException e) { return null; } } } 32 How To... On-board users with SMP 3.0 OData API (Android) Appendix D – OfflineManager package com.sap.sample.myapp.services; import android.content.Context; import android.text.TextUtils; import android.util.Log; import import import import import import import import import import import import import import import import import import import import com.sap.maf.tools.logon.core.LogonCore; com.sap.maf.tools.logon.core.LogonCoreContext; com.sap.maf.tools.logon.logonui.api.LogonUIFacade; com.sap.sample.myapp.GenericException; com.sap.sample.myapp.types.Agency; com.sap.smp.client.httpc.HttpConversationManager; com.sap.smp.client.httpc.IManagerConfigurator; com.sap.smp.client.httpc.authflows.CommonAuthFlowsConfigurator; com.sap.smp.client.odata.ODataEntity; com.sap.smp.client.odata.ODataEntitySet; com.sap.smp.client.odata.ODataPayload; com.sap.smp.client.odata.ODataPropMap; com.sap.smp.client.odata.ODataProperty; com.sap.smp.client.odata.impl.ODataErrorDefaultImpl; com.sap.smp.client.odata.offline.ODataOfflineStore; com.sap.smp.client.odata.offline.ODataOfflineStoreOptions; com.sap.smp.client.odata.store.ODataRequestParamSingle; com.sap.smp.client.odata.store.ODataRequestParamSingle.Mode; com.sap.smp.client.odata.store.ODataResponseSingle; com.sap.smp.client.odata.store.impl.ODataRequestParamSingleDefaultImpl; import java.net.URL; import java.util.ArrayList; import java.util.List; public class OfflineManager { public static final String TAG = OfflineManager.class.getSimpleName(); private static ODataOfflineStore offlineStore; private static final String TRAVEL_AGENCY_COLLECTION = "TravelAgencies_DQ"; public static final String TRAVEL_AGENCY_ENTRY_ID = "agencynum"; public static boolean openOfflineStore(Context context) throws GenericException { if (offlineStore==null){ try { //This instantiate the native UDB libraries which are located in the libodataofflinejni.so file ODataOfflineStore.globalInit(); LogonCoreContext lgCtx = LogonCore.getInstance().getLogonContext(); String endPointURL = lgCtx.getAppEndPointUrl(); URL url = new URL(endPointURL); Log.d(TAG, "openOfflineStore: appcid:" + lgCtx.getConnId()); Log.d(TAG, "openOfflineStore: endpointurl:"+ endPointURL); // Define the offline store options. // Connection parameter and credentials and the application connection id we got at the registration ODataOfflineStoreOptions options = new ODataOfflineStoreOptions(); options.host = url.getHost(); options.port = String.valueOf(url.getPort()); options.enableHTTPS = lgCtx.isHttps(); // the serviceRoot is the backend connector name, which is usually the same as the application configuration name // enter in the SMP management cockpit options.serviceRoot= <APPLICATION_NAME>; CredentialsProvider credProvider = CredentialsProvider .getInstance(lgCtx); //Without MAF Logon HttpConversationManager manager = new CommonAuthFlowsConfigurator( context).supportBasicAuthUsing(credProvider).configure( new HttpConversationManager(context)); options.conversationManager = manager; //put the APPCID in the HTTP Header options.enableRepeatableRequests = false; options.storeName="flight"; 33 How To... On-board users with SMP 3.0 OData API (Android) //This defines the oData collections which will be stored in the offline store options.addDefiningRequest("reg1", TRAVEL_AGENCY_COLLECTION, false); //Open offline store offlineStore = new ODataOfflineStore(context); offlineStore.openStoreSync(options); Log.d(TAG, "openOfflineStore: library version" + ODataOfflineStore.libraryVersion()); return true; } catch (Exception e) { throw new GenericException(e); } } else { return true; } } public static List<Agency> getAgencies() throws GenericException{ ArrayList<Agency> agencyList = new ArrayList<Agency>(); if (offlineStore!=null){ Agency agency; ODataProperty property; ODataPropMap properties; try { String resourcePath = TRAVEL_AGENCY_COLLECTION + "?$orderby="+TRAVEL_AGENCY_ENTRY_ID+"%20desc"; ODataRequestParamSingle request = new ODataRequestParamSingleDefaultImpl(); request.setMode(Mode.Read); request.setResourcePath(resourcePath); ODataResponseSingle response = (ODataResponseSingle) offlineStore.executeRequest(request); if (response.getPayloadType() == ODataPayload.Type.Error) { ODataErrorDefaultImpl error = (ODataErrorDefaultImpl) response.getPayload(); throw new GenericException(error.getMessage()); } else if (response.getPayloadType() == ODataPayload.Type.EntitySet) { ODataEntitySet feed = (ODataEntitySet) response.getPayload(); List<ODataEntity> entities = feed.getEntities(); for (ODataEntity entity: entities){ properties = entity.getProperties(); property = properties.get(TRAVEL_AGENCY_ENTRY_ID); agency = new Agency((String)property.getValue()); … agency.setEditResourceURL(entity.getEditResourcePath()); agencyList.add(agency); } } else { throw new GenericException("Invalid payload: EntitySet expected but got " + response.getPayloadType().name()); } } catch (Exception e) { throw new GenericException(e); } } return agencyList; } } 34 How To... On-board users with SMP 3.0 OData API (Android) Appendix E – Agency package com.sap.sample.myapp.types; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; public class Agency implements Parcelable { private String agencyId; private String agencyName; private String street; private String city; private String country; private String website; private String editResourceURL; public Agency(String agencyId) { super(); this.agencyId = agencyId; } public Agency(Parcel in){ readFromParcel(in); } public boolean isInitialized(){ return (!TextUtils.isEmpty(this.agencyId)); } public String getAgencyId() { return agencyId; } public void setAgencyId(String agencyId) { this.agencyId = agencyId; } public String getAgencyName() { return agencyName; } public void setAgencyName(String agencyName) { this.agencyName = agencyName; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getWebsite() { return website; } public void setWebsite(String website) { this.website = website; } public String getEditResourceURL() { return editResourceURL; } public void setEditResourceURL(String editResourceURL) { this.editResourceURL = editResourceURL; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.agencyId); dest.writeString(this.agencyName); 35 How To... On-board users with SMP 3.0 OData API (Android) dest.writeString(this.street); dest.writeString(this.city); dest.writeString(this.country); dest.writeString(this.website); dest.writeString(this.editResourceURL); } public void readFromParcel(Parcel in) { this.agencyId = in.readString(); this.agencyName = in.readString(); this.street = in.readString(); this.city = in.readString(); this.country = in.readString(); this.website = in.readString(); this.editResourceURL = in.readString(); } public static final Creator<Agency> CREATOR = new Creator<Agency>() { @Override public Agency createFromParcel(Parcel in) { return new Agency(in); } @Override public Agency[] newArray(int size) { return new Agency[size]; } }; } Appendix F – AsyncResult package com.sap.sample.myapp.types; public class AsyncResult<T> { public static final Void VOID_RESULT = (Void) null; private Exception exception; private T data; public AsyncResult(Exception exception) { this.exception = exception; } public AsyncResult(T data) { this.data = data; } public Exception getException() { return exception; } public T getData() { return data; } } 36 How To... On-board users with SMP 3.0 OData API (Android) Appendix G – MainActivity package com.sap.sample.myapp; import import import import import import import import android.os.AsyncTask; android.support.v7.app.ActionBarActivity; android.os.Bundle; android.support.v7.app.AppCompatActivity; android.util.Log; android.view.Menu; android.view.MenuItem; android.widget.Toast; import com.sap.sample.myapp.services.OfflineManager; import com.sap.sample.myapp.types.Agency; import com.sap.sample.myapp.types.AsyncResult; import java.util.List; public class MainActivity extends AppCompatActivity { public static final String TAG = MainActivity.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new GetTask().execute(); } @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; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } private class GetTask extends AsyncTask<Void, Void, AsyncResult<List<Agency>>>{ @Override protected AsyncResult<List<Agency>> doInBackground(Void... voids) { try { OfflineManager.openOfflineStore(MainActivity.this); return new AsyncResult<>(OfflineManager.getAgencies()); } catch (Exception e){ return new AsyncResult<>(e); } } @Override protected void onPostExecute(AsyncResult<List<Agency>> listAsyncResult) { if (listAsyncResult.getException() != null || listAsyncResult.getData() == null) { String message = String.format(getString(R.string.msg_offline_fail),listAsyncResult.getException() ); Toast.makeText(MainActivity.this,message, Toast.LENGTH_SHORT).show(); Log.e(TAG, "Error loading agencies", listAsyncResult.getException()); } else { final List<Agency> agencies = listAsyncResult.getData(); String message = String.format(getString(R.string.msg_offline_success), agencies.size()); Toast.makeText(MainActivity.this,message, Toast.LENGTH_SHORT).show(); Log.d(TAG, message); } } } } 37 www.sap.com/contactsap www.sdn.sap.com/irj/sdn/howtoguides © 2013 SAP AG or an SAP affiliate company. All rights reserved. No part of this publication may be reproduced or transmitted in any form or for any purpose without the express permission of SAP AG. The information contained herein may be changed without prior notice. Some software products marketed by SAP AG and its distributors contain proprietary software components of other software vendors. National product specifications may vary. These materials are provided by SAP AG and its affiliated companies (“SAP Group”) for informational purposes only, without representation or warranty of any kind, and SAP Group shall not be liable for errors or omissions with respect to the materials. The only warranties for SAP Group products and services are those that are set forth in the express warranty statements accompanying such products and services, if any. Nothing herein should be construed as constituting an additional warranty. SAP and other SAP products and services mentioned herein as well as their respective logos are trademarks or registered trademarks of SAP AG in Germany and other countries. Please see http://www.sap.com/corporate-en/legal/copyright/index.epx#trademark for additional trademark information and notices.