From e1c557e38daea637d95eb0ebbb19d43fc8c8e44e Mon Sep 17 00:00:00 2001 From: Dan Galpin Date: Tue, 25 Aug 2015 08:21:50 -0700 Subject: [PATCH 01/13] Revert "Adding Google Play Services and checking for Google Play Services before attempting GCM implementation" This reverts commit 2ede69cc3862eb3310b2a6f81940524e91b2b70f. --- app/build.gradle | 1 - .../android/sunshine/app/MainActivity.java | 43 ++----------------- 2 files changed, 3 insertions(+), 41 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 530d443f..c83808dc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,5 +24,4 @@ dependencies { compile 'com.github.bumptech.glide:glide:3.5.2' compile 'com.android.support:appcompat-v7:21.0.2' compile 'com.android.support:support-annotations:22.0.0' - compile 'com.google.android.gms:play-services-gcm:7.0.0' } diff --git a/app/src/main/java/com/example/android/sunshine/app/MainActivity.java b/app/src/main/java/com/example/android/sunshine/app/MainActivity.java index 4960cfa2..43d15aa5 100644 --- a/app/src/main/java/com/example/android/sunshine/app/MainActivity.java +++ b/app/src/main/java/com/example/android/sunshine/app/MainActivity.java @@ -19,19 +19,15 @@ import android.net.Uri; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; -import android.util.Log; import android.view.Menu; import android.view.MenuItem; import com.example.android.sunshine.app.sync.SunshineSyncAdapter; -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GooglePlayServicesUtil; public class MainActivity extends ActionBarActivity implements ForecastFragment.Callback { private final String LOG_TAG = MainActivity.class.getSimpleName(); private static final String DETAILFRAGMENT_TAG = "DFTAG"; - private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000; private boolean mTwoPane; private String mLocation; @@ -65,13 +61,6 @@ protected void onCreate(Bundle savedInstanceState) { forecastFragment.setUseTodayLayout(!mTwoPane); SunshineSyncAdapter.initializeSyncAdapter(this); - - if (!checkPlayServices()) { - // this is where we could either prompt a user that they should install - // the latest version of Google Play Services, or add an error snackbar - // that some features won't be available. - } - } @Override @@ -93,22 +82,16 @@ public boolean onOptionsItemSelected(MenuItem item) { startActivity(new Intent(this, SettingsActivity.class)); return true; } + return super.onOptionsItemSelected(item); } @Override protected void onResume() { super.onResume(); - - // If Google Play Services is not available, some features, such as GCM-powered weather - // alerts, will not be available. - if (!checkPlayServices()) { - // Store regID as null - } - - String location = Utility.getPreferredLocation(this); + String location = Utility.getPreferredLocation( this ); // update the location in our second pane using the fragment manager - if (location != null && !location.equals(mLocation)) { + if (location != null && !location.equals(mLocation)) { ForecastFragment ff = (ForecastFragment)getSupportFragmentManager().findFragmentById(R.id.fragment_forecast); if ( null != ff ) { ff.onLocationChanged(); @@ -142,24 +125,4 @@ public void onItemSelected(Uri contentUri) { startActivity(intent); } } - - /** - * Check the device to make sure it has the Google Play Services APK. If - * it doesn't, display a dialog that allows users to download the APK from - * the Google Play Store or enable it in the device's system settings. - */ - 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(LOG_TAG, "This device is not supported."); - finish(); - } - return false; - } - return true; - } } From 447189d1fdfe0c65fa4d7a362f66489b515aaec3 Mon Sep 17 00:00:00 2001 From: Joanna Smith Date: Sun, 14 Jun 2015 17:08:14 -0700 Subject: [PATCH 02/13] Adding required support for Google services and Google Play services --- app/build.gradle | 2 ++ .../android/sunshine/app/MainActivity.java | 31 +++++++++++++++++++ build.gradle | 1 + 3 files changed, 34 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index c83808dc..ec39ab6d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.application' +apply plugin: 'com.google.gms.google-services' android { compileSdkVersion 21 @@ -24,4 +25,5 @@ dependencies { compile 'com.github.bumptech.glide:glide:3.5.2' compile 'com.android.support:appcompat-v7:21.0.2' compile 'com.android.support:support-annotations:22.0.0' + compile 'com.google.android.gms:play-services-gcm:7.5.0' } diff --git a/app/src/main/java/com/example/android/sunshine/app/MainActivity.java b/app/src/main/java/com/example/android/sunshine/app/MainActivity.java index 43d15aa5..0464c16f 100644 --- a/app/src/main/java/com/example/android/sunshine/app/MainActivity.java +++ b/app/src/main/java/com/example/android/sunshine/app/MainActivity.java @@ -19,15 +19,19 @@ import android.net.Uri; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import com.example.android.sunshine.app.sync.SunshineSyncAdapter; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; public class MainActivity extends ActionBarActivity implements ForecastFragment.Callback { private final String LOG_TAG = MainActivity.class.getSimpleName(); private static final String DETAILFRAGMENT_TAG = "DFTAG"; + private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000; private boolean mTwoPane; private String mLocation; @@ -61,6 +65,12 @@ protected void onCreate(Bundle savedInstanceState) { forecastFragment.setUseTodayLayout(!mTwoPane); SunshineSyncAdapter.initializeSyncAdapter(this); + + if (!checkPlayServices()) { + // This is where we could either prompt a user that they should install + // the latest version of Google Play Services, or add an error snackbar + // that some features won't be available. + } } @Override @@ -125,4 +135,25 @@ public void onItemSelected(Uri contentUri) { startActivity(intent); } } + + /** + * Check the device to make sure it has the Google Play Services APK. If + * it doesn't, display a dialog that allows users to download the APK from + * the Google Play Store or enable it in the device's system settings. + */ + private boolean checkPlayServices() { + GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); + int resultCode = apiAvailability.isGooglePlayServicesAvailable(this); + if (resultCode != ConnectionResult.SUCCESS) { + if (apiAvailability.isUserResolvableError(resultCode)) { + apiAvailability.getErrorDialog(this, resultCode, + PLAY_SERVICES_RESOLUTION_REQUEST).show(); + } else { + Log.i(LOG_TAG, "This device is not supported."); + finish(); + } + return false; + } + return true; + } } diff --git a/build.gradle b/build.gradle index 6356aabd..cfad1887 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:1.0.0' + classpath 'com.google.gms:google-services:1.3.0-beta1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From fa5355acde5013fb441228c9f7c69b5236c67caa Mon Sep 17 00:00:00 2001 From: Dan Galpin Date: Tue, 25 Aug 2015 08:27:48 -0700 Subject: [PATCH 03/13] Revert "Manifest updates for GCM" This reverts commit 4d7e147ba4207284ac7d3e93ef44cc48d17109c9. --- app/src/main/AndroidManifest.xml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0ca2809c..7374a5de 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,12 +32,6 @@ - - - - - - - - - - - - - - - From 5f5eeb7ad96d9bdb6c15321742ec9262abed1c33 Mon Sep 17 00:00:00 2001 From: Joanna Smith Date: Sun, 14 Jun 2015 17:48:21 -0700 Subject: [PATCH 04/13] Adding GCM permissions and class declarations to AndroidManifest --- app/src/main/AndroidManifest.xml | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7374a5de..d76c0187 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,6 +32,13 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + From aff5418d6f0b3974a84a74a55f835d1a3715c93d Mon Sep 17 00:00:00 2001 From: Dan Galpin Date: Tue, 25 Aug 2015 08:32:27 -0700 Subject: [PATCH 05/13] Revert "Switched to using the Google Developer lingo for project number." This reverts commit c18dcee63830d9e3d5c6cae78134a35951700f35. --- .../android/sunshine/app/MainActivity.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/example/android/sunshine/app/MainActivity.java b/app/src/main/java/com/example/android/sunshine/app/MainActivity.java index deee6f92..07068f53 100644 --- a/app/src/main/java/com/example/android/sunshine/app/MainActivity.java +++ b/app/src/main/java/com/example/android/sunshine/app/MainActivity.java @@ -46,10 +46,10 @@ public class MainActivity extends ActionBarActivity implements ForecastFragment. private static final String PROPERTY_APP_VERSION = "appVersion"; /** - * Substitute you own project number here. This project number comes - * from the Google Developers Console. + * Substitute you own sender ID here. This is the project number you got + * from the API Console. */ - static final String PROJECT_NUMBER = "Your Project Number"; + String SENDER_ID = "Your-Sender-ID"; private boolean mTwoPane; private String mLocation; @@ -91,10 +91,10 @@ protected void onCreate(Bundle savedInstanceState) { mGcm = GoogleCloudMessaging.getInstance(this); String regId = getRegistrationId(this); - if (PROJECT_NUMBER.equals("Your Project Number")) { + if (SENDER_ID.equals("Your-Sender-ID")) { new AlertDialog.Builder(this) - .setTitle("Needs Project Number") - .setMessage("GCM will not function in Sunshine until you set the Project Number to the one from the Google Developers Console.") + .setTitle("Needs Sender ID") + .setMessage("GCM will not function in Sunshine until you replace your Sender ID with a Sender ID from the Google Developers Console.") .setPositiveButton(android.R.string.ok, null) .create().show(); } else if (regId.isEmpty()) { @@ -263,7 +263,7 @@ protected Void doInBackground(Void... params) { if (mGcm == null) { mGcm = GoogleCloudMessaging.getInstance(context); } - String regId = mGcm.register(PROJECT_NUMBER); + String regId = mGcm.register(SENDER_ID); msg = "Device registered, registration ID=" + regId; // You should send the registration ID to your server over HTTP, @@ -279,7 +279,7 @@ protected Void doInBackground(Void... params) { storeRegistrationId(context, regId); } catch (IOException ex) { msg = "Error :" + ex.getMessage(); - // TODO: If there is an error, don't just keep trying to register. + // TODO(joannasmith): If there is an error, don't just keep trying to register. // Require the user to click a button again, or perform // exponential back-off. } From e4d650ac26fc02382c964c2f0c106b2992a38ec5 Mon Sep 17 00:00:00 2001 From: Dan Galpin Date: Tue, 25 Aug 2015 08:32:42 -0700 Subject: [PATCH 06/13] Revert "Checking for registration ID and registering app" This reverts commit 83cfcaff0f326d65e96cb366b88b87ebec88775f. --- .../android/sunshine/app/MainActivity.java | 152 +----------------- 1 file changed, 5 insertions(+), 147 deletions(-) diff --git a/app/src/main/java/com/example/android/sunshine/app/MainActivity.java b/app/src/main/java/com/example/android/sunshine/app/MainActivity.java index 07068f53..4960cfa2 100644 --- a/app/src/main/java/com/example/android/sunshine/app/MainActivity.java +++ b/app/src/main/java/com/example/android/sunshine/app/MainActivity.java @@ -15,14 +15,8 @@ */ package com.example.android.sunshine.app; -import android.app.AlertDialog; -import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.net.Uri; -import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.util.Log; @@ -32,28 +26,15 @@ import com.example.android.sunshine.app.sync.SunshineSyncAdapter; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; -import com.google.android.gms.gcm.GoogleCloudMessaging; - -import java.io.IOException; -import java.util.concurrent.atomic.AtomicInteger; public class MainActivity extends ActionBarActivity implements ForecastFragment.Callback { private final String LOG_TAG = MainActivity.class.getSimpleName(); private static final String DETAILFRAGMENT_TAG = "DFTAG"; private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000; - public static final String PROPERTY_REG_ID = "registration_id"; - private static final String PROPERTY_APP_VERSION = "appVersion"; - - /** - * Substitute you own sender ID here. This is the project number you got - * from the API Console. - */ - String SENDER_ID = "Your-Sender-ID"; private boolean mTwoPane; private String mLocation; - private GoogleCloudMessaging mGcm; @Override protected void onCreate(Bundle savedInstanceState) { @@ -85,26 +66,12 @@ protected void onCreate(Bundle savedInstanceState) { SunshineSyncAdapter.initializeSyncAdapter(this); - // If Google Play Services is not available, some features, such as GCM-powered weather - // alerts, will not be available. - if (checkPlayServices()) { - mGcm = GoogleCloudMessaging.getInstance(this); - String regId = getRegistrationId(this); - - if (SENDER_ID.equals("Your-Sender-ID")) { - new AlertDialog.Builder(this) - .setTitle("Needs Sender ID") - .setMessage("GCM will not function in Sunshine until you replace your Sender ID with a Sender ID from the Google Developers Console.") - .setPositiveButton(android.R.string.ok, null) - .create().show(); - } else if (regId.isEmpty()) { - registerInBackground(this); - } - } else { - Log.i(LOG_TAG, "No valid Google Play Services APK. Weather alerts will be disabled."); - // Store regID as null - storeRegistrationId(this, null); + if (!checkPlayServices()) { + // this is where we could either prompt a user that they should install + // the latest version of Google Play Services, or add an error snackbar + // that some features won't be available. } + } @Override @@ -195,113 +162,4 @@ private boolean checkPlayServices() { } return true; } - - /** - * Gets the current registration ID for application on GCM service. - *

- * If result is empty, the app needs to register. - * - * @return registration ID, or empty string if there is no existing - * registration ID. - */ - private String getRegistrationId(Context context) { - final SharedPreferences prefs = getGCMPreferences(context); - String registrationId = prefs.getString(PROPERTY_REG_ID, ""); - if (registrationId.isEmpty()) { - Log.i(LOG_TAG, "GCM Registration not found."); - return ""; - } - - // Check if app was updated; if so, it must clear the registration ID - // since the existing registration ID is not guaranteed to work with - // the new app version. - int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE); - int currentVersion = getAppVersion(context); - if (registeredVersion != currentVersion) { - Log.i(LOG_TAG, "App version changed."); - return ""; - } - return registrationId; - } - - /** - * @return Application's {@code SharedPreferences}. - */ - private SharedPreferences getGCMPreferences(Context context) { - // Sunshine persists the registration ID in shared preferences, but - // how you store the registration ID in your app is up to you. Just make sure - // that it is private! - return getSharedPreferences(MainActivity.class.getSimpleName(), Context.MODE_PRIVATE); - } - - /** - * @return Application's version code from the {@code PackageManager}. - */ - private static int getAppVersion(Context context) { - try { - PackageInfo packageInfo = context.getPackageManager() - .getPackageInfo(context.getPackageName(), 0); - return packageInfo.versionCode; - } catch (PackageManager.NameNotFoundException e) { - // Should never happen. WHAT DID YOU DO?!?! - throw new RuntimeException("Could not get package name: " + e); - } - } - - /** - * Registers the application with GCM servers asynchronously. - *

- * Stores the registration ID and app versionCode in the application's - * shared preferences. - */ - private void registerInBackground(final Context context) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - String msg = ""; - try { - if (mGcm == null) { - mGcm = GoogleCloudMessaging.getInstance(context); - } - String regId = mGcm.register(SENDER_ID); - msg = "Device registered, registration ID=" + regId; - - // You should send the registration ID to your server over HTTP, - // so it can use GCM/HTTP or CCS to send messages to your app. - // The request to your server should be authenticated if your app - // is using accounts. - //sendRegistrationIdToBackend(); - // For this demo: we don't need to send it because the device - // will send upstream messages to a server that echo back the - // message using the 'from' address in the message. - - // Persist the registration ID - no need to register again. - storeRegistrationId(context, regId); - } catch (IOException ex) { - msg = "Error :" + ex.getMessage(); - // TODO(joannasmith): If there is an error, don't just keep trying to register. - // Require the user to click a button again, or perform - // exponential back-off. - } - return null; - } - }.execute(null, null, null); - } - - /** - * Stores the registration ID and app versionCode in the application's - * {@code SharedPreferences}. - * - * @param context application's context. - * @param regId registration ID - */ - private void storeRegistrationId(Context context, String regId) { - final SharedPreferences prefs = getGCMPreferences(context); - int appVersion = getAppVersion(context); - Log.i(LOG_TAG, "Saving regId on app version " + appVersion); - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(PROPERTY_REG_ID, regId); - editor.putInt(PROPERTY_APP_VERSION, appVersion); - editor.commit(); - } } From b6d88a89c189fd874ed994d2e3c461754b2ff338 Mon Sep 17 00:00:00 2001 From: Joanna Smith Date: Sun, 14 Jun 2015 18:59:32 -0700 Subject: [PATCH 07/13] Registering the app with GCM --- .../android/sunshine/app/MainActivity.java | 23 +++++- .../app/gcm/MyInstanceIDListenerService.java | 36 +++++++++ .../app/gcm/RegistrationIntentService.java | 76 +++++++++++++++++++ 3 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/example/android/sunshine/app/gcm/MyInstanceIDListenerService.java create mode 100644 app/src/main/java/com/example/android/sunshine/app/gcm/RegistrationIntentService.java diff --git a/app/src/main/java/com/example/android/sunshine/app/MainActivity.java b/app/src/main/java/com/example/android/sunshine/app/MainActivity.java index 0464c16f..776e8925 100644 --- a/app/src/main/java/com/example/android/sunshine/app/MainActivity.java +++ b/app/src/main/java/com/example/android/sunshine/app/MainActivity.java @@ -16,13 +16,16 @@ package com.example.android.sunshine.app; import android.content.Intent; +import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; +import android.preference.PreferenceManager; import android.support.v7.app.ActionBarActivity; import android.util.Log; import android.view.Menu; import android.view.MenuItem; +import com.example.android.sunshine.app.gcm.RegistrationIntentService; import com.example.android.sunshine.app.sync.SunshineSyncAdapter; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; @@ -32,6 +35,7 @@ public class MainActivity extends ActionBarActivity implements ForecastFragment. private final String LOG_TAG = MainActivity.class.getSimpleName(); private static final String DETAILFRAGMENT_TAG = "DFTAG"; private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000; + public static final String SENT_TOKEN_TO_SERVER = "sentTokenToServer"; private boolean mTwoPane; private String mLocation; @@ -66,10 +70,21 @@ protected void onCreate(Bundle savedInstanceState) { SunshineSyncAdapter.initializeSyncAdapter(this); - if (!checkPlayServices()) { - // This is where we could either prompt a user that they should install - // the latest version of Google Play Services, or add an error snackbar - // that some features won't be available. + // If Google Play Services is up to date, we'll want to register GCM. If it is not, we'll + // skip the registration and this device will not receive any downstream messages from + // our fake server. Because weather alerts are not a core feature of the app, this should + // not affect the behavior of the app, from a user perspective. + if (checkPlayServices()) { + // Because this is the initial creation of the app, we'll want to be certain we have + // a token. If we do not, then we will start the IntentService that will register this + // application with GCM. + SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(this); + boolean sentToken = sharedPreferences.getBoolean(SENT_TOKEN_TO_SERVER, false); + if (!sentToken) { + Intent intent = new Intent(this, RegistrationIntentService.class); + startService(intent); + } } } diff --git a/app/src/main/java/com/example/android/sunshine/app/gcm/MyInstanceIDListenerService.java b/app/src/main/java/com/example/android/sunshine/app/gcm/MyInstanceIDListenerService.java new file mode 100644 index 00000000..862bc476 --- /dev/null +++ b/app/src/main/java/com/example/android/sunshine/app/gcm/MyInstanceIDListenerService.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.sunshine.app.gcm; + +import android.content.Intent; +import com.google.android.gms.iid.InstanceIDListenerService; + +public class MyInstanceIDListenerService extends InstanceIDListenerService { + private static final String TAG = "MyInstanceIDLS"; + + /** + * Called if InstanceID token is updated. This may occur if the security of + * the previous token had been compromised. This call is initiated by the + * InstanceID provider. + */ + @Override + public void onTokenRefresh() { + // Fetch updated Instance ID token. + Intent intent = new Intent(this, RegistrationIntentService.class); + startService(intent); + } +} diff --git a/app/src/main/java/com/example/android/sunshine/app/gcm/RegistrationIntentService.java b/app/src/main/java/com/example/android/sunshine/app/gcm/RegistrationIntentService.java new file mode 100644 index 00000000..bfd9c207 --- /dev/null +++ b/app/src/main/java/com/example/android/sunshine/app/gcm/RegistrationIntentService.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.sunshine.app.gcm; + +import android.app.IntentService; +import android.content.Intent; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Log; + +import com.example.android.sunshine.app.MainActivity; +import com.example.android.sunshine.app.R; +import com.google.android.gms.gcm.GoogleCloudMessaging; +import com.google.android.gms.iid.InstanceID; + + +public class RegistrationIntentService extends IntentService { + private static final String TAG = "RegIntentService"; + + public RegistrationIntentService() { + super(TAG); + } + + @Override + protected void onHandleIntent(Intent intent) { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + + try { + // In the (unlikely) event that multiple refresh operations occur simultaneously, + // ensure that they are processed sequentially. + synchronized (TAG) { + // Initially this call goes out to the network to retrieve the token, subsequent calls + // are local. + InstanceID instanceID = InstanceID.getInstance(this); + String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId), + GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); + sendRegistrationToServer(token); + + // You should store a boolean that indicates whether the generated token has been + // sent to your server. If the boolean is false, send the token to your server, + // otherwise your server should have already received the token. + sharedPreferences.edit().putBoolean(MainActivity.SENT_TOKEN_TO_SERVER, true).apply(); + } + } catch (Exception e) { + Log.d(TAG, "Failed to complete token refresh", e); + + // If an exception happens while fetching the new token or updating our registration data + // on a third-party server, this ensures that we'll attempt the update at a later time. + sharedPreferences.edit().putBoolean(MainActivity.SENT_TOKEN_TO_SERVER, false).apply(); + } + } + + /** + * Normally, you would want to persist the registration to third-party servers. Because we do + * not have a server, and are faking it with a website, you'll want to log the token instead. + * That way you can see the value in logcat, and note it for future use in the website. + * + * @param token The new token. + */ + private void sendRegistrationToServer(String token) { + Log.i(TAG, "GCM Registration Token: " + token); + } +} From 2ed375e68f8c831b78de76fed8d9ce2e9cbd3625 Mon Sep 17 00:00:00 2001 From: Dan Galpin Date: Tue, 25 Aug 2015 08:36:01 -0700 Subject: [PATCH 08/13] Revert "Better message parsing and notification building" This reverts commit 3a0b035c69acb09849cccf28922f2ce9936d5448. --- app/src/main/AndroidManifest.xml | 49 +++++----- .../sunshine/app/GcmBroadcastReceiver.java | 93 ------------------- .../android/sunshine/app/MainActivity.java | 15 +-- 3 files changed, 33 insertions(+), 124 deletions(-) delete mode 100644 app/src/main/java/com/example/android/sunshine/app/GcmBroadcastReceiver.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f492e8c5..0ca2809c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,17 +21,20 @@ - - - + + + - + - @@ -43,8 +46,8 @@ android:supportsRtl="true"> + android:theme="@style/ForecastTheme" + android:label="@string/app_name" > @@ -63,24 +66,22 @@ android:name=".SettingsActivity" android:label="@string/title_activity_settings" android:parentActivityName=".MainActivity" - android:theme="@style/SettingsTheme" > + android:theme="@style/SettingsTheme"> - - + - @@ -89,25 +90,25 @@ + android:exported="true" + > - - - - - - - - + + + + + + + + + diff --git a/app/src/main/java/com/example/android/sunshine/app/GcmBroadcastReceiver.java b/app/src/main/java/com/example/android/sunshine/app/GcmBroadcastReceiver.java deleted file mode 100644 index 2b748805..00000000 --- a/app/src/main/java/com/example/android/sunshine/app/GcmBroadcastReceiver.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.example.android.sunshine.app; - -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.support.v4.app.NotificationCompat; -import android.util.Log; - -import com.google.android.gms.gcm.GoogleCloudMessaging; - -public class GcmBroadcastReceiver extends BroadcastReceiver { - private final String LOG_TAG = BroadcastReceiver.class.getSimpleName(); - - private static final String EXTRA_SENDER = "from"; - private static final String EXTRA_WEATHER = "weather"; - private static final String EXTRA_LOCATION = "location"; - - public static final int NOTIFICATION_ID = 1; - private NotificationManager mNotificationManager; - - public GcmBroadcastReceiver() { - super(); - } - - @Override - public void onReceive(Context context, Intent intent) { - Bundle extras = intent.getExtras(); - GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context); - String messageType = gcm.getMessageType(intent); - - if (!extras.isEmpty()) { // has effect of unparcelling Bundle - /* - * Filter messages based on message type. Since it is likely that GCM - * will be extended in the future with new message types, just ignore - * any message types you're not interested in, or that you don't - * recognize. - */ - if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) { - // Is this our message?? Better be if you're going to act on it! - if (MainActivity.PROJECT_NUMBER.equals(extras.getString(EXTRA_SENDER))) { - // Process message and then post a notification of the received message. - String weather = extras.getString(EXTRA_WEATHER); - String location = extras.getString(EXTRA_LOCATION); - String alert = "Heads up: " + weather + " in " + location + "!"; - - sendNotification(context, alert); - } - - Log.i(LOG_TAG, "Received: " + extras.toString()); - } - } - } - - // Put the message into a notification and post it. - // This is just one simple example of what you might choose to do with a GCM message. - private void sendNotification(Context context, String msg) { - mNotificationManager = (NotificationManager) - context.getSystemService(Context.NOTIFICATION_SERVICE); - - PendingIntent contentIntent = - PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0); - - NotificationCompat.Builder mBuilder = - new NotificationCompat.Builder(context) - .setSmallIcon(R.drawable.art_storm) - .setContentTitle("Weather Alert!") - .setStyle(new NotificationCompat.BigTextStyle() - .bigText(msg)) - .setContentText(msg) - .setPriority(NotificationCompat.PRIORITY_HIGH); - - mBuilder.setContentIntent(contentIntent); - mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build()); - } -} diff --git a/app/src/main/java/com/example/android/sunshine/app/MainActivity.java b/app/src/main/java/com/example/android/sunshine/app/MainActivity.java index a76b011f..4c42469d 100644 --- a/app/src/main/java/com/example/android/sunshine/app/MainActivity.java +++ b/app/src/main/java/com/example/android/sunshine/app/MainActivity.java @@ -35,6 +35,7 @@ import com.google.android.gms.gcm.GoogleCloudMessaging; import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; public class MainActivity extends ActionBarActivity implements ForecastFragment.Callback { @@ -45,10 +46,10 @@ public class MainActivity extends ActionBarActivity implements ForecastFragment. private static final String PROPERTY_APP_VERSION = "appVersion"; /** - * Substitute you own project number here. This project number comes - * from the Google Developers Console. + * Substitute you own sender ID here. This is the project number you got + * from the API Console. */ - static final String PROJECT_NUMBER = "Your Project Number"; + String SENDER_ID = "Your-Sender-ID"; private boolean mTwoPane; private String mLocation; @@ -90,10 +91,10 @@ protected void onCreate(Bundle savedInstanceState) { mGcm = GoogleCloudMessaging.getInstance(this); String regId = getRegistrationId(this); - if (PROJECT_NUMBER.equals("Your Project Number")) { + if (SENDER_ID.equals("Your-Sender-ID")) { new AlertDialog.Builder(this) - .setTitle("Needs Project Number") - .setMessage("GCM will not function in Sunshine until you set the Project Number to the one from the Google Developers Console.") + .setTitle("Needs Sender ID") + .setMessage("GCM will not function in Sunshine until you replace your Sender ID with a Sender ID from the Google Developers Console.") .setPositiveButton(android.R.string.ok, null) .create().show(); } else if (regId.isEmpty()) { @@ -262,7 +263,7 @@ protected Void doInBackground(Void... params) { if (mGcm == null) { mGcm = GoogleCloudMessaging.getInstance(context); } - String regId = mGcm.register(PROJECT_NUMBER); + String regId = mGcm.register(SENDER_ID); msg = "Device registered, registration ID=" + regId; // You should send the registration ID to your server over HTTP, From d93b5ff352481d488c0bd9758abfd7cc0d2d5242 Mon Sep 17 00:00:00 2001 From: Dan Galpin Date: Wed, 26 Aug 2015 15:10:34 -0700 Subject: [PATCH 09/13] Commented out the GCM sender ID --- .../sunshine/app/gcm/RegistrationIntentService.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/android/sunshine/app/gcm/RegistrationIntentService.java b/app/src/main/java/com/example/android/sunshine/app/gcm/RegistrationIntentService.java index bfd9c207..9a7bc934 100644 --- a/app/src/main/java/com/example/android/sunshine/app/gcm/RegistrationIntentService.java +++ b/app/src/main/java/com/example/android/sunshine/app/gcm/RegistrationIntentService.java @@ -45,9 +45,11 @@ protected void onHandleIntent(Intent intent) { // Initially this call goes out to the network to retrieve the token, subsequent calls // are local. InstanceID instanceID = InstanceID.getInstance(this); - String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId), - GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); - sendRegistrationToServer(token); + + // TODO: gcm_default sender ID comes from the API console +// String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId), +// GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); +// sendRegistrationToServer(token); // You should store a boolean that indicates whether the generated token has been // sent to your server. If the boolean is false, send the token to your server, From a70753561a5a45549d9b36bba52e753a5d21c460 Mon Sep 17 00:00:00 2001 From: Joanna Smith Date: Sun, 14 Jun 2015 19:14:35 -0700 Subject: [PATCH 10/13] Adding the GCM listener to process downstream messages --- .../app/gcm/MyGcmListenerService.java | 104 ++++++++++++++++++ app/src/main/res/values/strings.xml | 5 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/example/android/sunshine/app/gcm/MyGcmListenerService.java diff --git a/app/src/main/java/com/example/android/sunshine/app/gcm/MyGcmListenerService.java b/app/src/main/java/com/example/android/sunshine/app/gcm/MyGcmListenerService.java new file mode 100644 index 00000000..6253fca1 --- /dev/null +++ b/app/src/main/java/com/example/android/sunshine/app/gcm/MyGcmListenerService.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.sunshine.app.gcm; + +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.support.v4.app.NotificationCompat; +import android.util.Log; + +import com.example.android.sunshine.app.MainActivity; +import com.example.android.sunshine.app.R; +import com.google.android.gms.gcm.GcmListenerService; + +import org.json.JSONException; +import org.json.JSONObject; + +public class MyGcmListenerService extends GcmListenerService { + + private static final String TAG = "MyGcmListenerService"; + + private static final String EXTRA_DATA = "data"; + private static final String EXTRA_WEATHER = "weather"; + private static final String EXTRA_LOCATION = "location"; + + public static final int NOTIFICATION_ID = 1; + + /** + * Called when message is received. + * + * @param from SenderID of the sender. + * @param data Data bundle containing message data as key/value pairs. + * For Set of keys use data.keySet(). + */ + @Override + public void onMessageReceived(String from, Bundle data) { + // Time to unparcel the bundle! + if (!data.isEmpty()) { + // Not a bad idea to check that the message is coming from your server. + if ((getString(R.string.gcm_defaultSenderId)).equals(from)) { + // Process message and then post a notification of the received message. + try { + JSONObject jsonObject = new JSONObject(data.getString(EXTRA_DATA)); + String weather = jsonObject.getString(EXTRA_WEATHER); + String location = jsonObject.getString(EXTRA_LOCATION); + String alert = + String.format(getString(R.string.gcm_weather_alert), weather, location); + sendNotification(alert); + } catch (JSONException e) { + // JSON parsing failed, so we just let this message go, since GCM is not one + // of our critical features. + } + } + Log.i(TAG, "Received: " + data.toString()); + } + } + + /** + * Put the message into a notification and post it. + * This is just one simple example of what you might choose to do with a GCM message. + * + * @param message The alert message to be posted. + */ + private void sendNotification(String message) { + NotificationManager mNotificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + PendingIntent contentIntent = + PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0); + + // Notifications using both a large and a small icon (which yours should!) need the large + // icon as a bitmap. So we need to create that here from the resource ID, and pass the + // object along in our notification builder. Generally, you want to use the app icon as the + // small icon, so that users understand what app is triggering this notification. + Bitmap largeIcon = BitmapFactory.decodeResource(this.getResources(), R.drawable.art_storm); + NotificationCompat.Builder mBuilder = + new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.art_clear) + .setLargeIcon(largeIcon) + .setContentTitle("Weather Alert!") + .setStyle(new NotificationCompat.BigTextStyle().bigText(message)) + .setContentText(message) + .setPriority(NotificationCompat.PRIORITY_HIGH); + mBuilder.setContentIntent(contentIntent); + mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build()); + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f330d644..cdfffd38 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -205,4 +205,7 @@ Hurricane Unknown (%1$s) - \ No newline at end of file + + + Heads up: %1$s in %2$s! + From c25402108986ac359f34cafaaba44be0c5a68f33 Mon Sep 17 00:00:00 2001 From: Dan Galpin Date: Wed, 26 Aug 2015 15:46:18 -0700 Subject: [PATCH 11/13] Added a blank sender id and a check for it to keep things compiling. --- .../sunshine/app/gcm/RegistrationIntentService.java | 10 +++++++--- app/src/main/res/values/strings.xml | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/android/sunshine/app/gcm/RegistrationIntentService.java b/app/src/main/java/com/example/android/sunshine/app/gcm/RegistrationIntentService.java index 9a7bc934..fae7157f 100644 --- a/app/src/main/java/com/example/android/sunshine/app/gcm/RegistrationIntentService.java +++ b/app/src/main/java/com/example/android/sunshine/app/gcm/RegistrationIntentService.java @@ -20,6 +20,7 @@ import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.util.Log; +import android.widget.Toast; import com.example.android.sunshine.app.MainActivity; import com.example.android.sunshine.app.R; @@ -47,9 +48,12 @@ protected void onHandleIntent(Intent intent) { InstanceID instanceID = InstanceID.getInstance(this); // TODO: gcm_default sender ID comes from the API console -// String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId), -// GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); -// sendRegistrationToServer(token); + String senderId = getString(R.string.gcm_defaultSenderId); + if ( senderId.length() != 0 ) { + String token = instanceID.getToken(senderId, + GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); + sendRegistrationToServer(token); + } // You should store a boolean that indicates whether the generated token has been // sent to your server. If the boolean is false, send the token to your server, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f330d644..e951ffd5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -205,4 +205,7 @@ Hurricane Unknown (%1$s) + + // TODO: Get the SenderID from the Developer Console + \ No newline at end of file From 29972d84e005f6a7d860229a00a6dcb011100325 Mon Sep 17 00:00:00 2001 From: Dan Galpin Date: Wed, 26 Aug 2015 17:48:32 -0700 Subject: [PATCH 12/13] Added translatable=false to stub string. --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e951ffd5..05f8ae04 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -207,5 +207,5 @@ Unknown (%1$s) // TODO: Get the SenderID from the Developer Console - + \ No newline at end of file From 8b5aeaaf6e7c0d430d85a601705fdc8f0f72eaf2 Mon Sep 17 00:00:00 2001 From: Dan Galpin Date: Wed, 26 Aug 2015 17:55:12 -0700 Subject: [PATCH 13/13] Added helper toast if senderId is zero length. --- .../android/sunshine/app/gcm/MyGcmListenerService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/com/example/android/sunshine/app/gcm/MyGcmListenerService.java b/app/src/main/java/com/example/android/sunshine/app/gcm/MyGcmListenerService.java index 69e06d82..14627478 100644 --- a/app/src/main/java/com/example/android/sunshine/app/gcm/MyGcmListenerService.java +++ b/app/src/main/java/com/example/android/sunshine/app/gcm/MyGcmListenerService.java @@ -25,6 +25,7 @@ import android.os.Bundle; import android.support.v4.app.NotificationCompat; import android.util.Log; +import android.widget.Toast; import com.example.android.sunshine.app.MainActivity; import com.example.android.sunshine.app.R; @@ -56,6 +57,9 @@ public void onMessageReceived(String from, Bundle data) { if (!data.isEmpty()) { // TODO: gcm_default sender ID comes from the API console String senderId = getString(R.string.gcm_defaultSenderId); + if (senderId.length() == 0) { + Toast.makeText(this, "SenderID string needs to be set", Toast.LENGTH_LONG).show(); + } // Not a bad idea to check that the message is coming from your server. if ((senderId).equals(from)) { // Process message and then post a notification of the received message.