ランチャー的なアプリを作る場合はインストールされているアプリを呼び出す機能が必要かと思います
実装はAndroid TVじゃなくても同様ですがAndroid TVの場合はバナーがありますのでバナーがある場合と無い場合の配慮がいります
YoutubeやGoogle Playがプリインストールされている場合の配慮
設定アプリはintent呼び出しできないようですがバナー取得のためターゲットに追加
UIはとりあえずグリットで表示でCardはimageを表示するためClassicCardを使用しました
Card長押しで順番入れ替えとかができるといい感じですが、入れ替えの実装とデータの管理方法がまだ未調査です…
data class InstalledApplication(
val packageName: String,
val name: String,
val icon: Drawable
)
@SuppressLint("QueryPermissionsNeeded")
fun getInstalledAppList(
context: Context
): List<InstalledApplication> {
val packageManager = context.packageManager
val flags = PackageManager.MATCH_UNINSTALLED_PACKAGES or PackageManager.MATCH_DISABLED_COMPONENTS
val applicationInfo = packageManager.getInstalledApplications(flags)
val myPackageName = context.packageName
val target = listOf(
"com.android.tv.settings",
"com.android.vending", //Google Play Store
"com.google.android.youtube.tv"
)
val list = mutableListOf<InstalledApplication>()
applicationInfo.forEach { info ->
val name = info.loadLabel(packageManager).toString()
//Log.d(TAG, "$name(${info.packageName})")
if (target.indexOf(info.packageName) >= 0 ||
(info.packageName != myPackageName && (info.flags and ApplicationInfo.FLAG_SYSTEM) != ApplicationInfo.FLAG_SYSTEM)
) {
var banner: Drawable? = null
try {
banner = info.loadBanner(packageManager)
} catch (_: Exception) {}
list += InstalledApplication(info.packageName, name, banner ?: info.loadIcon(packageManager))
}
}
return list.toList()
}@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
fun InstalledAppListScreen(modifier: Modifier = Modifier) {
val context = LocalContext.current
val list = remember { mutableStateListOf<InstalledApplication>() }
LaunchedEffect(Unit) {
list.clear()
list.addAll(getInstalledAppList(context))
}
val columnCount = 4
val margin = 24
val screenWidth = LocalConfiguration.current.screenWidthDp
val width = (screenWidth - (margin * (columnCount + 1))) / columnCount
val tvLazyGridState = rememberTvLazyGridState()
TvLazyVerticalGrid(
state = tvLazyGridState,
modifier = modifier,
columns = TvGridCells.Fixed(columnCount),
contentPadding = PaddingValues(margin.dp),
verticalArrangement = Arrangement.spacedBy(margin.dp)
) {
itemsIndexed(list) {_, item ->
Column {
val image = remember {
item.icon.toBitmap(item.icon.intrinsicWidth, item.icon.intrinsicHeight, null)
}
// bannerのサイズは320*180
ClassicCard(
onClick = {
val intent = context.packageManager.getLaunchIntentForPackage(item.packageName)
intent?.let {
context.startActivity(it)
}
},
image = {
val bitmap = item.icon.toBitmap(item.icon.intrinsicWidth, item.icon.intrinsicHeight, null)
Image(image.asImageBitmap(), null, Modifier.width(width.dp).aspectRatio(320f / 180), contentScale = ContentScale.Fit)
},
title = {},
subtitle = {},
description = {}
)
Spacer(Modifier.height(8.dp))
//TODO: fontSizeはコアテキストは最低16.sp/その他は最低12.sp
Text(item.name, Modifier.width(width.dp), fontSize = 16.sp, textAlign = TextAlign.Center)
}
}
}
}Android Studio Hedgehog 2023.1.1 RC 3 built on November 3, 2023
