Resolving an Implicit Intent

NerdLauncher will show the user a list of launchable apps on the device. (A launchable app is an app the user can open by clicking an icon on the Home screen or launcher screen.) To do so, it will query the system for launchable main activities.

The PackageManager, which you learned about in Chapter 15, is used to resolve the activities. Launchable main activities are simply activities with intent filters that include a MAIN action and a LAUNCHER category. You have seen this intent filter in the manifests/AndroidManifest.xml file in your previous projects:

    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

When you made NerdLauncherActivity a launcher activity, these intent filters were added for you automatically. (Check the manifest, if you like.)

In NerdLauncherActivity.kt, add a function named setupAdapter() and call that function from onCreate(…). (Ultimately this function will create a RecyclerView.Adapter instance and set it on your RecyclerView object. For now, it will just generate a list of application data.)

Also, create an implicit intent and get a list of activities that match the intent from the PackageManager. Log the number of activities that the PackageManager returns.

Listing 23.3  Querying the PackageManager (NerdLauncherActivity.kt)

private const val TAG = "NerdLauncherActivity"

class NerdLauncherActivity : AppCompatActivity() {

    private lateinit var recyclerView: RecyclerView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_nerd_launcher)

        recyclerView = findViewById(R.id.app_recycler_view)
        recyclerView.layoutManager = LinearLayoutManager(this)

        setupAdapter()
    }

    private fun setupAdapter() {
        val startupIntent = Intent(Intent.ACTION_MAIN).apply {
            addCategory(Intent.CATEGORY_LAUNCHER)
        }

        val activities = packageManager.queryIntentActivities(startupIntent, 0)

        Log.i(TAG, "Found ${activities.size} activities")
    }
}

Here you create an implicit intent with the action set to ACTION_MAIN. You add CATEGORY_LAUNCHER to the intent’s categories.

Calling PackageManager.queryIntentActivities(Intent, Int) returns a list containing the ResolveInfo for all the activities that have a filter matching the given intent. You can specify flags to modify the results. For example, the PackageManager.GET_SHARED_LIBRARY_FILES flag causes the query to include extra data (paths to libraries that are associated with each matching application) in the results. Here you pass 0, indicating you do not want to modify the results.

Run NerdLauncher and check Logcat to see how many apps the PackageManager returned. (We got 30 the first time we tried it.)

In CriminalIntent, you used an implicit intent to send a crime report. You presented an activity chooser by creating an implicit intent, wrapping it in a chooser intent, and sending it to the OS with startActivity(Intent):

    val intent = Intent(Intent.ACTION_SEND)
    ... // Create and put intent extras
    chooserIntent = Intent.createChooser(intent, getString(R.string.send_report)
    startActivity(chooserIntent)

You may be wondering why you are not using that approach here. The short explanation is that the MAIN/LAUNCHER intent filter may or may not match a MAIN/LAUNCHER implicit intent that is sent via startActivity(Intent).

startActivity(Intent) does not mean, Start an activity matching this implicit intent. It means, Start the default activity matching this implicit intent. When you send an implicit intent via startActivity(Intent) (or startActivityForResult(…)), the OS secretly adds the Intent.CATEGORY_DEFAULT category to the intent.

Thus, if you want an intent filter to match implicit intents sent via startActivity(Intent), you must include the DEFAULT category in that intent filter.

An activity that has the MAIN/LAUNCHER intent filter is the main entry point for the app that it belongs to. It only wants the job of main entry point for that application. It typically does not care about being the default main entry point, so it does not have to include the CATEGORY_DEFAULT category.

Because MAIN/LAUNCHER intent filters may not include CATEGORY_DEFAULT, you cannot reliably match them to an implicit intent sent via startActivity(Intent). Instead, you use the intent to query the PackageManager directly for activities with the MAIN/LAUNCHER intent filter.

The next step is to display the labels of these activities in NerdLauncherFragment’s RecyclerView. An activity’s label is its display name – something the user should recognize. Given that these activities are launcher activities, the label is most likely the application name.

You can find the labels for the activities, along with other metadata, in the ResolveInfo objects that the PackageManager returned.

First, sort the ResolveInfo objects returned from the PackageManager alphabetically by label using the ResolveInfo.loadLabel(PackageManager) function.

Listing 23.4  Sorting alphabetically (NerdLauncherActivity.kt)

class NerdLauncherActivity : AppCompatActivity() {
    ...
    private fun setupAdapter() {
        val startupIntent = Intent(Intent.ACTION_MAIN).apply {
            addCategory(Intent.CATEGORY_LAUNCHER)
        }

        val activities = packageManager.queryIntentActivities(startupIntent, 0)
        activities.sortWith(Comparator { a, b ->
            String.CASE_INSENSITIVE_ORDER.compare(
                a.loadLabel(packageManager).toString(),
                b.loadLabel(packageManager).toString()
            )
        })

        Log.i(TAG, "Found ${activities.size} activities")
    }
}

Now define a ViewHolder that displays an activity’s label. Store the activity’s ResolveInfo in a property (you will use it more than once later on).

Listing 23.5  Implementing ViewHolder (NerdLauncherActivity.kt)

class NerdLauncherActivity : AppCompatActivity() {
    ...
    private fun setupAdapter() {
        ...
    }

    private class ActivityHolder(itemView: View) :
            RecyclerView.ViewHolder(itemView) {

        private val nameTextView = itemView as TextView
        private lateinit var resolveInfo: ResolveInfo

        fun bindActivity(resolveInfo: ResolveInfo) {
            this.resolveInfo = resolveInfo
            val packageManager = itemView.context.packageManager
            val appName = resolveInfo.loadLabel(packageManager).toString()
            nameTextView.text = appName
        }
    }
}

Next add a RecyclerView.Adapter implementation.

Listing 23.6  Implementing RecyclerView.Adapter (NerdLauncherActivity.kt)

class NerdLauncherActivity : AppCompatActivity() {
    ...
    private class ActivityHolder(itemView: View) :
            RecyclerView.ViewHolder(itemView) {
            ...
    }

    private class ActivityAdapter(val activities: List<ResolveInfo>) :
            RecyclerView.Adapter<ActivityHolder>() {

        override fun onCreateViewHolder(container: ViewGroup, viewType: Int):
                ActivityHolder {
            val layoutInflater = LayoutInflater.from(container.context)
            val view = layoutInflater
                .inflate(android.R.layout.simple_list_item_1, container, false)
            return ActivityHolder(view)
        }

        override fun onBindViewHolder(holder: ActivityHolder, position: Int) {
            val resolveInfo = activities[position]
            holder.bindActivity(resolveInfo)
        }

        override fun getItemCount(): Int {
            return activities.size
        }
    }
}

Here you inflate android.R.layout.simple_list_item_1 in onCreateViewHolder(…). The simple_list_item_1 layout file is part of the Android framework, which is why you reference it as android.R.layout instead of R.layout. This file contains a single TextView.

Last, but not least, update setupAdapter() to create an instance of ActivityAdapter and set it as the RecyclerView’s adapter.

Listing 23.7  Setting RecyclerView’s adapter (NerdLauncherActivity.kt)

class NerdLauncherActivity : AppCompatActivity() {
    ...
    private fun setupAdapter() {
        ...
        Log.i(TAG, "Found ${activities.size} activities")
        recyclerView.adapter = ActivityAdapter(activities)
    }
    ...
}

Run NerdLauncher, and you will see a RecyclerView populated with activity labels (Figure 23.3).

Figure 23.3  All your activities are belong to us

All your activities are belong to us
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.150.55