Pages

Monday 23 January 2012

Creating a custom Android Intent Chooser

I recently added a feature to my Zedusa image resizer application that required me to write a custom intent chooser. The reason for this was so I could add a checkbox on the list so the user could choose to make the application they selected the default one going forward.


I thought I'd share a snippet of the code on my blog as it could be useful for some. Feel free to ask any questions about the inner workings of it, otherwise I'll leave it up to you to read and understand. Please be aware I stripped some code out so I haven't actually ran the exact code below on my phone.

public void startDefaultAppOrPromptUserForSelection() {
 String action = Intent.ACTION_SEND;

 // Get list of handler apps that can send
 Intent intent = new Intent(action);
 intent.setType("image/jpeg");
 PackageManager pm = getPackageManager();
 List<ResolveInfo> resInfos = pm.queryIntentActivities(intent, 0);

 boolean useDefaultSendApplication = sPrefs.getBoolean("useDefaultSendApplication", false);
 if (!useDefaultSendApplication) {
  // Referenced http://stackoverflow.com/questions/3920640/how-to-add-icon-in-alert-dialog-before-each-item

  // Class for a singular activity item on the list of apps to send to
  class ListItem {
   public final String name;
   public final Drawable icon;
   public final String context;
   public final String packageClassName;
   public ListItem(String text, Drawable icon, String context, String packageClassName) {
    this.name = text;
    this.icon = icon;
    this.context = context;
    this.packageClassName = packageClassName;
   }
   @Override
   public String toString() {
    return name;
   }
  }

  // Form those activities into an array for the list adapter
  final ListItem[] items = new ListItem[resInfos.size()];
  int i = 0;
  for (ResolveInfo resInfo : resInfos) {
   String context = resInfo.activityInfo.packageName;
   String packageClassName = resInfo.activityInfo.name;
   CharSequence label = resInfo.loadLabel(pm);
   Drawable icon = resInfo.loadIcon(pm);
   items[i] = new ListItem(label.toString(), icon, context, packageClassName);
   i  ;
  }
  ListAdapter adapter = new ArrayAdapter<ListItem>(
    this,
    android.R.layout.select_dialog_item,
    android.R.id.text1,
    items){

   public View getView(int position, View convertView, ViewGroup parent) {
    // User super class to create the View
    View v = super.getView(position, convertView, parent);
    TextView tv = (TextView)v.findViewById(android.R.id.text1);

    // Put the icon drawable on the TextView (support various screen densities)
    int dpS = (int) (32 * getResources().getDisplayMetrics().density   0.5f);
    items[position].icon.setBounds(0, 0, dpS, dpS);
    tv.setCompoundDrawables(items[position].icon, null, null, null);

    // Add margin between image and name (support various screen densities)
    int dp5 = (int) (5 * getResources().getDisplayMetrics().density   0.5f);
    tv.setCompoundDrawablePadding(dp5);

    return v;
   }
  };

  // Build the list of send applications
  AlertDialog.Builder builder = new AlertDialog.Builder(this);
  builder.setTitle("Choose your app:");
  builder.setIcon(R.drawable.dialog_icon);
  CheckBox checkbox = new CheckBox(getApplicationContext());
  checkbox.setText(getString(R.string.enable_default_send_application));
  checkbox.setOnCheckedChangeListener(new OnCheckedChangeListener() {

   // Save user preference of whether to use default send application
   @Override
   public void onCheckedChanged(CompoundButton paramCompoundButton,
     boolean paramBoolean) {
    SharedPreferences.Editor editor = sPrefs.edit();
    editor.putBoolean("useDefaultSendApplication", paramBoolean);
    editor.commit();
   }
  });
  builder.setView(checkbox);
  builder.setOnCancelListener(new OnCancelListener() {

   @Override
   public void onCancel(DialogInterface paramDialogInterface) {
    // do something
   }
  });

  // Set the adapter of items in the list
  builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
   @Override
   public void onClick(DialogInterface dialog, int which) {
    SharedPreferences.Editor editor = sPrefs.edit();
    editor.putString("defaultSendApplicationName", items[which].name);
    editor.putString("defaultSendApplicationPackageContext", items[which].context);
    editor.putString("defaultSendApplicationPackageClassName", items[which].packageClassName);
    editor.commit();

    dialog.dismiss();

    // Start the selected activity sending it the URLs of the resized images
    Intent intent;
    intent = new Intent(Intent.ACTION_SEND);
    intent.setType("image/jpeg");
    intent.setClassName(items[which].context, items[which].packageClassName);
    startActivity(intent);
    finish();
   }
  });

  AlertDialog dialog = builder.create();
  dialog.show();


 } else { // Start the default send application

  // Get default app name saved in preferences
  String defaultSendApplicationName = sPrefs.getString("defaultSendApplicationName", "<null>");
  String defaultSendApplicationPackageContext = sPrefs.getString("defaultSendApplicationPackageContext", "<null>");
  String defaultSendApplicationPackageClassName = sPrefs.getString("defaultSendApplicationPackageClassName", "<null>");
  if (defaultSendApplicationPackageContext == "<null>" || defaultSendApplicationPackageClassName == "<null>") {
   Toast.makeText(getApplicationContext(), "Can't find app: "  defaultSendApplicationName  
     " ("   defaultSendApplicationPackageClassName   ")", Toast.LENGTH_LONG).show();

   // don't have default application details in prefs file so set use default app to null and rerun this method
   SharedPreferences.Editor editor = sPrefs.edit();
   editor.putBoolean("useDefaultSendApplication", false);
   editor.commit();
   startDefaultAppOrPromptUserForSelection();
   return;
  }

  // Check app is still installed
  try {
   ApplicationInfo info = getPackageManager().getApplicationInfo(defaultSendApplicationPackageContext, 0);
  } catch (PackageManager.NameNotFoundException e){
   Toast.makeText(getApplicationContext(),  "Can't find app: "   defaultSendApplicationName  
     " ("   defaultSendApplicationPackageClassName   ")", Toast.LENGTH_LONG).show();

   // don't have default application installed so set use default app to null and rerun this method
   SharedPreferences.Editor editor = sPrefs.edit();
   editor.putBoolean("useDefaultSendApplication", false);
   editor.commit();
   startDefaultAppOrPromptUserForSelection();
   return;
  }

  // Start the selected activity
  intent = new Intent(Intent.ACTION_SEND);
  intent.setType("image/jpeg");
  intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
  intent.setClassName(defaultSendApplicationPackageContext, defaultSendApplicationPackageClassName);
  startActivity(intent);
  finish();
  return;
 }
}

8 comments:

  1. Excellent, thanks a lot for this

    ReplyDelete
  2. Nice tutorial, very very useful to me. Many thanks!

    ReplyDelete
  3. could be please share this particular project? i mean only the 'set this as default application' working project?

    ReplyDelete
    Replies
    1. Sorry I don't have a project just for that and cannot share any of the other source code. If you have any questions please ask here and I'll try and help.

      Delete
  4. sPrefs? What is this adressed for, getting error on some places after importing everything fine?

    and density is given in 0.5f, it showing error too.

    ReplyDelete
  5. please make this tutorial more understandable by adding a share button implementation to it.

    i am getting error at this line in log:

    boolean useDefaultSendApplication = sPrefs.getBoolean("useDefaultSendApplication", false);

    trying to solve it but it still giving errors.

    a little help will be appreciated.

    Thanks in advance.

    ReplyDelete
  6. Thanks a lot, i found this article really useful

    ReplyDelete
  7. i copied the code and it was working but now the checkbox no longef shows. might therd be a verson dependency?

    ReplyDelete