0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-01-28

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

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

準備

実機でテストするなら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()
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?