Androidの暗黙的Intent発行後に表示されるダイアログのアプリ表示順について

Androidで暗黙的Intentを発行すると、そのIntentを受け取れるアプリがダイアログでリスト表示される。 

![Screenshot_2015-04-16-21-04-05.jpg](https://qiita-image-store.s3.amazonaws.com/0/47437/50f12653-46c9-7b6a-5c31-de21f5ca6a92.jpeg)


端末にインストールされたアプリが増えてくると、アプリによっては下のほうに表示されたりして、たくさんスクロールをするはめになり面倒。
なので、そのダイアログで自分のアプリを上のほうに表示させるため、どんな感じにソートされているか等、少し調べてみました。


## Androidのソースコードを読む
実際に、暗黙的インテントを発行してダイアログを表示させてみると、先ほどのスクリーンショットのように、何となく、Activityのラベル名であいうえお順に並んでいるような雰囲気だけど、実際にどういう順序でソートされているのかAndroidのソースコードを読んで確認してみる。 

Androidのソースコードを読むには、Android Studioでctrl+h（もしくはカーソルを対象クラスにホバーしてctrl+enter）で辿って行ったり、以下サイトを使って読む。

[GrepCode.com - Java Source Code Search 2.0](http://grepcode.com/)
[android/platform_frameworks_base](https://github.com/android/platform_frameworks_base)

読んでいくと以下クラスを使ってソートしているっぽかった。


```java:ResolveInfo.java
public static class DisplayNameComparator
        implements Comparator<ResolveInfo> {
    public DisplayNameComparator(PackageManager pm) {
        mPM = pm;
        mCollator.setStrength(Collator.PRIMARY);
    }

    public final int compare(ResolveInfo a, ResolveInfo b) {
        // We want to put the one targeted to another user at the end of the dialog.
        if (a.targetUserId != UserHandle.USER_CURRENT) {
            return 1;
        }
        if (b.targetUserId != UserHandle.USER_CURRENT) {
            return -1;
        }
        CharSequence  sa = a.loadLabel(mPM);
        if (sa == null) sa = a.activityInfo.name;
        CharSequence  sb = b.loadLabel(mPM);
        if (sb == null) sb = b.activityInfo.name;
        
        return mCollator.compare(sa.toString(), sb.toString());
    }

    private final Collator   mCollator = Collator.getInstance();
    private PackageManager   mPM;
}
```

[platform_frameworks_base/ResolveInfo.java](https://github.com/android/platform_frameworks_base/blob/59701b9ba5c453e327bc0e6873a9f6ff87a10391/core/java/android/content/pm/ResolveInfo.java#L394-L419)

ResolveInfoから取得したActivityのラベル名を、mCollator(java.text.Collator)のcompare(String source, String target)を使って比較している。

```java:
// このへん
CharSequence  sa = a.loadLabel(mPM);
if (sa == null) sa = a.activityInfo.name;
CharSequence  sb = b.loadLabel(mPM);
if (sb == null) sb = b.activityInfo.name;

return mCollator.compare(sa.toString(), sb.toString()); 
```

## java.text.Collator
java.text.Collatorを使ったことがなかったので調べてみた。

```
Collator クラスは、ロケールに依存する String の比較を行います。このクラスを使って、自然言語テキストの検索とソートのルーチンを構築します。
```
[Collator (Java Platform SE 6)](https://docs.oracle.com/javase/jp/6/api/java/text/Collator.html)

とあり、ロケールに応じて、良い感じに比較してくれるみたい。 

さらに調べていくとCollator（抽象クラス）の具象サブクラスとして、RuleBasedCollatorがあった。

```
RuleBasedCollator クラスは Collator の具象サブクラスで、簡単な操作でデータドリブンのテーブルコレータを使用可能にします。このクラスを使うと、カスタマイズしたテーブルベースの Collator を作成することができます。RuleBasedCollator は、文字をソートキーにマップします。 
効率化のために、RuleBasedCollator には次の制約があります (さらに複雑な言語にはほかのサブクラスが使用される)。
```
[RuleBasedCollator (Java Platform SE 6)](https://docs.oracle.com/javase/jp/6/api/java/text/RuleBasedCollator.html)

```
　Collatorは非常に抽象化された存在で、その内部の比較の処理の実装が どうなっているかは公にはされていません。 通常はむしろそうしたブラックボックスとして取り扱うのが正しい態度です。
```
[Summer2000](http://www.wakhok.ac.jp/~tatsuo/summer2000/12shuu/all.html)

日本語の場合はかなり大変そう。
とりあえずソートしているクラス（Collator）はわかったので、Collatorでソートした際に最初の方にくる文字を調べてみる。




**Collatorを調べるためのサンプルコード**

```java
// ソート対象
String[] sampleArray = {
    "希林", "阿部", "北", "表彰", "たいよう", "その他", "遊ぶ", "アリス", "ァリス", "a", "A", "B", "b", "っ", "1",
    "100", "5", "1.0", "-1", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "-", "=", "^",
    "~", "¥", "|", "\\", "きりん", " zenkaku", "`", "*", "+", ";", ":", "?", "<", ">", "[", "]", "{",
    "}", "‘", "«", "@", "“", "/", ".", ",", "あんどろいｄ", "ぁんどろぃど", "　", "漢字", "？", "＞", "￥",
    "＄", "＋", "■", "໑", "๑", "൧", "੧", "†", "안녕", "終わり", "+　Pocket", "+ Pocket", "+アpocket",
    "+-pocket", " hankaku", "", " 半角", "　全角", "  "
};
List sampleList = Arrays.asList(sampleArray);
StringBuilder result = new StringBuilder();

result.append("\n");
result.append("--------------------------------------\n");
result.append("COLLATOR JP\n");
result.append("--------------------------------------\n");

// ロケールを日本語にしてインスタンス取得
Collator collator = Collator.getInstance(Locale.JAPANESE);
// Collator（日本語）でソート
Collections.sort(sampleList, collator);
result.append("START:");
for(int i = 0; i < sampleList.size(); i++) {
    result.append(sampleList.get(i).toString());
    result.append(" ");
}
result.append("\n");

result.append("--------------------------------------\n");
result.append("COLLATOR EN\n");
result.append("--------------------------------------\n");


// ロケールをEnglishにしてインスタンス取得
collator = Collator.getInstance(Locale.ENGLISH);
// Collator（English）でソート
Collections.sort(sampleList, collator);
result.append("START:");
for(int i = 0; i < sampleList.size(); i++) {
    result.append(sampleList.get(i).toString());
    result.append(" ");
}
result.append("\n");

result.append("--------------------------------------\n");
result.append("UNICODE\n");
result.append("--------------------------------------\n");

// Unicode順でソート
Collections.sort(sampleList);
result.append("START:");
for(int i = 0; i < sampleList.size(); i++) {
    result.append(sampleList.get(i).toString());
    result.append(" ");
}
result.append("\n");

Log.v("collator", result.toString());
```

実行すると以下のようになった。

```
--------------------------------------
COLLATOR JP
--------------------------------------
START: 　    　全角  半角  hankaku  zenkaku - -1 , ; : ! ? ？ . ' ‘ " “ « ( ) [ ] { } @ * / \ & # % † ` ^ + ＋ +　Pocket + Pocket +-pocket +アpocket < = > ＞ | ~ ■ $ ＄ ¥ ￥ 1 ੧ ൧ ๑ ໑ 1.0 100 5 ァリス アリス ぁんどろぃど あんどろいｄ きりん その他 たいよう っ 阿部 漢字 希林 終わり 表彰 北 遊ぶ a A b B 안녕
--------------------------------------
COLLATOR EN
--------------------------------------
START: 　     hankaku  zenkaku 　全角  半角 - -1 , ; : ! ? ？ . ' ‘ " “ « ( ) [ ] { } @ * / \ & # % † ` ^ + ＋ + Pocket +　Pocket +-pocket +アpocket < = > ＞ | ~ ■ $ ＄ ¥ ￥ 1 ੧ ൧ ๑ ໑ 1.0 100 5 a A b B 안녕 ァリス アリス あんどろいｄ ぁんどろぃど きりん その他 たいよう っ 北 希林 漢字 終わり 表彰 遊ぶ 阿部
--------------------------------------
UNICODE
--------------------------------------
START:     hankaku  zenkaku  半角 ! " # $ % & ' ( ) * + + Pocket +-pocket +　Pocket +アpocket , - -1 . / 1 1.0 100 5 : ; < = > ? @ A B [ \ ] ^ ` a b { | } ~ ¥ « ੧ ൧ ๑ ໑ ‘ “ † ■ 　 　全角 ぁんどろぃど あんどろいｄ きりん その他 たいよう っ ァリス アリス 北 希林 漢字 終わり 表彰 遊ぶ 阿部 안녕 ＄ ＋ ＞ ？ ￥
```

半角スペースが単体だと最も強力（上の結果では見えないけど）。だけど一文字目の時のみで、その後に文字が続くと全角のほうが強くなる。
あと、Activityのラベル名では最初が半角スペースだと、Collatorでソートする前の過程でtrimされて、半角スペースが消されてしまうっぽい。全角スペースはEnglishでも強い。

他でラベル名に使っても違和感ない、かつ、強そうなのは、+、>、()、[]、!くらいな気がする。
jpとenの違いは、jpだとカタカナ、ひらがな、漢字の順で優先され、enだとアルファベットが優先されるあたり。 

## 結局どうすれば良いのか
実際に自分の端末で、暗黙的Intent（テキスト）を発行した時に一番上に表示されているのは、Pocketアプリ。

![Screenshot_2015-04-16-21-04-05.jpg](https://qiita-image-store.s3.amazonaws.com/0/47437/50f12653-46c9-7b6a-5c31-de21f5ca6a92.jpeg)


Pocketは+と半角スペースで始まっているのでかなり強力。
これより強力なラベル名は、以下のようなラベル名が考えられるけど「+ Pocket」ほど自然な感じにはならない（上の方に表示させようとしている意図が利用者に伝わりそう）。

```
　追加する（一文字目が全角スペース） 
(hoge)追加する 
[hoge]追加する 
†追加する† 
!!追加する!! 
::追加する::
```

Pocketよりは表示順は下になるけど、>を使うと結構自然な感じになる。

```
> 追加する
```

![Screenshot_2015-04-16-22-03-01.jpg](https://qiita-image-store.s3.amazonaws.com/0/47437/be7b17f7-1ed5-6739-aeaa-4e511cac7f80.jpeg)