06. Custom 개발


Splash Window

HONE Smart Platform에서는 Splash Window 를 직접 관리하지 않으며 Splash Window 를 위한 예제를 샘플에 포함한다. 

Splash Window 를 띄울 때는 onCreate 에서 호출하면 되기 때문에 별다른 주의점은 필요하지 않다.

 

var mSplashManager: SplashManager

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)

    ActionBase.setOnActionFlowEnd(::hideSplash)
    showSplash()
}

fun showSplash() {
mSplashManager = SplashManager()
    mSplashManager.show(findViewById(R.id.base_layout))
}

fun hideSplash() {
    mSplashManager.hide(mDisposable)
}
SplashManager mSplashManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);

    ActionBase.setOnActionFlowEnd(this::hideSplash);
    showSplash();
}

private void showSplash() {
    mSplashManager = new SplashManager();
    mSplashManager.show(findViewById(R.id.base_layout));
}

public void hideSplash() {
   if (mSplashManager != null) {
        mSplashManager.hide(mDisposable);
   }
}

Example (XML)

<layout>
   <android.support.constraint.ConstraintLayout
       xmlns:app="http://schemas.android.com/apk/res-auto"
       xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:tools="http://schemas.android.com/tools"
       android:id="@+id/hone_main_splash"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:background="@drawable/hsp_splash"
       android:clickable="true"
       tools:ignore="KeyboardInaccessibleWidget,Overdraw">

       <ImageView
           android:id="@+id/logo"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_alignParentTop="true"
           android:layout_centerHorizontal="true"
           android:layout_marginTop="50dp"
           android:background="@drawable/logo"
           app:srcCompat="@drawable/logo"
           app:layout_constraintTop_toTopOf="parent"
           app:layout_constraintStart_toStartOf="parent"
           app:layout_constraintEnd_toEndOf="parent"
           />

   </android.support.constraint.ConstraintLayout>
</layout>

원하는 형태로 임의로 띄우면 되며 예제로 들어가는 세부 코드와 다르게 작성해도 무방하다.

이후 Splash 를 닫기 위해 Action 의 종료 시점에 hideSplash 를 등록하면 된다. 


Dialog 커스텀

HONE Smart Platform 내에 사용 중인 Alert / Confirm / Progress 다이얼로그들은 DialogDelegate 를 이용하여 커스텀 할 수 있다. 

AlertDialog

AlertDialog 는 AlertDialogBase 를 extends 하여 개발해야 하며 그에 대한 예제는 아래와 같다. 

Example

class CustomAlertDialog(activity: Activity) : AlertDialogBase(activity) {
    companion object {
       private val mLog = org.slf4j.LoggerFactory.getLogger(CustomAlertDialog::class.java)

        val TYPE_A_DLG = 1
        val TYPE_B_DLG = 2
   }

    override fun show(params: Dialog): AlertDialog? {
        when (params.type) {
           else -> return showDefault(params)
       }
   }

   private fun showDefault(params: Dialog): AlertDialog? {
        mWeakActivity.get()?.let {
            val alertBuilder = AlertDialog.Builder(it)
            params.title?.let { alertBuilder.setTitle(it) }
            params.message?.let { alertBuilder.setMessage(it) }

            alertBuilder.setCancelable(params.cancelable)

           if (params.view != null) {
                alertBuilder.setView(params.view)

                val dlg = alertBuilder.create()
                dlg.show()

               return dlg
           }

            it.runOnUiThread {
                val positiveText = if (TextUtils.isEmpty(params.positiveText))
                    it.getString(R.string.button_ok)
               else
                    params.positiveText

                alertBuilder.setPositiveButton(positiveText) { dialog, _ ->
                    params.listener?.onResult(OnResultListener.TRUE, dialog)

                    dialog.dismiss()

                   if (params.processKill) {
                        processKill()
                   }

                   if (params.finish) {
                        finish()
                   }
               }

                alertBuilder.show()
           }
       }

       return null
   }
}
public class CustomAlertDialog extends AlertDialogBase {
   private static final org.slf4j.Logger mLog = org.slf4j.LoggerFactory.getLogger(CustomAlertDialog.class);

   public static final int TYPE_A_DLG = 1;
   public static final int TYPE_B_DLG = 2;

   public CustomAlertDialog(Activity activity) {
       super(activity);
   }

   @Nullable
   @Override
   public AlertDialog show(@NonNull Dialog params) {
       switch (params.type) {
           default:
               return showDefault(params);
       }
   }

   private AlertDialog showDefault(@NonNull Dialog params) {
       if (mWeakActivity.get() == null) {
            mLog.error("ERROR: mWeakActivity.get() == null");
           return null;
       }

        AlertDialog.Builder alertBuilder = new AlertDialog.Builder(mWeakActivity.get());
       if (!TextUtils.isEmpty(params.title)) {
            alertBuilder.setTitle(params.title);
       }

       if (!TextUtils.isEmpty(params.message)) {
            alertBuilder.setMessage(params.message);
       }

        alertBuilder.setCancelable(params.cancelable);

       if (params.view != null) {
            alertBuilder.setView(params.view);

            AlertDialog dlg = alertBuilder.create();
            dlg.show();

           return dlg;
       }

        mWeakActivity.get().runOnUiThread(() -> {
            String positiveText = TextUtils.isEmpty(params.positiveText) ?
                    mWeakActivity.get().getString(R.string.button_ok) : params.positiveText;

            alertBuilder.setPositiveButton(positiveText, (dialog, which) -> {
               if (params.listener != null) {
                    params.listener.onResult(OnResultListener.TRUE, dialog);
               }

                dialog.dismiss();

               if (params.processKill) {
                    processKill();
               }

               if (params.finish) {
                    finish();
               }
           });

            alertBuilder.show();
       });

       return null;
   }
}

AlertDialog 의 구현이 완료 되었으면 이를 DialogDelegate 에 해당 클래스 정보를 설정해야 하며 이는 MainActivity 의 onCreate 에 super 를 호출하기 전 설정되어야지만 올바르게 작동 된다. 

Example

override fun onCreate(savedInstanceState: Bundle?) {
    DialogDelegate.get().setClassPath(DialogDelegate.DialogType.ALERT,
       "$packageName.dialog.CustomAlertDialog")

   super.onCreate(savedInstanceState)
}
@Override
protected void onCreate(Bundle savedInstanceState) {
    DialogDelegate.get().setClassPath(DialogDelegate.DialogType.ALERT,
        getPackageName() + ".dialog.CustomAlertDialog");

   super.onCreate(savedInstanceState);
}

ConfirmDialog

ConfirmDialog 의 경우 ConfirmDialogBase 를 상속하여 개발해야 하며 그에 대한 예제는 다음과 같다. 

Example

class CustomConfirmDialog(activity: Activity) : ConfirmDialogBase(activity) {
    companion object {
       private val mLog = org.slf4j.LoggerFactory.getLogger(CustomConfirmDialog::class.java)

        val TYPE_A_DLG = 1
        val TYPE_B_DLG = 2
   }
   
    override fun show(params: Dialog): AlertDialog? {
        when (params.type) {
           else -> return showDefault(params)
       }
   }

   private fun showDefault(params: Dialog): AlertDialog? {
        mWeakActivity.get()?.let {
            val alertBuilder = AlertDialog.Builder(it)
            params.title?.let { alertBuilder.setTitle(it) }
            params.message?.let { alertBuilder.setMessage(it) }

            alertBuilder.setCancelable(params.cancelable)

           if (params.view != null) {
                alertBuilder.setView(params.view)

                val dlg = alertBuilder.create()
                dlg.show()

               return dlg
           }

            it.runOnUiThread {
                val positiveText = if (params.positiveText.isNullOrEmpty())
                    it.getString(honemobile.android.core.R.string.common_confirm)
               else
                    params.positiveText

                val negativeText = if (params.negativeText.isNullOrEmpty())
                    it.getString(honemobile.android.core.R.string.common_cancel)
               else
                    params.negativeText

                alertBuilder.setPositiveButton(positiveText) { dialog, _ ->
                    params.listener?.onResult(OnResultListener.TRUE, dialog)
                    dialog.dismiss()
               }

                alertBuilder.setNegativeButton(negativeText) { dialog, _ ->
                    params.listener?.onResult(OnResultListener.FALSE, dialog)

                    dialog.dismiss()
               }

                alertBuilder.setOnCancelListener { dialog ->
                    params.listener?.onResult(OnResultListener.FALSE, dialog)
               }

                alertBuilder.show()
           }
       }

       return null
   }
}
public class CustomConfirmDialog extends ConfirmDialogBase {
   private static final org.slf4j.Logger mLog = org.slf4j.LoggerFactory.getLogger(CustomConfirmDialog.class);

   public static final int TYPE_A_DLG = 1;
   public static final int TYPE_B_DLG = 2;

   public CustomConfirmDialog(Activity activity) {
       super(activity);
   }

   @Override
   public @Nullable
    AlertDialog show(@NonNull Dialog params) {
       switch (params.type) {
           default:
               return showDefault(params);
       }
   }

   private AlertDialog showDefault(@NonNull Dialog params) {
       if (mWeakActivity.get() == null) {
            mLog.error("ERROR: mWeakActivity == null");
           return null;
       }

        AlertDialog.Builder alertBuilder = new AlertDialog.Builder(mWeakActivity.get());
       if (!TextUtils.isEmpty(params.title)) {
            alertBuilder.setTitle(params.title);
       }

       if (!TextUtils.isEmpty(params.message)) {
            alertBuilder.setMessage(params.message);
       }

        alertBuilder.setCancelable(params.cancelable);

       if (params.view != null) {
            alertBuilder.setView(params.view);

            AlertDialog dlg = alertBuilder.create();
            dlg.show();

           return dlg;
       }

        mWeakActivity.get().runOnUiThread(() -> {
            String positiveText = TextUtils.isEmpty(params.positiveText) ?
                    mWeakActivity.get().getString(honemobile.android.core.R.string.common_confirm) : params.positiveText;

            String negativeText = TextUtils.isEmpty(params.negativeText) ?
                    mWeakActivity.get().getString(honemobile.android.core.R.string.common_cancel) : params.negativeText;

            alertBuilder.setPositiveButton(positiveText, (dialog, which) -> {
               if (params.listener != null) {
                    params.listener.onResult(OnResultListener.TRUE, dialog);
               }

                dialog.dismiss();
           });

            alertBuilder.setNegativeButton(negativeText, (dialog, which) -> {
               if (params.listener != null) {
                    params.listener.onResult(OnResultListener.FALSE, dialog);
               }

                dialog.dismiss();
           });

            alertBuilder.setOnCancelListener(dialog -> {
               if (params.listener != null) {
                    params.listener.onResult(OnResultListener.FALSE, dialog);
               }
           });

            alertBuilder.show();
       });

       return null;
   }
}

구현이 완료되었으면 AlertDialog 와 동일한 방식으로 클래스 정보를 설정해야 한다.

Example

override fun onCreate(savedInstanceState: Bundle?) {
    DialogDelegate.get().setClassPath(DialogDelegate.DialogType.CONFIRM,
       "$packageName.dialog.CustomConfirmDialog")

   super.onCreate(savedInstanceState)
}
@Override
protected void onCreate(Bundle savedInstanceState) {
    DialogDelegate.get().setClassPath(DialogDelegate.DialogType.CONFIRM,
        getPackageName() + ".dialog.CustomConfirmDialog");

   super.onCreate(savedInstanceState);
}

LoadingDialog

LoadingDialog 는 LoadingDialogBase 를 extends하여 구현해야 하며 그에 대한 예제는 아래와 같다. 

첫번째 예제는 HSP 에서 제공하는 XML 에 레이아웃만 변경하여 사용할 경우 의 예제 이다. 

Example

class CustomLoadingDialog(activity: Activity) : LoadingDialogBase(activity) {
   // xml 의 id 는 동일하게 사용하고 xml 내 view 의 위치가 다를때
   override fun layoutId() =
        R.layout.custom_loading_dialog
}
public class CustomLoadingDialog extends LoadingDialogBase {
   public CustomLoadingDialog(Activity activity) {
       super(activity);
   }

   // xml 의 id 는 동일하게 사용하고 xml 내 view 의 위치가 다를때
   @Override
   protected int layoutId() {
       return R.layout.custom_loading_dialog;
   }
}

XML Example

<androidx.constraintlayout.widget.ConstraintLayout
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/loading_layout"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:padding="20dp"
   >

   <androidx.constraintlayout.widget.ConstraintLayout
       android:id="@+id/spinner_layout"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:visibility="gone"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       >

       <ProgressBar
           android:id="@+id/spinner_progress"
           style="?android:attr/progressBarStyle"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           app:layout_constraintTop_toTopOf="parent"
           app:layout_constraintStart_toStartOf="parent"
           app:layout_constraintBottom_toBottomOf="parent"
           />

       <TextView
           android:id="@+id/spinner_message"
           android:layout_width="0dp"
           android:layout_height="0dp"
           android:layout_marginStart="8dp"
           android:text="@string/popup_loading"
           android:gravity="start|center_vertical"
           app:layout_constraintTop_toTopOf="parent"
           app:layout_constraintStart_toEndOf="@+id/spinner_progress"
           app:layout_constraintEnd_toEndOf="parent"
           app:layout_constraintBottom_toBottomOf="parent"
           tools:text="LOADING"
           />

   </androidx.constraintlayout.widget.ConstraintLayout>

   <androidx.constraintlayout.widget.ConstraintLayout
       android:id="@+id/horizontal_layout"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent">

       <TextView
           android:id="@+id/horizontal_message"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:textSize="16sp"
           app:layout_constraintTop_toTopOf="parent"
           app:layout_constraintStart_toStartOf="parent"
           tools:text="LOADING DIALOG"
           android:text="@string/popup_loading"
           />

       <LinearLayout
           android:id="@+id/progress_layout"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:layout_marginTop="19dp"
           android:orientation="horizontal"
           android:weightSum="1"
           app:layout_constraintTop_toBottomOf="@+id/horizontal_message"
           app:layout_constraintStart_toStartOf="parent"
           >

           <ProgressBar
               android:id="@+id/main_progress"
               style="?android:attr/progressBarStyleHorizontal"
               android:layout_width="match_parent"
               android:layout_height="@dimen/hone_loading_progress_height"
               android:layout_weight=".5"
               android:progressDrawable="@drawable/hone_shape_progress"
               />

           <ProgressBar
               android:id="@+id/second_progress"
               style="?android:attr/progressBarStyleHorizontal"
               android:layout_width="match_parent"
               android:layout_height="@dimen/hone_loading_progress_height"
               android:layout_weight=".5"
               android:visibility="gone"
               android:progressDrawable="@drawable/hone_shape_progress"
               />

       </LinearLayout>

       <TextView
           android:id="@+id/percent"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_marginTop="20dp"
           app:layout_constraintTop_toBottomOf="@+id/progress_layout"
           app:layout_constraintStart_toStartOf="parent"
           app:layout_constraintBottom_toBottomOf="parent"
           tools:text="0%"
           android:text="0%"
           tools:ignore="HardcodedText"/>

       <TextView
           android:id="@+id/download"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_marginTop="20dp"
           app:layout_constraintTop_toBottomOf="@+id/progress_layout"
           app:layout_constraintEnd_toEndOf="parent"
           app:layout_constraintBottom_toBottomOf="parent"
           android:text="0MB/0MB"
           tools:ignore="HardcodedText"/>

   </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

두번째 예제는 새롭게 로딩 다이얼로그를 생성할 경우 예제 이다. 

Example

class CustomLoadingDialog(activity: Activity) : LoadingDialogBase(activity) {
    companion object {
       private val mLog = org.slf4j.LoggerFactory.getLogger(CustomLoadingDialog::class.java)

        val T_TRANSPARENT_LOADING = 1
   }

   private var mTask: TimerTask? = null
   private var mTimer: Timer? = null
   private var mPos = 1

    override fun setLoadingDialog(params: LoadingDialog) {
        when (params.type) {
            T_TRANSPARENT_LOADING ->
               // UI 를 변경한 로딩 다이얼로그를 호출할 수 도 있다.
               transparentLoading(params)

           else ->
               // 기존 로딩 다이얼로그를 호출할 수 도 있고
               super.setLoadingDialog(params)
       }
   }

   ////////////////////////////////////////////////////////////////////////////////////
   //
   // T_TRANSPARENT_LOADINGS
   //
   ////////////////////////////////////////////////////////////////////////////////////

   private fun transparentLoading(params: LoadingDialog) {
        mWeakActivity.get()?.let {
            val inflater = LayoutInflater.from(it)

            val vmodel = ViewModelProviders.of(it as FragmentActivity)
                   .get(DialogLoadingViewModel::class.java)

            val binding = DataBindingUtil.inflate<DetailDlgCustomLoadingLayoutBinding>(inflater,
                    R.layout.detail_dlg_custom_loading_layout, null, false)
            binding.model = vmodel

           if (!mLoadingDialog.isView) {
                mLoadingDialog.setView(binding.root)
           }

           if (!mLoadingDialog.isShowing) {
                mLoadingDialog.show()
           }

            mLoadingDialog.window!!.setBackgroundDrawable(
                    ColorDrawable(android.graphics.Color.TRANSPARENT))

            mTask = object : TimerTask() {
                override fun run() {
                   try {
                       if (mLog.isDebugEnabled) {
                            mLog.debug("EXPLODE TIMER $mPos")
                       }

                        val size = vmodel.pinList.size()
                       for (i in 0 until size) {
                           if (mPos == i) {
                                vmodel.pinList.get(i).set(R.drawable.hone_lockscreen_ic_dot_enable_24dp)
                           } else {
                                vmodel.pinList.get(i).set(R.drawable.ic_brightness_1_white_24dp)
                           }
                       }

                       if (++mPos == size) {
                            mPos = 0
                       }
                   } catch (ignored: Exception) {
                       if (mLog.isDebugEnabled) {
                            mLog.debug("STOP TIMER")
                       }

                        mTimer!!.cancel()
                        mTimer = null
                   }

               }
           }

            mTimer = Timer()
            mTimer!!.schedule(mTask, 1000, 500)
       }
   }

    override fun setProgressValue(value: Long) {
       if (mLoadingDialog.findViewById(R.id.other_loading_layout) == null) {
           super.setProgressValue(value)
           return
       }
   }
}
public class CustomLoadingDialog extends LoadingDialogBase {
   private static final org.slf4j.Logger mLog = org.slf4j.LoggerFactory.getLogger(CustomLoadingDialog.class);

   public static final int T_TRANSPARENT_LOADING = 1;

   public CustomLoadingDialog(Activity activity) {
       super(activity);
   }

   @Override
   protected void setLoadingDialog(@NonNull LoadingDialog params) {
       switch (params.type) {
           case T_TRANSPARENT_LOADING:
               // UI 를 변경한 로딩 다이얼로그를 호출할 수 도 있다.
               transparentLoading(params);
               break;

            default:

               // 기존 로딩 다이얼로그를 호출할 수 도 있고
               super.setLoadingDialog(params);
               break;
       }
   }

   ////////////////////////////////////////////////////////////////////////////////////
   //
   // T_TRANSPARENT_LOADINGS
   //
   ////////////////////////////////////////////////////////////////////////////////////

   private TimerTask mTask;
   private Timer mTimer;
   private int mPos = 1;

   private void transparentLoading(LoadingDialog params) {
        LayoutInflater inflater = LayoutInflater.from(mActivity);

        DialogLoadingViewModel vmodel = ViewModelProviders.of((FragmentActivity) mActivity)
               .get(DialogLoadingViewModel.class);

        DetailDlgCustomLoadingLayoutBinding binding = DataBindingUtil.inflate(inflater,
                R.layout.detail_dlg_custom_loading_layout, null, false);
        binding.setModel(vmodel);

       if (!mLoadingDialog.isView()) {
            mLoadingDialog.setView(binding.getRoot());
       }

       if (!mLoadingDialog.isShowing()) {
            mLoadingDialog.show();
       }

        mLoadingDialog.getWindow().setBackgroundDrawable(
               new ColorDrawable(android.graphics.Color.TRANSPARENT));

        mTask = new TimerTask() {
           @Override
           public void run() {
               try {
                   if (mLog.isDebugEnabled()) {
                        mLog.debug("EXPLODE TIMER " + mPos);
                   }

                   int size = vmodel.pinList.size();
                   for (int i = 0; i < size; ++i) {
                       if (mPos == i) {
                            vmodel.pinList.get(i).set(R.drawable.hone_lockscreen_ic_dot_enable_24dp);
                       } else {
                            vmodel.pinList.get(i).set(R.drawable.ic_brightness_1_white_24dp);
                       }
                   }

                   if (++mPos == size) {
                        mPos = 0;
                   }
               } catch (Exception ignored) {
                   if (mLog.isDebugEnabled()) {
                        mLog.debug("STOP TIMER");
                   }

                    mTimer.cancel();
                    mTimer = null;
               }
           }
       };

        mTimer = new Timer();
        mTimer.schedule(mTask, 1000, 500);
   }

   @Override
   protected void setProgressValue(final long value) {
       if (mLoadingDialog.findViewById(R.id.other_loading_layout) == null) {
           super.setProgressValue(value);
           return ;
       }
   }
}

XML Example

<layout>
   <data>
       <variable
           name="model"
           type="com.hanwha.sample.widget.dialog.viewmodel.DialogLoadingViewModel" />
       <variable
           name="imageadapter"
           type="com.hanwha.sample.base.bindingadapter.ImageBindingAdapter" />
   </data>

   <androidx.constraintlayout.widget.ConstraintLayout
       xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:tools="http://schemas.android.com/tools"
       xmlns:app="http://schemas.android.com/apk/res-auto"
       android:id="@+id/other_loading_layout"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:background="@android:color/transparent"
       android:padding="20dp">

       <androidx.constraintlayout.widget.ConstraintLayout
           android:id="@+id/other_spinner_layout"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:background="@drawable/shape_custom_progress_bg"
           android:padding="30dp"
           app:layout_constraintStart_toStartOf="parent"
           app:layout_constraintTop_toTopOf="parent"
           >

           <androidx.constraintlayout.widget.ConstraintLayout
               android:id="@+id/other_spinner_progress"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:layout_marginTop="@dimen/hone_lockscreen_dot_layout_margin_top"
               app:layout_constraintLeft_toLeftOf="parent"
               app:layout_constraintRight_toRightOf="parent"
               app:layout_constraintTop_toTopOf="parent"
               >

               <ImageView
                   android:id="@+id/pin1"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:src="@{model.pin1}"
                   tools:ignore="ContentDescription,RtlHardcoded"
                   app:layout_constraintTop_toTopOf="parent"
                   app:layout_constraintLeft_toLeftOf="parent"
                   />
               <ImageView
                   android:id="@+id/pin2"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:layout_marginLeft="@dimen/hone_lockscreen_pin_margin_right"
                   android:src="@{model.pin2}"
                   tools:ignore="ContentDescription,RtlHardcoded"
                   app:layout_constraintTop_toTopOf="parent"
                   app:layout_constraintLeft_toRightOf="@+id/pin1"
                   />
               <ImageView
                   android:id="@+id/pin3"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:layout_marginLeft="@dimen/hone_lockscreen_pin_margin_right"
                   android:src="@{model.pin3}"
                   tools:ignore="ContentDescription,RtlHardcoded"
                   app:layout_constraintTop_toTopOf="parent"
                   app:layout_constraintLeft_toRightOf="@+id/pin2"
                   />
               <ImageView
                   android:id="@+id/pin4"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:layout_marginLeft="@dimen/hone_lockscreen_pin_margin_right"
                   android:src="@{model.pin4}"
                   tools:ignore="ContentDescription"
                   app:layout_constraintTop_toTopOf="parent"
                   app:layout_constraintLeft_toRightOf="@+id/pin3"
                   />
           </androidx.constraintlayout.widget.ConstraintLayout>

           <TextView
               android:id="@+id/other_spinner_message"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:layout_marginTop="10dp"
               android:textSize="23sp"
               android:textColor="@android:color/white"
               android:text="@{model.spinnerMsg}"
               android:textAppearance="@style/TextAppearance.nanum_bold"
               app:layout_constraintLeft_toLeftOf="parent"
               app:layout_constraintRight_toRightOf="parent"
               app:layout_constraintTop_toBottomOf="@id/other_spinner_progress"
               />
       </androidx.constraintlayout.widget.ConstraintLayout>
   </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

LoadingDialog 도 다른 다이얼로그와 마찬가지로 커스텀을 클래스를 구성한 후에는 클래스 정보를 DialogDelegate 에 설정해야 올바르게 동작 된다. 

Example

override fun onCreate(savedInstanceState: Bundle?) {
    DialogDelegate.get().setClassPath(DialogDelegate.DialogType.LOADING,
           "$packageName.dialog.CustomLoadingDialog")

   super.onCreate(savedInstanceState)
}
@Override
protected void onCreate(Bundle savedInstanceState) {
    DialogDelegate.get().setClassPath(DialogDelegate.DialogType.LOADING,
          getPackageName() + ".dialog.CustomLoadingDialog");

   super.onCreate(savedInstanceState);
}

통신 구간 암/복호화

HONE Smart Platform에서는 통신 구간 암/복호화를 위해 IKeyProvider 와 IEncryptionProvider 인터페이스가 제공되며
이를 이용하여 3rd party 암/복호화 라이브러리를 사용할 수 있다

PreConfiguration 설정

사용자는 preConfiguration.json 파일에 네트워크 암호화에 사용 할 모든 Provider의 정보를 등록할 수 있다. 

keyProviders 에는 암호화에 필요한 키를 등록할 수 있으며, encryptionProviders 에는 keyProviders 에 등록된 키를 이용하여 암호화를 하는 Provider 를 등록할 수 있다.

KeyProviders

  • preConfiguration.json 의 keyProviders 안에 만들고자 하는 keyProvider 에 대한 정보를 작성한다. (IKeyProvider 를 상속)

 

Example

"keyProviders": {
    "myKeyProviderName": {
        "className": "${your_class_path}.MyKeyProvider"
    }
}

Example

class MyKeyProvider : IKeyProvider {
   private val key: ByteArray? = null

    override fun getKey(sync: Boolean): ByteArray? {
       // TODO
   }

    override fun setKey(key: ByteArray) {
       // TODO
   }

    override fun isLoaded() =
       true

    override fun isEncrypted(sync: Boolean) =
       true    
}
public final class MyKeyProvider implements IKeyProvider {
   private byte[] key;

   public MyKeyProvider() {

   }

   @Override
   public byte[] getKey(boolean sync) {
       // TODO
   }

   @Override
   public void setKey(final byte[] key) {
       // TODO
   }

   @Override
   public boolean isLoaded() {
       return true;
   }

   @Override
   public boolean isEncrypted(boolean sync) {
       return true;
   }
}

EncryptionProviders

  • preConfiguration.json 의 encryptionProviders 안에 만들고자 하는 IEncryptionProvider 에 대한 정보를 작성한다. (IEncryptionProvider 를 상속)

 

Example

"encryptionProviders": {
    "myEncryptionProviderName": {
        "className": "${your_class_path}.MyEncryptionProvider"
    }
}

Example

class MyEncryptionProvider : IEncryptionProvider {
    override fun encrypt(key: ByteArray, planTextData: ByteArray): ByteArray? {
       // TODO
   }

    override fun decrypt(key: ByteArray, encryptedData: ByteArray): ByteArray? {
       // TODO
   }
}
public class MyEncryptionProvider implements IEncryptionProvider {
   @Nullable
   @Override
   public final byte[] encrypt(@NonNull final byte[] key, @NonNull final byte[] planTextData) {
       // TODO
   }

   @Nullable
   @Override
   public final byte[] decrypt(@NonNull final byte[] key, @NonNull final byte[] encryptedData) {
       // TODO
   }
}

Configuration 설정

configuration.json 파일에 Hub 통신시 사용 될 KeyProvider 와 EncryptionProvider 의 이름을 등록하여 사용할 수 있다. 이때 등록된 이름은 preConfiguartion.json 의 encryptionProviders, keyProviders 에 등록된 정보를 기입하면 되는데 앞선 예를 통해서 보면 KeyProvider 의 경우 myKeyProviderName 를 encryptionProvider 의 경우 myEncryptionProviderName 를 이름으로 이용하면 된다. 

Example

{
   "serviceTargets": {
       "hub1": {
           "baseAddress": "https://hone.hanwha.co.kr",
           "port": "443",
           "contextPath": "/smarthub",
           "applicationPath": "/janggyo/service",
           "contentType": "application/json",
           "networkEncryption": {
               "enabled": true,
               "encryptionProviderName": "myEncryptionProviderName",
               "keyProviderName": "myKeyProviderName"
            }
        }
    }
}

BizApp 리소스 위/변조

HONE Smart Platform에서는 BizApp 리소스 위/변조 기능이 제공되며 이를 이용하기 위해서는 아래와 같은 설정 변경이 필요하다.

Actionflow 설정

Configuration 파일 중 actionflow.json에서 verifyBizAppAction을 updateBizAppAction 이후에 추가한다. 

Example

[
    {
       "name": "updateBizAppAction",
       "className": "honemobile.client.actionflow.action.updateBizAppAction",
       "properties": {}
    },
    {
       "name": "verifyBizAppAction",
       "className": "honemobile.client.actionflow.action.verifyBizAppAction",
       "properties": {}
    }
]

제품 String 변경

HONE Smart Platform에서 정의한 String은 사용자가 재 지정 하여 변경할 수 있으며 이를 위해서는 제품에서 정의한 String의 키와 동일한 String의 키를 App Level에서 재정의하면 변경된다. 

재 지정 파일 위치 : app/src/main/res/values/strings.xml

<string name="activity_backkey_exit">변경된 메시지</string>
<!-- <string name="activity_backkey_exit">"'뒤로' 버튼을 한번 더 누르시면 종료됩니다."</string> -->

HONE Smart Platform에서 정의한 String 정보는 아래와 같다. 

Table. HONE Smart Platform String

String IDKoreanEnglishComment
 button_ok  확인  OK  
 button_cancel  취소  Cancel  
 button_confirm  승인  Approve  
 button_send  보내기  Send  
 button_clear  지우기  Clear  
 button_delete  삭제  Delete  
 button_error  오류  Error  
 button_notice  알림  Notification  
 button_alert  경고  Warning  
 button_exit  종료  Exit  
 button_update  업데이트   Update  
 button_retry  재시도  Retry  
 button_done  완료  Done  
 button_connect  연결  Connect  
 button_set   설정  Set  
 activity_backkey_exit  \'뒤로\'버튼을 한번 더 누르시면 종료됩니다.  Press the \’Back\’ button again to exit.  
 config_not_found_plugin  플러그인을 찾지 못하였습니다. (%s) 이 plugin.json 내에 존재하는지 확인하세요.  Unable to fund the plug-in. Check whether (%s) exists in plugin.json.  
 config_check_error_log  Json 파일에 오류가 발생하였습니다. 오류 로그를 확인하세요.  An error has occurred in the JSON file. Check the error log.  
 config_not_found_class  클래스가 존재하지 않습니다. (%s)  The class doesn’t exist. (%s)  
 config_invalid_networkcompress  networkCompression 활성화 시\nrequestCompress, responseCompress 값이 존재해야 합니다.\n\n예)\nrequestCompression: gzip\nresponseCompression: gzip  When activating networkCompression\nthe requestCompress and responseCompress values must exist.\n\nExample)\nrequestCompression: gzip\nresponseCompression: gzip  
 config_not_define_launcherbizapp  launcherBizAppId 가 설정되어 있지 않습니다.  launcherBizAppId is not set.  
 config_not_enable_bizappencryption  bizAppSecurity 을 활성화 하면\nbizAppEncryption 도 활성화 되어야 합니다.  To activate bizAppSecurity,\nbizAppEncryption should be also activated.  
 config_call_updatebizapp_before_requestlogin  RequestLoginAction은 UpdateBizAppAction 전에 호출되어야 합니다.  RequestLoginAction should be called before UpdateBizAppAction.  
 config_add_verifybizapp_after_updatebizapp  위변조 활성화가 되어있을 경우 actionflow.json 에 UpdateBizAppAction 이후에 VerifyBizAppAction 추가 되어야 합니다.  If forgery and alteration are activated, VerifyBizAppAction should be added after UpdateBizAppAction in actionflow.json.  
 config_not_found_window  윈도우를 찾을 수 없습니다. (%s)\nwindow.json을 확인하세요.  Unable to find the Window. (%s)\nCheck window.json.  
 config_no_data_in_configuration  설정 정보 중 (%s) 값이 없습니다.\nconfiguration.json을 확인하세요.  (%s) is not set.\nCheck configuration.json.  
 config_invalid_data_in_configuration  설정 정보 중 (%s) 값에 오류가 있습니다.\nconfiguration.json을 확인하세요.  An error has occurred in (%s).\nCheck configuration.json.  
 config_invalid_action_in_startup  resume용 Action을 startup에서 사용 하실 수 없습니다. (%s)  Unable to use (%s) Action in startup.\nCheck actionflow.json   
 config_invalid_action_in_resume  startup용 Action을 resume에서 사용 하실 수 없습니다. (%s)  Unable to use (%s) Action in resume.\nCheck actionflow.json   
 window_unknown_error  화면 구성 중에 에러가 발생하였습니다.  An error has occurred while composing the screen.  
 webview_http_auth_error  서버에서 사용자 인증에 실패하였습니다.  Failed to authenticate the user in the server.  
 webview_http_bad_url  잘못된 주소입니다.  Invalid address.  
 webview_http_connect_to_server_error  서버 연결에 실패하였습니다.  Failed to connect the server.  
 webview_http_ssl_error  SSL 수행에 실패하였습니다.  Failed to execute SSL.  
 webview_http_file_error  파일 오류가 발생하였습니다.  A file error has occurred.  
 webview_http_file_not_found  요청하신 파일을 찾을 수 없습니다.  Unable to find the requested file.  
 webview_http_host_lookup_error  서버 또는 프록시 호스트 이름 조회 실패입니다.  Failed to retrieve the name of the server or proxy host.  
 webview_http_io_error  서버에서 읽거나 서버로 쓰기 실패입니다.  Failed to read from or write to the server.  
 webview_http_proxy_auth_error  프록시에서 사용자 인증에 실패하였습니다.  Failed to authenticate the user in the proxy.  
 webview_http_many_redirect_loop  너무 많은 리디렉션이 발생하였습니다.  Too many redirection has occurred.  
 webview_http_timeout  연결 시간이 초과되었습니다.  The connection time is exceeded.  
 webview_http_many_request  페이지 로드 중 너무 많은 요청이 발생하였습니다.  To may requests have occurred while loading the page.  
 webview_unsupported_auth_scheme  지원되지 않는 인증체계입니다.  Unsupported authentication system.  
 webview_unsupported_uri_scheme  지원되지 않는 URI입니다.  Unsupported URI  
 webview_unknown_error  수행 중 알 수 없는 오류가 발생하였습니다.  An unknown error has occurred during execution.  
 popup_loading  로딩 중   Loading   
 popup_invalid_classpath  사용자가 설정한 다이얼로그의 클래스 경로가 올바르지 않습니다. (%s)  Invalid classpath (%s)  
 popup_window_is_popupwindow  (%s)는 팝업 윈도우입니다.\nshowPopupWindow 메소드를 이용해야 합니다.  (%s) is a popup window.\nNeed to use the showPopupWindow method.   
 popup_window_not_popupwindow  (%s)는 팝업 윈도우가 아닙니다.  (%s) isn't a popup window.  
 plugin_success  성공  Success  
 plugin_known_error  플러그인 수행 중에 에러가 발생하였습니다.  An error has occurred while executing the plug-in.  
 plugin_invalid_parameter  플러그인 파라미터가 유효하지 않습니다.  Invalid plug-in parameter.  
 plugin_invalid_action  요청하신 플러그인 액션은 지원하지 않습니다.  The requested plug-in action is not supported.  
 plugin_invalid_service  요청하신 플러그인 서비스는 지원하지 않습니다.  The requested plug-in service is not supported.  
 plugin_execute_error  플러그인 실행 중 오류가 발생하였습니다.  An error has occurred while running the plug-in.  
 plugin_json_parse_error  JSON Parsing 중 에러가 발생하였습니다.  An error has occurred while parsing JSON.  
 plugin_urlencode_error  URL Encode 중 에러가 발생하였습니다.  An error has occurred while encoding the URL.  
 plugin_permission_error  플러그인 실행 시 필요한 권한이 없습니다.   There is no right needed to execute the plug-in.   
 filerepository_file_already_exist  해당 파일이 존재합니다.  The pertinent file already exists.  
 filerepository_unknown_address_scheme  알 수 없는 주소 형식입니다.  Unknown address format.  
 filerepository_decompress_error  압축을 해제하는 과정에서 오류가 발생하였습니다.  An error has occurred while decompressing.  
 filerepository_file_already_downloading  해당 파일은 다운로드 중입니다.  The pertinent file is being downloaded now.  
 filerepository_path_not_exist  (%s)는 존재하지 않는 경로입니다.  The (%s) path doesn’t exist.  
 filerepository_not_directory  (%s)는 디렉토리가 아닙니다.  The (%s) is not a directory.  
 filerepository_file_save_error  파일 저장 시 오류가 발생하였습니다.  An error has occurred while saving the file.  
 filerepository_filename_empty  파일 이름이 누락되었습니다.  The file name is omitted.  
 filerepository_file_not_found  해당 파일이 존재하지 않습니다.  The pertinent file doesn't exist.  
 securefilerepository_insert_error  데이터베이스 추가 중 오류가 발생하였습니다.  An error has occurred while adding a database.  
 securefilerepository_write_error  파일 쓰기 중 오류가 발생하였습니다.  A file error has occurred while writing the file.  
 securefilerepository_delete_error  삭제 중에 오류가 발생하였습니다.  An error has occurred during deletion.  
 securefilerepository_cannot_read_file  해당 파일을 읽을 수 없습니다.  Unable to read the requested file.  
 contact_search_error  주소록 검색에 실패하였습니다.  Failed to search for an address book.  
 contact_insert_error  주소록 추가에 실패하였습니다.  Failed to add an address book.  
 contact_name_is_empty  주소록에 추가할 이름이 없습니다.  There is no name to add to the address book.  
 geolocation_bad_gps_info  잘못된 위치 정보가 전달되었습니다.  The incorrect location information has been transferred.  
 geolocation_no_location_provider  위치정보 지원 가능한 Provider가 없습니다.  There is no provider that can supports the location information.  
 execexternal_execute_error  수행 중에 오류가 발생하였습니다.  An error has occurred during execution.  
 camera_not_create_image  사진를 생성할 수 없습니다.  Unable to create a picture.  
 camera_capturing_image_error  사진 캡쳐 중 오류가 발생하였습니다.  An error has occurred while capturing a picture.  
 camera_image_retrieve_cancelled  사진가져오기가 취소 되었습니다.  Importing a picture has been canceled.  
 camera_not_complete  수행이 완료되지 않았습니다.  Execution is not completed yet.  
 camera_image_not_retrieved  사진의 경로를 검색할 수 없습니다.  Unable to search for the picture path.  
 camera_retrieving_image_error  이미지 가져오기 중 오류가 발생하였습니다.  An error has occurred while importing an image.  
 camera_selection_cancelled  선택이 취소되었습니다.  Selection is canceled.  
 camera_selection_not_complete  선택이 완료되지 않았습니다.  Selection is not completed yet.  
 camera_compressing_image_error  이미지 압축 중 오류가 발생하였습니다.  An error has occurred while compressing an image.  
 sqlite_unsupported_data_type  지원하지 않는 자료형입니다.  Unsupported data type.  
 sqlite_not_found_db   데이터 베이스를 찾을 수 없습니다.  Unable to find a database.  
 sqlite_delete_error  데이터 베이스 삭제에 실패 하였습니다.  Failed to delete the database.  
 preference_key_not_found  요청하신 Key 를 찾을 수 없습니다.  Unable to find the requested key.  
 notice_never_seen_a_day  오늘 하루 보지 않기  Never seen a day  
 notice_never_see_again  다시 보지 않기  Never see again  
 fingerprint_not_registered  지문이 등록되어 있지 않습니다. 설정에서 지문을 추가해 주세요  The fingerprint is not registered. Please add fingerprints in settings  
 fingerprint_too_many_attempt  시도 횟수가 너무 많습니다. 나중에 다시 시도 하세요.  Too many attempts Please try again later.  
 fingerprint_try_again  다시 시도  Try again  
 fingerprint_auth_error  인증 오류  Authentication error  
 fingerprint_ready_atttempt  지문을 인식시켜 주세요.  Please recognize the fingerprint.  
 gallery_title  갤러리  Gallery  
 gallery_all_files  모든 파일  All Files  
 gallery_no_storage  저장공간이 부족하여 기능을 수행할 수 없습니다.  Unable to execute the Function due to an insufficient storage space. iOS에서만 사용
 securestorage_encryption_db_init_error  암호화 DB 초기화를 실패하였습니다.  Encryption DB initialization failed.  
 lockscreen_enter_new_pincode  새롭게 설정할 비밀번호를 입력 하세요.  Please enter a new password  
 lockscreen_enter_your_pincode  비밀번호를 입력 하세요.  Please enter a your password  
 lockscreen_reenter_your_pincode  비밀번호를 다시 입력 하세요.  Please re-enter your password  
 lockscreen_enter_your_old_pincode  기존 비밀번호를 입력 하세요.  Please enter your old password.  
 screen_brightness_enable_system_write_setting  '시스템 설정 쓰기 허용'을 활성화 시켜야 올바르게 동작 합니다.  You must enable ' Allow system settings to write ' for the correct operation.  
 action_known_error  초기화 수행 중에 에러가 발생하였습니다.  An error has occurred while executing initialization.  
 action_invalid_class  지정하신 초기화 클래스가 존재하지 않습니다.  The designated initialization class doesn’t exist.  
 action_invalid_property  초기화 속성이 유효하지 않습니다.  The initialization property is not valid.  
 action_execute_error  초기화 실행 중 오류가 발생하였습니다.  An error has occurred while executing initialization.  
 action_retry_toast  재시도 중입니다.  Re-attempting.  
 action_invalid_license  라이센스가 유효하지 않습니다.  The license is invalid.  
 installbuiltinbizapp_loading  필수 파일을 설치 중입니다.  Required files are being installed.  
 installbuiltinbizapp_is_empty  설치할 필수 파일 목록이 없습니다.  There is no list of required files to install.  
 installbuiltinbizapp_no_launcherbizapp  시작화면이 지정되지 않았습니다.  An start screen is not designated.  
 installbuiltinbizapp_invalid_launcherbizapp  지정된 시작화면이 유효하지 않습니다.  The designated start screen is not valid.  
 installbuiltinbizapp_invalid_error  내장된 필수 파일이 손상되었습니다.  The integrated required file is damaged.  
 installbuiltinbizapp_unformatted_error  내장된 필수 파일의 구조는 지원하지 않습니다.  The structure of the integrated required file is not supported.  
 installbuiltinbizapp_not_found_error  설치할 필수 파일을 찾을 수 없습니다.  Unable to find the required file to install.  
 installbuiltinbizapp_no_storage  저장공간이 부족하여 내장된 필수 파일을 설치할 수 없습니다.  Unable to install the  integrated required file due to an insufficient storage space.  
 requestlogin_loading  단말기 정보를 등록 중입니다.  Registering the device information.  
 updateconfiguration_loading  서버로부터 설정 정보를 가져옵니다.  Getting the configuration information from the server.  
 updatebizapp_force_update  필수 파일의 업데이트가 있습니다.   There are updates for the required file.  
 updatebizapp_common_update  업데이트된 파일이 있습니다.  There are updated files.  
 updatebizapp_update  파일 업데이트  File update  
 updatebizapp_loading  파일 업데이트 중입니다.  Updating the file.  
 updatebizapp_complete_toast  업데이트가 완료되었습니다.  Update is completed.  
 updatebizapp_request_info_error  업데이트 정보 요청 중 에러가 발생하였습니다.  An error has occurred while requesting the update information.  
 updatebizapp_request_permitted_error  업데이트 권한정보 요청 중 에러가 발생하였습니다.  An error has occurred while requesting the update right information.  
 updatebizapp_invalid_download_path  다운로드 경로가 잘못 되었습니다.  Invalid download path.  
 updatebizapp_downloading_error  다운로드 중 에러가 발생하였습니다.  An error has occurred while downloading.  
 updatebizapp_copy_error  파일 복사 중 에러가 발생하였습니다.  An error has occurred while copying the file.  
 updatebizapp_decompressing_error  압축 해제 중 에러가 발생하였습니다.  An error has occurred while decompressing.  
 updatebizapp_db_update_error  DB 갱신 중 에러가 발생하였습니다.  An error has occurred while updating the database.  
 updatebizapp_installing_error  설치 중 에러가 발생하였습니다.  An error has occurred while installing.  
 updatebizapp_is_empty  설치할 필수 파일 목록이 없습니다.  There is no list of required files to install.  
 updatebizapp_no_launcherbizapp  시작화면이 지정되지 않았습니다.  An start screen is not designated.  
 updatebizapp_invalid_launcherbizapp  지정된 시작화면이 유효하지 않습니다.  The designated start screen is not valid.  
 updatebizapp_invalid_error  내려받은 필수 파일이 손상되었습니다.  The downloaded required file is damaged.  
 updatebizapp_unformatted_error  내려받은 필수 파일의 구조는 지원하지 않습니다.  The structure of the downloaded required file is not supported.  
 updatebizapp_no_storage  저장공간이 부족하여 필수 파일을 설치할 수 없습니다.  Unable to install the  required file due to an insufficient storage space.  
 updatelauncher_op_force_update  어플리케이션이 업데이트 되었습니다.  The application has been updated.  
 updatelauncher_check_version_loading  스토어 버전 확인 중입니다.  Checking the Store version.  
 updatelauncher_common_update  최신버전 어플리케이션이 있습니다.  The latest version application is available.  
 updatelauncher_update  업데이트 공지  Update notice  
 updatelauncher_store_force_update  새로운 버전이 출시되었습니다.  A newer version has been released.  
 verifybizapp_loading  보안검사를 실행 중입니다.  Performing security check.  
 verifybizapp_forgery_app  설치된 파일의 변형이 의심되어 프로그램을 실행 할 수 없습니다.  Unable to run the program because the installed file could have been changed.  
 showlauncherbizapp_progress  화면을 준비중 입니다.   Preparing the screen.   
 showlauncherbizapp_no_data  시작화면이 지정되지 않았습니다.  An start screen is not designated.  
 showlauncherbizapp_invalid_bizapp  필수 파일을 찾을 수 없습니다.\n 앱을 재 시작해 주세요.  Unable to find the required file.\nPlease restart.  
 checkupdate_common_update  업데이트된 파일이 있습니다.\n 앱 종료 후 재실행 바랍니다.  There are updated files.  
 checkupdate_force_update  필수 파일의 업데이트가 있습니다.\n 앱 종료 후 재실행 바랍니다.  There are updates for the required file.  
 server_response_error  비정상 응답입니다.  Abnormal response.  
 server_invalid_data  수신데이터가 유효하지 않습니다.  The receiving data is not valid.  
 server_no_launcherbizapp  서버에 비즈앱 런처가 설정되어 있지 않습니다\nOperation Center 에 접속하여 설정하세요  The BizApp Launcher is not set in the server.\nSet the BizApp Launcher by connecting Operation Center.  
 network_unknown_error  네트워크 수행 중 에러가 발생하였습니다.  An error has occurred while operating the network.  
 network_net_io_error  네트워크 통신 중 에러가 발생하였습니다.  An error has occurred while communicating over the network.  
 network_no_connectivity  네트워크 연결이 원활하지 않습니다.  Network connection is not stable.  
 network_timeout_exceed  네트워크 요청시간을 초과하였습니다.  The network request time is exceeded.  
 network_invalid_data_format  지원하지 않는 데이터 포멧입니다.  Unsupported data format.  
 network_invalid_request_type  통신상태가 원활하지 않습니다. 통신상태를 확인해주세요.  The communication state is not stable. Please check the communication state.  
 network_load_loading  로드 중입니다.  Loading.  
 network_upload_loading  업로드 중입니다.  Uploading.  
 network_occur_error  네트워크에 문제가 있습니다.\n앱을 종료합니다. (%s)  There is a network problem.\nThe app will be stopped. (%s)  
 network_exit_app  앱을 종료합니다.  The App will be stopped.  
 network_not_found_upload_file  업로드할 대상 파일을 찾을 수 없습니다.  Unable to find the target file to upload.  
 network_not_found_file_payload  payload 내 files 를 찾을 수 없습니다.  Unable to find files inside payload.  
 network_no_storage  저장공간이 부족하여 앱을 설치할 수 없습니다.  Unable to install the App due to an insufficient storage space.  
 network_invalid_target_address  타겟서버의 주소를 찾을 수 없습니다. 타겟의 이름을 확인하세요.  Unable to find the target server address. Please check the name of the target.  
 network_invalid_send_data  전송데이터가 유효하지 않습니다.  The sending data is not valid.  
 network_invalid_receive_data  수신데이터가 유효하지 않습니다.  The receiving data is not valid.  
 network_server_comm_error  서버통신 시 오류가 발생하였습니다.  An error has occurred in server communication.  
 network_no_honemobile_header  X-HONEMobile-Header가 없습니다.  There is no X-HONEMobile-Header.  
 network_downloading_file  파일을 다운로드 중입니다.   Downloading a file.   
 network_ssl_cert_error  SSL 인증 오류  SSL authentication error  
 network_ssl_cert_error_msg  이 웹사이트에 대한 인증서가 유효하지 않습니다.  The certificate for this web site is not valid.  
 network_ssl_ignore_cert_error  이 메시지 창을 더 이상 띄우지 않고 항상 연결 하기  Alway connect without displaying this message window  
 exception_thread_interrupted  스레드가 중단되었습니다.  The thread has stopped.  
 exception_execution_interrupted  실행이 중단되었습니다.  Execution has stopped.  
 permission_title  권한 추가  Add a privilege  
 permission_error  권한 오류가 발생하였습니다.  A privilege error has occurred.  
 permission_message  앱을 올바르게 실행하기 위해서는 권한 추가가 필요 합니다.  You need to add a privilege to run the App properly.