суббота, 1 октября 2011 г.

Реализация собственного AppChooser-а

В прошлой статье шла речь о работе с PackageManager. Вдогонку к ней расскажу я, как можно реализовать chooser приложений

Допустим, нам нужно выполнить действие с помощью внешней программы. К примеру, открыть страницу в браузере. Для этого мы пишем такой простой код:

    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setData(Uri.parse("http://www.google.com"));
    startActivity(intent);

И вдруг сложилось так, что браузеров несколько. Тогда после выполнения этого кода мы увидим диалог выбора приложения:

Стандартный диалог выбора приложения

Максимум, что можно здесь поменять — заголовок диалога. Для этого нужно написать следующее:

    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setData(Uri.parse("http://www.google.com"));
    startActivity(Intent.createChooser(intent, "Каким браузером просмотреть желаете?"));

Результат будет следующим:

Выбор приложения через chooser

Но что, если нам хочется предоставить какой-то другой интерфейс? Тогда нам снова поможет PackageManager и метод queryIntentActivities.

Приведу пример реализации chooser-а в виде галереи. На сей раз в качестве экшена будем использовать отправку текста (ACTION_SEND), т.к. приложений, обрабатывающих его, больше, и будет более наглядно.

private void sendViaCustomChooser() {
    Intent intent = new Intent(android.content.Intent.ACTION_SEND);
    intent.setType("text/plain");

    PackageManager pm = getPackageManager();
    List<ResolveInfo> infos = pm.queryIntentActivities(intent, 0);

    View dialogView = View.inflate(this, R.layout.custom_chooser, null);

    final Gallery gallery = (Gallery)dialogView.findViewById(R.id.gallery);
    gallery.setAdapter(new AppAdapter(pm, infos));
    gallery.setOnItemClickListener(new OnItemClickListener() {

        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            ResolveInfo ri = (ResolveInfo)gallery.getAdapter().getItem(position);

            Intent intent = new Intent(Intent.ACTION_SEND);
            intent.setClassName(ri.activityInfo.packageName, ri.activityInfo.name);
            intent.setType("text/plain");
            intent.putExtra(Intent.EXTRA_SUBJECT, "Sample subject");
            intent.putExtra(Intent.EXTRA_TEXT, "Sample text");

            startActivity(intent);
        }
    });

    Dialog d = new Dialog(this, android.R.style.Theme_Light_NoTitleBar);
    d.setContentView(dialogView);
    d.show();
}

private class AppAdapter extends BaseAdapter {
    List<ResolveInfo> mInfos;
    PackageManager mPm;

    private AppAdapter(PackageManager pm, List<ResolveInfo> infos) {
        this.mInfos = infos;
        this.mPm = pm;
    }

    @Override
    public int getCount() {
        return mInfos.size();
    }

    @Override
    public Object getItem(int position) {
        return mInfos.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ResolveInfo ri = mInfos.get(position);

        if (convertView == null) {
            convertView = View.inflate(StartActivity.this, R.layout.gallery_item, null);
        }

        ViewGroup container = (ViewGroup) convertView;

        ImageView image = (ImageView)container.findViewById(R.id.app_image);
        image.setImageDrawable(ri.activityInfo.loadIcon(mPm));

        TextView title = (TextView)container.findViewById(R.id.app_title);
        title.setText(ri.activityInfo.loadLabel(mPm));

        return container;
    }
}

Здесь довольно много кода, и поэтому то, что относится к PackageManager, выделено.

А результат будет следующим:

Chooser в виде галереи

Дам кое-какие пояснения. Мы выбрали из всех приложений активности, обрабатывающие экшен ACTION_SEND. Обратите внимание, что у одного приложения может быть несколько таких активностей (например, приложение ВКонтакте). Потом мы создали на основе этого списка адаптер для галереи. Кроме того, мы обработали событие выбора элемента и запускаем по нему соответствующее приложение.

Обращу внимание на несколько вещей:

  • Поле name у activityInfo — это не заголовок, а имя класса активности.
  • Заголовок же можно получить с помощью функции loadLabel, иконку — с помощью loadIcon.
  • Если хочется получить заголовок самого приложения, а не активности, это можно сделать так:
    ri.activityInfo.applicationInfo.loadLabel(mPm)
    
    или так:
    mPm.getApplicationLabel(ri.activityInfo.applicationInfo)
    

Заключение

В статье описана возможная реализация собственного AppChooser-а. Если есть другие варианты, прошу высказаться.

Исходники примера

3 комментария:

slampy комментирует...

Здравствуйте. Спс за пост. Но вот вопрос: возможно ли запретить (или вообще убрать) из chooser'а какое-нибудь приложение. Например, отправка через блютус.
То есть, я вот не хочу чтобы у пользователя была возможность выбрать другую программу, а только gmail. Возможно ли это?

darja комментирует...

Тогда не AppChooser нужен, а просто запуск приложения. Этот вопрос рассмотрен в предыдущей статье

Антон комментирует...

здравствуйте, очень позновательный пост.
подскажите можно ли реализовать вот такое:
в choose'ре должны быть только приложения для отправки по email и отправки по sms, потратил день но так и не придумал солюшена.

заранее спсб.