The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.


PackageManager Tricks

PackageManager is your primary means of introspection at the component level, to determine what else is installed on the device and what components they export (activities, etc.). As such, there are many ways you can use PackageManager to determine if something you want is possible or not, so you can modify your behavior accordingly (e.g., disable action bar items that are not possible).

This chapter will outline some ways you can use PackageManager to find out what components are available to you on a device.

Prerequisites

Understanding this chapter requires that you have read the core chapters of this book.

Asking Around

The ways to find out whether there is an activity that will respond to a given Intent are by means of queryIntentActivityOptions() and the somewhat simpler queryIntentActivities().

The queryIntentActivityOptions() method takes the caller ComponentName, the “specifics” array of Intent instances, the overall Intent representing the actions you are seeking, and the set of flags. It returns a List of Intent instances matching the stated criteria, with the “specifics” ones first.

If you would like to offer alternative actions to users, but by means other than addIntentOptions(), you could call queryIntentActivityOptions(), get the Intent instances, then use them to populate some other user interface (e.g., a toolbar).

A simpler version of this method, queryIntentActivities(), is used by the Introspection/Launchalot sample application. This presents a “launcher” — an activity that starts other activities — but uses a ListView rather than a grid like the Android default home screen uses.

Here is the Java code for Launchalot itself:

package com.commonsware.android.launchalot;

import android.app.ListActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import java.util.Collections;
import java.util.List;

public class Launchalot extends ListActivity {
  AppAdapter adapter=null;
  
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    PackageManager pm=getPackageManager();
    Intent main=new Intent(Intent.ACTION_MAIN, null);
        
    main.addCategory(Intent.CATEGORY_LAUNCHER);

    List<ResolveInfo> launchables=pm.queryIntentActivities(main, 0);
    
    Collections.sort(launchables,
                     new ResolveInfo.DisplayNameComparator(pm)); 
    
    adapter=new AppAdapter(pm, launchables);
    setListAdapter(adapter);
  }
  
  @Override
  protected void onListItemClick(ListView l, View v,
                                 int position, long id) {
    ResolveInfo launchable=adapter.getItem(position);
    ActivityInfo activity=launchable.activityInfo;
    ComponentName name=new ComponentName(activity.applicationInfo.packageName,
                                         activity.name);
    Intent i=new Intent(Intent.ACTION_MAIN);
    
    i.addCategory(Intent.CATEGORY_LAUNCHER);
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    i.setComponent(name);
    
    startActivity(i);    
  }
  
  class AppAdapter extends ArrayAdapter<ResolveInfo> {
    private PackageManager pm=null;
    
    AppAdapter(PackageManager pm, List<ResolveInfo> apps) {
      super(Launchalot.this, R.layout.row, apps);
      this.pm=pm;
    }
    
    @Override
    public View getView(int position, View convertView,
                          ViewGroup parent) {
      if (convertView==null) {
        convertView=newView(parent);
      }
      
      bindView(position, convertView);
      
      return(convertView);
    }
    
    private View newView(ViewGroup parent) {
      return(getLayoutInflater().inflate(R.layout.row, parent, false));
    }
    
    private void bindView(int position, View row) {
      TextView label=(TextView)row.findViewById(R.id.label);
      
      label.setText(getItem(position).loadLabel(pm));
      
      ImageView icon=(ImageView)row.findViewById(R.id.icon);
      
      icon.setImageDrawable(getItem(position).loadIcon(pm));
    }
  }
}

In onCreate(), we:

  1. Get a PackageManager object via getPackageManager()
  2. Create an Intent for ACTION_MAIN in CATEGORY_LAUNCHER, which identifies activities that wish to be considered “launchable”
  3. Call queryIntentActivities() to get a List of ResolveInfo objects, each one representing one launchable activity
  4. Sort those ResolveInfo objects via a ResolveInfo.DisplayNameComparator instance
  5. Pour them into a custom AppAdapter and set that to be the contents of our ListView

AppAdapter is an ArrayAdapter subclass that maps the icon and name of the launchable Activity to a row in the ListView, using a custom row layout.

Finally, in onListItemClick(), we construct an Intent that will launch the clicked-upon Activity, given the information from the corresponding ResolveInfo object. Not only do we need to populate the Intent with ACTION_MAIN and CATEGORY_LAUNCHER, but we also need to set the component to be the desired Activity. We also set FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flags, following Android’s own launcher implementation from the Home sample project. Finally, we call startActivity() with that Intent, which opens up the activity selected by the user.

The result is a simple list of launchable activities:

The Launchalot sample application
Figure 896: The Launchalot sample application

There is also a resolveActivity() method that takes a template Intent, as do queryIntentActivities() and queryIntentActivityOptions(). However, resolveActivity() returns the single best match, rather than a list.

NOTE: On modern versions of Android, there is a LauncherApps class that simplifies a lot of this and takes things like Android Work profiles into account. For really implementing a home screen-style launcher, you will probably want to use LauncherApps. However, using PackageManager to find what can handle certain Intent structures is used for other purposes beyond home screen launchers.

Preferred Activities

The preview of this section is in the process of being translated from its native Klingon.

Middle Management

The preview of this section was the victim of a MITM ('Martian in the middle') attack.