From ca40a5cc0f95f37d2ac3008d5fa3f0adaf47d736 Mon Sep 17 00:00:00 2001 From: Marius Gavrilescu Date: Sat, 27 Jul 2013 09:55:02 +0300 Subject: [PATCH] Replace GCM and polling with long polling * Remove everything related to GCM * Remove simple polling and related things (such as the Poll server on screen on pref) * Add long polling --- AndroidManifest.xml | 21 +- .../android/gcm/GCMBaseIntentService.java | 345 ------------------ .../android/gcm/GCMBroadcastReceiver.java | 75 ---- lib/com/google/android/gcm/GCMConstants.java | 161 -------- lib/com/google/android/gcm/GCMRegistrar.java | 316 ---------------- res/xml/prefs.xml | 5 +- src/ro/ieval/fonbot/DynamicEventReceiver.java | 5 +- src/ro/ieval/fonbot/FonBotApplication.java | 2 - src/ro/ieval/fonbot/FonBotMainService.java | 33 +- src/ro/ieval/fonbot/GCMIntentService.java | 63 ---- src/ro/ieval/fonbot/Heavy.java | 28 +- .../fonbot/HttpCallExecutableRunnable.java | 6 +- .../fonbot/ProtectedBroadcastReceiver.java | 4 +- src/ro/ieval/fonbot/Utils.java | 45 +-- 14 files changed, 45 insertions(+), 1064 deletions(-) delete mode 100644 lib/com/google/android/gcm/GCMBaseIntentService.java delete mode 100644 lib/com/google/android/gcm/GCMBroadcastReceiver.java delete mode 100644 lib/com/google/android/gcm/GCMConstants.java delete mode 100644 lib/com/google/android/gcm/GCMRegistrar.java delete mode 100644 src/ro/ieval/fonbot/GCMIntentService.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 00a913a..cc92df3 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -8,14 +8,6 @@ android:minSdkVersion="8" android:targetSdkVersion="18" /> - - - - - @@ -146,21 +138,9 @@ android:theme="@android:style/Theme.Dialog" > - - - - - - - - - - @@ -189,6 +169,7 @@ + diff --git a/lib/com/google/android/gcm/GCMBaseIntentService.java b/lib/com/google/android/gcm/GCMBaseIntentService.java deleted file mode 100644 index ab1e489..0000000 --- a/lib/com/google/android/gcm/GCMBaseIntentService.java +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright 2012 Google Inc. - * - * 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.google.android.gcm; - -import static com.google.android.gcm.GCMConstants.ERROR_SERVICE_NOT_AVAILABLE; -import static com.google.android.gcm.GCMConstants.EXTRA_ERROR; -import static com.google.android.gcm.GCMConstants.EXTRA_REGISTRATION_ID; -import static com.google.android.gcm.GCMConstants.EXTRA_SPECIAL_MESSAGE; -import static com.google.android.gcm.GCMConstants.EXTRA_TOTAL_DELETED; -import static com.google.android.gcm.GCMConstants.EXTRA_UNREGISTERED; -import static com.google.android.gcm.GCMConstants.INTENT_FROM_GCM_LIBRARY_RETRY; -import static com.google.android.gcm.GCMConstants.INTENT_FROM_GCM_MESSAGE; -import static com.google.android.gcm.GCMConstants.INTENT_FROM_GCM_REGISTRATION_CALLBACK; -import static com.google.android.gcm.GCMConstants.VALUE_DELETED_MESSAGES; - -import android.app.AlarmManager; -import android.app.IntentService; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.os.PowerManager; -import android.os.SystemClock; -import android.util.Log; - -import java.util.Random; -import java.util.concurrent.TimeUnit; - -/** - * Skeleton for application-specific {@link IntentService}s responsible for - * handling communication from Google Cloud Messaging service. - *

- * The abstract methods in this class are called from its worker thread, and - * hence should run in a limited amount of time. If they execute long - * operations, they should spawn new threads, otherwise the worker thread will - * be blocked. - *

- * Subclasses must provide a public no-arg constructor. - */ -public abstract class GCMBaseIntentService extends IntentService { - - public static final String TAG = "GCMBaseIntentService"; - - // wakelock - private static final String WAKELOCK_KEY = "GCM_LIB"; - private static PowerManager.WakeLock sWakeLock; - - // Java lock used to synchronize access to sWakelock - private static final Object LOCK = GCMBaseIntentService.class; - - private final String[] mSenderIds; - - // instance counter - private static int sCounter = 0; - - private static final Random sRandom = new Random(); - - private static final int MAX_BACKOFF_MS = - (int) TimeUnit.SECONDS.toMillis(3600); // 1 hour - - // token used to check intent origin - private static final String TOKEN = - Long.toBinaryString(sRandom.nextLong()); - private static final String EXTRA_TOKEN = "token"; - - /** - * Constructor that does not set a sender id, useful when the sender id - * is context-specific. - *

- * When using this constructor, the subclass must - * override {@link #getSenderIds(Context)}, otherwise methods such as - * {@link #onHandleIntent(Intent)} will throw an - * {@link IllegalStateException} on runtime. - */ - protected GCMBaseIntentService() { - this(getName("DynamicSenderIds"), null); - } - - /** - * Constructor used when the sender id(s) is fixed. - */ - protected GCMBaseIntentService(String... senderIds) { - this(getName(senderIds), senderIds); - } - - private GCMBaseIntentService(String name, String[] senderIds) { - super(name); // name is used as base name for threads, etc. - mSenderIds = senderIds; - } - - private static String getName(String senderId) { - String name = "GCMIntentService-" + senderId + "-" + (++sCounter); - Log.v(TAG, "Intent service name: " + name); - return name; - } - - private static String getName(String[] senderIds) { - String flatSenderIds = GCMRegistrar.getFlatSenderIds(senderIds); - return getName(flatSenderIds); - } - - /** - * Gets the sender ids. - * - *

By default, it returns the sender ids passed in the constructor, but - * it could be overridden to provide a dynamic sender id. - * - * @throws IllegalStateException if sender id was not set on constructor. - */ - protected String[] getSenderIds() { - if (mSenderIds == null) { - throw new IllegalStateException("sender id not set on constructor"); - } - return mSenderIds; - } - - /** - * Called when a cloud message has been received. - * - * @param context application's context. - * @param intent intent containing the message payload as extras. - */ - protected abstract void onMessage(Context context, Intent intent); - - /** - * Called when the GCM server tells pending messages have been deleted - * because the device was idle. - * - * @param context application's context. - * @param total total number of collapsed messages - */ - protected void onDeletedMessages(Context context, int total) { - //do nothing - } - - /** - * Called on a registration error that could be retried. - * - *

By default, it does nothing and returns {@literal true}, but could be - * overridden to change that behavior and/or display the error. - * - * @param context application's context. - * @param errorId error id returned by the GCM service. - * - * @return if {@literal true}, failed operation will be retried (using - * exponential backoff). - */ - protected static boolean onRecoverableError(Context context, String errorId) { - return true; - } - - /** - * Called on registration or unregistration error. - * - * @param context application's context. - * @param errorId error id returned by the GCM service. - */ - protected abstract void onError(Context context, String errorId); - - /** - * Called after a device has been registered. - * - * @param context application's context. - * @param registrationId the registration id returned by the GCM service. - */ - protected abstract void onRegistered(Context context, - String registrationId); - - /** - * Called after a device has been unregistered. - * - * @param registrationId the registration id that was previously registered. - * @param context application's context. - */ - protected abstract void onUnregistered(Context context, - String registrationId); - - @Override - public final void onHandleIntent(Intent intent) { - try { - Context context = getApplicationContext(); - String action = intent.getAction(); - if (action.equals(INTENT_FROM_GCM_REGISTRATION_CALLBACK)) { - GCMRegistrar.setRetryBroadcastReceiver(context); - handleRegistration(context, intent); - } else if (action.equals(INTENT_FROM_GCM_MESSAGE)) { - // checks for special messages - String messageType = - intent.getStringExtra(EXTRA_SPECIAL_MESSAGE); - if (messageType != null) { - if (messageType.equals(VALUE_DELETED_MESSAGES)) { - String sTotal = - intent.getStringExtra(EXTRA_TOTAL_DELETED); - if (sTotal != null) { - try { - int total = Integer.parseInt(sTotal); - Log.v(TAG, "Received deleted messages " + - "notification: " + total); - onDeletedMessages(context, total); - } catch (NumberFormatException e) { - Log.e(TAG, "GCM returned invalid number of " + - "deleted messages: " + sTotal); - } - } - } else { - // application is not using the latest GCM library - Log.e(TAG, "Received unknown special message: " + - messageType); - } - } else { - onMessage(context, intent); - } - } else if (action.equals(INTENT_FROM_GCM_LIBRARY_RETRY)) { - String token = intent.getStringExtra(EXTRA_TOKEN); - if (!TOKEN.equals(token)) { - // make sure intent was generated by this class, not by a - // malicious app. - Log.e(TAG, "Received invalid token: " + token); - return; - } - // retry last call - if (GCMRegistrar.isRegistered(context)) { - GCMRegistrar.internalUnregister(context); - } else { - String[] senderIds = getSenderIds(); - GCMRegistrar.internalRegister(context, senderIds); - } - } - } finally { - // Release the power lock, so phone can get back to sleep. - // The lock is reference-counted by default, so multiple - // messages are ok. - - // If onMessage() needs to spawn a thread or do something else, - // it should use its own lock. - synchronized (LOCK) { - // sanity check for null as this is a public method - if (sWakeLock != null) { - Log.v(TAG, "Releasing wakelock"); - sWakeLock.release(); - } else { - // should never happen during normal workflow - Log.e(TAG, "Wakelock reference is null"); - } - } - } - } - - /** - * Called from the broadcast receiver. - *

- * Will process the received intent, call handleMessage(), registered(), - * etc. in background threads, with a wake lock, while keeping the service - * alive. - */ - static void runIntentInService(Context context, Intent intent, - String className) { - synchronized (LOCK) { - if (sWakeLock == null) { - // This is called from BroadcastReceiver, there is no init. - PowerManager pm = (PowerManager) - context.getSystemService(Context.POWER_SERVICE); - sWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - WAKELOCK_KEY); - } - } - Log.v(TAG, "Acquiring wakelock"); - sWakeLock.acquire(); - intent.setClassName(context, className); - context.startService(intent); - } - - private void handleRegistration(final Context context, Intent intent) { - String registrationId = intent.getStringExtra(EXTRA_REGISTRATION_ID); - String error = intent.getStringExtra(EXTRA_ERROR); - String unregistered = intent.getStringExtra(EXTRA_UNREGISTERED); - Log.d(TAG, "handleRegistration: registrationId = " + registrationId + - ", error = " + error + ", unregistered = " + unregistered); - - // registration succeeded - if (registrationId != null) { - GCMRegistrar.resetBackoff(context); - GCMRegistrar.setRegistrationId(context, registrationId); - onRegistered(context, registrationId); - return; - } - - // unregistration succeeded - if (unregistered != null) { - // Remember we are unregistered - GCMRegistrar.resetBackoff(context); - String oldRegistrationId = - GCMRegistrar.clearRegistrationId(context); - onUnregistered(context, oldRegistrationId); - return; - } - - // last operation (registration or unregistration) returned an error; - Log.d(TAG, "Registration error: " + error); - // Registration failed - if (ERROR_SERVICE_NOT_AVAILABLE.equals(error)) { - boolean retry = onRecoverableError(context, error); - if (retry) { - int backoffTimeMs = GCMRegistrar.getBackoff(context); - int nextAttempt = backoffTimeMs / 2 + - sRandom.nextInt(backoffTimeMs); - Log.d(TAG, "Scheduling registration retry, backoff = " + - nextAttempt + " (" + backoffTimeMs + ")"); - Intent retryIntent = - new Intent(INTENT_FROM_GCM_LIBRARY_RETRY); - retryIntent.putExtra(EXTRA_TOKEN, TOKEN); - PendingIntent retryPendingIntent = PendingIntent - .getBroadcast(context, 0, retryIntent, 0); - AlarmManager am = (AlarmManager) - context.getSystemService(Context.ALARM_SERVICE); - am.set(AlarmManager.ELAPSED_REALTIME, - SystemClock.elapsedRealtime() + nextAttempt, - retryPendingIntent); - // Next retry should wait longer. - if (backoffTimeMs < MAX_BACKOFF_MS) { - GCMRegistrar.setBackoff(context, backoffTimeMs * 2); - } - } else { - Log.d(TAG, "Not retrying failed operation"); - } - } else { - // Unrecoverable error, notify app - onError(context, error); - } - } - -} diff --git a/lib/com/google/android/gcm/GCMBroadcastReceiver.java b/lib/com/google/android/gcm/GCMBroadcastReceiver.java deleted file mode 100644 index bbe7b66..0000000 --- a/lib/com/google/android/gcm/GCMBroadcastReceiver.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2012 Google Inc. - * - * 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.google.android.gcm; - -import static com.google.android.gcm.GCMConstants.DEFAULT_INTENT_SERVICE_CLASS_NAME; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -/** - * {@link BroadcastReceiver} that receives GCM messages and delivers them to - * an application-specific {@link GCMBaseIntentService} subclass. - *

- * By default, the {@link GCMBaseIntentService} class belongs to the application - * main package and is named - * {@link GCMConstants#DEFAULT_INTENT_SERVICE_CLASS_NAME}. To use a new class, - * the {@link #getGCMIntentServiceClassName(Context)} must be overridden. - */ -public final class GCMBroadcastReceiver extends BroadcastReceiver { - - private static final String TAG = "GCMBroadcastReceiver"; - private static boolean mReceiverSet = false; - - @Override - public final void onReceive(Context context, Intent intent) { - Log.v(TAG, "onReceive: " + intent.getAction()); - // do a one-time check if app is using a custom GCMBroadcastReceiver - if (!mReceiverSet) { - mReceiverSet = true; - String myClass = getClass().getName(); - if (!myClass.equals(GCMBroadcastReceiver.class.getName())) { - GCMRegistrar.setRetryReceiverClassName(myClass); - } - } - String className = getGCMIntentServiceClassName(context); - Log.v(TAG, "GCM IntentService class: " + className); - // Delegates to the application-specific intent service. - GCMBaseIntentService.runIntentInService(context, intent, className); - setResult(Activity.RESULT_OK, null /* data */, null /* extra */); - } - - /** - * Gets the class name of the intent service that will handle GCM messages. - */ - protected static String getGCMIntentServiceClassName(final Context context) { - return getDefaultIntentServiceClassName(context); - } - - /** - * Gets the default class name of the intent service that will handle GCM - * messages. - */ - private static final String getDefaultIntentServiceClassName(final Context context) { - String className = context.getPackageName() + - DEFAULT_INTENT_SERVICE_CLASS_NAME; - return className; - } -} diff --git a/lib/com/google/android/gcm/GCMConstants.java b/lib/com/google/android/gcm/GCMConstants.java deleted file mode 100644 index abcdcfb..0000000 --- a/lib/com/google/android/gcm/GCMConstants.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2012 Google Inc. - * - * 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.google.android.gcm; - -/** - * Constants used by the GCM library. - */ -public final class GCMConstants { - - /** - * Intent sent to GCM to register the application. - */ - public static final String INTENT_TO_GCM_REGISTRATION = - "com.google.android.c2dm.intent.REGISTER"; - - /** - * Intent sent to GCM to unregister the application. - */ - public static final String INTENT_TO_GCM_UNREGISTRATION = - "com.google.android.c2dm.intent.UNREGISTER"; - - /** - * Intent sent by GCM indicating with the result of a registration request. - */ - public static final String INTENT_FROM_GCM_REGISTRATION_CALLBACK = - "com.google.android.c2dm.intent.REGISTRATION"; - - /** - * Intent used by the GCM library to indicate that the registration call - * should be retried. - */ - public static final String INTENT_FROM_GCM_LIBRARY_RETRY = - "com.google.android.gcm.intent.RETRY"; - - /** - * Intent sent by GCM containing a message. - */ - public static final String INTENT_FROM_GCM_MESSAGE = - "com.google.android.c2dm.intent.RECEIVE"; - - /** - * Extra used on {@link #INTENT_TO_GCM_REGISTRATION} to indicate the sender - * account (a Google email) that owns the application. - */ - public static final String EXTRA_SENDER = "sender"; - - /** - * Extra used on {@link #INTENT_TO_GCM_REGISTRATION} to get the application - * id. - */ - public static final String EXTRA_APPLICATION_PENDING_INTENT = "app"; - - /** - * Extra used on {@link #INTENT_FROM_GCM_REGISTRATION_CALLBACK} to indicate - * that the application has been unregistered. - */ - public static final String EXTRA_UNREGISTERED = "unregistered"; - - /** - * Extra used on {@link #INTENT_FROM_GCM_REGISTRATION_CALLBACK} to indicate - * an error when the registration fails. See constants starting with ERROR_ - * for possible values. - */ - public static final String EXTRA_ERROR = "error"; - - /** - * Extra used on {@link #INTENT_FROM_GCM_REGISTRATION_CALLBACK} to indicate - * the registration id when the registration succeeds. - */ - public static final String EXTRA_REGISTRATION_ID = "registration_id"; - - /** - * Type of message present in the {@link #INTENT_FROM_GCM_MESSAGE} intent. - * This extra is only set for special messages sent from GCM, not for - * messages originated from the application. - */ - public static final String EXTRA_SPECIAL_MESSAGE = "message_type"; - - /** - * Special message indicating the server deleted the pending messages. - */ - public static final String VALUE_DELETED_MESSAGES = "deleted_messages"; - - /** - * Number of messages deleted by the server because the device was idle. - * Present only on messages of special type - * {@link #VALUE_DELETED_MESSAGES} - */ - public static final String EXTRA_TOTAL_DELETED = "total_deleted"; - - /** - * Permission necessary to receive GCM intents. - */ - public static final String PERMISSION_GCM_INTENTS = - "com.google.android.c2dm.permission.SEND"; - - /** - * @see GCMBroadcastReceiver - */ - public static final String DEFAULT_INTENT_SERVICE_CLASS_NAME = - ".GCMIntentService"; - - /** - * The device can't read the response, or there was a 500/503 from the - * server that can be retried later. The application should use exponential - * back off and retry. - */ - public static final String ERROR_SERVICE_NOT_AVAILABLE = - "SERVICE_NOT_AVAILABLE"; - - /** - * There is no Google account on the phone. The application should ask the - * user to open the account manager and add a Google account. - */ - public static final String ERROR_ACCOUNT_MISSING = - "ACCOUNT_MISSING"; - - /** - * Bad password. The application should ask the user to enter his/her - * password, and let user retry manually later. Fix on the device side. - */ - public static final String ERROR_AUTHENTICATION_FAILED = - "AUTHENTICATION_FAILED"; - - /** - * The request sent by the phone does not contain the expected parameters. - * This phone doesn't currently support GCM. - */ - public static final String ERROR_INVALID_PARAMETERS = - "INVALID_PARAMETERS"; - /** - * The sender account is not recognized. Fix on the device side. - */ - public static final String ERROR_INVALID_SENDER = - "INVALID_SENDER"; - - /** - * Incorrect phone registration with Google. This phone doesn't currently - * support GCM. - */ - public static final String ERROR_PHONE_REGISTRATION_ERROR = - "PHONE_REGISTRATION_ERROR"; - - private GCMConstants() { - throw new UnsupportedOperationException(); - } -} diff --git a/lib/com/google/android/gcm/GCMRegistrar.java b/lib/com/google/android/gcm/GCMRegistrar.java deleted file mode 100644 index 0bd5c9f..0000000 --- a/lib/com/google/android/gcm/GCMRegistrar.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright 2012 Google Inc. - * - * 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.google.android.gcm; - -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Build; -import android.util.Log; - -/** - * Utilities for device registration. - *

- * Note: this class uses a private {@link SharedPreferences} - * object to keep track of the registration token. - */ -public final class GCMRegistrar { - - /** - * Default lifespan (7 days) of the {@link #isRegisteredOnServer(Context)} - * flag until it is considered expired. - */ - // NOTE: cannot use TimeUnit.DAYS because it's not available on API Level 8 - public static final long DEFAULT_ON_SERVER_LIFESPAN_MS = - 1000 * 3600 * 24 * 7; - - private static final String TAG = "GCMRegistrar"; - private static final String BACKOFF_MS = "backoff_ms"; - private static final String GSF_PACKAGE = "com.google.android.gsf"; - private static final String PREFERENCES = "com.google.android.gcm"; - private static final int DEFAULT_BACKOFF_MS = 3000; - private static final String PROPERTY_REG_ID = "regId"; - private static final String PROPERTY_APP_VERSION = "appVersion"; - /** - * {@link GCMBroadcastReceiver} instance used to handle the retry intent. - * - *

- * This instance cannot be the same as the one defined in the manifest - * because it needs a different permission. - */ - private static GCMBroadcastReceiver sRetryReceiver; - - private static String sRetryReceiverClassName; - - /** - * Checks if the device has the proper dependencies installed. - *

- * This method should be called when the application starts to verify that - * the device supports GCM. - * - * @param context application context. - * @throws UnsupportedOperationException if the device does not support GCM. - */ - public static void checkDevice(final Context context) { - final int version = Build.VERSION.SDK_INT; - if (version < 8) { - throw new UnsupportedOperationException("Device must be at least " + - "API Level 8 (instead of " + version + ")"); - } - final PackageManager packageManager = context.getPackageManager(); - try { - packageManager.getPackageInfo(GSF_PACKAGE, 0); - } catch (NameNotFoundException e) { - throw new UnsupportedOperationException( - "Device does not have package " + GSF_PACKAGE); - } - } - - /** - * Initiate messaging registration for the current application. - *

- * The result will be returned as an - * {@link GCMConstants#INTENT_FROM_GCM_REGISTRATION_CALLBACK} intent with - * either a {@link GCMConstants#EXTRA_REGISTRATION_ID} or - * {@link GCMConstants#EXTRA_ERROR}. - * - * @param context application context. - * @param senderIds Google Project ID of the accounts authorized to send - * messages to this application. - * @throws IllegalStateException if device does not have all GCM - * dependencies installed. - */ - public static void register(Context context, String... senderIds) { - GCMRegistrar.resetBackoff(context); - internalRegister(context, senderIds); - } - - static void internalRegister(Context context, String... senderIds) { - String flatSenderIds = getFlatSenderIds(senderIds); - Log.v(TAG, "Registering app " + context.getPackageName() + - " of senders " + flatSenderIds); - Intent intent = new Intent(GCMConstants.INTENT_TO_GCM_REGISTRATION); - intent.setPackage(GSF_PACKAGE); - intent.putExtra(GCMConstants.EXTRA_APPLICATION_PENDING_INTENT, - PendingIntent.getBroadcast(context, 0, new Intent(), 0)); - intent.putExtra(GCMConstants.EXTRA_SENDER, flatSenderIds); - context.startService(intent); - } - - static String getFlatSenderIds(String... senderIds) { - if (senderIds == null || senderIds.length == 0) { - throw new IllegalArgumentException("No senderIds"); - } - StringBuilder builder = new StringBuilder(senderIds[0]); - for (int i = 1; i < senderIds.length; i++) { - builder.append(',').append(senderIds[i]); - } - return builder.toString(); - } - - /** - * Clear internal resources. - * - *

- * This method should be called by the main activity's {@code onDestroy()} - * method. - */ - public static synchronized void onDestroy(Context context) { - if (sRetryReceiver != null) { - Log.v(TAG, "Unregistering receiver"); - context.unregisterReceiver(sRetryReceiver); - sRetryReceiver = null; - } - } - - static void internalUnregister(Context context) { - Log.v(TAG, "Unregistering app " + context.getPackageName()); - Intent intent = new Intent(GCMConstants.INTENT_TO_GCM_UNREGISTRATION); - intent.setPackage(GSF_PACKAGE); - intent.putExtra(GCMConstants.EXTRA_APPLICATION_PENDING_INTENT, - PendingIntent.getBroadcast(context, 0, new Intent(), 0)); - context.startService(intent); - } - - /** - * Lazy initializes the {@link GCMBroadcastReceiver} instance. - */ - static synchronized void setRetryBroadcastReceiver(Context context) { - if (sRetryReceiver == null) { - if (sRetryReceiverClassName == null) { - // should never happen - Log.e(TAG, "internal error: retry receiver class not set yet"); - sRetryReceiver = new GCMBroadcastReceiver(); - } else { - Class clazz; - try { - clazz = Class.forName(sRetryReceiverClassName); - sRetryReceiver = (GCMBroadcastReceiver) clazz.newInstance(); - } catch (Exception e) { - Log.e(TAG, "Could not create instance of " + - sRetryReceiverClassName + ". Using " + - GCMBroadcastReceiver.class.getName() + - " directly."); - sRetryReceiver = new GCMBroadcastReceiver(); - } - } - String category = context.getPackageName(); - IntentFilter filter = new IntentFilter( - GCMConstants.INTENT_FROM_GCM_LIBRARY_RETRY); - filter.addCategory(category); - // must use a permission that is defined on manifest for sure - String permission = category + ".permission.C2D_MESSAGE"; - Log.v(TAG, "Registering receiver"); - context.registerReceiver(sRetryReceiver, filter, permission, null); - } - } - - /** - * Sets the name of the retry receiver class. - */ - static void setRetryReceiverClassName(String className) { - Log.v(TAG, "Setting the name of retry receiver class to " + className); - sRetryReceiverClassName = className; - } - - /** - * Gets the current registration id for application on GCM service. - *

- * If result is empty, the registration has failed. - * - * @return registration id, or empty string if the registration is not - * complete. - */ - public static String getRegistrationId(Context context) { - final SharedPreferences prefs = getGCMPreferences(context); - String registrationId = prefs.getString(PROPERTY_REG_ID, ""); - // check if app was updated; if so, it must clear registration id to - // avoid a race condition if GCM sends a message - int oldVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE); - int newVersion = getAppVersion(context); - if (oldVersion != Integer.MIN_VALUE && oldVersion != newVersion) { - Log.v(TAG, "App version changed from " + oldVersion + " to " + - newVersion + "; resetting registration id"); - clearRegistrationId(context); - registrationId = ""; - } - return registrationId; - } - - /** - * Checks whether the application was successfully registered on GCM - * service. - */ - static boolean isRegistered(Context context) { - return getRegistrationId(context).length() > 0; - } - - /** - * Clears the registration id in the persistence store. - * - * @param context application's context. - * @return old registration id. - */ - static String clearRegistrationId(Context context) { - return setRegistrationId(context, ""); - } - - /** - * Sets the registration id in the persistence store. - * - * @param context application's context. - * @param regId registration id - */ - static String setRegistrationId(Context context, String regId) { - final SharedPreferences prefs = getGCMPreferences(context); - String oldRegistrationId = prefs.getString(PROPERTY_REG_ID, ""); - int appVersion = getAppVersion(context); - Log.v(TAG, "Saving regId on app version " + appVersion); - Editor editor = prefs.edit(); - editor.putString(PROPERTY_REG_ID, regId); - editor.putInt(PROPERTY_APP_VERSION, appVersion); - editor.commit(); - return oldRegistrationId; - } - - /** - * Gets the application version. - */ - private static int getAppVersion(Context context) { - try { - PackageInfo packageInfo = context.getPackageManager() - .getPackageInfo(context.getPackageName(), 0); - return packageInfo.versionCode; - } catch (NameNotFoundException e) { - // should never happen - throw new RuntimeException("Coult not get package name: " + e); - } - } - - /** - * Resets the backoff counter. - *

- * This method should be called after a GCM call succeeds. - * - * @param context application's context. - */ - static void resetBackoff(Context context) { - Log.d(TAG, "resetting backoff for " + context.getPackageName()); - setBackoff(context, DEFAULT_BACKOFF_MS); - } - - /** - * Gets the current backoff counter. - * - * @param context application's context. - * @return current backoff counter, in milliseconds. - */ - static int getBackoff(Context context) { - final SharedPreferences prefs = getGCMPreferences(context); - return prefs.getInt(BACKOFF_MS, DEFAULT_BACKOFF_MS); - } - - /** - * Sets the backoff counter. - *

- * This method should be called after a GCM call fails, passing an - * exponential value. - * - * @param context application's context. - * @param backoff new backoff counter, in milliseconds. - */ - static void setBackoff(Context context, int backoff) { - final SharedPreferences prefs = getGCMPreferences(context); - Editor editor = prefs.edit(); - editor.putInt(BACKOFF_MS, backoff); - editor.commit(); - } - - private static SharedPreferences getGCMPreferences(Context context) { - return context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE); - } - - private GCMRegistrar() { - throw new UnsupportedOperationException(); - } -} diff --git a/res/xml/prefs.xml b/res/xml/prefs.xml index b47e0f9..5911061 100644 --- a/res/xml/prefs.xml +++ b/res/xml/prefs.xml @@ -3,9 +3,10 @@ - + + - \ No newline at end of file + diff --git a/src/ro/ieval/fonbot/DynamicEventReceiver.java b/src/ro/ieval/fonbot/DynamicEventReceiver.java index cb1e805..e6d7455 100644 --- a/src/ro/ieval/fonbot/DynamicEventReceiver.java +++ b/src/ro/ieval/fonbot/DynamicEventReceiver.java @@ -45,10 +45,7 @@ public final class DynamicEventReceiver extends BroadcastReceiver { final String action=intent.getAction(); - if(action.equals(Intent.ACTION_SCREEN_ON)){ - if(PreferenceManager.getDefaultSharedPreferences(context).getBoolean("poll_on_screen_on", false)) - Utils.safePollServer(context); - } else if(action.equals(Intent.ACTION_BATTERY_CHANGED)) + if(action.equals(Intent.ACTION_BATTERY_CHANGED)) Heavy.describeBatteryLevel(context, null, toNonNull(MessageType.BATTERY_CHANGED)); else if(action.equals(Intent.ACTION_HEADSET_PLUG)){ final int state=intent.getIntExtra("state", 0); diff --git a/src/ro/ieval/fonbot/FonBotApplication.java b/src/ro/ieval/fonbot/FonBotApplication.java index af5a70c..64eb602 100644 --- a/src/ro/ieval/fonbot/FonBotApplication.java +++ b/src/ro/ieval/fonbot/FonBotApplication.java @@ -48,8 +48,6 @@ public final class FonBotApplication extends Application { final TelephonyManager tman=(TelephonyManager) getSystemService(TELEPHONY_SERVICE); tman.listen(new FonBotPhoneStateListener(this), PhoneStateListener.LISTEN_CALL_STATE); - Utils.pollServer(this); - startService(new Intent(this, FonBotMainService.class)); // Thread.setDefaultUncaughtExceptionHandler(new RemoteCrashdumpHandler(this)); diff --git a/src/ro/ieval/fonbot/FonBotMainService.java b/src/ro/ieval/fonbot/FonBotMainService.java index dba5389..e0bc6c0 100644 --- a/src/ro/ieval/fonbot/FonBotMainService.java +++ b/src/ro/ieval/fonbot/FonBotMainService.java @@ -17,10 +17,12 @@ import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.content.IntentFilter; +import android.net.ConnectivityManager; import android.os.IBinder; import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat; import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; /* * Copyright © 2013 Marius Gavrilescu @@ -62,6 +64,27 @@ public final class FonBotMainService extends Service { } } + /** + * Runnable that continously long polls the server for commands + * + * @author Marius Gavrilescu + */ + private final class LongPollRunnable implements Runnable{ + public void run(){ + final ConnectivityManager man=(ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); + final HttpCallExecutableRunnable runnable=new HttpCallExecutableRunnable("/get", null, FonBotMainService.this, new PollResultCallback(FonBotMainService.this), false); + + Log.d("LongPollRunnable", "Long polling started"); + while(man.getActiveNetworkInfo() != null && man.getActiveNetworkInfo().isConnected()) + try { + runnable.run(); + } catch (final Exception ex){ + ex.printStackTrace(); + } + Log.d("LongPollRunnable", "Long polling stopped"); + } + } + /** * Broadcast action: add an ongoing event */ @@ -90,7 +113,6 @@ public final class FonBotMainService extends Service { private static final IntentFilter DYNAMIC_BROADCAST_FILTER=new IntentFilter(); static{ - DYNAMIC_BROADCAST_FILTER.addAction(Intent.ACTION_SCREEN_ON); DYNAMIC_BROADCAST_FILTER.addAction(Intent.ACTION_BATTERY_CHANGED); DYNAMIC_BROADCAST_FILTER.addAction(Intent.ACTION_HEADSET_PLUG); } @@ -107,6 +129,9 @@ public final class FonBotMainService extends Service { /** true if running in foreground, false otherwise */ private boolean isForeground = false; + /** Thread that runs a {@link LongPollRunnable} */ + private Thread longPollThread; + /** * Get the set of ongoing events. * @@ -147,8 +172,10 @@ public final class FonBotMainService extends Service { updateNotification=true; } - if(intent!=null && intent.getAction()==ACTION_TRIGGER_POLL) - Utils.pollServer(this); + if(longPollThread == null || !longPollThread.isAlive()){ + longPollThread = new Thread(new LongPollRunnable()); + longPollThread.start(); + } final boolean runForeground=PreferenceManager.getDefaultSharedPreferences(this).getBoolean("foreground", false); if(!runForeground) diff --git a/src/ro/ieval/fonbot/GCMIntentService.java b/src/ro/ieval/fonbot/GCMIntentService.java deleted file mode 100644 index 84a3c79..0000000 --- a/src/ro/ieval/fonbot/GCMIntentService.java +++ /dev/null @@ -1,63 +0,0 @@ -package ro.ieval.fonbot; - -import org.eclipse.jdt.annotation.Nullable; - -import android.content.Context; -import android.content.Intent; -import com.google.android.gcm.GCMBaseIntentService; - -/* - * Copyright © 2013 Marius Gavrilescu - * - * This file is part of FonBot. - * - * FonBot is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * FonBot is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with FonBot. If not, see . - */ - -/** - * Service that responds to GCM events - * - * @author Marius Gavrilescu - */ -public class GCMIntentService extends GCMBaseIntentService { - /** - * Constructs a GCMIntentService with the iEval sender id - */ - public GCMIntentService() { - super(FonBotApplication.GCM_SENDER_ID); - } - - @Override - protected void onError(@Nullable final Context context, @Nullable final String errID) { - //TODO error handling here - } - - @Override - protected void onMessage(@Nullable final Context context, @Nullable final Intent intent) { - if(context==null) - return; - - Utils.pollServer(context); - } - - @Override - protected void onRegistered(@Nullable final Context context, @Nullable final String regID) { - //do nothing - } - - @Override - protected void onUnregistered(@Nullable final Context context, @Nullable final String regID) { - //do nothing - } -} diff --git a/src/ro/ieval/fonbot/Heavy.java b/src/ro/ieval/fonbot/Heavy.java index f1afb0b..4e064a7 100644 --- a/src/ro/ieval/fonbot/Heavy.java +++ b/src/ro/ieval/fonbot/Heavy.java @@ -737,9 +737,6 @@ final class Heavy { case LOCATION: nolocation(context, toNonNull(Address.BLACKHOLE)); break; - case POLL: - poll(context, toNonNull(Address.BLACKHOLE), 0); - break; case RING: ring(context, toNonNull(Address.BLACKHOLE), false); break; @@ -1548,30 +1545,7 @@ final class Heavy { */ public static void poll(final Context context, final Address replyTo) { Utils.sendMessage(context, replyTo, polling_server); - Utils.pollServer(context); - } - - /** - * Change the server poll interval. - * - * @param context Context instance - * @param replyTo reply Address - * @param ms server poll interval in milliseconds. If 0, server poll is disabled - */ - public static void poll(final Context context, final Address replyTo, final long ms){ - final AlarmManager man=(AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - final Intent pollAlarm=new Intent(context, FonBotMainService.class); - pollAlarm.setAction(FonBotMainService.ACTION_TRIGGER_POLL); - final PendingIntent intent=PendingIntent.getService(context, 0, pollAlarm, 0); - if(ms==0){ - Utils.unregisterOngoing(context, toNonNull(OngoingEvent.POLL)); - man.cancel(intent); - Utils.sendMessage(context, replyTo, polling_stopped); - } else { - Utils.registerOngoing(context, toNonNull(OngoingEvent.POLL)); - man.setRepeating(AlarmManager.RTC_WAKEUP, 0, ms, intent); - Utils.sendMessage(context, replyTo, polling_every_milliseconds, Long.valueOf(ms)); - } + context.startService(new Intent(context, FonBotMainService.class)); } /** diff --git a/src/ro/ieval/fonbot/HttpCallExecutableRunnable.java b/src/ro/ieval/fonbot/HttpCallExecutableRunnable.java index 1f96b13..e66a7f8 100644 --- a/src/ro/ieval/fonbot/HttpCallExecutableRunnable.java +++ b/src/ro/ieval/fonbot/HttpCallExecutableRunnable.java @@ -8,7 +8,7 @@ import java.io.OutputStream; import java.net.URL; import java.util.Collection; -import javax.net.ssl.HttpsURLConnection; +import java.net.HttpURLConnection; import org.eclipse.jdt.annotation.Nullable; import android.content.Context; @@ -38,7 +38,7 @@ import com.google.android.gcm.GCMRegistrar; */ /** - * ExecutableRunnable that makes a HTTPS call to the server and hands the response to a callback + * ExecutableRunnable that makes a HTTP(S) call to the server and hands the response to a callback * * @author Marius Gavrilescu */ @@ -134,7 +134,7 @@ public final class HttpCallExecutableRunnable extends ExecutableRunnable{ public void run() { try { final URL url=Utils.getServerURL(toNonNull(context),toNonNull(path)); - final HttpsURLConnection conn=(HttpsURLConnection) url.openConnection(); + final HttpURLConnection conn=(HttpURLConnection) url.openConnection(); if(data!=null){ conn.setDoOutput(true); conn.setFixedLengthStreamingMode(data.length); diff --git a/src/ro/ieval/fonbot/ProtectedBroadcastReceiver.java b/src/ro/ieval/fonbot/ProtectedBroadcastReceiver.java index 17f99f0..7ad1537 100644 --- a/src/ro/ieval/fonbot/ProtectedBroadcastReceiver.java +++ b/src/ro/ieval/fonbot/ProtectedBroadcastReceiver.java @@ -10,6 +10,7 @@ import ro.ieval.fonbot.Utils.MessageType; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.net.ConnectivityManager; /* * Copyright © 2013 Marius Gavrilescu @@ -54,7 +55,8 @@ public final class ProtectedBroadcastReceiver extends BroadcastReceiver { } else if(action.equals(Intent.ACTION_BOOT_COMPLETED)){ Utils.sendMessage(context, toNonNull(MessageType.BOOT), toNonNull(context.getString(device_booted))); - } + } else if(action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) + context.startService(new Intent(context, FonBotMainService.class)); } } diff --git a/src/ro/ieval/fonbot/Utils.java b/src/ro/ieval/fonbot/Utils.java index 79ff2e1..8548404 100644 --- a/src/ro/ieval/fonbot/Utils.java +++ b/src/ro/ieval/fonbot/Utils.java @@ -184,9 +184,7 @@ public final class Utils { /** Location tracking is active. Registered by {@link Command#LOCATION}, unregistered by {@link Command#NOLOCATION} */ LOCATION(location_tracking_is_active), /** The phone is ringing. Registered/unregistered by {@link Command#RING} */ - RING(ringing), - /** The polling alarm is on. Registered/unregistered by {@link Command#POLL} */ - POLL(the_polling_service_is_running); + RING(ringing); /** String resource: the event description */ public final int resource; @@ -409,29 +407,10 @@ public final class Utils { public static URL getServerURL(final Context context, final String path) throws MalformedURLException{ final String hostname=PreferenceManager.getDefaultSharedPreferences(context).getString("hostname", "fonbot.ieval.ro"); final int port=Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(context).getString("port", "443")); - final URL url=new URL("https", hostname, port, path); + final URL url=new URL("http", hostname, port, path); return url; } - /** - * Poll the server for pending commands. This function must not be called from BroadcastReceivers - * - * @param context Context instance - */ - public static void pollServer(final Context context){ - new HttpCallExecutableRunnable("/get", null, context, new PollResultCallback(context), false).execute(); - } - - /** - * Poll the server for pending commands from {@link FonBotMainService}. This function should be used from BroadcastReceviers instead of {@link #pollServer} - * - * @param context Context instance - */ - public static void safePollServer(final Context context){ - final Intent intent=new Intent(context, FonBotMainService.class); - intent.setAction(FonBotMainService.ACTION_TRIGGER_POLL); - context.startService(intent); - } /** * Executes a given command * @@ -907,25 +886,7 @@ public final class Utils { break; case POLL: - if(args.length>1){ - Heavy.help(context, replyTo, toNonNull(Command.POLL)); - break; - } - - if(args.length==0){ - Heavy.poll(context, replyTo); - break; - } - - final long interval; - try{ - interval=Long.parseLong(args[0]); - } catch(NumberFormatException e){ - sendMessage(context, replyTo, cannot_parse_interval); - break; - } - - Heavy.poll(context, replyTo, interval); + Heavy.poll(context, replyTo); break; case HANGUP: -- 2.30.2