TerminalHelper APIs
All terminal helper functions work by binding to the TerminalHelperService.
This page will show you how to send requests and receive responses.
We use a standard Android ServiceConnection for the communication.
Flow
- Bind to Service
- Wait for onServiceConnected callback from the ServiceConnection
- Register a client to listen to Rubean events
- Perform an action of your choice
- Unregister your client as a listener to Rubean events
- Unbind the service to remove the connection
Methods
GetStatus()
Before performing a transaction with PhonePOS, you must call this method and wait for the response.
With the release of PhonePOS 4.X (MPoC) we have introduced additional error codes that make it possible for integrators to better react on internal states of PhonePOS.
As a response you will receive the current state of PhonePOS and the included terminal component is currently in.
In case the terminal process is not running, it will be automatically started by this call.
When the terminal is already running in the background you will receive the response in 10 - 100ms.
Only when the terminal is not running in the background the response will take a longer time.
On regular devices you can expect around 30 seconds as a maximum startup time.
Please keep in mind that very old devices with slow processors and multiple apps in memory might take up to 2 minutes only for the initial GetStatus call.
The GetStatus() command needs to be sent from a foreground context.
This is a limitation from Android 10 upwards (API level 29+), see Android Documentation.
Communication
Send an ACTIONS.GET_STATUS to the TerminalHelperService.
You will receive one of the following status codes:
| Code | Name | Description |
|---|---|---|
| 0 | STATUS_TERMINAL_OPERATIONAL | Personalization, Terminal startup and initialization have been completed. |
| 1 | STATUS_PERSONALIZATION_NOT_DONE_YET | Terminal has not been personalized yet. |
| 2 | STATUS_TERMINAL_NOT_OPERATIONAL | The personalization generally finished but there are still issues that prevent full operation. |
| 102 | STATUS_APP_IS_OUTDATED | The app needs to be updated because the internal key box cycle has expired. Available from version 3.08.01.12 and up. |
See Personalisation API for further documentation on terminal status codes.
Reset()
This method will delete the personalisation of the terminal and delete all of PhonePOS data. A new personalisation can be performed after.
From PhonePOS 3.14.02.18 you will receive a callback when the reset function has finished. It is highly recommended to wait for the callback until you perform another personalisation action.
Communication
Send an ACTIONS.RESET_PERSO to the TerminalHelperService. From PhonePOS 3.14.02.18 you will receive a callback.
GetSDKInfo()
Available from PhonePOS version 3.09.02.02 and up
This method retrieves several pieces of information: the version number of the PhonePOS build, the terminal identification (TID) if the terminal has already been personalized, and the orientation currently set on the SDK.
Communication
Send an action ACTIONS.GET_SDK_INFO to the TerminalHelperService. You will receive a json response with key GET_SDK_INFO_RESPONSE.
Example response json.
{
"terminalId": "TID123456789",
"orientation": "PORTRAIT",
"sdkGeneralInfo": {
"version": "4.01.15.0198_81"
}
}
If the terminal is not personalized, the terminalId field in json response will contain an empty string value.
UpdateOrientation()
Available from PhonePOS version 3.10.01.07 and up
The UpdateOrientation method sends a message to update the screen orientation of the SDK. It prepares and dispatches a message containing the orientation information, allowing the SDK to respond accordingly.
Supported Parameters as String:
The orientation to which the screen should be set:
- "PORTRAIT": Sets the screen orientation to portrait.
- "LANDSCAPE": Sets the screen orientation to landscape.
- "NONE": Resets the screen orientation to the defaults.
Example
public void messageUpdateOrientation(String orientation) {
Message message = Message.obtain(null, UPDATE_SCREEN_ORIENTATION);
Bundle bundle = new Bundle();
bundle.putString(UPDATE_SCREEN_ORIENTATION_RESPONSE, orientation);
message.setData(bundle);
sendMessage(message);
}
The Handler's response handling should look like this:
if (msg.what == UPDATE_SCREEN_ORIENTATION) {
boolean isSuccess = msg.getData().getBoolean(UPDATE_SCREEN_ORIENTATION_RESPONSE, false);
// Handle isSuccess
}
UpdateTheme()
The PhonePOS personalisation display can be themed by sending a JSON stylesheet with the action ACTIONS.UPDATE_THEME to the TerminalHelperService.
Available from PhonePOS version 3.12.01.11 and up
Themes can be configured anytime in the process and are permanently stored within PhonePOS until the next theme update.
Communication
A theme should be provided in a JSON format as follows:
{
"day": {
// custom logo shown on activation screen (PNG/JPG in base64 encoding)
"logoBase64": "...",
"activationLogoEnabled": true,
// progress spinner color
"colorAccentAnimation": "#hex",
// button colors
"colorButton": "#hex",
"colorButtonText": "#hex",
// bar colors
"colorStatusBar": "#hex",
"colorNavigationBar": "#hex",
"colorActionBar": "#hex"
}
}
If you wish to leave certain elements, such as the navigation bar color, unchanged, simply omit the corresponding entry in the JSON. Keep in mind that boolean values default to false. Currently only the day theme is supported.
You can create the above-mentioned JSON with this POJO (using Gson & Lombok):
@Data
@Builder(setterPrefix = "with")
public class AppTheme {
@SerializedName("day")
private Stylesheet day;
@Data
@Builder(setterPrefix = "with")
private static class Stylesheet {
@SerializedName("activationLogoEnabled")
public boolean activationLogoEnabled;
@SerializedName("logoBase64")
public String logoBase64;
@SerializedName("colorAccentAnimation")
public String colorAccentAnimation;
@SerializedName("colorButton")
public String colorButton;
@SerializedName("colorButtonText")
public String colorButtonText;
@SerializedName("colorStatusBar")
public String colorStatusBar;
@SerializedName("colorNavigationBar")
public String colorNavigationBar;
@SerializedName("colorActionBar")
public String colorActionBar;
}
}
Please note that the theming settings are not affected by the reset function.
If you want to reset the theme to the default, please call the same theme update api again with an empty stylesheet (e.g. null).
If the design options are not sufficient for your use case, you can contact our support team and we will analyse further proceedings.
AttestationPreflight()
A device attestation is required before every transaction. Executing this call at the right time can make the startup of transactions significantly faster. For further description and guidance please have a look at MPoC attestation processing changes.
Communication
Send an action ACTIONS.ATTESTATION_PREFLIGHT to the TerminalHelperService.
public void messageRequestPreflight() {
sendMessage(Message.obtain(null, ATTESTATION_PREFLIGHT));
}
No response will be sent.
This command can be called in every PhonePOS version but will only have an effect with MPoC releases of the SDK (version 4.XX.XX.XX).
Example implementation
This is an example of a TerminalHelperInteractor.
You are not required to follow this example, but it is the fastest way to achieve the terminal communication.
- Java
public class TerminalHelperServiceClient {
private static final String TAG = TerminalHelperServiceClient.class.toString();
private final Messenger responseMessenger;
private final Context context;
private boolean isServiceBound = false;
private Messenger requestMessenger;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
requestMessenger = new Messenger(service);
isServiceBound = true;
messageRegister();
}
@Override
public void onServiceDisconnected(ComponentName name) {
isServiceBound = false;
Log.i(TAG, "TerminalHelperServiceClient disconnected");
}
};
public TerminalHelperServiceClient(Context context) {
this.context = context;
responseMessenger = new Messenger(new TerminalHelperServiceHandler());
}
private Intent getTerminalHelperServiceIntent() {
Intent intent = new Intent();
// -> APK: <packageName> is the packageName of the PhonePOS APK
// -> SDK: <packageName> is your own packageName (context.getPackageName())
intent.setComponent(new ComponentName("<packageName>", "com.rubean.sdkphonepos.TerminalHelperService"));
return intent;
}
/**
* Initiates the binding process to a service. This method internally calls the asynchronous
* method `context.bindService` to establish a connection to a service specified by the
* intent returned from `getTerminalHelperServiceIntent()`.
* <p>
* A `ServiceConnection` object, which was created during the initialization, is passed as a parameter
* to handle the connection lifecycle callbacks. It is crucial to wait for the `ServiceConnection.onServiceConnected()`
* callback to be invoked before attempting to send messages to the service.
* <p>
* If the service binding is initiated successfully, a log entry will indicate the start of the binding process.
* Otherwise, it will log an error suggesting to check if the correct application ID is configured in the manifest.
* <p>
* Note:
* The `bindService` method is asynchronous. More information can be found in the official
* documentation: https://developer.android.com/reference/android/content/Context#bindService(android.content.Intent,%20android.content.ServiceConnection,%20int)
*
* @catch Exception if the binding process fails, an error message will be logged.
*/
public void bindToService() {
if (!isServiceBound) {
Intent intent = getTerminalHelperServiceIntent();
try {
// This call is asynchronous
// Read more at https://developer.android.com/reference/android/content/Context#bindService(android.content.Intent,%20android.content.ServiceConnection,%20int)
boolean bound = context.bindService(getTerminalHelperServiceIntent(), serviceConnection, Context.BIND_AUTO_CREATE);
Log.i(TAG, "Start of binding process to " + intent.getComponent() + " was successful: " + bound);
if (!bound) {
// see APK Setup instructions
Log.e(TAG, "Correct application id configured in the manifest query section?");
}
} catch (Exception exception) {
Log.e(TAG, "Binding to service failed with " + exception.getMessage());
}
}
}
public void unbindFromService() {
try {
messageUnregister();
context.unbindService(serviceConnection);
} catch (Exception exception) {
Log.e(TAG, "Failed to unbind TerminalHelperServiceClient");
} finally {
isServiceBound = false;
}
}
private void messageRegister() {
sendMessage(Message.obtain(null, REGISTER_CLIENT));
}
private void messageUnregister() {
sendMessage(Message.obtain(null, UNREGISTER_CLIENT));
}
public void messageGetStatus() {
sendMessage(Message.obtain(null, GET_STATUS));
}
public void messageGetSDKInfo() {
sendMessage(Message.obtain(null, GET_SDK_INFO));
}
public void messageResetPerso() {
sendMessage(Message.obtain(null, RESET_PERSO));
}
// supported orientation: "PORTRAIT", "LANDSCAPE", "NONE"
public void messageUpdateOrientation(String orientation) {
Message message = Message.obtain(null, UPDATE_SCREEN_ORIENTATION);
Bundle bundle = new Bundle();
bundle.putString(UPDATE_SCREEN_ORIENTATION_RESPONSE, orientation);
message.setData(bundle);
sendMessage(message);
}
public void messageUpdateTheme(String themeJson) {
Message message = Message.obtain(null, UPDATE_THEME);
Bundle bundle = new Bundle();
bundle.putString(UPDATE_THEME_RESPONSE, themeJson);
message.setData(bundle);
sendMessage(message);
}
public void messageUpdateNfcReaderPlatformSound(boolean enabled) {
Message message = Message.obtain(null, UPDATE_NFC_READER_PLATFORM_SOUNDS);
Bundle bundle = new Bundle();
bundle.putBoolean(UPDATE_NFC_READER_PLATFORM_SOUNDS_RESPONSE, enabled);
message.setData(bundle);
sendMessage(message);
}
public void messageRequestTerminalRecovery() {
sendMessage(Message.obtain(null, TERMINAL_RECOVERY));
}
public void messageRequestPreflight() {
sendMessage(Message.obtain(null, ATTESTATION_PREFLIGHT));
}
private void sendMessage(Message message) {
try {
message.replyTo = responseMessenger;
requestMessenger.send(message);
} catch (RemoteException remoteException) {
Log.e(TAG, "Failed to send message", remoteException);
} catch (NullPointerException nullPointerException) {
// was the service successfully bound?
Log.e(TAG, "TerminalHelperServiceClient not properly initialized");
}
}
}
public class TerminalHelperServiceHandler extends Handler {
private final static String TAG = TerminalHelperServiceHandler.class.toString();
public TerminalHelperServiceHandler() {
super(Looper.getMainLooper());
}
@Override
public void handleMessage(Message msg) {
Log.i(TAG, "IncomingHandler.handleMessage(" + msg.toString() + ")");
// Important, always filter for the right action
if (msg.what == GET_STATUS) {
int statusCode = msg.arg1;
if (STATUS_TERMINAL_OPERATIONAL == statusCode) {
boolean isUpdateComingSoon = msg.getData().getBoolean(UPDATE_REMINDER_EXTRA, false);
// if isUpdateComingSoon is true, you can notify the user in your app that there soon will be a mandatory update
// Handle, e.g. start a payment
} else if (STATUS_TERMINAL_NOT_OPERATIONAL == statusCode) {
// If STATUS_TERMINAL_NOT_OPERATIONAL is returned, you will also receive a reason
int reason = msg.getData().getInt(TERMINAL_STATUS_ERROR_MESSAGE_NAME, -1);
if (reason == TERMINAL_LOW_BATTERY) {
// e.g. instruct the user to charge their phone
}
// ... depending on your need, handle more reasons
// see codes and possible reactions in the TerminalErrorCodes below
} else if (STATUS_PERSONALIZATION_NOT_DONE_YET == statusCode) {
// Handle, e.g. start a personalization
} else if (STATUS_PERSONALIZATION_IN_PROGRESS == statusCode) {
// Handle, e.g. if you want to abort the ongoing personalisation, perform a reset
} else if (STATUS_APP_IS_OUTDATED == statusCode) {
// Handle, e.g. notify user that phonepos requires an update
}
} else if (msg.what == RESET_PERSO) {
boolean isSuccess = msg.getData().getBoolean(GET_SDK_INFO_RESPONSE, false);
// Handle isSuccess
} else if (msg.what == GET_SDK_INFO) {
String sdkInfoJson = msg.getData().getString(GET_SDK_INFO_RESPONSE);
// Handle sdkInfoJson
} else if (msg.what == UPDATE_SCREEN_ORIENTATION) {
boolean isSuccess = msg.getData().getBoolean(UPDATE_SCREEN_ORIENTATION_RESPONSE, false);
// Handle isSuccess
} else if (msg.what == UPDATE_THEME) {
boolean isSuccess = msg.getData().getBoolean(UPDATE_THEME_RESPONSE, false);
// Handle isSuccess
} else if (msg.what == TERMINAL_RECOVERY) {
// Recovery returns a response equivalent to a GET_STATUS, representing the state after recovery
// You can therefore handle GET_STATUS and RECOVER_TERMINAL responses in the same way
} else if (msg.what == UPDATE_OPTIONS) {
boolean isSuccess = msg.getData().getBoolean(OPTIONS_STATUS_EXTRA, false);
String updatedOptionsJson = msg.getData().getString(OPTIONS_EXTRA);
// Handle isSuccess and updatedOptionsJson
} else if (msg.what == UPDATE_NFC_READER_PLATFORM_SOUNDS) {
boolean isSuccess = msg.getData().getBoolean(UPDATE_NFC_READER_PLATFORM_SOUNDS_RESPONSE, false);
// Handle isSuccess
}
}
}
To bind to the TerminalHelperService you need to provide a package name to the intent.
If you implement the SDK this will be your own application id.
If you implement the APK this will be the application id of the APK that will be provided to you.
Please make sure to filter for msg.what to always interpret responses correctly.
Messages should be processed according to their action.
All TerminalHelperService methods are designed to handle common errors gracefully.
Fatal errors will lead to a mostly empty response with msg.what set to the requested action and msg.arg2 holding an error code.
Please report to our support if this occurs.
Communication Constants
The constants used in the example code are listed below:
- Java
public interface TerminalHelperServiceCommunication {
interface ACTIONS {
int REGISTER_CLIENT = 0;
int UNREGISTER_CLIENT = 1;
int GET_STATUS = 3;
int RESET_PERSO = 4;
int GET_SDK_INFO = 10;
int UPDATE_SCREEN_ORIENTATION = 11;
int UPDATE_THEME = 12;
int ATTESTATION_PREFLIGHT = 15;
int UPDATE_NFC_READER_PLATFORM_SOUNDS = 16;
int TERMINAL_RECOVERY = 19;
int UPDATE_OPTIONS = 20;
}
interface TerminalStatusCodes {
// see descriptions in Personalisation API
int STATUS_TERMINAL_OPERATIONAL = 0;
int STATUS_PERSONALIZATION_NOT_DONE_YET = 1;
int STATUS_TERMINAL_NOT_OPERATIONAL = 2;
int STATUS_PERSONALIZATION_IN_PROGRESS = 3;
int STATUS_APP_IS_OUTDATED = 102;
}
interface ExtraNames {
String OPTIONS_EXTRA = "options";
String GET_SDK_INFO_RESPONSE = "sdk_info";
String UPDATE_SCREEN_ORIENTATION_RESPONSE = "screen_orientation";
String UPDATE_THEME_RESPONSE = "theme";
String OPTIONS_STATUS_EXTRA = "options_update_status";
String UPDATE_REMINDER_EXTRA = "update_reminder";
String UPDATE_NFC_READER_PLATFORM_SOUNDS_RESPONSE = "nfc_reader_platform_sounds_enabled";
String TERMINAL_STATUS_ERROR_MESSAGE_NAME = "terminal_status_error_message";
}
interface TerminalErrorCodes {
// see descriptions in Personalisation API
int TERMINAL_LOW_BATTERY = 2310;
// These additional error codes are available with PhonePOS 4.X
int INSECURE_KEYSTORE = 2316;
int PERSONALISATION_RECOVERY_REQUIRED_SECURITY = 2318;
int PERSONALISATION_RECOVERY_REQUIRED_CONFIGURATION = 2319;
}
}