Android
AWS
SNS
Firebase
FCM

Android から Amazon SNS を使ってみる

0.はじめに

以前、こちらの記事を投稿したんですが、

書いた以上はやらないとな…、
と思い、

Android 全く使ったことないですが…、
頑張って Android から Amazon SNS を使ってみました。

1.Android Studio で、新規プロジェクトを作成する

  1. Android Studio を起動し、「Start a new Android Studio project」リンクをクリックします。
    • 1001.png

  2. 「Create Android Project」画面が表示されるので、以下の項目を入力し「Next」ボタンを押下します。
    • Application name : ※任意
    • Company domain : ※任意

    • 1002.png

  3. 「Target Android Devices」画面が表示されるので、とりあえずデフォルトの値のままで「Next」ボタンを押下します。
    • 1003.png

  4. 「Add an Activity to Mobile」画面が表示されるので、「Empty Activity」を選択し「Next」ボタンを押下します。
    • 1004.png

  5. 「Configure Activity」画面が表示されるので、とりあえずデフォルトの値のままで「Next」ボタンを押下します。
    • 1005.png

  6. そのまま待っていると、プロジェクトが作成されます。
    • 1006.png

2.Android Studio で、作成したプロジェクトを実機で動かす

  1. 実機を接続して、以下のサイトを参考に開発モードの設定をします。
  2. Android Studio からプロジェクトを開き、「Run」アイコンをクリックします。
    • 2001.png

  3. 「Select Deployment Target」ダイアログが表示されるので、接続した実機が表示されていることを確認し、選択後、「OK」ボタンを押下します。
    • 2002.png

  4. 実機にアプリがインストール後、実行され、以下の「Hello World!」の画面が表示されます。
    • 2003.jpg

3.Firebase プロジェクトを作成する

  1. 以下のサイトから、FireBase のコンソールを開き、「プロジェクトを追加」ボタンを押下します。
    • 3001.png

  2. 「プロジェクトを追加」ダイアログが表示されるので、以下の項目を設定し、「プロジェクトを作成」ボタンを押下します。
    • プロジェクト名 : ※任意
    • 国 / 地域 : ※任意

    • 3002.png

  3. 以下の画面が表示され、Firebase プロジェクトが作成されます。
    • 3003.png

4.Firebase プロジェクトにアプリを登録する。

  1. 作成した Firebase プロジェクトのオーバービューから、「Android アプリに Firebase を追加」ボタンを押下します。
    • 4001.png

  2. 「Android アプリに Firebase を追加」ダイアログが表示されるので、以下の項目を設定し、「アプリの登録」ボタンを押下します。
    • Android パッケージ名 : ※作成した Android アプリのパッケージ名を入力
      • ※ MainActivity.java に記載。

    • 4002.png

  3. 「Android アプリに Firebase を追加」ダイアログが更新され、「Android Studio の使用手順」が表示されるので、手順に従って google-services.json をダウンロードし、所定の場所に移動後、「続行」ボタンを押下します。
    • 4003.png

  4. 「Android アプリに Firebase を追加」ダイアログが更新され、「Gradle の使用手順」が表示されるので、手順に従って2つの build.gradle を変更し、「Sync now」リンククリック後、「終了」ボタンを押下します。
    • 4004.png

  5. 「Android アプリに Firebase を追加」ダイアログが閉じ、Firebase プロジェクトの「概要」画面が表示されるので、アプリが登録されたことを確認します。
    • 4005.png

5.Firebase プロジェクトに登録したアプリのサーバーキーを確認する

  1. Firebase プロジェクトの「概要」画面に表示されている登録したアプリエリアの右上のメニューを開き、「設定」を選択します。
    • 5001.png

  2. Firebase プロジェクトの「設定」画面が表示されるので、「クラウドメッセージング」タブを選択します。
    • 5002.png

  3. Firebase プロジェクトの「設定」画面に「クラウドメッセージング」の情報が表示されるので、「プロジェクト認証情報」エリアの「サーバーキー」を確認します。
    • 5003.png

6.Amazon SNS にプラットフォームアプリケーションを作成する

  1. 以下のサイトから、Amazon SNS のコンソールを開きます。
  2. Amazon SNS のコンソールの左側ペインから、「アプリケーション」を選択します。
    • 6001.png

  3. Amazon SNS の「アプリケーション」画面が表示されるので、「プラットフォームアプリケーションの作成」ボタンを押下します。
    • 6002.png

  4. 「プラットフォームアプリケーションの作成」ダイアログが表示されるので、以下の項目を設定し、「プラットフォームアプリケーションの作成」ボタンを押下します。
    • アプリケーション名 : ※任意
    • プッシュ通知プラットフォーム : 「Google Cloud Messaging (GCM)」
    • API キー : ※確認しておいた Firebase プロジェクトのクラウドメッセージングの「サーバーキー」
      • ※「以前のサーバーキー」ではないので、注意!!

    • 6003.png

  5. Amazon SNS のプラットフォームアプリケーションが作成されます。
    • 6004.png

7.Amazon Cognito に ID プールを作成する

  1. 以下のサイトから、Amazon Cognito フェデレーテッドアイデンティティのコンソールを開きます。
  2. 「新しい ID プールの作成」ボタンを押下します。
    • 7001.png

  3. 「使用開始ウィザード - ステップ 1:ID プールを作成する」画面が表示され、以下の項目を設定し、「プールの作成」ボタンを押下します。
    • ID プール名 : ※任意
    • 認識されていない ID
      • 認識されていない ID に対してアクセスを有効にする : ☑︎

    • 7002.png

  4. 画面が更新されるので、新しく作成される2つ(Auth と Unauth)の IAM ロールのロール名を確認し、「許可」ボタンを押下します。
    • 7003.png

  5. 「サンプルコード」画面が表示され、ID プールが作成されます。
    • 7004.png

8.作成した Amazon Cognito ID プールの IAM ロール に Amazon SNS 用のインラインポリシーを追加する

  1. 以下のサイトから、AWS Identity and Access Management (IAM) のコンソールを開きます。
  2. AWS Identity and Access Management (IAM) のコンソールの左側ペインから、「ロール」を選択します。
    • 8001.png

  3. ロールの一覧が表示されるので、作成した Amazon Cognito ID プールの IAM ロール (Unauth) の「ロール名」リンクをクリックします。
    • 8002.png

  4. 作成した Amazon Cognito ID プールの IAM ロール (Unauth) の概要の画面が表示されるので、「アクセス権限」のタブを選択し、「インラインポリシーの追加」リンクをクリックします。
    • 8003.png

  5. 「ポリシーの作成」画面が表示されるので、「ビジュアルエディタ」のタブを選択後、以下の項目を設定し、「Review policy 」ボタンを押下します。
    • サービス : SNS
    • アクション :
      • 書き込み :
        • ☑︎ CreatePlatformEndpoint
        • ☑︎ SetEndpointAttributes

    • 8004.png

  6. 「ポリシーの作成 - ポリシーの確認」画面が表示されるので、以下の項目を設定し、「Create policy 」ボタンを押下します。
    • 名前 : ※任意

    • 8005.png

  7. 作成したインラインポリシーの内容を確認します。
    • 8006.png

9.Android アプリへ実装する

  1. <project>/<app-module>/build.gradle へ、以下のライブラリの登録の設定を追加する。
  2. build.gradle
    
    
    dependencies {
    
        
    
        // [2018.01.12] ADD - BGN
        // 「Android端末で、FCM経由でAWSSNSを受け取るまで - Qiita」
        // <https://qiita.com/k_keisuke/items/24afa62955808721033e>
        // ...
        compile 'com.google.firebase:firebase-core:10.2.1'
        compile 'com.google.firebase:firebase-messaging:10.2.1'
        // ...
        compile 'com.amazonaws:aws-android-sdk-core:2.4.+'
        compile 'com.amazonaws:aws-android-sdk-sns:2.4.+'
        // [2018.01.12] ADD - END
    }
    
    
    

  3. AndroidManifest.xml へ、追加するクラスの設定を追加する。
  4. AndroidManifest.xml
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.kusokamayarou.android_sns_sample0001">
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme"><!-- [2018.01.12] ADD - BGN -->
            <!--「マニフェストの設定」-->
            <!-- https://docs.kii.com/ja/samples/push-notifications/push-notifications-android-fcm/modify-manifest/ -->
            <service android:name=".MyFirebaseMessagingService">
                <intent-filter>
                    <action android:name="com.google.firebase.MESSAGING_EVENT"/>
                </intent-filter>
            </service>
            <service android:name=".MyFirebaseInstanceIDService">
                <intent-filter>
                    <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
                </intent-filter>
            </service>
            <!-- [2018.01.12] ADD - END -->
    
        </application>
    
    </manifest>
    

  5. 以下の MyFirebaseInstanceIDService.java を追加する。
  6. MyFirebaseInstanceIDService.java
    package com.kusokamayarou.android_sns_sample0001;
    
    import android.annotation.SuppressLint;
    import android.content.SharedPreferences;
    import android.util.Log;
    
    import com.google.firebase.iid.FirebaseInstanceId;
    import com.google.firebase.iid.FirebaseInstanceIdService;
    
    import java.util.HashMap;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    // 「Android端末で、FCM経由でAWSSNSを受け取るまで - Qiita」
    // <https://qiita.com/k_keisuke/items/24afa62955808721033e>
    
    import com.amazonaws.regions.Regions;
    import com.amazonaws.auth.CognitoCachingCredentialsProvider;
    import com.amazonaws.services.sns.AmazonSNSClient;
    import com.amazonaws.services.sns.model.CreatePlatformEndpointRequest;
    import com.amazonaws.services.sns.model.CreatePlatformEndpointResult;
    import com.amazonaws.services.sns.model.InvalidParameterException;
    import com.amazonaws.services.sns.model.SetEndpointAttributesRequest;
    
    public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService {
    
        private final static String TAG = MyFirebaseInstanceIDService.class.getSimpleName();
    
        private static final String APPLICATION_ARN = "[プラットフォームアプリケーションの ARN]";
        private static final String ENDPOINT = "https://sns.ap-northeast-1.amazonaws.com";
        //private static final String ACCESS_KEY = "AWSのアクセスキー";
        //private static final String SECRET_KEY = "AWSのSecretKey";
    
        @Override
        public void onTokenRefresh() {
            super.onTokenRefresh();
            String token = FirebaseInstanceId.getInstance().getToken();
            sendRegistrationToServer(token);
        }
    
        @SuppressLint("LongLogTag")
        private void sendRegistrationToServer(String token) {
            //AmazonSNSClient client = new AmazonSNSClient(generateAWSCredentials());
            CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
                    getApplicationContext(),
                    "[Amazon Cognito の ID プールの ID]", // ID プールの ID
                    Regions.AP_NORTHEAST_1 // リージョン
            );
            AmazonSNSClient client = new AmazonSNSClient(credentialsProvider);
            client.setEndpoint(ENDPOINT);
            //SharedPreferenceに保存したendpointArnが存在したらそちらから取得するようにしてもOK
            String endpointArn = createEndpointArn(token, client);
            // 「[Android] データを保存し Android Studio で確認 SharedPreferences | nyanのアプリ開発」
            // <https://akira-watson.com/android/sharedpreferences.html>
            SharedPreferences dataStore = getSharedPreferences("DataStore", MODE_PRIVATE);
            SharedPreferences.Editor editor = dataStore.edit();
            editor.putString("DeviceToken", token);
            editor.putString("EndpointArn", endpointArn);
            editor.apply();
            Log.i(TAG, "@@@@@@@@@@@@@@@@@@@@ | DeviceTokenArray: " + token);
            Log.i(TAG, "@@@@@@@@@@@@@@@@@@@@ | EndpointArnArray: " + endpointArn);
            HashMap<String, String> attr = new HashMap<>();
            attr.put("Token", token);
            attr.put("Enabled", "true");
            SetEndpointAttributesRequest req = new SetEndpointAttributesRequest().withEndpointArn(endpointArn).withAttributes(attr);
            client.setEndpointAttributes(req);
        }
    
        @SuppressLint("LongLogTag")
        private String createEndpointArn(String token, AmazonSNSClient client) {
            String endpointArn;
            try {
                System.out.println("Creating platform endpoint with token " + token);
                CreatePlatformEndpointRequest cpeReq =
                        new CreatePlatformEndpointRequest()
                                .withPlatformApplicationArn(APPLICATION_ARN)
                                .withToken(token);
                CreatePlatformEndpointResult cpeRes = client
                        .createPlatformEndpoint(cpeReq);
                endpointArn = cpeRes.getEndpointArn();
            } catch (InvalidParameterException ipe) {
                String message = ipe.getErrorMessage();
                System.out.println("Exception message: " + message);
                Pattern p = Pattern
                        .compile(".*Endpoint (arn:aws:sns[^ ]+) already exists " +
                                "with the same token.*");
                Matcher m = p.matcher(message);
                if (m.matches()) {
                    // The platform endpoint already exists for this token, but with
                    // additional custom data that
                    // createEndpoint doesn't want to overwrite. Just use the
                    // existing platform endpoint.
                    endpointArn = m.group(1);
                } else {
                    // Rethrow the exception, the input is actually bad.
                    throw ipe;
                }
            }
            storeEndpointArn(endpointArn);
            return endpointArn;
        }
    
        /*
        private AWSCredentials generateAWSCredentials() {
            return new AWSCredentials() {
                @Override
                public String getAWSAccessKeyId() {
                    return ACCESS_KEY;
                }
    
                @Override
                public String getAWSSecretKey() {
                    return SECRET_KEY;
                }
            };
        }
        */
    
        private void storeEndpointArn(String endpointArn) {
            //SharedPreferenceにでもendpointArnを保存して、次回以降はcreateEndpointArnの処理を省略しても良い(公式はその方式になってる)
        }
    
        /*
        private String getEndPointArn() {
            //SharedPreferenceからendpointArnを取得して、次回以降はcreateEndpointArnの処理を省略しても良い(公式はその方式になってる)
        }
        */
    }
    

  7. 以下の MyFirebaseMessagingService.java を追加する。
  8. MyFirebaseMessagingService.java
    package com.kusokamayarou.android_sns_sample0001;
    
    import android.annotation.SuppressLint;
    import android.app.NotificationManager;
    import android.app.PendingIntent;
    import android.content.Context;
    import android.content.Intent;
    import android.support.v4.app.NotificationCompat;
    import android.util.Log;
    
    import com.google.firebase.messaging.FirebaseMessagingService;
    import com.google.firebase.messaging.RemoteMessage;
    
    public class MyFirebaseMessagingService extends FirebaseMessagingService {
    
        private final static String TAG = MyFirebaseMessagingService.class.getSimpleName();
    
        /**
         * Called when message is received.
         *
         * @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
         */
        // [START receive_message]
        @SuppressLint("LongLogTag")
        @Override
        public void onMessageReceived(RemoteMessage remoteMessage) {
            // TODO(developer): Handle FCM messages here.
            // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
            Log.i(TAG, "From: " + remoteMessage.getFrom());
    
            // Check if message contains a data payload.
            if (remoteMessage.getData().size() > 0) {
                Log.i(TAG, "Message data payload: " + remoteMessage.getData());
            }
    
            // 「【Android】NotificationからIntentでデータを渡してアプリ起動するときのIntent.setFlagsメモ - Qiita」
            // <https://qiita.com/SnowMonkey/items/7cdc52c83bb9490d97a5>
            NotificationManager notificationManager = (NotificationManager)this.getSystemService(Context.NOTIFICATION_SERVICE);
            Intent intent = new Intent(this, MainActivity.class);
            intent.putExtra("hoge", "fuga");
            intent.setFlags(
                    Intent.FLAG_ACTIVITY_CLEAR_TOP  // 起動中のアプリがあってもこちらを優先する
                            | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED  // 起動中のアプリがあってもこちらを優先する
                            | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS  // 「最近利用したアプリ」に表示させない
            );
            PendingIntent contentIntent =
                    PendingIntent.getActivity(
                            this,
                            0,
                            intent,
                            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT);
            NotificationCompat.Builder mBuilder =
                    new NotificationCompat.Builder(this)
                            .setSmallIcon(android.R.drawable.sym_def_app_icon)
                            .setContentTitle(remoteMessage.getData().toString())
                            .setContentText("Notification Message")
                            .setStyle(new NotificationCompat.BigTextStyle().bigText("Notification Message"));
            mBuilder.setContentIntent(contentIntent);
            notificationManager.notify(0, mBuilder.build());
        }
    }
    

  9. MainActivity.java へ、以下のコードを追加する。
  10. MainActivity.java
    package com.kusokamayarou.android_sns_sample0001;
    
    import android.content.SharedPreferences;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    
    public class MainActivity extends AppCompatActivity {
    
        // [2018.01.12] ADD - BGN
        private final static String TAG = MainActivity.class.getSimpleName();
        // [2018.01.12] ADD - END
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            // [2018.01.12] ADD - BGN
            // 「[Android] データを保存し Android Studio で確認 SharedPreferences | nyanのアプリ開発」
            // <https://akira-watson.com/android/sharedpreferences.html>
            SharedPreferences dataStore = getSharedPreferences("DataStore", MODE_PRIVATE);
            String DeviceTokenArray = dataStore.getString("DeviceToken", "");
            Log.i(TAG, "@@@@@@@@@@@@@@@@@@@@ | DeviceTokenArray: " + DeviceTokenArray);
            String EndpointArnArray = dataStore.getString("EndpointArn", "");
            Log.i(TAG, "@@@@@@@@@@@@@@@@@@@@ | EndpointArnArray: " + EndpointArnArray);
            // [2018.01.12] ADD - END
        }
    }
    

    10.アプリを実行し、Amazon SNS からの通知を確認する。

    1. こちらのサイト ( Android に Firebase Cloud Messaging クライアント アプリを設定する  |  Firebase ) にも記載がありますが、FCM の登録トークンを改めて取得したい場合は、アプリをアンインストールしたりすればいい様なので、とりあえず一度アプリをアンインストールします。アプリのアンインストールは、ググれば出て来ると思います。
    2. Android Studio からデバッグモードで RUN 実行します。
      • 10-01.png

    3. Android Studio の Logcat に登録トークンがログ出力されます。
      • 10-02.png

    4. Amazon SNS のプラットフォームアプリケーションの詳細画面を開き、登録トークンからエンドポイントが作成されたか確認します。
      • 10-03.png

    5. 作成されたエンドポイントを選択し、「エンドポイントの発行」ボタンを押下します。
      • 10-04.png

    6. 「メッセージを発行」画面が表示されるので、以下の項目を設定し、「メッセージの発行」ボタンを押下します。
      • メッセージ形式 : JSON
      • メッセージ
        • { "GCM": "{ \"data\": { \"message\": \"Test\" } }" }

      • 10-05.png

    7. 実機に通知が届いたか確認します。
      • 10-06.jpg

    99.ハマりポイント

  • 通知の確認のところでも確認しましたが、登録トークンがどうやったら改めて取得できるのかよくわからなく、ちょっと戸惑いました。

  • 今回、Firebase プロジェクトのアプリの登録を手動で行いましたが、以下のサイトを見ると Firebase Assistant を使用して登録する手順を推奨している様にも思いました。
  • 後は単純に Android 開発自体ほぼ初めてだったので、Android Studio の使い方からよくわからなかったので、ちょっと苦労しました。

  • っていうか、Android も触ったことなかったので、アプリのアンインストールのやり方とか、通知の確認の仕方とか、逐一わからなくて苦労しました。

XX.まとめ

コードにもコメントで記載していますが、今回も多くのサイトに助けられました。

特に、こちらのサイトはお世話になりました。

っていうか、
ほぼ丸コピに近い様な…。
本当にありがとうございました。

他にも、
* [Android]プッシュ通知実装[Firebase][FCM] – チェンぴログ
* マニフェストの設定
* [Android] データを保存し Android Studio で確認 SharedPreferences | nyanのアプリ開発
* 【Android】NotificationからIntentでデータを渡してアプリ起動するときのIntent.setFlagsメモ - Qiita

など、
参考にさせて頂きました。
ありがとうございました。

次は、iOS だな…。