Browse Source

Bug fix

hbw_build
Wong Joon Hui 2 years ago
parent
commit
0a7e1c2db4
  1. 1
      .idea/gradle.xml
  2. 1
      .idea/misc.xml
  3. 10
      app/build.gradle
  4. 13
      app/src/main/assets/CHANGELOG.md
  5. 1
      app/src/main/assets/CST_1640011627_KIOSK002.env
  6. 18
      app/src/main/assets/CST_1640011627_KIOSK002_hwb_build.env
  7. 19
      app/src/main/assets/CST_1640011627_KIOSK002_hwb_build.json
  8. 19
      app/src/main/assets/SampleConfig.json
  9. 15
      app/src/main/java/com/cst/im30/MainApplication.java
  10. 72
      app/src/main/java/com/cst/im30/activity/MainActivity.java
  11. 7
      app/src/main/java/com/cst/im30/activity/SettingActivity.java
  12. 11
      app/src/main/java/com/cst/im30/api/RetrofitAPICollection.java
  13. 3
      app/src/main/java/com/cst/im30/common/AppConfig.java
  14. 85
      app/src/main/java/com/cst/im30/utility/DebugPassword.java
  15. 18
      app/src/main/java/com/cst/im30/utility/DeviceUtils.java
  16. 13
      app/src/main/java/com/cst/im30/utility/JsonUtils.java
  17. 3
      app/src/main/java/com/cst/im30/utility/Logger.java
  18. 288
      app/src/main/java/com/cst/im30/utility/SetupUtils.java
  19. 2
      app/src/main/res/layout/activity_main.xml
  20. 2
      app/src/main/res/values/strings.xml
  21. 2
      gradle.properties

1
.idea/gradle.xml

@ -13,7 +13,6 @@
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>

1
.idea/misc.xml

@ -33,6 +33,7 @@
<entry key="..\:/StudioProjects/kiosk-im30/app/src/main/res/layout/pop_up_message_no_option.xml" value="0.3546875" />
<entry key="..\:/StudioProjects/kiosk-im30/app/src/main/res/layout/pop_up_message_option.xml" value="0.3546875" />
<entry key="..\:/StudioProjects/kiosk-im30/app/src/main/res/layout/pop_up_notification.xml" value="0.3546875" />
<entry key="..\:/StudioProjects/kiosk-im30/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.3295" />
<entry key="..\:/StudioProjects/kiosk-im30/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.37083333333333335" />
</map>
</option>

10
app/build.gradle

@ -7,14 +7,18 @@ android {
defaultConfig {
applicationId "com.cst.im30"
minSdkVersion 22
minSdkVersion 25
//noinspection ExpiredTargetSdkVersion
targetSdkVersion 28
versionCode 2
versionName "1.0.1"
versionCode 4
versionName "1.0.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// Terminal Config
buildConfigField("String", "CONFIG_URL", CONFIG_URL)
buildConfigField("String", "AUTH_KEY", AUTH_KEY)
// Bugfender
buildConfigField("String", "BUGFENDER_TOKEN", BUGFENDER_TOKEN)
}

13
app/src/main/assets/CHANGELOG.md

@ -0,0 +1,13 @@
V4
- Change from file to fetch config via web
- Add fixed url/token to gradle.properties
- Added DebugCode to access config
V3
- Bug fix for Echo Socket
V2
- Extract environment from gradle.properties to external file at sdcard/CST/<filename>.env
V1
- Initial

1
app/src/main/assets/CST_1640011627_KIOSK002.env

@ -1 +0,0 @@
IyBFY2hvIFNvY2tldApTRVJWRVJfVVJMPSJodHRwOi8vaGVsbG8td29ybGQtcmV3YXJkcy1ib29raW5nLWFwaS1idWlsZC50ZXN0cGlnZW9uLm5ldCIKQ0xJRU5UX0lEPSIzIgpDTElFTlRfU0VDUkVUPSJ3NVJKN2JNRjZOSDM5YURWeUpKVjBNMWdPZzBuWlFjTzJhdFZjWHlGIgpFQ0hPX1NFUlZFUl9VUkw9Imh0dHA6Ly9oZWxsby13b3JsZC1yZXdhcmRzLWJvb2tpbmctYXBpLWJ1aWxkLnRlc3RwaWdlb24ubmV0IgpFQ0hPX1NFUlZFUl9QT1JUPSI2MDAxIgpLSU9TS19DT0RFPSJLSU9TSzAwMiIKU09DS0VUX1BSRUZJWD0iSGVsbG9Cb29raW5nQXBpXyIKUEFZTUVOVF9DSEFOTkVMX0lEPSJwYXltZW50IyIKUEFZTUVOVF9FVkVOVF9UWVBFPSIubWFrZXBheW1lbnQiCklDX0NIQU5ORUxfSUQ9InZlcmlmaWNhdGlvbiMiCklDX0VWRU5UX1RZUEU9Ii5ndWVzdHZlcmlmaWNhdGlvbiIKCiMgQVdTCkFXU19BQ0NFU1NfS0VZPSJBS0lBWEhXRVBQUFNQT01TTEw1UiIKQVdTX1NFQ1JFVF9LRVk9Ik5oTzdJR2h3Y1RkV080MWdTMEtURUtnaVo3bWlrbVo0SGRNRzFXMlIiCgpMT0dfR1JPVVBfTkFNRT0iaG90ZWwta2lvc2si

18
app/src/main/assets/CST_1640011627_KIOSK002_hwb_build.env

@ -1,18 +0,0 @@
# Echo Socket
SERVER_URL="http://hello-world-rewards-booking-api-build.testpigeon.net"
CLIENT_ID="3"
CLIENT_SECRET="w5RJ7bMF6NH39aDVyJJV0M1gOg0nZQcO2atVcXyF"
ECHO_SERVER_URL="http://hello-world-rewards-booking-api-build.testpigeon.net"
ECHO_SERVER_PORT="6001"
KIOSK_CODE="KIOSK002"
SOCKET_PREFIX="HelloBookingApi_"
PAYMENT_CHANNEL_ID="payment#"
PAYMENT_EVENT_TYPE=".makepayment"
IC_CHANNEL_ID="verification#"
IC_EVENT_TYPE=".guestverification"
# AWS
AWS_ACCESS_KEY="AKIAXHWEPPPSPOMSLL5R"
AWS_SECRET_KEY="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"
LOG_GROUP_NAME="hotel-kiosk"

19
app/src/main/assets/CST_1640011627_KIOSK002_hwb_build.json

@ -0,0 +1,19 @@
{
"data" : {
"server_url" : "http://hello-world-rewards-booking-api-build.testpigeon.net",
"client_id" : "3",
"client_secret" : "w5RJ7bMF6NH39aDVyJJV0M1gOg0nZQcO2atVcXyF",
"echo_server_url" : "http://hello-world-rewards-booking-api-build.testpigeon.net",
"echo_server_port" : "6001",
"kiosk_code" : "KIOSK002",
"socket_prefix" : "HelloBookingApi_",
"payment_channel_id" : "payment#",
"payment_event_type" : ".makepayment",
"ic_channel_id" : "verification#",
"ic_event_type" : ".guestverification",
"aws_access_key" : "AKIAXHWEPPPSPOMSLL5R",
"aws_secret_key" : "NhO7IGhwcTdWO41gS0KTEKgiZ7mikmZ4HdMG1W2R",
"aws_log_group" : "hotel-kiosk",
"debug_code" : "1234"
}
}

19
app/src/main/assets/SampleConfig.json

@ -0,0 +1,19 @@
{
"data" : {
"server_url" : "http://hello-world-rewards-booking-api-build.testpigeon.net",
"client_id" : "3",
"client_secret" : "w5RJ7bMF6NH39aDVyJJV0M1gOg0nZQcO2atVcXyF",
"echo_server_url" : "http://hello-world-rewards-booking-api-build.testpigeon.net",
"echo_server_port" : "6001",
"kiosk_code" : "KIOSK002",
"socket_prefix" : "HelloBookingApi_",
"payment_channel_id" : "payment#",
"payment_event_type" : ".makepayment",
"ic_channel_id" : "verification#",
"ic_event_type" : ".guestverification",
"aws_access_key" : "AKIAXHWEPPPSPOMSLL5R",
"aws_secret_key" : "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",
"aws_log_group" : "hotel-kiosk",
"debug_code" : "1234"
}
}

15
app/src/main/java/com/cst/im30/MainApplication.java

@ -34,6 +34,7 @@ import com.cst.im30.model.SettlementResponse;
import com.cst.im30.model.VoidRequest;
import com.cst.im30.model.VoidResponse;
import com.cst.im30.utility.CloudWatchLogger;
import com.cst.im30.utility.DeviceUtils;
import com.cst.im30.utility.Logger;
import com.cst.im30.utility.SetupUtils;
@ -110,7 +111,7 @@ public class MainApplication extends Application {
super.onCreate();
instance = this;
if (new SetupUtils().readEnvFile()) {
if (new SetupUtils().setupConfig(getApplicationContext())) {
setupReady = true;
}
@ -126,18 +127,22 @@ public class MainApplication extends Application {
}
public void log(String logStreamName, String message) {
if (config == null) return;
this.cloudWatchLogger.log(config.getLogGroupName(), logStreamName, message);
}
public void logICScan(String message) {
if (config == null) return;
this.cloudWatchLogger.log(config.getLogGroupName(), "IcScanLog", message);
}
public void logPayment(String message) {
if (config == null) return;
this.cloudWatchLogger.log(config.getLogGroupName(), "Payment", message);
}
public void logError(String message) {
if (config == null) return;
this.cloudWatchLogger.log(config.getLogGroupName(), "Error", message);
}
@ -145,12 +150,8 @@ public class MainApplication extends Application {
public void initializeBugfender() {
Bugfender.init(this, BuildConfig.BUGFENDER_TOKEN, BuildConfig.DEBUG);
String serialNumber;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
serialNumber = android.os.Build.getSerial();
} else {
serialNumber = android.os.Build.SERIAL;
}
String serialNumber = DeviceUtils.getSerialNumber();
Bugfender.setDeviceString("Stage", BuildConfig.FLAVOR);
Bugfender.setDeviceString("S/N", serialNumber);
Bugfender.setDeviceString("Kiosk Code", config.getKioskCode());

72
app/src/main/java/com/cst/im30/activity/MainActivity.java

@ -13,11 +13,12 @@ import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@ -26,11 +27,9 @@ import android.widget.Toast;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import com.cst.im30.BuildConfig;
import com.cst.im30.EchoClient;
import com.cst.im30.MainApplication;
import com.cst.im30.PaymentHandler;
@ -43,8 +42,11 @@ import com.cst.im30.service.PaymentService;
import com.cst.im30.service.UploadTransactionPreAuthPaymentService;
import com.cst.im30.service.UploadTransactionSaleCompletionPaymentService;
import com.cst.im30.service.UploadTransactionSalePaymentService;
import com.cst.im30.utility.DebugPassword;
import com.cst.im30.utility.DeviceUtils;
import com.cst.im30.utility.Logger;
import com.cst.im30.utility.PaymentUtils;
import com.cst.im30.utility.SetupUtils;
import com.daimajia.slider.library.SliderLayout;
import com.daimajia.slider.library.SliderTypes.BaseSliderView;
import com.daimajia.slider.library.SliderTypes.TextSliderView;
@ -65,13 +67,14 @@ public class MainActivity extends AppCompatActivity implements CallableInterface
private AnimationDrawable animationDrawable;
// Echo Server Connection
private LinearLayout echoStatusLayout;
private FrameLayout paymentEchoStatusLayout, icEchoStatusLayout;
private ImageView paymentEchoStatusImageView, icEchoStatusImageView;
private TextView paymentEchoStatusTextView, icEchoStatusTextView;
private TextView testTitle; //todo until new screen
private SliderLayout sliderLayout;
private PaymentHandler paymentHandler;
private DebugPassword debugPassword;
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -79,11 +82,13 @@ public class MainActivity extends AppCompatActivity implements CallableInterface
checkPermissions();
sliderLayout = findViewById(R.id.slider_layout_ma);
sliderLayout.setVisibility(View.INVISIBLE); //todo until get good image
paymentEchoStatusLayout = findViewById(R.id.paymentEchoStatusLayout);
icEchoStatusLayout = findViewById(R.id.icEchoStatusLayout);
icEchoStatusLayout = findViewById(R.id.icEchoStatusLayout);
echoStatusLayout = findViewById(R.id.echoStatusLayout);
paymentEchoStatusImageView = findViewById(R.id.paymentEchoStatusImageView);
icEchoStatusImageView = findViewById(R.id.icEchoStatusImageView);
@ -96,6 +101,18 @@ public class MainActivity extends AppCompatActivity implements CallableInterface
//setupTextSlider();
paymentHandler = new PaymentHandler(this);
if (MainApplication.config != null && MainApplication.config.getDebugCode() != null && !MainApplication.config.getDebugCode().isEmpty()) {
try {
debugPassword = new DebugPassword(MainApplication.config.getDebugCode());
paymentEchoStatusLayout.setOnClickListener(v -> debugPress(DebugPassword.INPUT_0));
icEchoStatusLayout.setOnClickListener(v -> debugPress(DebugPassword.INPUT_1));
testTitle.setOnClickListener(v -> debugPress(DebugPassword.INPUT_RESET));
} catch (Exception e) {
Logger.logD("Debug Code: " + MainApplication.config.getDebugCode() + " is invalid! Debug Disabled.");
}
}
}
@Override
@ -247,7 +264,10 @@ public class MainActivity extends AppCompatActivity implements CallableInterface
private void setupNotReady() {
//echoStatusLayout.setVisibility(View.GONE);
String text = "S/N: " + DeviceUtils.getSerialNumber() + " has no configuration yet.";
TextView setupText = findViewById(R.id.setupText);
setupText.setText(text);
setupText.setVisibility(View.VISIBLE);
}
@ -617,7 +637,11 @@ public class MainActivity extends AppCompatActivity implements CallableInterface
private void onConnectSuccessPayment(Object[] args) {
try {
String channelID = MainApplication.config.getSocketPrefix() + MainApplication.config.getPaymentChannelId() + MainApplication.config.getKioskCode();
String channelId = MainApplication.config.getPaymentChannelId();
if (!channelId.endsWith("#")) {
channelId += "#";
}
String channelID = MainApplication.config.getSocketPrefix() + channelId + MainApplication.config.getKioskCode();
String eventType = MainApplication.config.getPaymentEventType();
EchoCallback echoCallback = this::receiveMessagePayment;
MainApplication.paymentClient.channel(channelID).listen(eventType, echoCallback);
@ -628,7 +652,7 @@ public class MainActivity extends AppCompatActivity implements CallableInterface
updatePaymentOnline();
} catch (Exception ex) {
Logger.logE("Error on contact: " + ex.getMessage());
Logger.logE("Error on contact: " + Log.getStackTraceString(ex));
}
}
@ -650,7 +674,11 @@ public class MainActivity extends AppCompatActivity implements CallableInterface
private void onConnectSuccessIC(Object[] args) {
try {
String channelID = MainApplication.config.getSocketPrefix() + MainApplication.config.getIcChannelId() + MainApplication.config.getKioskCode();
String channelId = MainApplication.config.getIcChannelId();
if (!channelId.endsWith("#")) {
channelId += "#";
}
String channelID = MainApplication.config.getSocketPrefix() + channelId + MainApplication.config.getKioskCode();
String eventType = MainApplication.config.getIcEventType();
EchoCallback echoCallback = this::receiveMessageIC;
MainApplication.icClient.channel(channelID).listen(eventType, echoCallback);
@ -661,7 +689,7 @@ public class MainActivity extends AppCompatActivity implements CallableInterface
updateICOnline();
} catch (Exception ex) {
Logger.logE("Error on contact: " + ex.getMessage());
Logger.logE("Error on contact: " + Log.getStackTraceString(ex));
}
}
@ -699,7 +727,7 @@ public class MainActivity extends AppCompatActivity implements CallableInterface
runOnUiThread(() -> {
paymentEchoStatusImageView.setImageResource(R.drawable.echo_payment_online);
paymentEchoStatusTextView.setText(R.string.online_caps);
paymentEchoStatusTextView.setTextColor(getResources().getColor(R.color.online));
paymentEchoStatusTextView.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.online));
});
}
@ -707,7 +735,7 @@ public class MainActivity extends AppCompatActivity implements CallableInterface
runOnUiThread(() -> {
paymentEchoStatusImageView.setImageResource(R.drawable.echo_payment_offline);
paymentEchoStatusTextView.setText(R.string.offline_caps);
paymentEchoStatusTextView.setTextColor(getResources().getColor(R.color.offline));
paymentEchoStatusTextView.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.offline));
});
}
@ -715,7 +743,7 @@ public class MainActivity extends AppCompatActivity implements CallableInterface
runOnUiThread(() -> {
icEchoStatusImageView.setImageResource(R.drawable.echo_ic_online);
icEchoStatusTextView.setText(R.string.online_caps);
icEchoStatusTextView.setTextColor(getResources().getColor(R.color.online));
icEchoStatusTextView.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.online));
});
}
@ -723,7 +751,7 @@ public class MainActivity extends AppCompatActivity implements CallableInterface
runOnUiThread(() -> {
icEchoStatusImageView.setImageResource(R.drawable.echo_ic_offline);
icEchoStatusTextView.setText(R.string.offline_caps);
icEchoStatusTextView.setTextColor(getResources().getColor(R.color.offline));
icEchoStatusTextView.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.offline));
});
}
@ -770,7 +798,6 @@ public class MainActivity extends AppCompatActivity implements CallableInterface
Logger.logI("Back Button Disabled!");
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void checkPermissions() {
List<String> permsArray = new ArrayList<>();
@ -829,4 +856,19 @@ public class MainActivity extends AppCompatActivity implements CallableInterface
mHandler.postDelayed(mRunnable, 3000);
}
private void debugPress(String input) {
debugPassword.input(input);
if (debugPassword.isUnlock()) {
runDebug();
}
}
private void runDebug() {
Logger.logD("Debug Code Entered!");
SetupUtils.showConfigDialog(MainActivity.this);
debugPassword.reset();
}
}

7
app/src/main/java/com/cst/im30/activity/SettingActivity.java

@ -168,7 +168,12 @@ public class SettingActivity extends AppCompatActivity {
runOnUiThread(() -> Toast.makeText(SettingActivity.this, "Connected!", Toast.LENGTH_SHORT).show());
echoClient.channel(MainApplication.config.getPaymentChannelId() + MainApplication.config.getKioskCode())
String channelId = MainApplication.config.getPaymentChannelId();
if (!channelId.endsWith("#")) {
channelId += "#";
}
echoClient.channel(channelId + MainApplication.config.getKioskCode())
.listen(MainApplication.config.getPaymentEventType(), this::receiveMessage);
}

11
app/src/main/java/com/cst/im30/api/RetrofitAPICollection.java

@ -46,4 +46,15 @@ public interface RetrofitAPICollection {
@Body String body
);
@Headers({
"Accept: application/json",
"Content-Type: application/json"
})
@GET("api/v1/terminal-im30/{serialNumber}")
Call<String> getConfig(
@Header("x-api-id") String authId,
@Header("x-api-key") String authKey,
@Path("serialNumber") String serialNumber
);
}

3
app/src/main/java/com/cst/im30/common/AppConfig.java

@ -23,6 +23,7 @@ public class AppConfig implements Serializable {
icEventType,
awsAccessKey,
awsSecretKey,
logGroupName;
logGroupName,
debugCode;
}

85
app/src/main/java/com/cst/im30/utility/DebugPassword.java

@ -0,0 +1,85 @@
package com.cst.im30.utility;
import java.security.InvalidParameterException;
import java.util.Objects;
public class DebugPassword {
private final String password;
private final String convertedPassword;
private String entered;
private boolean unlock = false;
public DebugPassword(String password) {
if (password == null || password.isEmpty()) {
throw new InvalidParameterException("Password is blank or null. Debug Menu disabled!");
}
if (!validatePassword(password)) {
throw new InvalidParameterException("Invalid Password: " + password + "! Only digits 1-9 accepted, e.g. \"1234\", \"123\"");
}
this.password = password;
this.convertedPassword = convertPassword(password);
this.entered = "";
Logger.logD("Debug Password Enabled.");
}
// Password must be a string with digits only from 1-9 (e.g. "1234", "123"). Any length accepted.
private boolean validatePassword(String password) {
if (password == null || password.isEmpty()) {
return false;
}
String regex = "[1-9]+";
return password.matches(regex);
}
private String convertPassword(String password) {
if (password == null || password.isEmpty()) { return null; }
StringBuilder tmp = new StringBuilder();
boolean left = true;
for (char ch : password.toCharArray()) {
int v = Integer.parseInt(Character.toString(ch));
for (int i = 0; i < v; i++) {
tmp.append((left ? INPUT_0 : INPUT_1));
}
left = !left;
}
return tmp.toString();
}
public static final String INPUT_0 = "0";
public static final String INPUT_1 = "1";
public static final String INPUT_RESET = "-1";
public void input(String input) {
if (Objects.equals(input, INPUT_0)) {
entered += INPUT_0;
} else if (Objects.equals(input, INPUT_1)) {
entered += INPUT_1;
} else {
entered = "";
}
// Compare
String filteredPassword = convertedPassword.substring(0, entered.length());
if (!entered.equalsIgnoreCase(filteredPassword)) {
entered = "";
}
if (entered.equalsIgnoreCase(convertedPassword)) {
entered = "";
unlock = true;
}
}
public boolean isUnlock() {
return unlock;
}
public void reset() {
entered = "";
unlock = false;
}
}

18
app/src/main/java/com/cst/im30/utility/DeviceUtils.java

@ -0,0 +1,18 @@
package com.cst.im30.utility;
import android.annotation.SuppressLint;
import android.os.Build;
public class DeviceUtils {
@SuppressLint("MissingPermission")
public static String getSerialNumber() {
String serialNumber;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
serialNumber = android.os.Build.getSerial();
} else {
serialNumber = android.os.Build.SERIAL;
}
return serialNumber;
}
}

13
app/src/main/java/com/cst/im30/utility/JsonUtils.java

@ -2,10 +2,23 @@ package com.cst.im30.utility;
import android.text.TextUtils;
import org.json.JSONException;
import org.json.JSONObject;
public class JsonUtils {
public static JSONObject convertJSONStringToJSONObject(String string) {
try {
if (string == null || string.isEmpty()) {
return null;
}
return new JSONObject(string);
} catch (JSONException e) {
e.printStackTrace();
return null;
}
}
public static String extractJsonString(JSONObject responseJSON, String name) {
try {
if (responseJSON.has(name)) {

3
app/src/main/java/com/cst/im30/utility/Logger.java

@ -53,7 +53,6 @@ public class Logger {
Log.e(TAG, title + " - " + message);
Bugfender.e("ERROR", title + " - " + message);
MainApplication.getInstance().logError(title + " - " + message);
Bugfender.sendIssue(title, message);
}
public static void logW(String message) {
@ -114,7 +113,7 @@ public class Logger {
MainApplication.getInstance().logICScan(data + " - " + extra);
} catch (IOException e) {
Log.e("Exception", "File write failed: " + e.toString());
Log.e("Exception", "File write failed: " + e);
}
}

288
app/src/main/java/com/cst/im30/utility/SetupUtils.java

@ -1,198 +1,208 @@
package com.cst.im30.utility;
import android.app.AlertDialog;
import android.content.Context;
import android.os.Environment;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.NonNull;
import com.cst.im30.BuildConfig;
import com.cst.im30.MainApplication;
import com.cst.im30.R;
import com.cst.im30.activity.MainActivity;
import com.cst.im30.api.RetrofitAPICollection;
import com.cst.im30.api.RetrofitClient;
import com.cst.im30.common.AppConfig;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class SetupUtils {
import lombok.SneakyThrows;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public boolean readEnvFile() {
if (isExternalStorageReadable()) {
File configFile = getFile();
if (configFile == null) { return false; }
public class SetupUtils {
long numberOfLines = countLines(configFile);
private final boolean LOCAL_CONFIG = false;
if (numberOfLines == 1) {
decodeAndParseFile(configFile);
} else {
parseFile(configFile);
}
private String envJsonString = null;
return true;
} else {
Logger.logD("External Storage Not Readable");
return false;
public static String getSampleEnv(Context context) {
try {
InputStream inputStream = context.getAssets().open("SampleConfig.json");
return new BufferedReader(
new InputStreamReader(inputStream, StandardCharsets.UTF_8))
.lines()
.collect(Collectors.joining("\n"));
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
private File getFile() {
File appDirectory = new File(Environment.getExternalStorageDirectory() + "/CST");
File[] files = appDirectory.listFiles();
if (files == null || files.length == 0) {
Logger.logD("ENV File Not Found! (Folder Empty)");
return null;
@SneakyThrows
public boolean setupConfig(Context context) {
if (LOCAL_CONFIG) {
envJsonString = SetupUtils.getSampleEnv(context);
} else {
Thread t = new Thread(this::getConfig);
t.start();
t.join();
}
for (File file : files) {
String fileName = file.getName().toLowerCase();
if (fileName.endsWith(".env")) {
Logger.logD("Found: " + file.getAbsolutePath());
return file;
}
if (envJsonString == null || envJsonString.isEmpty()) {
Logger.logE("Unsuccessful in obtaining Configuration");
return false;
}
Logger.logD("ENV File Not Found! (Got Files but no ENV)");
return null;
JSONObject json = JsonUtils.convertJSONStringToJSONObject(envJsonString);
if (json == null) {
Logger.logD("JSON is null");
return false;
}
private boolean decodeAndParseFile(File file) {
if (json.has("data")) {
try {
FileInputStream is = new FileInputStream(file);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String config;
String base64Line = reader.readLine();
byte[] decodedString = Base64.decode(base64Line.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT);
config = new String(decodedString);
Map<String, String> map = parseDecodedFile(config);
readConfiguration(map);
return true;
} catch (IOException e) {
json = json.getJSONObject("data");
} catch (JSONException e) {
e.printStackTrace();
}
return false;
}
private long countLines(File file) {
long lines = 0;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.trim().isEmpty()) {
continue;
}
lines++;
}
} catch (IOException e) {
if (json.has("current")) {
try {
json = json.getJSONObject("current");
} catch (JSONException e) {
e.printStackTrace();
return false;
}
return lines;
}
private Map<String, String> parseDecodedFile(String config) {
String[] lines = config.split(Pattern.quote("\n"));
LinkedHashMap<String, String> infoMap = new LinkedHashMap<>();
for (String line : lines) {
if (line.length() > 0 && !line.startsWith("#")) {
String[] value = line.split("=");
String k, v;
k = value[0];
if (value.length == 1) {
v = "";
} else {
// Filter In-Line # Comments
String vStr = value[1];
int commentIndex = vStr.indexOf("#");
if (commentIndex != -1) {
vStr = vStr.substring(0, commentIndex);
}
v = vStr;
Iterator<String> keys = json.keys();
while (keys.hasNext()) {
String key = keys.next();
if (json.has(key)) {
String value = JsonUtils.extractJsonString(json, key);
String k = key.trim().replaceAll("^\"|\"$", "");
String v = value.trim().replaceAll("^\"|\"$", "");
infoMap.put(k, v);
}
String key = k.trim().replaceAll("^\"|\"$", "");
String valueMap = v.trim().replaceAll("^\"|\"$", "");
infoMap.put(key, valueMap);
}
if (infoMap.size() > 0) {
readConfiguration(infoMap);
} else {
return false;
}
return infoMap;
return true;
}
private boolean parseFile(File file) {
try {
FileInputStream is = new FileInputStream(file);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
public void getConfig() {
Logger.logD("START");
String serialNumber = DeviceUtils.getSerialNumber();
//String serialNumber = "1640011627";
RetrofitAPICollection service = RetrofitClient.getRetrofitClient(BuildConfig.CONFIG_URL).create(RetrofitAPICollection.class);
Call<String> call = service.getConfig(serialNumber, BuildConfig.AUTH_KEY, serialNumber);
LinkedHashMap<String, String> infoMap = new LinkedHashMap<>();
String line, k, v;
String[] value;
while ((line = reader.readLine()) != null) {
if (line.length() > 0 && !line.startsWith("#")) {
value = line.split("=");
k = value[0];
if (value.length == 1) {
v = "";
} else {
// Allows # Comments in the line
String vStr = value[1];
int commentIndex = vStr.indexOf("#");
if (commentIndex != -1) {
vStr = vStr.substring(0, commentIndex);
Logger.logAPI(serialNumber);
/*call.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) {
onResponseGetConfig(response);
}
v = vStr;
@Override
public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) {
assert true;
//onFailureGetConfig(t);
}
String key = k.trim().replaceAll("^\"|\"$", "");
String valueMap = v.trim().replaceAll("^\"|\"$", "");
infoMap.put(key, valueMap);
});*/
try {
Response<String> response = call.execute();
onResponseGetConfig(response);
} catch (IOException e) {
e.printStackTrace();
}
}
if (infoMap.isEmpty()) { return false; }
readConfiguration(infoMap);
private void onResponseGetConfig(Response<String> response) {
try {
if (!response.isSuccessful()) {
JSONObject responseJSON;
if (response.errorBody() != null) {
responseJSON = new JSONObject(response.errorBody().string());
Logger.logAPI(responseJSON.toString());
}
} else {
JSONObject responseJSON = new JSONObject(response.body());
Logger.logAPI(responseJSON.toString());
} catch (IOException e) {
e.printStackTrace();
envJsonString = responseJSON.toString();
}
} catch (Exception e) {
Logger.logE(Log.getStackTraceString(e));
}
return true;
}
private void readConfiguration(Map<String, String> map) {
MainApplication.config = AppConfig.builder()
.serverURL(map.get("SERVER_URL"))
.clientId(map.get("CLIENT_ID"))
.clientSecret(map.get("CLIENT_SECRET"))
.echoServerUrl(map.get("ECHO_SERVER_URL"))
.echoServerPort(map.get("ECHO_SERVER_PORT"))
.kioskCode(map.get("KIOSK_CODE"))
.socketPrefix(map.get("SOCKET_PREFIX"))
.paymentChannelId(map.get("PAYMENT_CHANNEL_ID"))
.paymentEventType(map.get("PAYMENT_EVENT_TYPE"))
.icChannelId(map.get("IC_CHANNEL_ID"))
.icEventType(map.get("IC_EVENT_TYPE"))
.awsAccessKey(map.get("AWS_ACCESS_KEY"))
.awsSecretKey(map.get("AWS_SECRET_KEY"))
.logGroupName(map.get("LOG_GROUP_NAME"))
.serverURL(map.get("server_url"))
.clientId(map.get("client_id"))
.clientSecret(map.get("client_secret"))
.echoServerUrl(map.get("echo_server_url"))
.echoServerPort(map.get("echo_server_port"))
.kioskCode(map.get("kiosk_code"))
.socketPrefix(map.get("socket_prefix"))
.paymentChannelId(map.get("payment_channel_id"))
.paymentEventType(map.get("payment_event_type"))
.icChannelId(map.get("ic_channel_id"))
.icEventType(map.get("ic_event_type"))
.awsAccessKey(map.get("aws_access_key"))
.awsSecretKey(map.get("aws_secret_key"))
.logGroupName(map.get("aws_log_group"))
.debugCode(map.get("debug_code"))
.build();
}
/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
public static void showConfigDialog(final Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
String message = "Kiosk Code:" + MainApplication.config.getKioskCode() + "\n\n" +
"Host URL:" + MainApplication.config.getServerURL() + "\n\n" +
"Client ID:" + MainApplication.config.getClientId() + "\n" +
"Client Secret:" + MainApplication.config.getClientSecret() + "\n\n" +
"Echo URL:" + MainApplication.config.getEchoServerUrl() + "\n" +
"Echo Port:" + MainApplication.config.getEchoServerPort() + "\n\n" +
"Socket Prefix:" + MainApplication.config.getSocketPrefix() + "\n" +
"Payment Channel ID:" + MainApplication.config.getPaymentChannelId() + "\n" +
"Payment Event Type:" + MainApplication.config.getPaymentEventType() + "\n" +
"IC Channel ID:" + MainApplication.config.getIcChannelId() + "\n" +
"IC Event Type:" + MainApplication.config.getIcEventType() + "\n\n" +
"AWS Access Key:" + MainApplication.config.getAwsAccessKey() + "\n\n" +
"AWS Secret Key:" + MainApplication.config.getClientSecret() + "\n\n" +
"AWS Log Group Name:" + MainApplication.config.getLogGroupName();
builder.setMessage(message)
.setCancelable(true).setPositiveButton("Dismiss", (dialog, which) -> dialog.dismiss());
AlertDialog alert = builder.create();
alert.setTitle(context.getString(R.string.app_name) + " - " + BuildConfig.VERSION_NAME);
alert.show();
}
}

2
app/src/main/res/layout/activity_main.xml

@ -15,6 +15,7 @@
android:baselineAligned="false">
<FrameLayout
android:id="@+id/paymentEchoStatusLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
@ -45,6 +46,7 @@
</FrameLayout>
<FrameLayout
android:id="@+id/icEchoStatusLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"

2
app/src/main/res/values/strings.xml

@ -1,6 +1,6 @@
<resources>
<string name="app_name">CST - Hello Kiosk</string>
<string name="app_name">CST - IM30 Terminal</string>
<!--for General usage-->
<string name="txt_title">Title</string>

2
gradle.properties

@ -20,6 +20,8 @@ android.enableJetifier=true
# Terminal Specific Config
CONFIG_URL="https://dlrkv6rvha.execute-api.ap-southeast-1.amazonaws.com/production"
AUTH_KEY="G7AdPthrwWp2EoxT5gn6KE6WVgsfjRdtHBhTzSRNKIKmb4jsRMkcS6O5xSdW"
# Bugfender
BUGFENDER_TOKEN="rocgWcZ0aLZH7s1XqK6PpV3WeyBVb0w0"
Loading…
Cancel
Save