-
@@ -88,25 +90,45 @@
+ 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 77bccfbc..f3f99376 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,45 +15,31 @@
*/
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.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
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.GooglePlayServicesUtil;
-import com.google.android.gms.gcm.GoogleCloudMessaging;
-
-import java.io.IOException;
+import com.google.android.gms.common.GoogleApiAvailability;
public class MainActivity extends AppCompatActivity 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 project number here. This project number comes
- * from the Google Developers Console.
- */
- static final String PROJECT_NUMBER = "Your Project Number";
+ public static final String SENT_TOKEN_TO_SERVER = "sentTokenToServer";
private boolean mTwoPane;
private String mLocation;
- private GoogleCloudMessaging mGcm;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -89,25 +75,21 @@ 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 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()) {
- mGcm = GoogleCloudMessaging.getInstance(this);
- String regId = getRegistrationId(this);
-
- if (PROJECT_NUMBER.equals("Your Project Number")) {
- 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.")
- .setPositiveButton(android.R.string.ok, null)
- .create().show();
- } else if (regId.isEmpty()) {
- registerInBackground(this);
+ // 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);
}
- } else {
- Log.i(LOG_TAG, "No valid Google Play Services APK. Weather alerts will be disabled.");
- // Store regID as null
- storeRegistrationId(this, null);
}
}
@@ -130,22 +112,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();
@@ -186,10 +162,11 @@ public void onItemSelected(Uri contentUri) {
* the Google Play Store or enable it in the device's system settings.
*/
private boolean checkPlayServices() {
- int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
+ GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
+ int resultCode = apiAvailability.isGooglePlayServicesAvailable(this);
if (resultCode != ConnectionResult.SUCCESS) {
- if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
- GooglePlayServicesUtil.getErrorDialog(resultCode, this,
+ if (apiAvailability.isUserResolvableError(resultCode)) {
+ apiAvailability.getErrorDialog(this, resultCode,
PLAY_SERVICES_RESOLUTION_REQUEST).show();
} else {
Log.i(LOG_TAG, "This device is not supported.");
@@ -199,113 +176,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(PROJECT_NUMBER);
- 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: 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();
- }
}
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..14627478
--- /dev/null
+++ b/app/src/main/java/com/example/android/sunshine/app/gcm/MyGcmListenerService.java
@@ -0,0 +1,110 @@
+/*
+ * 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 android.widget.Toast;
+
+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()) {
+ // 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.
+ 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/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..fae7157f
--- /dev/null
+++ b/app/src/main/java/com/example/android/sunshine/app/gcm/RegistrationIntentService.java
@@ -0,0 +1,82 @@
+/*
+ * 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 android.widget.Toast;
+
+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);
+
+ // TODO: gcm_default sender ID comes from the API console
+ 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,
+ // 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);
+ }
+}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1263e484..06bdb4d0 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -211,4 +211,9 @@
Hurricane
Unknown (%1$s)
-
\ No newline at end of file
+
+
+ Heads up: %1$s in %2$s!
+ // TODO: Get the SenderID from the Developer Console
+
+
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