如何在 Android 中为 MAFLogon 创建自定义 UI?

How to create custom UI for MAFLogon in Android?

我在使用 SAP SDK 为 HANA Cloud Platform 上的 SAP Mobile 构建的应用程序中使用 MAFLogon 作为我的登录屏幕。

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);

上面的代码创建了一个内置登录 Activity。我如何创建自定义 Activity 或自定义这个? (更改徽标、颜色等)

唉,MAF 登录组件并未设计为可根据您的需要进行定制。 Claudia Pacheco 的博客 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。我为您整理了一个小示例,以帮助您入门:

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;
    }

}