remove Samsung Face Widget (signature-restricted, unusable by third-party apps)
Samsung's FACE_WIDGET permission requires platform signing, making it impossible for non-Samsung apps to register lock screen face widgets. The existing AppWidgetProvider already works via Good Lock LockStar and will be natively supported on lock screens with One UI 8 / Android 16 QPR1.
This commit is contained in:
@@ -2,8 +2,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="com.samsung.systemui.permission.FACE_WIDGET" />
|
||||
|
||||
<!-- AndroidTV support -->
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
|
||||
@@ -100,20 +98,6 @@
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- Samsung Lock Screen Face Widget -->
|
||||
<receiver
|
||||
android:name=".LockScreenWidgetReceiver"
|
||||
android:permission="com.samsung.systemui.permission.FACE_WIDGET"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.samsung.android.intent.action.REQUEST_SERVICEBOX_REMOTEVIEWS" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<meta-data
|
||||
android:name="com.samsung.systemui.facewidget.executable"
|
||||
android:resource="@raw/facewidgets" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
package com.agentui.desktop
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.json.JSONObject
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Samsung Face Widget receiver for the lock screen / AOD.
|
||||
*
|
||||
* Samsung's proprietary system sends REQUEST_SERVICEBOX_REMOTEVIEWS
|
||||
* and expects RESPONSE_SERVICEBOX_REMOTEVIEWS back with RemoteViews
|
||||
* for both the lock screen ("origin") and AOD ("aod").
|
||||
*/
|
||||
class LockScreenWidgetReceiver : BroadcastReceiver() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AgentUI.FaceWidget"
|
||||
private const val ACTION_REQUEST =
|
||||
"com.samsung.android.intent.action.REQUEST_SERVICEBOX_REMOTEVIEWS"
|
||||
private const val ACTION_RESPONSE =
|
||||
"com.samsung.android.intent.action.RESPONSE_SERVICEBOX_REMOTEVIEWS"
|
||||
private const val PAGE_ID = "agent_ui_transcript"
|
||||
|
||||
private val STATUS_COLORS = mapOf(
|
||||
"idle" to 0xFF6b7280.toInt(),
|
||||
"thinking" to 0xFF60a5fa.toInt(),
|
||||
"reading" to 0xFF22d3ee.toInt(),
|
||||
"writing" to 0xFF4ade80.toInt(),
|
||||
"toolUse" to 0xFFfbbf24.toInt(),
|
||||
"permissionRequest" to 0xFFfb923c.toInt(),
|
||||
"interrupted" to 0xFFf87171.toInt(),
|
||||
"error" to 0xFFf87171.toInt(),
|
||||
"sessionStart" to 0xFF60a5fa.toInt(),
|
||||
"sessionEnd" to 0xFF6b7280.toInt()
|
||||
)
|
||||
|
||||
// Dimmed versions for AOD
|
||||
private val AOD_STATUS_COLORS = mapOf(
|
||||
"idle" to 0xFF3b3f47.toInt(),
|
||||
"thinking" to 0xFF304f7a.toInt(),
|
||||
"reading" to 0xFF116670.toInt(),
|
||||
"writing" to 0xFF256b40.toInt(),
|
||||
"toolUse" to 0xFF7a5f12.toInt(),
|
||||
"permissionRequest" to 0xFF7a491e.toInt(),
|
||||
"interrupted" to 0xFF7a3838.toInt(),
|
||||
"error" to 0xFF7a3838.toInt(),
|
||||
"sessionStart" to 0xFF304f7a.toInt(),
|
||||
"sessionEnd" to 0xFF3b3f47.toInt()
|
||||
)
|
||||
|
||||
private val client = OkHttpClient.Builder()
|
||||
.connectTimeout(5, TimeUnit.SECONDS)
|
||||
.readTimeout(5, TimeUnit.SECONDS)
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action != ACTION_REQUEST) return
|
||||
|
||||
val pageId = intent.getStringExtra("pageId")
|
||||
if (pageId != PAGE_ID) return
|
||||
|
||||
Log.d(TAG, "Face widget update requested for pageId=$pageId")
|
||||
|
||||
// Fetch data on a background thread to avoid ANR
|
||||
val pendingResult = goAsync()
|
||||
|
||||
Thread {
|
||||
try {
|
||||
val terminals = fetchTerminals(context)
|
||||
val lockViews = buildLockScreenViews(context, terminals)
|
||||
val aodViews = buildAodViews(context, terminals)
|
||||
|
||||
val response = Intent(ACTION_RESPONSE).apply {
|
||||
setPackage("com.android.systemui")
|
||||
putExtra("package", context.packageName)
|
||||
putExtra("pageId", PAGE_ID)
|
||||
putExtra("show", true)
|
||||
putExtra("origin", lockViews)
|
||||
putExtra("aod", aodViews)
|
||||
}
|
||||
|
||||
context.sendBroadcast(response)
|
||||
Log.d(TAG, "Face widget response sent with ${terminals.size} terminals")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to build face widget", e)
|
||||
// Send empty response so widget doesn't get stuck
|
||||
val response = Intent(ACTION_RESPONSE).apply {
|
||||
setPackage("com.android.systemui")
|
||||
putExtra("package", context.packageName)
|
||||
putExtra("pageId", PAGE_ID)
|
||||
putExtra("show", true)
|
||||
putExtra("origin", buildEmptyLockScreenViews(context))
|
||||
putExtra("aod", buildEmptyAodViews(context))
|
||||
}
|
||||
context.sendBroadcast(response)
|
||||
} finally {
|
||||
pendingResult.finish()
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun buildLockScreenViews(
|
||||
context: Context,
|
||||
terminals: List<FaceWidgetTerminal>
|
||||
): RemoteViews {
|
||||
val views = RemoteViews(context.packageName, R.layout.face_widget_lockscreen)
|
||||
|
||||
val statusText = if (terminals.isEmpty()) "offline"
|
||||
else "${terminals.size} agent${if (terminals.size > 1) "s" else ""}"
|
||||
views.setTextViewText(R.id.fw_status, statusText)
|
||||
|
||||
if (terminals.isEmpty()) {
|
||||
views.setViewVisibility(R.id.fw_empty, View.VISIBLE)
|
||||
} else {
|
||||
views.setViewVisibility(R.id.fw_empty, View.GONE)
|
||||
}
|
||||
|
||||
// Lockscreen terminal slot IDs
|
||||
val slotIds = listOf(
|
||||
Triple(R.id.fw_terminal_1, R.id.fw_dot_1, R.id.fw_name_1),
|
||||
Triple(R.id.fw_terminal_2, R.id.fw_dot_2, R.id.fw_name_2),
|
||||
Triple(R.id.fw_terminal_3, R.id.fw_dot_3, R.id.fw_name_3)
|
||||
)
|
||||
|
||||
for (i in slotIds.indices) {
|
||||
val (container, dot, name) = slotIds[i]
|
||||
if (i < terminals.size) {
|
||||
val t = terminals[i]
|
||||
views.setViewVisibility(container, View.VISIBLE)
|
||||
views.setTextColor(dot, t.statusColor)
|
||||
views.setTextViewText(name, "T${t.index} ${t.agent} ${t.status}")
|
||||
} else {
|
||||
views.setViewVisibility(container, View.GONE)
|
||||
}
|
||||
}
|
||||
|
||||
return views
|
||||
}
|
||||
|
||||
private fun buildAodViews(
|
||||
context: Context,
|
||||
terminals: List<FaceWidgetTerminal>
|
||||
): RemoteViews {
|
||||
val views = RemoteViews(context.packageName, R.layout.face_widget_aod)
|
||||
|
||||
if (terminals.isEmpty()) {
|
||||
views.setViewVisibility(R.id.fw_aod_empty, View.VISIBLE)
|
||||
} else {
|
||||
views.setViewVisibility(R.id.fw_aod_empty, View.GONE)
|
||||
}
|
||||
|
||||
val slotIds = listOf(
|
||||
Triple(R.id.fw_aod_terminal_1, R.id.fw_aod_dot_1, R.id.fw_aod_name_1),
|
||||
Triple(R.id.fw_aod_terminal_2, R.id.fw_aod_dot_2, R.id.fw_aod_name_2),
|
||||
Triple(R.id.fw_aod_terminal_3, R.id.fw_aod_dot_3, R.id.fw_aod_name_3)
|
||||
)
|
||||
|
||||
for (i in slotIds.indices) {
|
||||
val (container, dot, name) = slotIds[i]
|
||||
if (i < terminals.size) {
|
||||
val t = terminals[i]
|
||||
views.setViewVisibility(container, View.VISIBLE)
|
||||
val aodColor = AOD_STATUS_COLORS[t.status] ?: AOD_STATUS_COLORS["idle"]!!
|
||||
views.setTextColor(dot, aodColor)
|
||||
views.setTextViewText(name, "T${t.index} ${t.agent}")
|
||||
} else {
|
||||
views.setViewVisibility(container, View.GONE)
|
||||
}
|
||||
}
|
||||
|
||||
return views
|
||||
}
|
||||
|
||||
private fun buildEmptyLockScreenViews(context: Context): RemoteViews {
|
||||
return RemoteViews(context.packageName, R.layout.face_widget_lockscreen)
|
||||
}
|
||||
|
||||
private fun buildEmptyAodViews(context: Context): RemoteViews {
|
||||
return RemoteViews(context.packageName, R.layout.face_widget_aod)
|
||||
}
|
||||
|
||||
private fun fetchTerminals(context: Context): List<FaceWidgetTerminal> {
|
||||
val apiBase = ServerConfig.apiBaseUrl(context) ?: return emptyList()
|
||||
|
||||
try {
|
||||
val url = "$apiBase/session-state"
|
||||
val req = Request.Builder().url(url).build()
|
||||
val resp = client.newCall(req).execute()
|
||||
if (!resp.isSuccessful) return emptyList()
|
||||
|
||||
val json = JSONObject(resp.body?.string() ?: "{}")
|
||||
val registry = json.optJSONArray("registry") ?: return emptyList()
|
||||
val agents = json.optJSONObject("agents")
|
||||
|
||||
val result = mutableListOf<FaceWidgetTerminal>()
|
||||
|
||||
for (i in 0 until minOf(registry.length(), 3)) { // Max 3 for face widget
|
||||
val entry = registry.getJSONObject(i)
|
||||
val agentName = entry.optString("agent", "")
|
||||
val alive = entry.optBoolean("alive", false)
|
||||
|
||||
val agentState = agents?.optJSONObject(agentName)
|
||||
val status = agentState?.optString("status", if (alive) "idle" else "closed")
|
||||
?: if (alive) "idle" else "closed"
|
||||
val statusColor = STATUS_COLORS[status] ?: STATUS_COLORS["idle"]!!
|
||||
|
||||
result.add(
|
||||
FaceWidgetTerminal(
|
||||
index = i + 1,
|
||||
agent = agentName,
|
||||
status = status,
|
||||
statusColor = statusColor
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to fetch terminals for face widget", e)
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private data class FaceWidgetTerminal(
|
||||
val index: Int,
|
||||
val agent: String,
|
||||
val status: String,
|
||||
val statusColor: Int
|
||||
)
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#ff171717" />
|
||||
<corners android:radius="22dp" />
|
||||
</shape>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#99010101" />
|
||||
<corners android:radius="22dp" />
|
||||
</shape>
|
||||
@@ -1,124 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp"
|
||||
android:background="@drawable/face_widget_bg_aod">
|
||||
|
||||
<!-- Minimal AOD layout: title + terminal count -->
|
||||
<TextView
|
||||
android:id="@+id/fw_aod_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Agent UI"
|
||||
android:textColor="#555588"
|
||||
android:textSize="11sp"
|
||||
android:fontFamily="monospace"
|
||||
android:paddingBottom="4dp" />
|
||||
|
||||
<!-- Terminal 1 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/fw_aod_terminal_1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="1dp"
|
||||
android:paddingBottom="1dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fw_aod_dot_1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="\u25CF"
|
||||
android:textColor="#444466"
|
||||
android:textSize="7sp"
|
||||
android:paddingEnd="4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fw_aod_name_1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#777777"
|
||||
android:textSize="9sp"
|
||||
android:fontFamily="monospace"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Terminal 2 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/fw_aod_terminal_2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="1dp"
|
||||
android:paddingBottom="1dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fw_aod_dot_2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="\u25CF"
|
||||
android:textColor="#444466"
|
||||
android:textSize="7sp"
|
||||
android:paddingEnd="4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fw_aod_name_2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#777777"
|
||||
android:textSize="9sp"
|
||||
android:fontFamily="monospace"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Terminal 3 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/fw_aod_terminal_3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="1dp"
|
||||
android:paddingBottom="1dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fw_aod_dot_3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="\u25CF"
|
||||
android:textColor="#444466"
|
||||
android:textSize="7sp"
|
||||
android:paddingEnd="4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fw_aod_name_3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#777777"
|
||||
android:textSize="9sp"
|
||||
android:fontFamily="monospace"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Empty state -->
|
||||
<TextView
|
||||
android:id="@+id/fw_aod_empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="No agents"
|
||||
android:textColor="#444444"
|
||||
android:textSize="9sp"
|
||||
android:fontFamily="monospace"
|
||||
android:gravity="center" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,140 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp"
|
||||
android:background="@drawable/face_widget_bg_dark">
|
||||
|
||||
<!-- Title bar -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingBottom="6dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fw_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Agent UI"
|
||||
android:textColor="#8888FF"
|
||||
android:textSize="12sp"
|
||||
android:fontFamily="monospace" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fw_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#888888"
|
||||
android:textSize="10sp"
|
||||
android:fontFamily="monospace" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Terminal 1 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/fw_terminal_1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fw_dot_1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="\u25CF"
|
||||
android:textColor="#6b7280"
|
||||
android:textSize="8sp"
|
||||
android:paddingEnd="4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fw_name_1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#DDDDDD"
|
||||
android:textSize="10sp"
|
||||
android:fontFamily="monospace"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Terminal 2 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/fw_terminal_2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fw_dot_2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="\u25CF"
|
||||
android:textColor="#6b7280"
|
||||
android:textSize="8sp"
|
||||
android:paddingEnd="4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fw_name_2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#DDDDDD"
|
||||
android:textSize="10sp"
|
||||
android:fontFamily="monospace"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Terminal 3 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/fw_terminal_3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fw_dot_3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="\u25CF"
|
||||
android:textColor="#6b7280"
|
||||
android:textSize="8sp"
|
||||
android:paddingEnd="4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fw_name_3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#DDDDDD"
|
||||
android:textSize="10sp"
|
||||
android:fontFamily="monospace"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Empty state -->
|
||||
<TextView
|
||||
android:id="@+id/fw_empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="No agents active"
|
||||
android:textColor="#666666"
|
||||
android:textSize="10sp"
|
||||
android:fontFamily="monospace"
|
||||
android:gravity="center" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"agent_ui_transcript": {
|
||||
"menuInSetting": 1,
|
||||
"labelResNameInSetting": "face_widget_label",
|
||||
"actionDetailSetting": "com.agentui.desktop.FACE_WIDGET_SETTINGS"
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,4 @@
|
||||
<string name="app_name">Agent UI</string>
|
||||
<string name="main_activity_title">Agent UI</string>
|
||||
<string name="widget_description">Shows recent transcript messages from Agent UI</string>
|
||||
<string name="face_widget_label">Agent UI Terminals</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user