diff --git a/.gitignore b/.gitignore
index 9f00b09..e70e33a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,5 +8,13 @@ local.properties
bin/
target
gen/
+node/node_modules
+sf-gcm-demo/build
+sf-gcm-demo/google-services.json
+sf-gcm-demo/gradle
+sf-gcm-demo/app/build
+sf-gcm-demo/app/gradle
+sf-gcm-demo/app/google-services.json
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ba3a209
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 SungKwang Song
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README b/README
deleted file mode 100644
index e69de29..0000000
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c67af5e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,56 @@
+## 블로그 글
+
+이 예제는 [saltfactory's blog](http://blog.saltfactory.net)의 [최신 Android Studio, GCM(Google Cloud Messaging), Node.js를 이용하여 Android 푸시 서비스 구현하기](http://blog.saltfactory.net/android/implement-push-service-via-gcm.html) 글의 예제 소스 코드입니다.
+
+이 소스코드는 **Android** 개발을 할 때 [GCM(Google Cloud Messaging)](https://developers.google.com/cloud-messaging/)을 이용하여 푸시 서비스를 **Android Studio**로 개발하는 내용을 포함하고 있습니다.
+
+## 디렉토리 구조
+
+* **sf-gcm-demo/** : Android Studio로 개발한 GCM Demo 앱 소스코드가 포함되어 있습니다.
+* **node/** : Node.js로 만든 GCM을 이용하여 Android 디바이스로 메세지를 보내는 소스코드가 포함되어 있습니다.
+* **bash/** : GCM 서비스의 Google Connection Server를 이용하여 Android 디바이스로 메세지를 보내는 소스코드가 포함되어 있습니다.
+
+
+## 연구원 소개
+
+* 작성자 : [송성광](http://about.me/saltfactory) 개발 연구원
+* 블로그 : http://blog.saltfactory.net
+* 이메일 : [saltfactory@gmail.com](mailto:saltfactory@gmail.com)
+* 트위터 : [@saltfactory](https://twitter.com/saltfactory)
+* 페이스북 : https://facebook.com/salthub
+* 연구소 : [하이브레인넷](http://www.hibrain.net) 부설연구소
+* 연구실 : [창원대학교 데이터베이스 연구실](http://dblab.changwon.ac.kr)
+
+
+## 기부하기
+
+> 기부금은 연구활동과 블로그 운영에 사용됩니다.
+
+기부방법은 [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=NR99D2BERKK8Y&lc=KR&item_name=donate%2esaltfactory%2enet&item_number=net%2esaltfactory%2edonate¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)을 이용하는 방법이 있습니다.
+
+[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=NR99D2BERKK8Y&lc=KR&item_name=donate%2esaltfactory%2enet&item_number=net%2esaltfactory%2edonate¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
+
+
+## 라이센스
+
+The MIT License (MIT)
+
+Copyright (c) 2014 SungKwang Song
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/bash/gcm_sender.sh b/bash/gcm_sender.sh
new file mode 100644
index 0000000..b949a81
--- /dev/null
+++ b/bash/gcm_sender.sh
@@ -0,0 +1,6 @@
+server_api_key='server api key를 입력합니다.'
+token='Instance ID의 token을 입력합니다.'
+curl --header "Authorization: key=$server_api_key" \
+--header Content-Type:"application/json" \
+https://gcm-http.googleapis.com/gcm/send \
+-d "{\"data\":{\"title\":\"saltfactory GCM demo\",\"message\":\"Google Cloud Messaging 테스트\"},\"to\":\"$token\"}
diff --git a/node/gcm_provider.js b/node/gcm_provider.js
new file mode 100644
index 0000000..027f527
--- /dev/null
+++ b/node/gcm_provider.js
@@ -0,0 +1,26 @@
+var gcm = require('node-gcm');
+var fs = require('fs');
+
+var message = new gcm.Message({
+ collapseKey: 'demo',
+ delayWhileIdle: true,
+ timeToLive: 3,
+ data: {
+ title: 'saltfactory GCM demo',
+ message: 'Google Cloud Messaging 테스트',
+ custom_key1: 'custom data1',
+ custom_key2: 'custom data2'
+ }
+});
+
+
+var server_api_key = 'Server API Key를 입력합니다.';
+var sender = new gcm.Sender(server_api_key);
+var registrationIds = [];
+
+var token = 'Instance ID 의 token을 입력합니다.';
+registrationIds.push(token);
+
+sender.send(message, registrationIds, 4, function (err, result) {
+ console.log(result);
+});
diff --git a/node/package.json b/node/package.json
new file mode 100644
index 0000000..b1b5e34
--- /dev/null
+++ b/node/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "gcm-demo",
+ "version": "0.0.0",
+ "description": "",
+ "dependencies": {
+ "node-gcm":"latest"
+ }
+}
diff --git a/sf-gcm-demo/.gitignore b/sf-gcm-demo/.gitignore
new file mode 100644
index 0000000..9c4de58
--- /dev/null
+++ b/sf-gcm-demo/.gitignore
@@ -0,0 +1,7 @@
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
diff --git a/sf-gcm-demo/app/.gitignore b/sf-gcm-demo/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/sf-gcm-demo/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/sf-gcm-demo/app/build.gradle b/sf-gcm-demo/app/build.gradle
new file mode 100644
index 0000000..8883cf8
--- /dev/null
+++ b/sf-gcm-demo/app/build.gradle
@@ -0,0 +1,28 @@
+apply plugin: 'com.android.application'
+apply plugin: 'com.google.gms.google-services'
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion "22.0.1"
+
+ defaultConfig {
+ applicationId "net.saltfactory.demo.gcm"
+ minSdkVersion 14
+ targetSdkVersion 22
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.google.android.gms:play-services-gcm:7.5.+'
+ compile 'com.android.support:appcompat-v7:22.1.1'
+
+}
diff --git a/sf-gcm-demo/app/proguard-rules.pro b/sf-gcm-demo/app/proguard-rules.pro
new file mode 100644
index 0000000..36cb415
--- /dev/null
+++ b/sf-gcm-demo/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Projects/Libraries/adt-bundle-mac-x86_64/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/sf-gcm-demo/app/src/androidTest/java/net/saltfactory/demo/gcm/ApplicationTest.java b/sf-gcm-demo/app/src/androidTest/java/net/saltfactory/demo/gcm/ApplicationTest.java
new file mode 100644
index 0000000..32ea580
--- /dev/null
+++ b/sf-gcm-demo/app/src/androidTest/java/net/saltfactory/demo/gcm/ApplicationTest.java
@@ -0,0 +1,13 @@
+package net.saltfactory.demo.gcm;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * Testing Fundamentals
+ */
+public class ApplicationTest extends ApplicationTestCase {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
\ No newline at end of file
diff --git a/sf-gcm-demo/app/src/main/AndroidManifest.xml b/sf-gcm-demo/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b77e809
--- /dev/null
+++ b/sf-gcm-demo/app/src/main/AndroidManifest.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/MainActivity.java b/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/MainActivity.java
new file mode 100644
index 0000000..40412df
--- /dev/null
+++ b/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/MainActivity.java
@@ -0,0 +1,144 @@
+package net.saltfactory.demo.gcm;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GooglePlayServicesUtil;
+
+public class MainActivity extends AppCompatActivity {
+
+ private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
+ private static final String TAG = "MainActivity";
+
+ private Button mRegistrationButton;
+ private ProgressBar mRegistrationProgressBar;
+ private BroadcastReceiver mRegistrationBroadcastReceiver;
+ private TextView mInformationTextView;
+
+ /**
+ * Instance ID를 이용하여 디바이스 토큰을 가져오는 RegistrationIntentService를 실행한다.
+ */
+ public void getInstanceIdToken() {
+ if (checkPlayServices()) {
+ // Start IntentService to register this application with GCM.
+ Intent intent = new Intent(this, RegistrationIntentService.class);
+ startService(intent);
+ }
+ }
+
+ /**
+ * LocalBroadcast 리시버를 정의한다. 토큰을 획득하기 위한 READY, GENERATING, COMPLETE 액션에 따라 UI에 변화를 준다.
+ */
+ public void registBroadcastReceiver(){
+ mRegistrationBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+
+ if(action.equals(QuickstartPreferences.REGISTRATION_READY)){
+ // 액션이 READY일 경우
+ mRegistrationProgressBar.setVisibility(ProgressBar.GONE);
+ mInformationTextView.setVisibility(View.GONE);
+ } else if(action.equals(QuickstartPreferences.REGISTRATION_GENERATING)){
+ // 액션이 GENERATING일 경우
+ mRegistrationProgressBar.setVisibility(ProgressBar.VISIBLE);
+ mInformationTextView.setVisibility(View.VISIBLE);
+ mInformationTextView.setText(getString(R.string.registering_message_generating));
+ } else if(action.equals(QuickstartPreferences.REGISTRATION_COMPLETE)){
+ // 액션이 COMPLETE일 경우
+ mRegistrationProgressBar.setVisibility(ProgressBar.GONE);
+ mRegistrationButton.setText(getString(R.string.registering_message_complete));
+ mRegistrationButton.setEnabled(false);
+ String token = intent.getStringExtra("token");
+ mInformationTextView.setText(token);
+ }
+
+ }
+ };
+ }
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_main);
+
+ registBroadcastReceiver();
+
+ // 토큰을 보여줄 TextView를 정의
+ mInformationTextView = (TextView) findViewById(R.id.informationTextView);
+ mInformationTextView.setVisibility(View.GONE);
+ // 토큰을 가져오는 동안 인디케이터를 보여줄 ProgressBar를 정의
+ mRegistrationProgressBar = (ProgressBar) findViewById(R.id.registrationProgressBar);
+ mRegistrationProgressBar.setVisibility(ProgressBar.GONE);
+ // 토큰을 가져오는 Button을 정의
+ mRegistrationButton = (Button) findViewById(R.id.registrationButton);
+ mRegistrationButton.setOnClickListener(new View.OnClickListener() {
+ /**
+ * 버튼을 클릭하면 토큰을 가져오는 getInstanceIdToken() 메소드를 실행한다.
+ * @param view
+ */
+ @Override
+ public void onClick(View view) {
+ getInstanceIdToken();
+ }
+ });
+
+ }
+
+ /**
+ * 앱이 실행되어 화면에 나타날때 LocalBoardcastManager에 액션을 정의하여 등록한다.
+ */
+ @Override
+ protected void onResume() {
+ super.onResume();
+ LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,
+ new IntentFilter(QuickstartPreferences.REGISTRATION_READY));
+ LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,
+ new IntentFilter(QuickstartPreferences.REGISTRATION_GENERATING));
+ LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,
+ new IntentFilter(QuickstartPreferences.REGISTRATION_COMPLETE));
+
+ }
+
+ /**
+ * 앱이 화면에서 사라지면 등록된 LocalBoardcast를 모두 삭제한다.
+ */
+ @Override
+ protected void onPause() {
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mRegistrationBroadcastReceiver);
+ super.onPause();
+ }
+
+
+ /**
+ * Google Play Service를 사용할 수 있는 환경이지를 체크한다.
+ */
+ private boolean checkPlayServices() {
+ int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
+ if (resultCode != ConnectionResult.SUCCESS) {
+ if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
+ GooglePlayServicesUtil.getErrorDialog(resultCode, this,
+ PLAY_SERVICES_RESOLUTION_REQUEST).show();
+ } else {
+ Log.i(TAG, "This device is not supported.");
+ finish();
+ }
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/MyGcmListenerService.java b/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/MyGcmListenerService.java
new file mode 100644
index 0000000..d1e4141
--- /dev/null
+++ b/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/MyGcmListenerService.java
@@ -0,0 +1,66 @@
+package net.saltfactory.demo.gcm;
+
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
+
+import com.google.android.gms.gcm.GcmListenerService;
+
+/**
+ * Created by saltfactory on 6/8/15.
+ */
+public class MyGcmListenerService extends GcmListenerService {
+
+ private static final String TAG = "MyGcmListenerService";
+
+ /**
+ *
+ * @param from SenderID 값을 받아온다.
+ * @param data Set형태로 GCM으로 받은 데이터 payload이다.
+ */
+ @Override
+ public void onMessageReceived(String from, Bundle data) {
+ String title = data.getString("title");
+ String message = data.getString("message");
+
+ Log.d(TAG, "From: " + from);
+ Log.d(TAG, "Title: " + title);
+ Log.d(TAG, "Message: " + message);
+
+ // GCM으로 받은 메세지를 디바이스에 알려주는 sendNotification()을 호출한다.
+ sendNotification(title, message);
+ }
+
+
+ /**
+ * 실제 디바에스에 GCM으로부터 받은 메세지를 알려주는 함수이다. 디바이스 Notification Center에 나타난다.
+ * @param title
+ * @param message
+ */
+ private void sendNotification(String title, String message) {
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
+ PendingIntent.FLAG_ONE_SHOT);
+
+ Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+ NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
+ .setSmallIcon(R.drawable.ic_stat_ic_notification)
+ .setContentTitle(title)
+ .setContentText(message)
+ .setAutoCancel(true)
+ .setSound(defaultSoundUri)
+ .setContentIntent(pendingIntent);
+
+ NotificationManager notificationManager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+
+ notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
+ }
+}
diff --git a/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/MyInstanceIDListenerService.java b/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/MyInstanceIDListenerService.java
new file mode 100644
index 0000000..8cde9ec
--- /dev/null
+++ b/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/MyInstanceIDListenerService.java
@@ -0,0 +1,19 @@
+package net.saltfactory.demo.gcm;
+
+import android.content.Intent;
+
+import com.google.android.gms.iid.InstanceIDListenerService;
+
+/**
+ * Created by saltfactory on 6/8/15.
+ */
+public class MyInstanceIDListenerService extends InstanceIDListenerService {
+
+ private static final String TAG = "MyInstanceIDLS";
+
+ @Override
+ public void onTokenRefresh() {
+ Intent intent = new Intent(this, RegistrationIntentService.class);
+ startService(intent);
+ }
+}
diff --git a/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/QuickstartPreferences.java b/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/QuickstartPreferences.java
new file mode 100644
index 0000000..92a9cf5
--- /dev/null
+++ b/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/QuickstartPreferences.java
@@ -0,0 +1,12 @@
+package net.saltfactory.demo.gcm;
+
+/**
+ * Created by saltfactory on 6/8/15.
+ */
+public class QuickstartPreferences {
+
+ public static final String REGISTRATION_READY = "registrationReady";
+ public static final String REGISTRATION_GENERATING = "registrationGenerating";
+ public static final String REGISTRATION_COMPLETE = "registrationComplete";
+
+}
diff --git a/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/RegistrationIntentService.java b/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/RegistrationIntentService.java
new file mode 100644
index 0000000..77fbaa5
--- /dev/null
+++ b/sf-gcm-demo/app/src/main/java/net/saltfactory/demo/gcm/RegistrationIntentService.java
@@ -0,0 +1,61 @@
+package net.saltfactory.demo.gcm;
+
+import android.annotation.SuppressLint;
+import android.app.IntentService;
+import android.content.Intent;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+
+import com.google.android.gms.gcm.GoogleCloudMessaging;
+import com.google.android.gms.iid.InstanceID;
+
+import java.io.IOException;
+
+/**
+ * Created by saltfactory on 6/8/15.
+ */
+public class RegistrationIntentService extends IntentService {
+
+ private static final String TAG = "RegistrationIntentService";
+
+ public RegistrationIntentService() {
+ super(TAG);
+ }
+
+ /**
+ * GCM을 위한 Instance ID의 토큰을 생성하여 가져온다.
+ * @param intent
+ */
+ @SuppressLint("LongLogTag")
+ @Override
+ protected void onHandleIntent(Intent intent) {
+
+ // GCM Instance ID의 토큰을 가져오는 작업이 시작되면 LocalBoardcast로 GENERATING 액션을 알려 ProgressBar가 동작하도록 한다.
+ LocalBroadcastManager.getInstance(this)
+ .sendBroadcast(new Intent(QuickstartPreferences.REGISTRATION_GENERATING));
+
+ // GCM을 위한 Instance ID를 가져온다.
+ InstanceID instanceID = InstanceID.getInstance(this);
+ String token = null;
+ try {
+ synchronized (TAG) {
+ // GCM 앱을 등록하고 획득한 설정파일인 google-services.json을 기반으로 SenderID를 자동으로 가져온다.
+ String default_senderId = getString(R.string.gcm_defaultSenderId);
+ // GCM 기본 scope는 "GCM"이다.
+ String scope = GoogleCloudMessaging.INSTANCE_ID_SCOPE;
+ // Instance ID에 해당하는 토큰을 생성하여 가져온다.
+ token = instanceID.getToken(default_senderId, scope, null);
+
+ Log.i(TAG, "GCM Registration Token: " + token);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ // GCM Instance ID에 해당하는 토큰을 획득하면 LocalBoardcast에 COMPLETE 액션을 알린다.
+ // 이때 토큰을 함께 넘겨주어서 UI에 토큰 정보를 활용할 수 있도록 했다.
+ Intent registrationComplete = new Intent(QuickstartPreferences.REGISTRATION_COMPLETE);
+ registrationComplete.putExtra("token", token);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete);
+ }
+}
diff --git a/sf-gcm-demo/app/src/main/res/drawable-hdpi-v11/ic_stat_ic_notification.png b/sf-gcm-demo/app/src/main/res/drawable-hdpi-v11/ic_stat_ic_notification.png
new file mode 100644
index 0000000..db2441b
Binary files /dev/null and b/sf-gcm-demo/app/src/main/res/drawable-hdpi-v11/ic_stat_ic_notification.png differ
diff --git a/sf-gcm-demo/app/src/main/res/drawable-hdpi/ic_stat_ic_notification.png b/sf-gcm-demo/app/src/main/res/drawable-hdpi/ic_stat_ic_notification.png
new file mode 100644
index 0000000..3cbd048
Binary files /dev/null and b/sf-gcm-demo/app/src/main/res/drawable-hdpi/ic_stat_ic_notification.png differ
diff --git a/sf-gcm-demo/app/src/main/res/drawable-mdpi-v11/ic_stat_ic_notification.png b/sf-gcm-demo/app/src/main/res/drawable-mdpi-v11/ic_stat_ic_notification.png
new file mode 100644
index 0000000..660e2a2
Binary files /dev/null and b/sf-gcm-demo/app/src/main/res/drawable-mdpi-v11/ic_stat_ic_notification.png differ
diff --git a/sf-gcm-demo/app/src/main/res/drawable-mdpi/ic_stat_ic_notification.png b/sf-gcm-demo/app/src/main/res/drawable-mdpi/ic_stat_ic_notification.png
new file mode 100644
index 0000000..105b2d7
Binary files /dev/null and b/sf-gcm-demo/app/src/main/res/drawable-mdpi/ic_stat_ic_notification.png differ
diff --git a/sf-gcm-demo/app/src/main/res/drawable-xhdpi-v11/ic_stat_ic_notification.png b/sf-gcm-demo/app/src/main/res/drawable-xhdpi-v11/ic_stat_ic_notification.png
new file mode 100644
index 0000000..d071e8f
Binary files /dev/null and b/sf-gcm-demo/app/src/main/res/drawable-xhdpi-v11/ic_stat_ic_notification.png differ
diff --git a/sf-gcm-demo/app/src/main/res/drawable-xhdpi/ic_stat_ic_notification.png b/sf-gcm-demo/app/src/main/res/drawable-xhdpi/ic_stat_ic_notification.png
new file mode 100644
index 0000000..f749b60
Binary files /dev/null and b/sf-gcm-demo/app/src/main/res/drawable-xhdpi/ic_stat_ic_notification.png differ
diff --git a/sf-gcm-demo/app/src/main/res/drawable-xxhdpi-v11/ic_stat_ic_notification.png b/sf-gcm-demo/app/src/main/res/drawable-xxhdpi-v11/ic_stat_ic_notification.png
new file mode 100644
index 0000000..79dd369
Binary files /dev/null and b/sf-gcm-demo/app/src/main/res/drawable-xxhdpi-v11/ic_stat_ic_notification.png differ
diff --git a/sf-gcm-demo/app/src/main/res/drawable-xxhdpi/ic_stat_ic_notification.png b/sf-gcm-demo/app/src/main/res/drawable-xxhdpi/ic_stat_ic_notification.png
new file mode 100644
index 0000000..c1985da
Binary files /dev/null and b/sf-gcm-demo/app/src/main/res/drawable-xxhdpi/ic_stat_ic_notification.png differ
diff --git a/sf-gcm-demo/app/src/main/res/layout/activity_main.xml b/sf-gcm-demo/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..d4d6266
--- /dev/null
+++ b/sf-gcm-demo/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
diff --git a/sf-gcm-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png b/sf-gcm-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..e8a2b7f
Binary files /dev/null and b/sf-gcm-demo/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/sf-gcm-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png b/sf-gcm-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..ccb271d
Binary files /dev/null and b/sf-gcm-demo/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/sf-gcm-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/sf-gcm-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bd50e00
Binary files /dev/null and b/sf-gcm-demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/sf-gcm-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sf-gcm-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..71aae42
Binary files /dev/null and b/sf-gcm-demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/sf-gcm-demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sf-gcm-demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..01bcbca
Binary files /dev/null and b/sf-gcm-demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/sf-gcm-demo/app/src/main/res/values-v21/styles.xml b/sf-gcm-demo/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..e28e575
--- /dev/null
+++ b/sf-gcm-demo/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/sf-gcm-demo/app/src/main/res/values-w820dp/dimens.xml b/sf-gcm-demo/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/sf-gcm-demo/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/sf-gcm-demo/app/src/main/res/values/colors.xml b/sf-gcm-demo/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c63d7cc
--- /dev/null
+++ b/sf-gcm-demo/app/src/main/res/values/colors.xml
@@ -0,0 +1,8 @@
+
+
+ #607D8B
+ #546E7A
+ #455A64
+ #37474F
+ #263238
+
\ No newline at end of file
diff --git a/sf-gcm-demo/app/src/main/res/values/dimens.xml b/sf-gcm-demo/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/sf-gcm-demo/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/sf-gcm-demo/app/src/main/res/values/strings.xml b/sf-gcm-demo/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..520af32
--- /dev/null
+++ b/sf-gcm-demo/app/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+ GCM Demo
+ InstanceID 토큰 가져오기
+ InstanceID 토큰 생성중...
+ 완료!
+
diff --git a/sf-gcm-demo/app/src/main/res/values/styles.xml b/sf-gcm-demo/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..fcb3882
--- /dev/null
+++ b/sf-gcm-demo/app/src/main/res/values/styles.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/sf-gcm-demo/build.gradle b/sf-gcm-demo/build.gradle
new file mode 100644
index 0000000..8788824
--- /dev/null
+++ b/sf-gcm-demo/build.gradle
@@ -0,0 +1,20 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+// classpath 'com.android.tools.build:gradle:1.2.3'
+ classpath 'com.android.tools.build:gradle:1.3.0-beta2'
+ classpath 'com.google.gms:google-services:1.3.0-beta2'
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/sf-gcm-demo/gradle.properties b/sf-gcm-demo/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/sf-gcm-demo/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/sf-gcm-demo/gradlew b/sf-gcm-demo/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/sf-gcm-demo/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/sf-gcm-demo/gradlew.bat b/sf-gcm-demo/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/sf-gcm-demo/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/sf-gcm-demo/settings.gradle b/sf-gcm-demo/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/sf-gcm-demo/settings.gradle
@@ -0,0 +1 @@
+include ':app'