Direct Shareを試してみる


Android 6.0の新機能の一つとして *Direct Share* があります。
この機能はドキュメントのAPIの概要で次のように説明されています。

> この機能を使うと、他のアプリ内にある連絡先などのターゲットにコンテンツを共有できます。
たとえば、ダイレクト シェアのターゲットによって他のソーシャルネットワークアプリのアクティビティが起動し、
そのアプリ内の特定の友人やコミュニティに直接コンテンツを共有できるようになります。


![Image](https://developer.android.com/preview/images/direct-share-screen.png)

[Anroid M APIの概要](https://developer.android.com/intl/ja/preview/api-overview.html#direct-share)

つまり *Direct Share* ではコンテンツ共有時に「誰に共有するか」を指定することができるので、
ユーザは共有先のアプリで選択する必要がなくなり、より便利になるというものです。

SNS系やメッセンジャー、メールアプリなどでよく連絡をしている人を共有先に表示してくれていると、
わざわざ選択する手間が省けるのでよさそうですね。
人だけではなく、ファイルの保存先などよく使っているものを共有先に表示するのもありかもしれませんね。

ドキュメントやAPIリファレンスに実装方法は少し載っていますが、他にも情報が少ないと感じたので、試しに実装してみました。

* [APIリファレンス](https://developer.android.com/intl/ja/reference/android/service/chooser/ChooserTargetService.html)

実装したサンプルアプリはGitHubにあります。

* [GitHub](https://github.com/droibit/directshare)


## Direct Shareの実装方法

実装方法は従来通共有のものとあまり変わりませんが、
Direct Shareに対応するには共有先で `ChooserTargetService` を継承したサービスを作成する必要があります。

以下は、プレーンテキストをDirect Share対応アプリで共有し、画面に表示するようなものになっています。
（GitHubのサンプルものそうなっています）

### 共有先アプリの実装

まずはDirect Shareに対応するため、まずは共有先をチューザー画面に表示するためにサービスを実装します。

#### 独自ChooserTargetService

```java
public class MyChooserService extends ChooserTargetService {

    public static final String EXTRA_NAME = "name";
    public static final String EXTRA_ID = "id";

    private static final String[] NAMES = { ... }; // 共有先の名前

    /** {@inheritDoc} */
    @Override
    public List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) {
    	 // チューザー画面に表示するターゲットを作成します
        final List<ChooserTarget> targets = new ArrayList<>(NAMES.length);
        for (int i = 0; i < 10; i++) {
            // 共有先の名前
            final String title = String.format("%d %s", i, NAMES[i]);
            // 共有先の人のアイコン
            final Icon icon = Icon.createWithResource(this, R.drawable.ic_man);
            // 関連性の重みづけ（0.0f ~ 1.0f)で高いほど関連性あり
            final float score = .01f + (i / 10);
            // その他付随情報（アカウント情報の場合はIDなど必要かも）
            final Bundle extra = new Bundle();
            extra.putString(EXTRA_NAME, title);
            extra.putInt(EXTRA_ID, i);

			  // #onGetChooserTargetsの引数のtargetActivityNameはこのサービスに関連付けられている
			  // 共有先のActivityの名前
            final ChooserTarget target = new ChooserTarget(title, icon, score, targetActivityName, extra);
            targets.add(target);
        }
        
        // ※ ターゲットに設定し他スコアに基づき降順ソートする必要あり
        // 参照: https://developer.android.com/intl/ja/reference/android/service/chooser/ChooserTargetService.html
        
        return targets;
    }
}

```

以上の用に共有先であるターゲットを作成していきます。
ターゲットを10作成していますが、現在は最高でも8までしか表示されません。

#### マニフェスト

作成したサービスをマニフェストで宣言します。サービス名以外は固定的になるかと思います。

``` AndroidManifest.xml
<service
	android:name=".MyChooserService"
    android:label="@string/service_name"
    android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE" >
    <intent-filter>
        <action android:name="android.service.chooser.ChooserTargetService" />
    </intent-filter>
</service>

```

#### 共有結果表示画面

ターゲットから受け取った情報を画面に表示して、受け渡しが正しく行われているか確認することができます。

```java
public class MainActivity extends AppCompatActivity {

    /** {@inheritDoc} */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final Intent intent = getIntent();
        if (Intent.ACTION_SEND.equals(intent.getAction())) {
            final StringBuilder sb = new StringBuilder()
            		  // 以下は共有元でIntent#putExtra(...)で設定した値
                    .append(intent.getStringExtra(Intent.EXTRA_TITLE))
                    .append("\n")
                    .append(intent.getStringExtra(Intent.EXTRA_TEXT))
                    .append("\n")
                    // 以下はターゲット作成時の付随情報のBundleに設定した値
                    .append(intent.getExtras().getInt(MyChooserService.EXTRA_ID))
                    .append("\n")
                    .append(intent.getExtras().getString(MyChooserService.EXTRA_NAME));

            ((TextView) findViewById(R.id.text)).setText(sb.toString());
        }
    }
}
```

マニフェストには先ほど作成したサービスと関連付けるためにメタデータを記述します。

``` AndroidManifest.xml
<activity
    android:name=".MainActivity"
    android:label="@string/app_name" >

	<!-- テキストを共有できるようにする -->
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" />
    </intent-filter>

	<!-- 作成したサービスをvalueに設定する。 -->
    <meta-data
        android:name="android.service.chooser.chooser_target_service"
        android:value=".MyChooserService" />
</activity>

```

`android:value` への書き方はactivityタグの `android:name` と同じ書き方ができるようです。
なので、`apppackage.service.MyChooserService` というような *service* パッケージ内にある場合は次のように書くことができます。

```xml
<meta-data
        android:name="android.service.chooser.chooser_target_service"
        android:value=".service.MyChooserService" />

```

これでDirect Shareに対応した共有先アプリの実装は完了です。

### 共有元アプリの実装

共有元のアプリは変わりなく暗黙Intentを使用してテキストを共有します。
Context#startActivity()を呼ぶ際は `Intent.createChooser()` を使用します。チューザーを使用しないと共有先のターゲットは表示されません。


```java
final Intent intent = new Intent(Intent.ACTION_SEND)
                            .setType("text/plain")
                            .putExtra(Intent.EXTRA_TITLE, "From Direct Share")
                            .putExtra(Intent.EXTRA_TEXT, "Share Text!!!");
startActivity(Intent.createChooser(intent, "ChooserTargetService"));
```

上記の処理を呼ぶと次のように表示されます。

![Imgur](http://i.imgur.com/8kZMhEK.png)

共有先として「0 Sato」を選択します。

![Imgur](http://i.imgur.com/U0K6tEW.png)

サービスではターゲットの付随情報としてIDと名前を設定していたので、それを受け取れていることが画面から分かります。

DirectShareを有効にするには以上のようにChooserTargetServiceを拡張し、マニフェストに必要な設定をかくだけでいけます。
しかし、実際のアプリではターゲットのスコア（表示順位）の計算のために必要な情報を保存しておかなければいけないので、そのあたりのほうが重要になってくるのではないかと思います。

#### 課題

また、複数のアプリがターゲットを提供している場合、表示の際にスコアなどを元にシステムが順番を決めると思うのですがその辺りの挙動がつかめておらず、要調査ということもわかりました。

以上、簡単ですがDirect Shareの実装方法でした。