Androidホームアプリ作ってみた

流用できそうな処理があるので初心者の練習に。
これを元にお洒落で秘匿性のある画面ができるはず。

途中で先駆者様に気付き、参考にしました。

https://qiita.com/ryo_mm2d/items/00326b0d8f088975fa0e

## 準備
実機でテストするならAmdroidManifestの記述は最後でいい。
```xml: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を保存したい
今回はグローバル変数を置いてみよう。これでリセットも容易だ。

https://qiita.com/Ritz/items/0f6b34e0fcf6d3cd1ce5

```kotlin: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>()
    }
}
```
ホームアプリから各アプリを起動するデータクラス設定
```kotlin: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データクラスに注視すると面白い。
```kotlin: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
画面にリスト化して並べる。
詳しくはここに。

https://qiita.com/A42/items/d77dfd1613b3ae3d0659

ホルダー作成
```xml: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の変更はクラッシュするので安全にここで記述しよう。
```kotlin: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
ようやく整ったので画面に反映。
最初からデータクラスのみでいけばもっとシンプルになるが
カスタム性を重視。

```kotlin: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()
    }
}
```