流用できそうな処理があるので初心者の練習に。
これを元にお洒落で秘匿性のある画面ができるはず。
途中で先駆者様に気付き、参考にしました。
準備
実機でテストするならAmdroidManifestの記述は最後でいい。
AmdroidManifest
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission"
/>
<uses-permission android:name="android.permission.REORDER_TASKS" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<!-- ホームアプリとしての振る舞い -->
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
アプリケーションの取捨選択
1. アクセス可能な場所にアプリListを保存したい
今回はグローバル変数を置いてみよう。これでリセットも容易だ。
MyVoid
class MyVoid {
var id = ""//後述:起動時に変数受け渡したい
var appNameList = mutableListOf<String>()
var appVersionList = mutableListOf<String>()
//var appUpDateList = mutableListOf<String>()//最新版があった場合
var otherUpdate= mutableListOf()//後述:テスト用,遊ばないなら消していい
companion object{
private var instance :MyVoid? = null
fun getInstance():MyVoid{
if(instance == null)
instance = MyVoid()
return instance!!
}
}
fun reset(){
id = ""
appNameList = mutableListOf()
appVersionList = mutableListOf<String>()
//appUpDateList = mutableListOf<String>()
}
}
ホームアプリから各アプリを起動するデータクラス設定
AppInfo
ata class AppInfo(
val icon: Drawable,
val label: String,
val componentName: ComponentName,
val version: String,
val packageName: String
){
fun launch(context: Context) {
try {
val name by lazy {MyVoid.getInstance().appNameList }
val ver by lazy {MyVoid.getInstance().appVersionList }
val myVoid = MyVoid.getInstance()
//起動時の設定
val intent = Intent(Intent.ACTION_MAIN).also {
//変数の受け渡しもここで可能
it.putExtra("ID",myVoid.id)
//既存のタスクを破棄し新しく始める
it.flags = Intent.FLAG_ACTIVITY_NEW_TASK or
FLAG_ACTIVITY_CLEAR_TASK
it.addCategory(Intent.CATEGORY_LAUNCHER)
it.component = componentName
}
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
Log.e("error",e.message!!)
}
}
}
2. アプリ情報を取得
先ほどのappNameListは選択した(表示したい)アプリとする。
上記のAppInfoデータクラスに注視すると面白い。
GetAppInfoList
class GetAppInfoList {
fun create(context: Context): List<AppInfo> {
val myVoid = MyVoid.getInstance()
var list = myVoid.appNameList
val pm = context.packageManager
val intent = Intent(Intent.ACTION_MAIN)
.also { it.addCategory(Intent.CATEGORY_LAUNCHER)
}
return pm.queryIntentActivities(intent, PackageManager.MATCH_ALL)
.asSequence()
.mapNotNull { it.activityInfo }
.filter {(list.contains(it.packageName))//ここで取捨選択
}
.map {
AppInfo(
it.loadIcon(pm)?: getDefaultIcon(context),
it.loadLabel(pm).toString(),
ComponentName(it.packageName,it.name),
pm.getPackageInfo(it.packageName,0).versionName!!,
it.packageName
)
}
.sortedBy { it.label }
.toList()
}
//各アプリのデフォルトアイコンの取得
private fun getDefaultIcon(context: Context): Drawable {
return context.resources.getDrawable(R.mipmap.ic_launcher, null)
}
}
3. Adapter
画面にリスト化して並べる。
詳しくはここに。
ホルダー作成
holder
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:layout_marginBottom="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/border"
android:orientation="horizontal">
<ImageView
android:id="@+id/icon"
android:layout_width="55dp"
android:layout_height="55dp"
android:padding="8dp"
android:background="@color/アイコンのバックの色"
tools:ignore="ContentDescription" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="8"
android:orientation="vertical">
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:textColor="@color/アプリ名の色"
android:textSize="14sp"
tools:text="Launcher" />
<TextView
android:id="@+id/packageName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textColor="@color/パッケージ名の色"
android:textSize="10sp"
tools:text="net.mm2d.launcher" />
</LinearLayout>
<!-- visibilityテスト
<TextView
android:id="@+id/update"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_weight="2"
android:drawableBottom="@drawable/baseline_notification_important_24"
android:gravity="center"
android:visibility="gone"
android:paddingBottom="10dp" />-->
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
データの繋ぎと表示設定。
メインスレッドでない場所でのUIの変更はクラッシュするので安全にここで記述しよう。
Adapter
class Adapter (
private val inflater: LayoutInflater,
private val list: List<AppInfo>,
private val onClick: (view: View, info: AppInfo) -> Unit
) : RecyclerView.Adapter<AppAdapter.AppViewHolder>(){
private val name by lazy { MyVoid.getInstance().appNameList }
private val ver by lazy {MyVoid.getInstance().appVersionList }
//private val up by lazy { MyVoid.getInstance().appUpDateList }
//visibilityチェック用
//private val otherList by lazy {MyVoid.getInstance().otherUpdate }
private val colorRed by lazy { Color.parseColor("#c5001a")}
private val colorB by lazy { R.color.black}
class AppViewHolder (itemView: View) : RecyclerView.ViewHolder(itemView) {
val icon: ImageView = itemView.findViewById(R.id.icon)
val label: TextView = itemView.findViewById(R.id.label)
val packageName: TextView = itemView.findViewById(R.id.packageName)
//val up: TextView = itemView.findViewById(R.id.update)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder =
AppViewHolder(inflater.inflate(R.layout.li_application, parent, false))
override fun getItemCount(): Int = list.size
override fun onBindViewHolder(holder: AppViewHolder, position: Int) {
val info = list[position]
val versionString = "ver. " + info.version
val pin = name.indexOf(info.packageName)
val version = ver[pin]
//val upDate = up[pin]
holder.apply {
//AppInfoの launchで設定したアクションをリスナーに反映する
itemView.setOnClickListener { onClick(it, info) }
icon.setImageDrawable(info.icon)
label.text = info.label
//アプリのバージョン確認をし、注意する
if(version == info.version) packageName.text = versionString
else {
//自作アプリだったらパッケージ名を出さず下記メッセージを上書きする
if(info.packageName.startsWith("com.example.")){
packageName.text = "アップデートが必要です"
packageName.setTextColor(colorRed)
}
else {
packageName.setTextColor(colorB)
packageName.text = versionString
}
}
//visibilityなどの設定もここで
//if(otherList.contains(upDate))holder.up.visibility = TextView.VISIBLE
//else holder.up.visibility = TextView.GONE
}
}
}
3. MainActivity
ようやく整ったので画面に反映。
最初からデータクラスのみでいけばもっとシンプルになるが
カスタム性を重視。
MainActivity
private val myVoid = MyVoid.getInstance()
class MainActivity : AppCompatActivity(),LoginInterface {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
//バックボタン無効化
val callback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
}
}
//chromeのみ取得する
val selectList = mutableListOf("com.android.chrome")
//全てのアプリを取得しappNameListと一致するもの(chrome)を選別
val pm = this.packageManager
val intent = Intent(Intent.ACTION_MAIN)
.also { it.addCategory(Intent.CATEGORY_LAUNCHER)
}
val allAppList = pm.packageManager.queryIntentActivities(intent,PackageManager.MATCH_ALL).asSequence()
.mapNotNull { it.activityInfo }
.filter {(selectList.contains(it.packageName))}
.toList()
//グローバル変数に渡す
allAppList.forEach { p ->
Log.e("???",p.packageName)
myVoid.apply {
appNameList.add(p.packageName)
appVersionList.add(pm.getPackageInfo(p.packageName,0).versionName!!)
}
}
//Adapterへ渡す
val adapter = AppAdapter(layoutInflater, AppInfoList().create(this)) { _, info ->
info.launch(this)}
binding.recyclerView.adapter = adapter
binding.recyclerView.layoutManager = LinearLayoutManager(this)
}
//アプリを終了させない
override fun finish() {
//super.finish()
}
}