Skip to content

Commit 0975600

Browse files
authored
ADFA-3644 | Support pop-out tooltip display for tablets and DeX (#1164)
* feat: support pop-out display for tooltips on tablets and DeX Refactors HelpActivity launch logic and adds device form factor detection * fix: refresh activity title
1 parent 89d6a69 commit 0975600

File tree

3 files changed

+168
-13
lines changed

3 files changed

+168
-13
lines changed

common/src/main/java/com/itsaky/androidide/activities/editor/HelpActivity.kt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,20 @@
1717

1818
package com.itsaky.androidide.activities.editor
1919

20+
import android.app.Activity
21+
import android.content.Context
22+
import android.content.Intent
2023
import android.os.Bundle
2124
import android.view.View
2225
import android.webkit.WebViewClient
2326
import androidx.activity.OnBackPressedCallback
27+
import androidx.core.net.toUri
2428
import androidx.core.view.WindowCompat
2529
import org.adfa.constants.CONTENT_KEY
2630
import com.itsaky.androidide.resources.R
2731
import com.itsaky.androidide.app.BaseIDEActivity
2832
import com.itsaky.androidide.common.databinding.ActivityHelpBinding
33+
import com.itsaky.androidide.utils.DeviceFormFactorUtils
2934
import com.itsaky.androidide.utils.isSystemInDarkMode
3035
import com.itsaky.androidide.utils.UrlManager
3136
import org.adfa.constants.CONTENT_TITLE_KEY
@@ -34,6 +39,29 @@ class HelpActivity : BaseIDEActivity() {
3439

3540
companion object {
3641
private val EXTERNAL_SCHEMES = listOf("mailto:", "tel:", "sms:")
42+
private const val MULTI_WINDOW_URI = "cogo-help://tooltip/active-window"
43+
44+
fun launch(context: Context, url: String, title: String) {
45+
val intent = Intent(context, HelpActivity::class.java).apply {
46+
putExtra(CONTENT_KEY, url)
47+
putExtra(CONTENT_TITLE_KEY, title)
48+
49+
val formFactor = DeviceFormFactorUtils.getCurrent(context)
50+
51+
if (formFactor.isLargeScreenLike) {
52+
addFlags(
53+
Intent.FLAG_ACTIVITY_NEW_TASK or
54+
Intent.FLAG_ACTIVITY_NEW_DOCUMENT or
55+
Intent.FLAG_ACTIVITY_SINGLE_TOP or
56+
Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
57+
)
58+
data = MULTI_WINDOW_URI.toUri()
59+
} else if (context !is Activity) {
60+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
61+
}
62+
}
63+
context.startActivity(intent)
64+
}
3765
}
3866

3967
private var _binding: ActivityHelpBinding? = null
@@ -113,6 +141,22 @@ class HelpActivity : BaseIDEActivity() {
113141
handleBackNavigation()
114142
}
115143
})
144+
updateUIFromIntent(intent)
145+
}
146+
147+
override fun onNewIntent(intent: Intent) {
148+
super.onNewIntent(intent)
149+
setIntent(intent)
150+
updateUIFromIntent(intent)
151+
}
152+
153+
private fun updateUIFromIntent(currentIntent: Intent) {
154+
val pageTitle = currentIntent.getStringExtra(CONTENT_TITLE_KEY)
155+
supportActionBar?.title = pageTitle ?: getString(R.string.help)
156+
157+
currentIntent.getStringExtra(CONTENT_KEY)?.let { url ->
158+
binding.webView.loadUrl(url)
159+
}
116160
}
117161

118162
private fun handleUrlLoading(view: android.webkit.WebView?, url: String?): Boolean {
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package com.itsaky.androidide.utils
2+
3+
import android.content.Context
4+
import android.content.res.Configuration
5+
import android.os.Build
6+
import android.util.DisplayMetrics
7+
import android.view.WindowManager
8+
9+
private const val TABLET_MIN_SMALLEST_WIDTH_DP = 500
10+
11+
data class DeviceFormFactor(
12+
val isTablet: Boolean,
13+
val isDexMode: Boolean,
14+
) {
15+
val isLargeScreenLike: Boolean
16+
get() = isTablet || isDexMode
17+
}
18+
19+
object DeviceFormFactorUtils {
20+
21+
fun getCurrent(context: Context): DeviceFormFactor {
22+
return DeviceFormFactor(
23+
isTablet = isTablet(context),
24+
isDexMode = isDexMode(context),
25+
)
26+
}
27+
28+
fun isDexMode(context: Context): Boolean {
29+
val uiModeType = context.resources.configuration.uiMode and Configuration.UI_MODE_TYPE_MASK
30+
if (uiModeType == Configuration.UI_MODE_TYPE_DESK) {
31+
return true
32+
}
33+
34+
if (isSamsungDexModeModern(context)) {
35+
return true
36+
}
37+
38+
return isSamsungDexModeLegacy(context.resources.configuration)
39+
}
40+
41+
fun isTablet(context: Context): Boolean {
42+
if (context.resources.configuration.smallestScreenWidthDp >= TABLET_MIN_SMALLEST_WIDTH_DP) {
43+
return true
44+
}
45+
46+
return isPhysicalLargeScreen(context)
47+
}
48+
49+
private fun isPhysicalLargeScreen(context: Context): Boolean {
50+
return try {
51+
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
52+
53+
val smallestPhysicalWidthDp = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
54+
val metrics = windowManager.maximumWindowMetrics
55+
val density = context.resources.configuration.densityDpi / 160f
56+
val widthDp = metrics.bounds.width() / density
57+
val heightDp = metrics.bounds.height() / density
58+
widthDp.coerceAtMost(heightDp)
59+
} else {
60+
val display = windowManager.defaultDisplay
61+
val realMetrics = DisplayMetrics()
62+
@Suppress("DEPRECATION")
63+
display.getRealMetrics(realMetrics)
64+
val widthDp = realMetrics.widthPixels / realMetrics.density
65+
val heightDp = realMetrics.heightPixels / realMetrics.density
66+
widthDp.coerceAtMost(heightDp)
67+
}
68+
69+
smallestPhysicalWidthDp >= TABLET_MIN_SMALLEST_WIDTH_DP
70+
} catch (_: Exception) {
71+
false
72+
}
73+
}
74+
75+
private fun isSamsungDexModeModern(context: Context): Boolean {
76+
return try {
77+
val desktopModeManager = context.applicationContext.getSystemService("desktopmode") ?: return false
78+
79+
val getDesktopModeStateMethod = desktopModeManager.javaClass.getDeclaredMethod("getDesktopModeState")
80+
val desktopModeState = getDesktopModeStateMethod.invoke(desktopModeManager) ?: return false
81+
val desktopModeStateClass = desktopModeState.javaClass
82+
83+
val getEnabledMethod = desktopModeStateClass.getDeclaredMethod("getEnabled")
84+
val enabled = getEnabledMethod.invoke(desktopModeState) as Int
85+
val enabledConstant = desktopModeStateClass.getDeclaredField("ENABLED").getInt(desktopModeStateClass)
86+
87+
enabled == enabledConstant
88+
} catch (_: Exception) {
89+
false
90+
}
91+
}
92+
93+
private fun isSamsungDexModeLegacy(configuration: Configuration): Boolean {
94+
val enabledValue = readSamsungDesktopModeValue(
95+
target = configuration.javaClass,
96+
fieldName = "SEM_DESKTOP_MODE_ENABLED",
97+
targetClass = configuration.javaClass,
98+
) ?: return false
99+
100+
val currentValue = readSamsungDesktopModeValue(
101+
target = configuration,
102+
fieldName = "semDesktopModeEnabled",
103+
targetClass = configuration.javaClass,
104+
) ?: return false
105+
106+
return currentValue == enabledValue
107+
}
108+
109+
private fun readSamsungDesktopModeValue(
110+
target: Any?,
111+
fieldName: String,
112+
targetClass: Class<*>,
113+
): Int? {
114+
return runCatching {
115+
targetClass.getField(fieldName).getInt(target)
116+
}.recoverCatching {
117+
targetClass.getDeclaredField(fieldName).apply {
118+
isAccessible = true
119+
}.getInt(target)
120+
}.getOrNull()
121+
}
122+
}

idetooltips/src/main/java/com/itsaky/androidide/idetooltips/ToolTipManager.kt

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
44
import android.app.Activity
55
import android.content.Context
66
import android.content.ContextWrapper
7-
import android.content.Intent
87
import android.database.sqlite.SQLiteDatabase
98
import android.graphics.Color
109
import android.graphics.drawable.ColorDrawable
@@ -38,8 +37,6 @@ import kotlinx.coroutines.CoroutineScope
3837
import kotlinx.coroutines.Dispatchers
3938
import kotlinx.coroutines.launch
4039
import kotlinx.coroutines.withContext
41-
import org.adfa.constants.CONTENT_KEY
42-
import org.adfa.constants.CONTENT_TITLE_KEY
4340
import java.io.File
4441

4542

@@ -217,19 +214,11 @@ object TooltipManager {
217214
tooltipItem = tooltipItem,
218215
requestFocus = requestFocus,
219216
onHelpLinkClicked = { context, url, title ->
220-
val intent =
221-
Intent(context, HelpActivity::class.java).apply {
222-
putExtra(CONTENT_KEY, url)
223-
putExtra(CONTENT_TITLE_KEY, context.getString(R.string.back_to_cogo))
224-
if (context !is android.app.Activity) {
225-
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
226-
}
227-
}
228-
context.startActivity(intent)
217+
HelpActivity.launch(context, url, title)
229218
}
230219
)
231220
} else {
232-
Log.e("TooltipManager", "Tooltip item $tooltipItem is null")
221+
Log.e(TAG, "Tooltip item $tooltipItem is null")
233222
}
234223
}
235224
}

0 commit comments

Comments
 (0)