Multi-device Login Management

Last Updated on : 2025-11-20 10:11:34download

Overview

The multi-device login feature allows users to view which devices their current account is logged into and remotely log out of the specified devices. This topic describes how to use the multi-device login interfaces provided by the SDK.

Feature description

The multi-device login feature includes the following core interfaces:

  • getUserLoginMultiTerminal: Gets the list of devices where the current account is logged in.
  • getMultiTerminalLogoutCode: Gets the device logout verification code (loginOutCode).
  • logoutMultiTerminal: Logs out a specified device.
  • registerTerminalLogoutListener: Registers a listener for account logout events (receives notifications when the account is logged out from another device).

API description

Get the list of logged-in devices

Method signature

void getUserLoginMultiTerminal(IUserBusinessCallback<UserLoginMultiterminalVO> callback)

Feature description

Get login information for the current account across all devices, including device ID, operating system, platform, login status, and login time.

Parameter description

Parameter Type Description
callback IUserBusinessCallback The callback interface that returns the list of logged-in devices.

Callback interface

IUserBusinessCallback<UserLoginMultiterminalVO>

public interface IUserBusinessCallback<ResponseValue> {
    /**
     * Success callback
     * @param success Returns a UserLoginMultiterminalVO object
     */
    void onSuccess(ResponseValue success);

    /**
     * Failure callback
     * @param code The error code
     * @param error The error message
     */
    void onError(String code, String error);
}

Returned data model

UserLoginMultiterminalVO

public class UserLoginMultiterminalVO {
    private List<UserLoginMultiterminalBean> terminalLoginList;

    public List<UserLoginMultiterminalBean> getTerminalLoginList() {
        return terminalLoginList;
    }
}

UserLoginMultiterminalBean

public class UserLoginMultiterminalBean {
    private String terminalId;      // The unique device identifier
    private String os;              // The operating system
    private String osSystem;        // The version of the operating system
    private String platform;        // The platform information
    private Integer status;         // The session status. 0 - invalid, 1 - valid
    private Long loginTime;        // The login timestamp

    // Getter and Setter methods
    public String getTerminalId() { return terminalId; }
    public String getOs() { return os; }
    public String getOsSystem() { return osSystem; }
    public String getPlatform() { return platform; }
    public Integer getStatus() { return status; }
    public Long getLoginTime() { return loginTime; }
}

Example

ThingHomeSdk.getUserInstance().getUserLoginMultiTerminal(object : IUserBusinessCallback<UserLoginMultiterminalVO> {
    override fun onSuccess(result: UserLoginMultiterminalVO) {
        val deviceList = result.terminalLoginList
        if (!deviceList.isNullOrEmpty()) {
            for (device in deviceList) {
                Log.d("MultiTerminal", "Device ID: ${device.terminalId}")
                Log.d("MultiTerminal", "Operating system: ${device.os}")
                Log.d("MultiTerminal", "Platform: ${device.platform}")
                Log.d("MultiTerminal", "Status: ${if (device.status == 1) "Valid" else "Invalid"}")
                Log.d("MultiTerminal", "Login time: ${Date(device.loginTime)}")
            }
        }
    }

    override fun onError(code: String, error: String) {
        Log.e("MultiTerminal", "Failed to get device list: $code - $error")
    }
})

Get the logout verification code

Method signature

void getMultiTerminalLogoutCode(String countryCode, String username, String password,
                                String code, Boolean ifencrypt,
                                Integer type, Integer verifyType,
                                IUserBusinessCallback<MultiTerminalBean> callback)

Feature description

Before performing a device logout operation, account verification is required. After successful verification, a logout verification code (loginOutCode) is obtained. This code is used for the subsequent logout operation.

For verifications through password and verification code, you only need to call this method directly. There is no need to call other interfaces beforehand.

Parameter description

Parameter Type Required Description
countryCode String Yes The country code, for example, “86”.
username String Yes The username. It can be a mobile phone number or email address.
password String Conditionally The password. This parameter is required when verifyType is 1, and can be empty when verifyType is 2.
code String Conditionally The verification code. This parameter is required when verifyType is 2, and can be empty when verifyType is 1.
ifencrypt Boolean Yes Indicates whether the password is encrypted. This parameter must be true when verifyType is 1, and can be false when verifyType is 2.
type Integer Yes The account type. 1: Mobile phone number, 2: email address.
verifyType Integer Yes The verification method. 1: Password, 2: verification code.
callback IUserBusinessCallback Yes Callback interface

Description of verification methods

  • Method 1: Password verification (verifyType is 1)

    • username and password: Required.
    • ifencrypt: Must be true.
    • code: Pass null.

    Call this method directly to complete verification and obtain the loginOutCode.

  • Method 2: Verification code (verifyType is 2)

    • username and code: Required.
    • password: Can be empty (Pass null).
    • ifencrypt: Pass false.

    When verifying through verification code, you must first call sendVerifyCodeWithUserName (with type of 8) to send a verification code. After receiving the code, call this method.

Returned data model

MultiTerminalBean

public class MultiTerminalBean {
    private String loginOutCode;    // Logout verification code
    private long expireTime;        // Expiration time of the verification code (timestamp in seconds)

    public String getLoginOutCode() {
        return loginOutCode;
    }

    public long getExpireTime() {
        return expireTime;
    }
}

Example

Example 1: Verify through password

val countryCode = "86"
val username = "13800138000"  // Mobile phone number or email address
val password = "your_password"
val type = 1  // 1 - 1 - Mobile phone number, 2 - email address
val verifyType = 1  // 1 - Verify through password

ThingHomeSdk.getUserInstance().getMultiTerminalLogoutCode(
    countryCode,
    username,
    password,
    null,  // code
    true,   // ifencrypt must be true
    type,
    verifyType,
    object : IUserBusinessCallback<MultiTerminalBean> {
        override fun onSuccess(result: MultiTerminalBean) {
            val loginOutCode = result.loginOutCode
            val expireTime = result.expireTime
            Log.d("MultiTerminal", "Logout verification code: $loginOutCode")
            Log.d("MultiTerminal", "Expiration time: ${Date(expireTime * 1000)}")
            // Save loginOutCode for subsequent logout operations
        }

        override fun onError(code: String, error: String) {
            Log.e("MultiTerminal", "Failed to get logout verification code: $code - $error")
        }
    }
)

Example 2: Verify through verification code

// Step 1: Send verification code
val countryCode = "86"
val username = "13800138000"
val type = 1  // 1 -  Mobile phone number, 2 - email address

ThingHomeSdk.getUserInstance().sendVerifyCodeWithUserName(
    username,
    null,  // region
    countryCode,
    8,     // type = 8, indicates verification code for account logout
    object : IResultCallback {
        override fun onSuccess() {
            // Verification code is sent successfully. Prompt the user to enter the code
        }

        override fun onError(code: String, error: String) {
            Log.e("MultiTerminal", "Failed to send verification code: $code - $error")
        }
    }
)

// Step 2: After the user enters the verification code, obtain logout verification code
val verifyCode = "123456"  // Verification code entered by the user
val verifyType = 2  // 2 - Verify through verification code

ThingHomeSdk.getUserInstance().getMultiTerminalLogoutCode(
    countryCode,
    username,
    null,  // password can be omitted for verification code authentication
    verifyCode,  // code - verification code
    false, // ifencrypt
    type,
    verifyType,
    object : IUserBusinessCallback<MultiTerminalBean> {
        override fun onSuccess(result: MultiTerminalBean) {
            val loginOutCode = result.loginOutCode
            // Save loginOutCode for subsequent logout operations
        }

        override fun onError(code: String, error: String) {
            Log.e("MultiTerminal", "Failed to get logout verification code: $code - $error")
        }
    }
)

Log out of the specified devices

Method signature

void logoutMultiTerminal(String terminalId, String loginOutCode, IBooleanCallback callback)

Feature description

Log out of the specified device remotely using the device ID and the logout verification code. After successful logout, the device will no longer be able to use the current account.

Parameter description

Parameter Type Required Description
terminalId String Yes The unique device identifier obtained from getUserLoginMultiTerminal.
loginOutCode String Yes The logout verification code obtained from getMultiTerminalLogoutCode.
callback IBooleanCallback Yes The callback interface.

Callback interface

IBooleanCallback

public interface IBooleanCallback {
    /**
     * Success callback
     */
    void onSuccess();

    /**
     * Failure callback
     * @param code The error code
     * @param error The error message
     */
    void onError(String code, String error);
}

Example

// terminalId obtained from getUserLoginMultiTerminal
val terminalId = "device_terminal_id_12345"
// loginOutCode obtained from getMultiTerminalLogoutCode
val loginOutCode = "logout_code_abc123"

ThingHomeSdk.getUserInstance().logoutMultiTerminal(terminalId, loginOutCode, object : IBooleanCallback {
    override fun onSuccess() {
        Log.d("MultiTerminal", "Device logged out successfully")
        // Refresh the device list to confirm the device has been logged out
    }

    override fun onError(code: String, error: String) {
        Log.e("MultiTerminal", "Device logout failed: $code - $error")
    }
})

Register a listener for account logout events

Feature description

When an account is logged out from another device, the current device can receive a notification via this listener. This effectively meets the need to promptly respond to user logout operations on other devices, such as clearing local cache and redirecting to the login page.

Usage

Please note that this feature depends on the thingsmart-businessapiextensionkit module.

Method signature

fun registerTerminalLogoutListener(callback: INotificationCallback)
fun unregisterTerminalLogoutListener()

Callback interface

INotificationCallback

interface INotificationCallback {
    fun onSuccess()
}

Example

import com.thingclips.businessapiextensionkit.ThingSmartExtNotificationFunc
import com.thingclips.businessapiextensionkit.callback.INotificationCallback

// Register a listener
ThingSmartExtNotificationFunc.registerTerminalLogoutListener(object : INotificationCallback {
    override fun onSuccess() {
        // Account was logged out from another device
        Log.d("MultiTerminal", "Account logged out from another device")
        // Execute cleanup operations, for example:
        // 1. Clear local user data
        // 2. Redirect to login page
        // 3. Clear cache
        handleTerminalLogout()
    }
})

// Unregister the listener (Call this interface when the register is no longer needed, such as during activity destruction)
ThingSmartExtNotificationFunc.unregisterTerminalLogoutListener()

Full example: Use in an activity

class MainActivity : AppCompatActivity() {
    private var terminalLogoutCallback: INotificationCallback? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Ensure initialization is completed (required if user just logged in)
        if (!ThingSmartExtNotificationFunc.isInitialized()) {
            ThingSmartExtNotificationFunc.initialize()
        }

        // Register multi-device logout listener
        terminalLogoutCallback = object : INotificationCallback {
            override fun onSuccess() {
                runOnUiThread {
                    // Account was logged out from another device, perform cleanup
                    Log.d("MultiTerminal", "Account logged out from another device")

                    // Clear user data
                    ThingHomeSdk.getUserInstance().removeUser()

                    // Redirect to login page
                    val intent = Intent(this@MainActivity, LoginActivity::class.java)
                    intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
                    startActivity(intent)
                    finish()
                }
            }
        }
        ThingSmartExtNotificationFunc.registerTerminalLogoutListener(terminalLogoutCallback!!)
    }

    override fun onDestroy() {
        super.onDestroy()
        // Unregister the listener to prevent memory leaks
        terminalLogoutCallback?.let {
            ThingSmartExtNotificationFunc.unregisterTerminalLogoutListener()
        }
    }
}

Initialization description

When a user logs out, the resources of ThingSmartExtNotificationFunc are automatically destroyed. Therefore, after a user successfully logs in, the initialize() method must be called again to re-initialize before registering the listener.

Initialization example

// Call after the user logs in successfully
ThingSmartExtNotificationFunc.initialize()

// After initialization completes, register the listener
ThingSmartExtNotificationFunc.registerTerminalLogoutListener(object : INotificationCallback {
    override fun onSuccess() {
        // Handle account logout logic
    }
})

Complete example: Initialization after successful login

// In login success callback
fun onLoginSuccess() {
    // 1. Initialize multi-device logout monitoring functionality
    ThingSmartExtNotificationFunc.initialize()

    // 2. Register a listener
    ThingSmartExtNotificationFunc.registerTerminalLogoutListener(object : INotificationCallback {
        override fun onSuccess() {
            // Account was logged out from another device
            handleTerminalLogout()
        }
    })
}

Considerations

  • Resource destroy mechanism: When the current account logs out, the resources of ThingSmartExtNotificationFunc are automatically destroyed (the destroy() method is called), and isInitialized() will return false.

  • Re-initialization after login: Since resources are destroyed during logout, you must call the initialize() method again to re-initialize after the user successfully logs back in, before registering the listener. Otherwise, the listener will not function correctly.

  • Listener registration timing: It is recommended to call initialize() immediately after successful user login, then register the listener to ensure timely receipt of logout notifications.

  • Listener unregistration: When the listener is no longer needed (for example, during activity destruction, or user logout), be sure to call unregisterTerminalLogoutListener() to unregister it and prevent memory leaks.

  • Thread safety: The onSuccess() callback might not execute on the main thread. If you need to update the UI within the callback, use runOnUiThread() or a handler to switch to the main thread.

Complete usage flow

Scenario: User views logged-in device and logs out of other devices

class MultiTerminalManager {
    private val userManager: IThingUser = ThingHomeSdk.getUserInstance()

    /**
     * Step 1: Get logged-in device list
     */
    fun getLoginDevices() {
        userManager.getUserLoginMultiTerminal(object : IUserBusinessCallback<UserLoginMultiterminalVO> {
            override fun onSuccess(result: UserLoginMultiterminalVO) {
                val deviceList = result.terminalLoginList
                // Show the device list to the user
                displayDeviceList(deviceList)
            }

            override fun onError(code: String, error: String) {
                Log.e("MultiTerminal", "Failed to get device list: $code - $error")
            }
        })
    }

    /**
     * Step 2: User selects device to log out, performs account verification, and gets a logout code
     * Use password verification method
     */
    fun prepareLogoutDeviceWithPassword(
        terminalId: String,
        countryCode: String,
        username: String,
        password: String,
        type: Int
    ) {
        userManager.getMultiTerminalLogoutCode(
            countryCode,
            username,
            password,
            null,  // code
            true,   // ifencrypt must be true for password verification
            type,
            1,      // Use password verification
            object : IUserBusinessCallback<MultiTerminalBean> {
                override fun onSuccess(result: MultiTerminalBean) {
                    val loginOutCode = result.loginOutCode
                    // Step 3: Execute logout operation
                    logoutDevice(terminalId, loginOutCode)
                }

                override fun onError(code: String, error: String) {
                    Log.e("MultiTerminal", "Failed to get logout verification code: $code - $error")
                }
            }
        )
    }

    /**
     * Step 2: User selects device to log out, performs account verification, and gets a logout code
     * Use verification code method
     */
    fun prepareLogoutDeviceWithCode(
        terminalId: String,
        countryCode: String,
        username: String,
        verifyCode: String,
        type: Int
    ) {
        userManager.getMultiTerminalLogoutCode(
            countryCode,
            username,
            null,  // password can be omitted for verification code authentication
            verifyCode,  // code
            false, // ifencrypt
            type,
            2,     // Use verification code authentication
            object : IUserBusinessCallback<MultiTerminalBean> {
                override fun onSuccess(result: MultiTerminalBean) {
                    val loginOutCode = result.loginOutCode
                    // Step 3: Execute logout operation
                    logoutDevice(terminalId, loginOutCode)
                }

                override fun onError(code: String, error: String) {
                    Log.e("MultiTerminal", "Failed to get logout verification code: $code - $error")
                }
            }
        )
    }

    /**
     * Step 3: Log out of the specified device
     */
    private fun logoutDevice(terminalId: String, loginOutCode: String) {
        userManager.logoutMultiTerminal(terminalId, loginOutCode, object : IBooleanCallback {
            override fun onSuccess() {
                Log.d("MultiTerminal", "Device logged out successfully")
                // Refresh the device list
                getLoginDevices()
            }

            override fun onError(code: String, error: String) {
                Log.e("MultiTerminal", "Device logout failed: $code - $error")
            }
        })
    }
    private fun displayDeviceList(deviceList: List<UserLoginMultiterminalBean>?) {
       // Show the device list on UI
    }
}

Considerations

Login status requirement

Before calling these interfaces, ensure the user is logged in. That is, ThingHomeSdk.getUserInstance().isLogin() returns true.

Verification code validity

The loginOutCode is time-sensitive. The expiration time can be obtained from the expireTime returned by getMultiTerminalLogoutCode. It is recommended to use the code promptly after retrieval.

Device identifier

The terminalId is a unique identifier for a device, used to distinguish between different logged-in devices. The same account will have different terminalId values when logged in on different devices.

Verification method selection

  • Password verification (verifyType is 1):
    • username and password: Required.
    • ifencrypt: Must be true.
    • code: Pass null.
    • Call getMultiTerminalLogoutCode directly. There is no need to call other interfaces first.
  • Verification code (verifyType is 2):
    • username and code: Required.
    • password: Can be empty (Pass null).
    • ifencrypt: Pass false.
    • When verifying through verification code, you must first call sendVerifyCodeWithUserName (with type of 8) to send a verification code. After receiving the code, call this method.

Error handling

All interfaces provide error callbacks. It is recommended to show user-friendly error messages in the UI.

Current device identification

Compare the terminalId with the current device’s identifier to determine if it is the currently active device, thus preventing accidental logout of the device in use.

Account logout listener

It is recommended to register the registerTerminalLogoutListener listener upon application startup to promptly handle scenarios where the account is logged out from another device. This allows for the timely cleanup of local data and guidance for the user to log in again.