diff --git a/samples/abox-sdk-sample/.gitignore b/samples/abox-sdk-sample/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/samples/abox-sdk-sample/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/samples/abox-sdk-sample/README.md b/samples/abox-sdk-sample/README.md
new file mode 100644
index 0000000..3cf013d
--- /dev/null
+++ b/samples/abox-sdk-sample/README.md
@@ -0,0 +1,77 @@
+# ABox SDK Sample
+
+## 中文说明
+
+本目录归档了原 `aboxtest` Android 工程,用于参考 ABSDK 0630 的接入和调用方式。归档只修改了外层目录名,原工程名、模块名、包名和源码结构均保持不变。
+
+### 模块
+
+- `login-mac`:主要参考模块,演示 ABSDK 登录、记住密码、自动登录、错误码映射和插座控制。
+- `myapplication`:原工程附带的 Android Studio 示例模块,主要包含基础导航、登录界面和 SQLite 辅助类骨架。
+
+### 重要文件
+
+- `login-mac/libs/libABSDK0630.jar`:ABSDK 0630 二进制依赖。
+- `login-mac/src/main/java/tr/dhee/abox/demo/data/LoginDataSource.java`:SDK 登录调用。
+- `login-mac/src/main/java/tr/dhee/abox/demo/data/SwitchDataSource.java`:插座控制调用。
+- `login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginActivity.java`:登录、记住密码和自动登录流程。
+- `login-mac/src/main/java/tr/dhee/abox/demo/ui/switchcontrol/SwitchControlActivity.java`:插座控制界面。
+- `login-mac/src/main/java/tr/dhee/abox/demo/utils/AboxConstants.java`:ABSDK 错误码映射。
+
+### 归档清理
+
+归档时已排除以下本机状态、构建产物和敏感文件:
+
+- `.gradle/`、`.idea/` 和所有 `build/` 目录;
+- `local.properties` 和 `.DS_Store`;
+- APK、keystore、JKS、备份文件和损坏的 JAR 备份。
+
+### 使用限制
+
+本目录仅作为历史实现和 SDK 行为参考,不代表 ABox Manager 正式应用的目标架构,也不保证能够在当前工具链中直接构建。正式代码不应直接沿用以下做法:
+
+- `AsyncTask` 或旧式线程管理;
+- 使用普通 `SharedPreferences` 明文保存密码;
+- 全局允许明文网络流量;
+- 未经封装直接调用同步阻塞的 SDK API。
+
+正式实现应通过类型化适配层、后台协程、安全凭据存储和受限网络安全配置接入 ABSDK。
+
+---
+
+## English
+
+This directory archives the original `aboxtest` Android project as a reference for integrating and calling ABSDK 0630. Only the outer directory name was changed; the original project name, module names, package names, and source layout remain unchanged.
+
+### Modules
+
+- `login-mac`: The primary reference module. It demonstrates ABSDK login, password remembering, automatic login, error-code mapping, and socket control.
+- `myapplication`: An Android Studio sample module included in the original project, containing basic navigation, a login screen, and a SQLite helper skeleton.
+
+### Notable files
+
+- `login-mac/libs/libABSDK0630.jar`: The ABSDK 0630 binary dependency.
+- `login-mac/src/main/java/tr/dhee/abox/demo/data/LoginDataSource.java`: SDK login invocation.
+- `login-mac/src/main/java/tr/dhee/abox/demo/data/SwitchDataSource.java`: Socket control invocation.
+- `login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginActivity.java`: Login, password remembering, and automatic-login flow.
+- `login-mac/src/main/java/tr/dhee/abox/demo/ui/switchcontrol/SwitchControlActivity.java`: Socket control UI.
+- `login-mac/src/main/java/tr/dhee/abox/demo/utils/AboxConstants.java`: ABSDK error-code mapping.
+
+### Archive cleanup
+
+The following machine-specific state, build output, and sensitive files were excluded from the archive:
+
+- `.gradle/`, `.idea/`, and all `build/` directories;
+- `local.properties` and `.DS_Store`;
+- APKs, keystores, JKS files, backup files, and the corrupted JAR backup.
+
+### Usage limitations
+
+This directory is provided only as a historical implementation and SDK behavior reference. It does not represent the target architecture of ABox Manager and is not guaranteed to build with the current toolchain. Production code should not directly reuse the following practices:
+
+- `AsyncTask` or legacy thread management;
+- storing passwords in plain `SharedPreferences`;
+- globally allowing cleartext network traffic;
+- calling synchronous, blocking SDK APIs without an adapter.
+
+The production implementation should integrate ABSDK through a typed adapter, background coroutines, secure credential storage, and narrowly scoped network security configuration.
diff --git a/samples/abox-sdk-sample/build.gradle b/samples/abox-sdk-sample/build.gradle
new file mode 100644
index 0000000..3756278
--- /dev/null
+++ b/samples/abox-sdk-sample/build.gradle
@@ -0,0 +1,4 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ alias(libs.plugins.android.application) apply false
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/gradle.properties b/samples/abox-sdk-sample/gradle.properties
new file mode 100644
index 0000000..796c04c
--- /dev/null
+++ b/samples/abox-sdk-sample/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# When enabled, the Configuration Cache allows Gradle to skip the configuration
+# phase entirely if nothing that affects the build configuration (such as build scripts)
+# has changed. Additionally, Gradle applies performance optimizations to task execution.
+# AGP 8.7.3 的 data binding 任务不支持配置缓存序列化, 关闭以免构建失败。
+org.gradle.configuration-cache=false
+# 启用 AndroidX (模板依赖 androidx.* 库, 必须开启)
+android.useAndroidX=true
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/gradle/gradle-daemon-jvm.properties b/samples/abox-sdk-sample/gradle/gradle-daemon-jvm.properties
new file mode 100644
index 0000000..63e5bbd
--- /dev/null
+++ b/samples/abox-sdk-sample/gradle/gradle-daemon-jvm.properties
@@ -0,0 +1,2 @@
+#This file is generated by updateDaemonJvm
+toolchainVersion=21
diff --git a/samples/abox-sdk-sample/gradle/libs.versions.toml b/samples/abox-sdk-sample/gradle/libs.versions.toml
new file mode 100644
index 0000000..2c374be
--- /dev/null
+++ b/samples/abox-sdk-sample/gradle/libs.versions.toml
@@ -0,0 +1,31 @@
+[versions]
+agp = "8.7.3"
+junit = "4.13.2"
+junitVersion = "1.1.5"
+espressoCore = "3.5.1"
+appcompat = "1.6.1"
+material = "1.10.0"
+constraintlayout = "2.1.4"
+activityKtx = "1.8.0"
+navigationFragment = "2.6.0"
+navigationUi = "2.6.0"
+annotation = "1.6.0"
+lifecycleLivedataKtx = "2.6.1"
+lifecycleViewmodelKtx = "2.6.1"
+
+[libraries]
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" }
+navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "navigationFragment" }
+navigation-ui = { group = "androidx.navigation", name = "navigation-ui", version.ref = "navigationUi" }
+annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" }
+lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" }
+lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
diff --git a/samples/abox-sdk-sample/gradle/wrapper/gradle-wrapper.jar b/samples/abox-sdk-sample/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8bdaf60
Binary files /dev/null and b/samples/abox-sdk-sample/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/samples/abox-sdk-sample/gradle/wrapper/gradle-wrapper.properties b/samples/abox-sdk-sample/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..6e45cdb
--- /dev/null
+++ b/samples/abox-sdk-sample/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,8 @@
+#Tue Aug 11 15:31:21 CST 2026
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
+networkTimeout=10000
+validateDistributionUrl=false
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/samples/abox-sdk-sample/gradlew b/samples/abox-sdk-sample/gradlew
new file mode 100644
index 0000000..ef07e01
--- /dev/null
+++ b/samples/abox-sdk-sample/gradlew
@@ -0,0 +1,251 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH="\\\"\\\""
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/samples/abox-sdk-sample/gradlew.bat b/samples/abox-sdk-sample/gradlew.bat
new file mode 100644
index 0000000..db3a6ac
--- /dev/null
+++ b/samples/abox-sdk-sample/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/samples/abox-sdk-sample/login-mac/.gitignore b/samples/abox-sdk-sample/login-mac/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/build.gradle b/samples/abox-sdk-sample/login-mac/build.gradle
new file mode 100644
index 0000000..1e2fd90
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/build.gradle
@@ -0,0 +1,59 @@
+plugins {
+ alias(libs.plugins.android.application)
+}
+
+android {
+ namespace 'tr.dhee.abox.demo'
+ compileSdk 34
+ // SDK(libABSDK0630.jar) 内部依赖 Apache HttpClient(org.apache.http)。
+ // 自包含方案: 四个 Apache jar 直接打进 APK, 不依赖设备系统库(任意机型可装)。
+
+ defaultConfig {
+ applicationId "tr.dhee.abox.demo"
+ minSdk 21
+ targetSdk 34
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+ buildFeatures {
+ viewBinding true
+ }
+
+ // 多个 Apache HTTP 相关 jar 均带 META-INF 元数据文件, 打包进同一 APK 会冲突, 排除这些重复元数据(不影响运行时类)。
+ packagingOptions {
+ resources {
+ excludes += ['META-INF/DEPENDENCIES', 'META-INF/LICENSE', 'META-INF/LICENSE.txt',
+ 'META-INF/NOTICE', 'META-INF/NOTICE.txt', 'META-INF/ASL2.0',
+ 'META-INF/license.txt', 'META-INF/notice.txt']
+ }
+ }
+}
+
+dependencies {
+ // 引入 libs 下全部 jar: SDK(libABSDK0630.jar) + Apache HttpClient 四件套(自包含, 不依赖系统库)。
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation libs.activity.ktx
+ implementation libs.annotation
+ implementation libs.appcompat
+ implementation libs.constraintlayout
+ implementation libs.lifecycle.livedata.ktx
+ implementation libs.lifecycle.viewmodel.ktx
+ implementation libs.material
+ testImplementation libs.junit
+ androidTestImplementation libs.espresso.core
+ androidTestImplementation libs.ext.junit
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/libs/commons-codec-1.6.jar b/samples/abox-sdk-sample/login-mac/libs/commons-codec-1.6.jar
new file mode 100644
index 0000000..ee1bc49
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/libs/commons-codec-1.6.jar differ
diff --git a/samples/abox-sdk-sample/login-mac/libs/commons-logging-1.1.3.jar b/samples/abox-sdk-sample/login-mac/libs/commons-logging-1.1.3.jar
new file mode 100644
index 0000000..ab51254
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/libs/commons-logging-1.1.3.jar differ
diff --git a/samples/abox-sdk-sample/login-mac/libs/httpclient-4.3.6.jar b/samples/abox-sdk-sample/login-mac/libs/httpclient-4.3.6.jar
new file mode 100644
index 0000000..091498c
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/libs/httpclient-4.3.6.jar differ
diff --git a/samples/abox-sdk-sample/login-mac/libs/httpcore-4.3.3.jar b/samples/abox-sdk-sample/login-mac/libs/httpcore-4.3.3.jar
new file mode 100644
index 0000000..a8747b0
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/libs/httpcore-4.3.3.jar differ
diff --git a/samples/abox-sdk-sample/login-mac/libs/libABSDK0630.jar b/samples/abox-sdk-sample/login-mac/libs/libABSDK0630.jar
new file mode 100644
index 0000000..6a7d171
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/libs/libABSDK0630.jar differ
diff --git a/samples/abox-sdk-sample/login-mac/src/androidTest/java/tr/dhee/abox/demo/ExampleInstrumentedTest.java b/samples/abox-sdk-sample/login-mac/src/androidTest/java/tr/dhee/abox/demo/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..ee04483
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/androidTest/java/tr/dhee/abox/demo/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package tr.dhee.abox.demo;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("tr.dhee.abox.demo", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/AndroidManifest.xml b/samples/abox-sdk-sample/login-mac/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..8b57a18
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/LoginDataSource.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/LoginDataSource.java
new file mode 100644
index 0000000..d0a5109
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/LoginDataSource.java
@@ -0,0 +1,43 @@
+package tr.dhee.abox.demo.data;
+
+import tr.dhee.abox.demo.data.model.LoggedInUser;
+import tr.dhee.abox.demo.utils.AboxConstants;
+import com.dhc.absdk.ABRet;
+import com.dhc.absdk.ABSDK;
+
+import java.io.IOException;
+
+/**
+ * Class that handles authentication w/ login credentials and retrieves user information.
+ * 接 base 真登录: 调用 libABSDK0630.jar 的 ABSDK.getInstance().loginWithUsername()。
+ */
+public class LoginDataSource {
+
+ public Result login(String username, String password) {
+
+ try {
+ // base 登录核心: 同步阻塞网络调用, 在 LoginRepository 的后台线程执行(不会 ANR)
+ ABRet abRet = ABSDK.getInstance().loginWithUsername(username, password);
+ if (abRet != null && "00000".equals(abRet.getCode())) {
+ // 成功: 用用户名作为展示名(与 base 行为一致, base 未返回用户信息)
+ LoggedInUser user = new LoggedInUser(username, username);
+ return new Result.Success<>(user);
+ } else {
+ // 失败: 用 AboxConstants.codeMap 把返回码映射成中文提示
+ String code = abRet != null ? abRet.getCode() : "unknown";
+ String msg = AboxConstants.codeMap.get(code);
+ if (msg == null) {
+ msg = "登录失败: " + code;
+ }
+ return new Result.Error(new IOException(msg));
+ }
+ } catch (Exception e) {
+ // 网络异常 / SDK 初始化异常等
+ return new Result.Error(new IOException("登录异常: " + e.getMessage(), e));
+ }
+ }
+
+ public void logout() {
+ // TODO: revoke authentication
+ }
+}
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/LoginRepository.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/LoginRepository.java
new file mode 100644
index 0000000..5a67f35
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/LoginRepository.java
@@ -0,0 +1,66 @@
+package tr.dhee.abox.demo.data;
+
+import tr.dhee.abox.demo.data.model.LoggedInUser;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * Class that requests authentication and user information from the remote data source and
+ * maintains an in-memory cache of login status and user credentials information.
+ * 登录在后台线程执行(SDK 的 loginWithUsername 是同步网络调用, 不能在主线程跑, 否则 ANR)。
+ */
+public class LoginRepository {
+
+ private static volatile LoginRepository instance;
+
+ private LoginDataSource dataSource;
+ private final Executor executor = Executors.newSingleThreadExecutor();
+
+ // If user credentials will be cached in local storage, it is recommended it be encrypted
+ // @see https://developer.android.com/training/articles/keystore
+ private LoggedInUser user = null;
+
+ // private constructor : singleton access
+ private LoginRepository(LoginDataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ public static LoginRepository getInstance(LoginDataSource dataSource) {
+ if (instance == null) {
+ instance = new LoginRepository(dataSource);
+ }
+ return instance;
+ }
+
+ public boolean isLoggedIn() {
+ return user != null;
+ }
+
+ public void logout() {
+ user = null;
+ dataSource.logout();
+ }
+
+ private void setLoggedInUser(LoggedInUser user) {
+ this.user = user;
+ // If user credentials will be cached in local storage, it is recommended it be encrypted
+ // @see https://developer.android.com/training/articles/keystore
+ }
+
+ public void login(String username, String password, LoginCallback callback) {
+ executor.execute(() -> {
+ Result result = dataSource.login(username, password);
+ if (result instanceof Result.Success) {
+ setLoggedInUser(((Result.Success) result).getData());
+ }
+ if (callback != null) {
+ callback.onComplete(result);
+ }
+ });
+ }
+
+ public interface LoginCallback {
+ void onComplete(Result result);
+ }
+}
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/Result.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/Result.java
new file mode 100644
index 0000000..8547cd0
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/Result.java
@@ -0,0 +1,48 @@
+package tr.dhee.abox.demo.data;
+
+/**
+ * A generic class that holds a result success w/ data or an error exception.
+ */
+public class Result {
+ // hide the private constructor to limit subclass types (Success, Error)
+ private Result() {
+ }
+
+ @Override
+ public String toString() {
+ if (this instanceof Result.Success) {
+ Result.Success success = (Result.Success) this;
+ return "Success[data=" + success.getData().toString() + "]";
+ } else if (this instanceof Result.Error) {
+ Result.Error error = (Result.Error) this;
+ return "Error[exception=" + error.getError().toString() + "]";
+ }
+ return "";
+ }
+
+ // Success sub-class
+ public final static class Success extends Result {
+ private T data;
+
+ public Success(T data) {
+ this.data = data;
+ }
+
+ public T getData() {
+ return this.data;
+ }
+ }
+
+ // Error sub-class
+ public final static class Error extends Result {
+ private Exception error;
+
+ public Error(Exception error) {
+ this.error = error;
+ }
+
+ public Exception getError() {
+ return this.error;
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/SwitchDataSource.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/SwitchDataSource.java
new file mode 100644
index 0000000..4192a9a
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/SwitchDataSource.java
@@ -0,0 +1,45 @@
+package tr.dhee.abox.demo.data;
+
+import com.dhc.absdk.ABRet;
+import com.dhc.absdk.ABSDK;
+import tr.dhee.abox.demo.utils.AboxConstants;
+
+import java.io.IOException;
+
+/**
+ * Class that handles socket device control via sockCtrl API.
+ * Must be called after login (ABSDK already has the session).
+ */
+public class SwitchDataSource {
+
+ public Result sockCtrl(String soDevName, String status) {
+
+ try {
+ // 同步阻塞调用 SDK 的 sockCtrl 接口, 在 SwitchRepository 的后台线程执行
+ ABRet abRet = ABSDK.getInstance().sockCtrl(soDevName, status);
+ if (abRet != null && "00000".equals(abRet.getCode())) {
+ // 成功: 返回状态描述
+ String desc = "操作成功: " + status;
+ return new Result.Success<>(desc);
+ } else {
+ // 失败: 映射返回码到中文提示, 并始终携带原始错误码
+ String code = abRet != null ? abRet.getCode() : "unknown";
+ String abMsg = abRet != null ? abRet.getMsg() : "";
+ String mapped = AboxConstants.codeMap.get(code);
+ String msg;
+ if (mapped != null) {
+ msg = mapped + " (" + code + ")";
+ } else if (abMsg != null && !abMsg.isEmpty()) {
+ msg = abMsg + " (" + code + ")";
+ } else {
+ msg = "控制失败: " + code;
+ }
+ return new Result.Error(new IOException(msg));
+ }
+ } catch (Exception e) {
+ // 网络异常 / SDK 异常等
+ String detail = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName();
+ return new Result.Error(new IOException("控制异常: " + detail, e));
+ }
+ }
+}
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/SwitchRepository.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/SwitchRepository.java
new file mode 100644
index 0000000..37f0846
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/SwitchRepository.java
@@ -0,0 +1,55 @@
+package tr.dhee.abox.demo.data;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * Class that handles socket device control via SwitchDataSource.
+ * Executes SDK calls on a background thread to avoid ANR.
+ */
+public class SwitchRepository {
+
+ private static volatile SwitchRepository instance;
+
+ private final SwitchDataSource dataSource;
+ private final Executor executor = Executors.newSingleThreadExecutor();
+
+ private SwitchRepository(SwitchDataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ public static SwitchRepository getInstance(SwitchDataSource dataSource) {
+ if (instance == null) {
+ instance = new SwitchRepository(dataSource);
+ }
+ return instance;
+ }
+
+ /**
+ * 控制插座开关, 在后台线程执行 SDK 调用
+ *
+ * @param soDevName 插座设备名称
+ * @param status 状态: "1"(开) 或 "0"(关)
+ * @param callback 结果回调 (在主线程调用)
+ */
+ public void sockCtrl(String soDevName, String status, SwitchCallback callback) {
+ executor.execute(() -> {
+ Result result = dataSource.sockCtrl(soDevName, status);
+ if (callback != null) {
+ callback.onComplete(result);
+ }
+ });
+
+
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+
+ }
+ });
+ }
+
+ public interface SwitchCallback {
+ void onComplete(Result result);
+ }
+}
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/model/LoggedInUser.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/model/LoggedInUser.java
new file mode 100644
index 0000000..ebf36c4
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/data/model/LoggedInUser.java
@@ -0,0 +1,23 @@
+package tr.dhee.abox.demo.data.model;
+
+/**
+ * Data class that captures user information for logged in users retrieved from LoginRepository
+ */
+public class LoggedInUser {
+
+ private String userId;
+ private String displayName;
+
+ public LoggedInUser(String userId, String displayName) {
+ this.userId = userId;
+ this.displayName = displayName;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoggedInUserView.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoggedInUserView.java
new file mode 100644
index 0000000..624de7c
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoggedInUserView.java
@@ -0,0 +1,17 @@
+package tr.dhee.abox.demo.ui.login;
+
+/**
+ * Class exposing authenticated user details to the UI.
+ */
+class LoggedInUserView {
+ private String displayName;
+ //... other data fields that may be accessible to the UI
+
+ LoggedInUserView(String displayName) {
+ this.displayName = displayName;
+ }
+
+ String getDisplayName() {
+ return displayName;
+ }
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginActivity.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginActivity.java
new file mode 100644
index 0000000..33a8f29
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginActivity.java
@@ -0,0 +1,173 @@
+package tr.dhee.abox.demo.ui.login;
+
+import androidx.activity.EdgeToEdge;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProvider;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import tr.dhee.abox.demo.R;
+import tr.dhee.abox.demo.ui.switchcontrol.SwitchControlActivity;
+import tr.dhee.abox.demo.databinding.ActivityLoginBinding;
+import tr.dhee.abox.demo.utils.AboxConstants;
+
+public class LoginActivity extends AppCompatActivity {
+
+ private LoginViewModel loginViewModel;
+ private ActivityLoginBinding binding;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ EdgeToEdge.enable(this);
+
+ binding = ActivityLoginBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ ViewCompat.setOnApplyWindowInsetsListener(binding.main, (v, insets) -> {
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
+ return insets;
+ });
+ loginViewModel = new ViewModelProvider(this, new LoginViewModelFactory())
+ .get(LoginViewModel.class);
+
+ final EditText usernameEditText = binding.username;
+ final EditText passwordEditText = binding.password;
+ final CheckBox checkBoxAutoLogin = binding.checkBoxAutoLogin;
+ final Button loginButton = binding.login;
+ final ProgressBar loadingProgressBar = binding.loading;
+
+ // 自动登录: 读取上次保存的凭据填充, 若勾选过"记住密码"则直接发起登录
+ SharedPreferences sp = getSharedPreferences(AboxConstants.AUTOLOGINFILE, Context.MODE_PRIVATE);
+ boolean autoFlag = sp.getBoolean(AboxConstants.AutoLoginClass.AUTOLOGIN_FLAG, false);
+ String savedUser = sp.getString(AboxConstants.AutoLoginClass.AUTOLOGIN_USERNAME, "");
+ String savedPwd = sp.getString(AboxConstants.AutoLoginClass.AUTOLOGIN_USERPWD, "");
+ if (!savedUser.isEmpty()) {
+ usernameEditText.setText(savedUser);
+ passwordEditText.setText(savedPwd);
+ checkBoxAutoLogin.setChecked(autoFlag);
+ // 显式触发表单校验, 确保预填充凭据后登录按钮可用
+ loginViewModel.loginDataChanged(savedUser, savedPwd);
+ if (autoFlag && !savedPwd.isEmpty()) {
+ // 延迟一帧, 等观察者注册完再自动登录
+ usernameEditText.post(() -> loginViewModel.login(savedUser, savedPwd));
+ }
+ }
+
+ loginViewModel.getLoginFormState().observe(this, new Observer() {
+ @Override
+ public void onChanged(@Nullable LoginFormState loginFormState) {
+ if (loginFormState == null) {
+ return;
+ }
+ loginButton.setEnabled(loginFormState.isDataValid());
+ if (loginFormState.getUsernameError() != null) {
+ usernameEditText.setError(getString(loginFormState.getUsernameError()));
+ }
+ if (loginFormState.getPasswordError() != null) {
+ passwordEditText.setError(getString(loginFormState.getPasswordError()));
+ }
+ }
+ });
+
+ loginViewModel.getLoginResult().observe(this, new Observer() {
+ @Override
+ public void onChanged(@Nullable LoginResult loginResult) {
+ if (loginResult == null) {
+ return;
+ }
+ loadingProgressBar.setVisibility(View.GONE);
+ if (loginResult.getError() != null) {
+ showLoginFailed(loginResult.getError());
+ }
+ if (loginResult.getSuccess() != null) {
+ // 登录成功: 按勾选状态保存凭据到 SharedPreferences(与 base 行为一致)
+ String user = usernameEditText.getText().toString();
+ String pwd = passwordEditText.getText().toString();
+ boolean remember = checkBoxAutoLogin.isChecked();
+ SharedPreferences sp = getSharedPreferences(AboxConstants.AUTOLOGINFILE, Context.MODE_PRIVATE);
+ sp.edit().putString(AboxConstants.AutoLoginClass.AUTOLOGIN_USERNAME, user)
+ .putString(AboxConstants.AutoLoginClass.AUTOLOGIN_USERPWD, pwd)
+ .putBoolean(AboxConstants.AutoLoginClass.AUTOLOGIN_FLAG, remember)
+ .apply();
+ updateUiWithUser(loginResult.getSuccess());
+ // 跳转到插座控制页面
+ Intent intent = new Intent(LoginActivity.this, SwitchControlActivity.class);
+ startActivity(intent);
+ finish();
+ }
+ }
+ });
+
+ TextWatcher afterTextChangedListener = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // ignore
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // ignore
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ loginViewModel.loginDataChanged(usernameEditText.getText().toString(),
+ passwordEditText.getText().toString());
+ }
+ };
+ usernameEditText.addTextChangedListener(afterTextChangedListener);
+ passwordEditText.addTextChangedListener(afterTextChangedListener);
+ passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ loginViewModel.login(usernameEditText.getText().toString(),
+ passwordEditText.getText().toString());
+ }
+ return false;
+ }
+ });
+
+ loginButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ loadingProgressBar.setVisibility(View.VISIBLE);
+ loginViewModel.login(usernameEditText.getText().toString(),
+ passwordEditText.getText().toString());
+ }
+ });
+ }
+
+ private void updateUiWithUser(LoggedInUserView model) {
+ String welcome = getString(R.string.welcome) + model.getDisplayName();
+ Toast.makeText(getApplicationContext(), welcome, Toast.LENGTH_LONG).show();
+ }
+
+ private void showLoginFailed(String errorString) {
+ Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show();
+ }
+}
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginFormState.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginFormState.java
new file mode 100644
index 0000000..b326e73
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginFormState.java
@@ -0,0 +1,40 @@
+package tr.dhee.abox.demo.ui.login;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Data validation state of the login form.
+ */
+class LoginFormState {
+ @Nullable
+ private Integer usernameError;
+ @Nullable
+ private Integer passwordError;
+ private boolean isDataValid;
+
+ LoginFormState(@Nullable Integer usernameError, @Nullable Integer passwordError) {
+ this.usernameError = usernameError;
+ this.passwordError = passwordError;
+ this.isDataValid = false;
+ }
+
+ LoginFormState(boolean isDataValid) {
+ this.usernameError = null;
+ this.passwordError = null;
+ this.isDataValid = isDataValid;
+ }
+
+ @Nullable
+ Integer getUsernameError() {
+ return usernameError;
+ }
+
+ @Nullable
+ Integer getPasswordError() {
+ return passwordError;
+ }
+
+ boolean isDataValid() {
+ return isDataValid;
+ }
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginResult.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginResult.java
new file mode 100644
index 0000000..5337c55
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginResult.java
@@ -0,0 +1,31 @@
+package tr.dhee.abox.demo.ui.login;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Authentication result : success (user details) or error message.
+ */
+class LoginResult {
+ @Nullable
+ private LoggedInUserView success;
+ @Nullable
+ private String error;
+
+ LoginResult(@Nullable String error) {
+ this.error = error;
+ }
+
+ LoginResult(@Nullable LoggedInUserView success) {
+ this.success = success;
+ }
+
+ @Nullable
+ LoggedInUserView getSuccess() {
+ return success;
+ }
+
+ @Nullable
+ String getError() {
+ return error;
+ }
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginViewModel.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginViewModel.java
new file mode 100644
index 0000000..086c3b6
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginViewModel.java
@@ -0,0 +1,64 @@
+package tr.dhee.abox.demo.ui.login;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+import tr.dhee.abox.demo.R;
+import tr.dhee.abox.demo.data.LoginRepository;
+import tr.dhee.abox.demo.data.Result;
+import tr.dhee.abox.demo.data.model.LoggedInUser;
+
+public class LoginViewModel extends ViewModel {
+
+ private MutableLiveData loginFormState = new MutableLiveData<>();
+ private MutableLiveData loginResult = new MutableLiveData<>();
+ private LoginRepository loginRepository;
+
+ LoginViewModel(LoginRepository loginRepository) {
+ this.loginRepository = loginRepository;
+ }
+
+ LiveData getLoginFormState() {
+ return loginFormState;
+ }
+
+ LiveData getLoginResult() {
+ return loginResult;
+ }
+
+ public void login(String username, String password) {
+ // 后台线程执行 SDK 登录, 结果回到主线程再更新 LiveData
+ loginRepository.login(username, password, result -> {
+ if (result instanceof Result.Success) {
+ LoggedInUser data = ((Result.Success) result).getData();
+ loginResult.postValue(new LoginResult(new LoggedInUserView(data.getDisplayName())));
+ } else {
+ // 取出 LoginDataSource 里封装的中文错误提示
+ Exception err = ((Result.Error) result).getError();
+ loginResult.postValue(new LoginResult(err != null ? err.getMessage() : "登录失败"));
+ }
+ });
+
+ }
+
+ public void loginDataChanged(String username, String password) {
+ if (!isUserNameValid(username)) {
+ loginFormState.setValue(new LoginFormState(R.string.invalid_username, null));
+ } else if (!isPasswordValid(password)) {
+ loginFormState.setValue(new LoginFormState(null, R.string.invalid_password));
+ } else {
+ loginFormState.setValue(new LoginFormState(true));
+ }
+ }
+
+ // 仅校验不为空
+ private boolean isUserNameValid(String username) {
+ return username != null && !username.trim().isEmpty();
+ }
+
+ // 仅校验不为空
+ private boolean isPasswordValid(String password) {
+ return password != null && !password.trim().isEmpty();
+ }
+}
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginViewModelFactory.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginViewModelFactory.java
new file mode 100644
index 0000000..f1f58b0
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/login/LoginViewModelFactory.java
@@ -0,0 +1,26 @@
+package tr.dhee.abox.demo.ui.login;
+
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.annotation.NonNull;
+
+import tr.dhee.abox.demo.data.LoginDataSource;
+import tr.dhee.abox.demo.data.LoginRepository;
+
+/**
+ * ViewModel provider factory to instantiate LoginViewModel.
+ * Required given LoginViewModel has a non-empty constructor
+ */
+public class LoginViewModelFactory implements ViewModelProvider.Factory {
+
+ @NonNull
+ @Override
+ @SuppressWarnings("unchecked")
+ public T create(@NonNull Class modelClass) {
+ if (modelClass.isAssignableFrom(LoginViewModel.class)) {
+ return (T) new LoginViewModel(LoginRepository.getInstance(new LoginDataSource()));
+ } else {
+ throw new IllegalArgumentException("Unknown ViewModel class");
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/switchcontrol/SwitchControlActivity.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/switchcontrol/SwitchControlActivity.java
new file mode 100644
index 0000000..c4f2dc5
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/switchcontrol/SwitchControlActivity.java
@@ -0,0 +1,91 @@
+package tr.dhee.abox.demo.ui.switchcontrol;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.activity.EdgeToEdge;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.ContextCompat;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+import androidx.lifecycle.ViewModelProvider;
+
+import tr.dhee.abox.demo.databinding.ActivitySwitchControlBinding;
+
+public class SwitchControlActivity extends AppCompatActivity {
+
+ private SwitchViewModel switchViewModel;
+ private ActivitySwitchControlBinding binding;
+
+ // 设备名称, 可根据实际需要调整
+ private static final String DEVICE_NAME = "智能插座";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ EdgeToEdge.enable(this);
+
+ binding = ActivitySwitchControlBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ ViewCompat.setOnApplyWindowInsetsListener(binding.main, (v, insets) -> {
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
+ return insets;
+ });
+
+ switchViewModel = new ViewModelProvider(this, new SwitchViewModelFactory())
+ .get(SwitchViewModel.class);
+
+ final Button btnOn = binding.btnOn;
+ final Button btnOff = binding.btnOff;
+ final ProgressBar loadingProgressBar = binding.loading;
+ final TextView statusText = binding.statusText;
+
+ // 观察控制结果
+ switchViewModel.getSwitchResult().observe(this, switchResult -> {
+ if (switchResult == null) {
+ return;
+ }
+ loadingProgressBar.setVisibility(View.GONE);
+ btnOn.setEnabled(true);
+ btnOff.setEnabled(true);
+
+ if (switchResult.getSuccess() != null) {
+ statusText.setVisibility(View.VISIBLE);
+ statusText.setText(switchResult.getSuccess());
+ statusText.setTextColor(ContextCompat.getColor(SwitchControlActivity.this,
+ android.R.color.holo_green_dark));
+ Toast.makeText(this, switchResult.getSuccess(), Toast.LENGTH_SHORT).show();
+ }
+ if (switchResult.getError() != null) {
+ statusText.setVisibility(View.VISIBLE);
+ statusText.setText(switchResult.getError());
+ statusText.setTextColor(ContextCompat.getColor(SwitchControlActivity.this,
+ android.R.color.holo_red_dark));
+ Toast.makeText(this, switchResult.getError(), Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ // "开" 按钮
+ btnOn.setOnClickListener(v -> {
+ loadingProgressBar.setVisibility(View.VISIBLE);
+ btnOn.setEnabled(false);
+ btnOff.setEnabled(false);
+ switchViewModel.controlSwitch(DEVICE_NAME, "1");
+ });
+
+ // "关" 按钮
+ btnOff.setOnClickListener(v -> {
+ loadingProgressBar.setVisibility(View.VISIBLE);
+ btnOn.setEnabled(false);
+ btnOff.setEnabled(false);
+ switchViewModel.controlSwitch(DEVICE_NAME, "0");
+ });
+ }
+}
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/switchcontrol/SwitchResult.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/switchcontrol/SwitchResult.java
new file mode 100644
index 0000000..91b28b2
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/switchcontrol/SwitchResult.java
@@ -0,0 +1,28 @@
+package tr.dhee.abox.demo.ui.switchcontrol;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Switch control result: success message or error message.
+ */
+class SwitchResult {
+ @Nullable
+ private String success;
+ @Nullable
+ private String error;
+
+ SwitchResult(@Nullable String success, @Nullable String error) {
+ this.success = success;
+ this.error = error;
+ }
+
+ @Nullable
+ String getSuccess() {
+ return success;
+ }
+
+ @Nullable
+ String getError() {
+ return error;
+ }
+}
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/switchcontrol/SwitchViewModel.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/switchcontrol/SwitchViewModel.java
new file mode 100644
index 0000000..029a6da
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/switchcontrol/SwitchViewModel.java
@@ -0,0 +1,41 @@
+package tr.dhee.abox.demo.ui.switchcontrol;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+import tr.dhee.abox.demo.data.Result;
+import tr.dhee.abox.demo.data.SwitchRepository;
+
+public class SwitchViewModel extends ViewModel {
+
+ private final MutableLiveData switchResult = new MutableLiveData<>();
+ private final SwitchRepository switchRepository;
+
+ SwitchViewModel(SwitchRepository switchRepository) {
+ this.switchRepository = switchRepository;
+ }
+
+ LiveData getSwitchResult() {
+ return switchResult;
+ }
+
+ /**
+ * 控制插座开关
+ *
+ * @param soDevName 插座设备名称
+ * @param status 状态: "1"(开) 或 "0"(关)
+ */
+ public void controlSwitch(String soDevName, String status) {
+ switchRepository.sockCtrl(soDevName, status, result -> {
+ if (result instanceof Result.Success) {
+ String data = ((Result.Success) result).getData();
+ switchResult.postValue(new SwitchResult(data, null));
+ } else {
+ Exception err = ((Result.Error) result).getError();
+ switchResult.postValue(new SwitchResult(null,
+ err != null ? err.getMessage() : "控制失败"));
+ }
+ });
+ }
+}
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/switchcontrol/SwitchViewModelFactory.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/switchcontrol/SwitchViewModelFactory.java
new file mode 100644
index 0000000..f93b325
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/ui/switchcontrol/SwitchViewModelFactory.java
@@ -0,0 +1,25 @@
+package tr.dhee.abox.demo.ui.switchcontrol;
+
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.annotation.NonNull;
+
+import tr.dhee.abox.demo.data.SwitchDataSource;
+import tr.dhee.abox.demo.data.SwitchRepository;
+
+/**
+ * ViewModel provider factory to instantiate SwitchViewModel.
+ */
+public class SwitchViewModelFactory implements ViewModelProvider.Factory {
+
+ @NonNull
+ @Override
+ @SuppressWarnings("unchecked")
+ public T create(@NonNull Class modelClass) {
+ if (modelClass.isAssignableFrom(SwitchViewModel.class)) {
+ return (T) new SwitchViewModel(SwitchRepository.getInstance(new SwitchDataSource()));
+ } else {
+ throw new IllegalArgumentException("Unknown ViewModel class");
+ }
+ }
+}
diff --git a/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/utils/AboxConstants.java b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/utils/AboxConstants.java
new file mode 100644
index 0000000..e624afe
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/java/tr/dhee/abox/demo/utils/AboxConstants.java
@@ -0,0 +1,71 @@
+package tr.dhee.abox.demo.utils;
+
+import java.util.HashMap;
+
+/**
+ * 登录相关常量: SDK 返回码 -> 中文提示; 自动登录 SharedPreferences key。
+ * 移植自 base 工程的 AboxConstants(仅保留登录部分)。
+ */
+public class AboxConstants {
+
+ public static final HashMap codeMap = new HashMap();
+ public static final String AUTOLOGINFILE = "autoLogin";
+
+ public static class AutoLoginClass {
+ public static final String AUTOLOGIN_USERNAME = "userName";
+ public static final String AUTOLOGIN_USERPWD = "userPwd";
+ public static final String AUTOLOGIN_FLAG = "flag";
+ }
+
+ static {
+ codeMap.put("00000", "处理成功");
+ codeMap.put("00001", "数据库操作失败");
+ codeMap.put("10001", "TOKEN不存在或长度不足");
+ codeMap.put("10002", "TOKEN已失效");
+ codeMap.put("10003", "TOKEN中的SN号不正");
+ codeMap.put("10004", "签名不存在");
+ codeMap.put("10005", "时间戳不存在");
+ codeMap.put("10006", "签名不正确");
+ codeMap.put("20000", "请求超时");
+ codeMap.put("20001", "用户名或密码不存在");
+ codeMap.put("20002", "用户不存在或已暂停使用");
+ codeMap.put("20003", "登录失败次数过多");
+ codeMap.put("20004", "用户名或密码不正确");
+ codeMap.put("20005", "本地用户权限不足");
+ codeMap.put("20101", "服务器端URL或酒店名或房间号或APIKEY不存在");
+ codeMap.put("20201", "红外KEY不存在");
+ codeMap.put("20202", "红外码值不存在");
+ codeMap.put("20203", "红外设备不存在");
+ codeMap.put("20204", "红外码库导入失败");
+ codeMap.put("20205", "红外发射失败");
+ codeMap.put("20206", "红外设备已离线");
+ codeMap.put("20301", "参数不正_CMD0不存在");
+ codeMap.put("20302", "参数不正_CMD1不存在");
+ codeMap.put("20303", "参数不正_PAYLOAD不存在");
+ codeMap.put("20304", "窗帘设备不存在");
+ codeMap.put("20305", "窗帘设备控制失败");
+ codeMap.put("20306", "窗帘设备已离线");
+ codeMap.put("20501", "参数不正_插座状态不存在");
+ codeMap.put("20502", "插座设备不存在");
+ codeMap.put("20503", "插座设备控制失败");
+ codeMap.put("20504", "插座设备已离线");
+ codeMap.put("20601", "参数不正_开关状态不存在");
+ codeMap.put("20602", "参数不正_开关类型不存在(标识几开开关)");
+ codeMap.put("20603", "开关设备不存在");
+ codeMap.put("20604", "开关设备控制失败");
+ codeMap.put("20605", "开关设备已掉线");
+ codeMap.put("20701", "参数不正_射灯操作类型不存在");
+ codeMap.put("20702", "参数不正_射灯操作类型不正确");
+ codeMap.put("20703", "参数不正_射灯开关状态不存在");
+ codeMap.put("20704", "参数不正_射灯色调饱和度不存在");
+ codeMap.put("20705", "参数不正_射灯亮度不存在");
+ codeMap.put("20706", "射灯设备不存在");
+ codeMap.put("20707", "射灯设备控制失败");
+ codeMap.put("20801", "参数不正_设备名称不存在");
+ codeMap.put("20802", "端末设备不存在");
+ codeMap.put("20803", "端末状态属性不存在");
+ codeMap.put("20804", "参数不正_报警时长不正确");
+ codeMap.put("20805", "端末设备已离线");
+ codeMap.put("20806", "设备控制失败");
+ }
+}
diff --git a/samples/abox-sdk-sample/login-mac/src/main/keepRules/rules.keep b/samples/abox-sdk-sample/login-mac/src/main/keepRules/rules.keep
new file mode 100644
index 0000000..d7e081a
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/keepRules/rules.keep
@@ -0,0 +1,12 @@
+# Add project specific R8 rules here.
+# AGP will combine all keep rule files in src/main/keepRules to pass to R8
+#
+# For more details, see
+# https://d.android.com/r/tools/r8/keep-rules
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/drawable/ic_launcher_background.xml b/samples/abox-sdk-sample/login-mac/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/drawable/ic_launcher_foreground.xml b/samples/abox-sdk-sample/login-mac/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/layout-w1240dp/activity_login.xml b/samples/abox-sdk-sample/login-mac/src/main/res/layout-w1240dp/activity_login.xml
new file mode 100644
index 0000000..7528c5b
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/layout-w1240dp/activity_login.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/layout-w936dp/activity_login.xml b/samples/abox-sdk-sample/login-mac/src/main/res/layout-w936dp/activity_login.xml
new file mode 100644
index 0000000..3aaa007
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/layout-w936dp/activity_login.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/layout/activity_login.xml b/samples/abox-sdk-sample/login-mac/src/main/res/layout/activity_login.xml
new file mode 100644
index 0000000..0fc5100
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/layout/activity_login.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/layout/activity_switch_control.xml b/samples/abox-sdk-sample/login-mac/src/main/res/layout/activity_switch_control.xml
new file mode 100644
index 0000000..6cc1374
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/layout/activity_switch_control.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-hdpi/ic_launcher.webp b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-mdpi/ic_launcher.webp b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xhdpi/ic_launcher.webp b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/samples/abox-sdk-sample/login-mac/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/values-land/dimens.xml b/samples/abox-sdk-sample/login-mac/src/main/res/values-land/dimens.xml
new file mode 100644
index 0000000..5f681ae
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/values-land/dimens.xml
@@ -0,0 +1,3 @@
+
+ 48dp
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/values-night/themes.xml b/samples/abox-sdk-sample/login-mac/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..5944a24
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/values-w1240dp/dimens.xml b/samples/abox-sdk-sample/login-mac/src/main/res/values-w1240dp/dimens.xml
new file mode 100644
index 0000000..7e06511
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/values-w1240dp/dimens.xml
@@ -0,0 +1,3 @@
+
+ 200dp
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/values-w600dp/dimens.xml b/samples/abox-sdk-sample/login-mac/src/main/res/values-w600dp/dimens.xml
new file mode 100644
index 0000000..5f681ae
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/values-w600dp/dimens.xml
@@ -0,0 +1,3 @@
+
+ 48dp
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/values/colors.xml b/samples/abox-sdk-sample/login-mac/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/values/dimens.xml b/samples/abox-sdk-sample/login-mac/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..e00c2dd
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/values/strings.xml b/samples/abox-sdk-sample/login-mac/src/main/res/values/strings.xml
new file mode 100644
index 0000000..a3d6f72
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/values/strings.xml
@@ -0,0 +1,16 @@
+
+ login
+
+ Email
+ Password
+ Sign in or register
+ Sign in
+ "Welcome !"
+ 用户名不能为空
+ 密码不能为空
+ "Login failed"
+ 记住密码
+ 插座控制
+ 开
+ 关
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/main/res/values/themes.xml b/samples/abox-sdk-sample/login-mac/src/main/res/values/themes.xml
new file mode 100644
index 0000000..b50d182
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/login-mac/src/test/java/tr/dhee/abox/demo/ExampleUnitTest.java b/samples/abox-sdk-sample/login-mac/src/test/java/tr/dhee/abox/demo/ExampleUnitTest.java
new file mode 100644
index 0000000..5ecc420
--- /dev/null
+++ b/samples/abox-sdk-sample/login-mac/src/test/java/tr/dhee/abox/demo/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package tr.dhee.abox.demo;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/.gitignore b/samples/abox-sdk-sample/myapplication/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/build.gradle b/samples/abox-sdk-sample/myapplication/build.gradle
new file mode 100644
index 0000000..773bc62
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/build.gradle
@@ -0,0 +1,44 @@
+plugins {
+ alias(libs.plugins.android.application)
+}
+
+android {
+ namespace 'com.example.myapplication'
+ compileSdk 34
+
+ defaultConfig {
+ applicationId "com.example.myapplication"
+ minSdk 34
+ targetSdk 34
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+ buildFeatures {
+ viewBinding true
+ }
+}
+
+dependencies {
+ implementation libs.activity.ktx
+ implementation libs.appcompat
+ implementation libs.constraintlayout
+ implementation libs.material
+ implementation libs.navigation.fragment
+ implementation libs.navigation.ui
+ testImplementation libs.junit
+ androidTestImplementation libs.espresso.core
+ androidTestImplementation libs.ext.junit
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/proguard-rules.pro b/samples/abox-sdk-sample/myapplication/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.java b/samples/abox-sdk-sample/myapplication/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..982ba51
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.example.myapplication;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("com.example.myapplication", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/AndroidManifest.xml b/samples/abox-sdk-sample/myapplication/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e1eb3b4
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/AboxSqliteOpenHelper.java b/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/AboxSqliteOpenHelper.java
new file mode 100644
index 0000000..9a9bfbf
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/AboxSqliteOpenHelper.java
@@ -0,0 +1,22 @@
+package com.example.myapplication;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import androidx.annotation.Nullable;
+
+public class AboxSqliteOpenHelper extends SQLiteOpenHelper {
+ public AboxSqliteOpenHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
+ super(context, name, factory, version);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+
+ }
+}
diff --git a/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/FirstFragment.java b/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/FirstFragment.java
new file mode 100644
index 0000000..d6a47af
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/FirstFragment.java
@@ -0,0 +1,44 @@
+package com.example.myapplication;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.navigation.fragment.NavHostFragment;
+
+import com.example.myapplication.databinding.FragmentFirstBinding;
+
+public class FirstFragment extends Fragment {
+
+ private FragmentFirstBinding binding;
+
+ @Override
+ public View onCreateView(
+ @NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState
+ ) {
+
+ binding = FragmentFirstBinding.inflate(inflater, container, false);
+ return binding.getRoot();
+
+ }
+
+ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ binding.buttonFirst.setOnClickListener(v ->
+ NavHostFragment.findNavController(FirstFragment.this)
+ .navigate(R.id.action_FirstFragment_to_SecondFragment)
+ );
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/MainActivity.java b/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/MainActivity.java
new file mode 100644
index 0000000..57cf059
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/MainActivity.java
@@ -0,0 +1,92 @@
+package com.example.myapplication;
+
+import android.os.Bundle;
+
+import androidx.activity.EdgeToEdge;
+
+import com.google.android.material.snackbar.Snackbar;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+import androidx.navigation.NavController;
+import androidx.navigation.ui.AppBarConfiguration;
+import androidx.navigation.ui.NavigationUI;
+import androidx.navigation.fragment.NavHostFragment;
+
+import com.example.myapplication.databinding.ActivityMainBinding;
+
+import android.view.Menu;
+import android.view.MenuItem;
+
+public class MainActivity extends AppCompatActivity {
+
+ private AppBarConfiguration appBarConfiguration;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ EdgeToEdge.enable(this);
+
+ ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ ViewCompat.setOnApplyWindowInsetsListener(binding.main, (v, insets) -> {
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
+ return insets;
+ });
+ setSupportActionBar(binding.toolbar);
+
+ NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.nav_host_fragment_content_main);
+
+ if (navHostFragment != null) {
+ NavController navController = navHostFragment.getNavController();
+
+ appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
+ NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
+ }
+
+ binding.fab.setOnClickListener(
+ view -> Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
+ .setAnchorView(R.id.fab)
+ .setAction("Action", null).show()
+ );
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.nav_host_fragment_content_main);
+ boolean handled = false;
+ if (navHostFragment != null) {
+ NavController navController = navHostFragment.getNavController();
+ handled = NavigationUI.navigateUp(navController, appBarConfiguration);
+ }
+ return handled || super.onSupportNavigateUp();
+ }
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/MainActivity2.java b/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/MainActivity2.java
new file mode 100644
index 0000000..70a1c29
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/MainActivity2.java
@@ -0,0 +1,24 @@
+package com.example.myapplication;
+
+import android.os.Bundle;
+
+import androidx.activity.EdgeToEdge;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
+public class MainActivity2 extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ EdgeToEdge.enable(this);
+ setContentView(R.layout.activity_main2);
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
+ return insets;
+ });
+ }
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/SecondFragment.java b/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/SecondFragment.java
new file mode 100644
index 0000000..aee6700
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/java/com/example/myapplication/SecondFragment.java
@@ -0,0 +1,44 @@
+package com.example.myapplication;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.navigation.fragment.NavHostFragment;
+
+import com.example.myapplication.databinding.FragmentSecondBinding;
+
+public class SecondFragment extends Fragment {
+
+ private FragmentSecondBinding binding;
+
+ @Override
+ public View onCreateView(
+ @NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState
+ ) {
+
+ binding = FragmentSecondBinding.inflate(inflater, container, false);
+ return binding.getRoot();
+
+ }
+
+ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ binding.buttonSecond.setOnClickListener(v ->
+ NavHostFragment.findNavController(SecondFragment.this)
+ .navigate(R.id.action_SecondFragment_to_FirstFragment)
+ );
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/drawable/ic_launcher_background.xml b/samples/abox-sdk-sample/myapplication/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/drawable/ic_launcher_foreground.xml b/samples/abox-sdk-sample/myapplication/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/layout/activity_main.xml b/samples/abox-sdk-sample/myapplication/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..545127c
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/layout/activity_main.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/layout/activity_main2.xml b/samples/abox-sdk-sample/myapplication/src/main/res/layout/activity_main2.xml
new file mode 100644
index 0000000..c31d2e3
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/layout/activity_main2.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/layout/content_main.xml b/samples/abox-sdk-sample/myapplication/src/main/res/layout/content_main.xml
new file mode 100644
index 0000000..740fd45
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/layout/content_main.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/layout/fragment_first.xml b/samples/abox-sdk-sample/myapplication/src/main/res/layout/fragment_first.xml
new file mode 100644
index 0000000..44baecd
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/layout/fragment_first.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/layout/fragment_second.xml b/samples/abox-sdk-sample/myapplication/src/main/res/layout/fragment_second.xml
new file mode 100644
index 0000000..d074310
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/layout/fragment_second.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/menu/menu_main.xml b/samples/abox-sdk-sample/myapplication/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..c40cb36
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/menu/menu_main.xml
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-anydpi/ic_launcher.xml b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-hdpi/ic_launcher.webp b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-mdpi/ic_launcher.webp b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xhdpi/ic_launcher.webp b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/samples/abox-sdk-sample/myapplication/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/navigation/nav_graph.xml b/samples/abox-sdk-sample/myapplication/src/main/res/navigation/nav_graph.xml
new file mode 100644
index 0000000..fc92c81
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/navigation/nav_graph.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/values-land/dimens.xml b/samples/abox-sdk-sample/myapplication/src/main/res/values-land/dimens.xml
new file mode 100644
index 0000000..22d7f00
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/values-land/dimens.xml
@@ -0,0 +1,3 @@
+
+ 48dp
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/values-night/themes.xml b/samples/abox-sdk-sample/myapplication/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..b01ab6c
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/values-night/themes.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/values-v23/themes.xml b/samples/abox-sdk-sample/myapplication/src/main/res/values-v23/themes.xml
new file mode 100644
index 0000000..03f5ed8
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/values-v23/themes.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/values-w1240dp/dimens.xml b/samples/abox-sdk-sample/myapplication/src/main/res/values-w1240dp/dimens.xml
new file mode 100644
index 0000000..d73f4a3
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/values-w1240dp/dimens.xml
@@ -0,0 +1,3 @@
+
+ 200dp
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/values-w600dp/dimens.xml b/samples/abox-sdk-sample/myapplication/src/main/res/values-w600dp/dimens.xml
new file mode 100644
index 0000000..22d7f00
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/values-w600dp/dimens.xml
@@ -0,0 +1,3 @@
+
+ 48dp
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/values/colors.xml b/samples/abox-sdk-sample/myapplication/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c8524cd
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/values/colors.xml
@@ -0,0 +1,5 @@
+
+
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/values/dimens.xml b/samples/abox-sdk-sample/myapplication/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..125df87
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/values/dimens.xml
@@ -0,0 +1,3 @@
+
+ 16dp
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/values/strings.xml b/samples/abox-sdk-sample/myapplication/src/main/res/values/strings.xml
new file mode 100644
index 0000000..1517db1
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/values/strings.xml
@@ -0,0 +1,47 @@
+
+ My Application
+ Settings
+
+ First Fragment
+ Second Fragment
+ Next
+ Previous
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam in scelerisque sem. Mauris
+ volutpat, dolor id interdum ullamcorper, risus dolor egestas lectus, sit amet mattis purus
+ dui nec risus. Maecenas non sodales nisi, vel dictum dolor. Class aptent taciti sociosqu ad
+ litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse blandit eleifend
+ diam, vel rutrum tellus vulputate quis. Aliquam eget libero aliquet, imperdiet nisl a,
+ ornare ex. Sed rhoncus est ut libero porta lobortis. Fusce in dictum tellus.\n\n
+ Suspendisse interdum ornare ante. Aliquam nec cursus lorem. Morbi id magna felis. Vivamus
+ egestas, est a condimentum egestas, turpis nisl iaculis ipsum, in dictum tellus dolor sed
+ neque. Morbi tellus erat, dapibus ut sem a, iaculis tincidunt dui. Interdum et malesuada
+ fames ac ante ipsum primis in faucibus. Curabitur et eros porttitor, ultricies urna vitae,
+ molestie nibh. Phasellus at commodo eros, non aliquet metus. Sed maximus nisl nec dolor
+ bibendum, vel congue leo egestas.\n\n
+ Sed interdum tortor nibh, in sagittis risus mollis quis. Curabitur mi odio, condimentum sit
+ amet auctor at, mollis non turpis. Nullam pretium libero vestibulum, finibus orci vel,
+ molestie quam. Fusce blandit tincidunt nulla, quis sollicitudin libero facilisis et. Integer
+ interdum nunc ligula, et fermentum metus hendrerit id. Vestibulum lectus felis, dictum at
+ lacinia sit amet, tristique id quam. Cras eu consequat dui. Suspendisse sodales nunc ligula,
+ in lobortis sem porta sed. Integer id ultrices magna, in luctus elit. Sed a pellentesque
+ est.\n\n
+ Aenean nunc velit, lacinia sed dolor sed, ultrices viverra nulla. Etiam a venenatis nibh.
+ Morbi laoreet, tortor sed facilisis varius, nibh orci rhoncus nulla, id elementum leo dui
+ non lorem. Nam mollis ipsum quis auctor varius. Quisque elementum eu libero sed commodo. In
+ eros nisl, imperdiet vel imperdiet et, scelerisque a mauris. Pellentesque varius ex nunc,
+ quis imperdiet eros placerat ac. Duis finibus orci et est auctor tincidunt. Sed non viverra
+ ipsum. Nunc quis augue egestas, cursus lorem at, molestie sem. Morbi a consectetur ipsum, a
+ placerat diam. Etiam vulputate dignissim convallis. Integer faucibus mauris sit amet finibus
+ convallis.\n\n
+ Phasellus in aliquet mi. Pellentesque habitant morbi tristique senectus et netus et
+ malesuada fames ac turpis egestas. In volutpat arcu ut felis sagittis, in finibus massa
+ gravida. Pellentesque id tellus orci. Integer dictum, lorem sed efficitur ullamcorper,
+ libero justo consectetur ipsum, in mollis nisl ex sed nisl. Donec maximus ullamcorper
+ sodales. Praesent bibendum rhoncus tellus nec feugiat. In a ornare nulla. Donec rhoncus
+ libero vel nunc consequat, quis tincidunt nisl eleifend. Cras bibendum enim a justo luctus
+ vestibulum. Fusce dictum libero quis erat maximus, vitae volutpat diam dignissim.
+
+ 登录
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/main/res/values/themes.xml b/samples/abox-sdk-sample/myapplication/src/main/res/values/themes.xml
new file mode 100644
index 0000000..f08929e
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/main/res/values/themes.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/myapplication/src/test/java/com/example/myapplication/ExampleUnitTest.java b/samples/abox-sdk-sample/myapplication/src/test/java/com/example/myapplication/ExampleUnitTest.java
new file mode 100644
index 0000000..bf43ee5
--- /dev/null
+++ b/samples/abox-sdk-sample/myapplication/src/test/java/com/example/myapplication/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.example.myapplication;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/samples/abox-sdk-sample/settings.gradle b/samples/abox-sdk-sample/settings.gradle
new file mode 100644
index 0000000..c1fca17
--- /dev/null
+++ b/samples/abox-sdk-sample/settings.gradle
@@ -0,0 +1,25 @@
+pluginManagement {
+ repositories {
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "abox-test"
+include ':login-mac'
+include ':myapplication'