Search code examples
androidauthenticationsap-cloud-platformsap-smp

How to create custom UI for MAFLogon in Android?


I am using MAFLogon for my login screen in an application build for SAP Mobile on HANA Cloud Platform, using SAP SDK.

LogonUIFacade mLogonUIFacade = LogonUIFacade.getInstance();

        //Initialize the Logon UI Facade
        mLogonUIFacade.init(this, mContext, getString(R.string.HCPMS_APP_ID));
// Present the logon screen to the user
        setContentView(mLogonUIFacade.logon());

        // Hide the splash screen (do this at the end, so defaults are not reset)
        mLogonUIFacade.showSplashScreen(false);

The code above creates a built-in login Activity. How can i create a custom Activity or customize this one? (change logo, colors etc)


Solution

  • Alas, the MAF Logon component is not designed to be customizable to the extent you require. Claudia Pacheco's blog post "Customizing MAF Logon Component in Android" nicely outlines the options you have. The issue with customization in terms of branding is that there may be no end to the requirements: Some are happy with a custom logo, others would require a totally different layout. Therefore you should implement your own login page and implement the communication on top of the HttpConversation flow, as described in the documentation. I put together a little example for you, to get you started:

    res/layout/layout_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="logon.example.com.basicauthconvflow.MainActivity">
    
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:paddingEnd="8dp"
            android:paddingStart="8dp"
            tools:layout_editor_absoluteY="8dp"
            tools:layout_editor_absoluteX="8dp">
    
            <EditText
                android:id="@+id/server_host"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ems="10"
                android:hint="Host (e.g. hcpms-p0123456trial.hanatrial.ondemand.com"
                android:inputType="text"
                android:maxLines="1" />
    
            <EditText
                android:id="@+id/application_id"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="com.logon.test"
                android:ems="10"
                android:hint="Application ID"
                android:inputType="none"
                android:maxLines="1" />
    
            <EditText
                android:id="@+id/user_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="pXXXXXXX"
                android:ems="10"
                android:hint="User name"
                android:inputType="none"
                android:maxLines="1" />
    
            <EditText
                android:id="@+id/password"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ems="10"
                android:hint="Password"
                android:inputType="textPassword"
                android:maxLines="1" />
    
            <Button
                android:id="@+id/login_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Log in"/>
    
        </LinearLayout>
    
    
    </ScrollView>
    

    src/main/java/logon/example/com/basicauthconflow/MainActivity.java

    package logon.example.com.basicauthconvflow;
    
    import android.app.Activity;
    import android.net.Uri;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.Toast;
    
    import com.sap.smp.client.httpc.HttpConversationManager;
    import com.sap.smp.client.httpc.HttpMethod;
    import com.sap.smp.client.httpc.IHttpConversation;
    import com.sap.smp.client.httpc.SAPCookieManager;
    import com.sap.smp.client.httpc.authflows.CommonAuthFlowsConfigurator;
    import com.sap.smp.client.httpc.authflows.UsernamePasswordProvider;
    import com.sap.smp.client.httpc.authflows.UsernamePasswordToken;
    import com.sap.smp.client.httpc.events.IReceiveEvent;
    import com.sap.smp.client.httpc.events.ISendEvent;
    import com.sap.smp.client.httpc.events.ITransmitEvent;
    import com.sap.smp.client.httpc.listeners.IRequestListener;
    import com.sap.smp.client.httpc.listeners.IResponseListener;
    import com.sap.smp.client.httpc.utils.EmptyFlowListener;
    
    import org.json.JSONException;
    import org.json.JSONObject;
    
    import java.io.IOException;
    import java.net.HttpCookie;
    import java.net.MalformedURLException;
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.net.URL;
    import java.util.List;
    
    public class MainActivity extends AppCompatActivity {
    
        /**
         * Mobile Services registration API URI for this app
         */
        URI registrationUri;
    
        /**
         * Mobile Services application ID
         */
        String applicationId;
    
        /**
         * User name for login
         */
        String user;
    
        /**
         * Password for login
         */
        char[] password;
    
        /**
         * Mobile Services application connection ID ("registration ID") after login
         */
        String appcid;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            ((Button)findViewById(R.id.login_button)).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    applicationId = ((EditText)findViewById(R.id.application_id)).getText().toString();
                    user = ((EditText)findViewById(R.id.user_name)).getText().toString();
                    password = ((EditText)findViewById(R.id.password)).getText().toString().toCharArray();
    
                    String serverAuthority = ((EditText)findViewById(R.id.server_host)).getText().toString() + ":443";
                    Uri.Builder builder = new Uri.Builder();
                    // Assemble path to the Mobile Services registration API for this app
                    builder.scheme("https")
                            .encodedAuthority(serverAuthority)
                            .appendPath("odata")
                            .appendPath("applications")
                            .appendPath("latest")
                            .appendPath(applicationId)
                            .appendPath("Connections");
    
                    registrationUri = URI.create(builder.build().toString());
    
                    login();
                }
            });
        }
    
        /**
         * Performs a login, registering with Mobile Services if required.
         */
        private void login() {
            if(isRegistered()) {
                // There is a valid registration and the cookie manager has a session cookie
                Toast.makeText(this, "Already registered with APPCID " + appcid, Toast.LENGTH_SHORT).show();
            } else {
                this.register();
            }
        }
    
        /**
         * Performs a registration request against Mobile Services and extracts the application connection
         * ID from the response.
         * The application connection ID is set as a side-effect of this method.
         */
        private void register() {
            final IHttpConversation conv = createConversation();
    
            conv.setMethod(HttpMethod.POST);
            conv.addHeader("Content-Type", "application/json; charset=utf-8");
            conv.setRequestListener(new IRequestListener() {
                @Override
                public Object onRequestHeaderSending(ISendEvent event) {
                    return null;
                }
    
                @Override
                public Object onRequestBodySending(ITransmitEvent event) throws IOException {
                    JSONObject json = new JSONObject();
                    try {
                        json.put("DeviceType", "Android");
                        event.getWriter().write(json.toString());
                        return null;
                    } catch (JSONException e) {
                        throw new IOException(e);
                    }
                }
            });
    
            conv.setResponseListener(new IResponseListener() {
                @Override
                public void onResponseReceived(final IReceiveEvent event) throws IOException {
                    final int statusCode = event.getResponseStatusCode();
                    final Activity activity = MainActivity.this;
                    if (activity != null)
                        activity.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                if (statusCode == 201) {
                                    // Extract the connection ID from the cookies.
                                    if(extractAppcidFromResponse(event)) {
                                        Toast.makeText(activity, "Registered with APPCID " + appcid, Toast.LENGTH_SHORT).show();
                                    }
                                } else {
                                    Toast.makeText(activity, "Registration was not succesful (" + statusCode + ")", Toast.LENGTH_SHORT).show();
                                }
                            }
                        });
                }
            });
    
            conv.start();
        }
    
        /**
         * Create an HTTP conversation targeting the Mobile Services registration API and configured
         * for Basic authentication based on the current activity instance state.
         *
         * @return The conversation
         */
        private IHttpConversation createConversation() {
            HttpConversationManager manager = new HttpConversationManager(this);
            CommonAuthFlowsConfigurator configurator = new CommonAuthFlowsConfigurator(this);
            configurator.supportBasicAuthUsing(new UsernamePasswordProvider() {
                @Override
                public Object onCredentialsNeededUpfront(ISendEvent event) {
                    return null;
                }
                @Override
                public Object onCredentialsNeededForChallenge(IReceiveEvent event) {
                    return new UsernamePasswordToken(user, new String(password));
                }
            });
            configurator.configure(manager);
    
            URL serverUrl;
            try {
                serverUrl = registrationUri.toURL();
            } catch (MalformedURLException e) {
                Log.e(this.getClass().getSimpleName(), "Unexpected error constructing registration URL.", e);
                return null;
            }
    
            // Create the conversation.
            return manager.create(serverUrl);
        }
    
        /**
         * Tells if there is an existing registration for this application.
         * The application connection ID is set as a side-effect of this method.
         *
         * @return true if there is an existing registration; false otherwise
         */
        private boolean isRegistered() {
            return extractAppcidFromCookies(SAPCookieManager.getInstance().getCookieStore().get(registrationUri));
        }
    
        /**
         * Extracts the Mobile Services application connection ID from the specified response event.
         * The application connection ID is set as a side-effect of this method.
         *
         * @param event The event to search
         * @return true if it could be found; false otherwise
         */
        private boolean extractAppcidFromResponse(IReceiveEvent event) {
            try {
                return extractAppcidFromCookies(SAPCookieManager.getInstance().getCookieStore().get(
                        event.getResponseURL().toURI()));
            } catch (URISyntaxException e) {
                Log.e(this.getClass().getSimpleName(), "Unable to extract APPCID", e);
            }
            return false;
        }
    
        /**
         * Extracts the Mobile Services application connection ID from the specified list of cookies.
         * The application connection ID is set as a side-effect of this method.
         *
         * @param httpCookies The cookies to search
         * @return true if it could be found; false otherwise
         */
        private boolean extractAppcidFromCookies(List<HttpCookie> httpCookies) {
                if (httpCookies != null)
                    for (HttpCookie httpCookie : httpCookies)
                        if ("X-SMP-APPCID".equals(httpCookie.getName())) {
                            appcid = httpCookie.getValue();
                            return true;
                        }
            return false;
        }
    
    }