android:minSdkVersion="8"
android:targetSdkVersion="18" />
- <permission
- android:name="ro.ieval.fonbot.permission.C2D_MESSAGE"
- android:label="Receive GCM messages"
- android:protectionLevel="signature" >
- </permission>
-
- <uses-permission android:name="ro.ieval.fonbot.permission.C2D_MESSAGE" />
- <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
android:theme="@android:style/Theme.Dialog" >
</activity>
- <service android:name=".GCMIntentService" >
- </service>
<service android:name=".FonBotMainService" >
</service>
- <receiver
- android:name="com.google.android.gcm.GCMBroadcastReceiver"
- android:permission="com.google.android.c2dm.permission.SEND" >
- <intent-filter>
- <action android:name="com.google.android.c2dm.intent.RECEIVE" />
- <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
-
- <category android:name="ro.ieval.fonbot" />
- </intent-filter>
- </receiver>
<receiver
android:name=".SmsReceiver"
android:permission="android.permission.BROADCAST_SMS" >
<action android:name="android.intent.action.BATTERY_LOW" />
<action android:name="android.intent.action.BATTERY_OKAY" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
+ <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
</application>
+++ /dev/null
-/*
- * 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.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * When using this constructor, the subclass <strong>must</strong>
- * 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.
- *
- * <p>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.
- *
- * <p>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.
- * <p>
- * 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);
- }
- }
-
-}
+++ /dev/null
-/*
- * 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.
- * <p>
- * 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;
- }
-}
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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.
- * <p>
- * <strong>Note:</strong> 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.
- *
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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.
- *
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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.
- * <p>
- * 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();
- }
-}
<EditTextPreference android:title="Username" android:key="username" android:dialogTitle="Username" android:dialogMessage="Enter your username"/>
<EditTextPreference android:dialogTitle="Password" android:dialogMessage="Enter your password" android:inputType="textPassword" android:title="Password" android:key="password"/>
<EditTextPreference android:dialogTitle="SMS Password" android:dialogMessage="Enter the password required to send SMS commands. Leave empty to disable SMS commands" android:title="SMS Password" android:key="smspassword"/>
- <CheckBoxPreference android:title="Device administration" android:summaryOn="Device administration is enabled." android:key="admin" android:summaryOff="Device administration is disabled. Some commands require device administration."/><CheckBoxPreference android:key="foreground" android:title="Run service in foreground" android:summaryOn="The FonBot service is kept in foreground. " android:summaryOff="The FonBot service is not kept in foreground."/><CheckBoxPreference android:key="poll_on_screen_on" android:summaryOff="The server is not polled when the screen is turned on" android:summaryOn="The server is polled when the screen is turned on" android:title="Poll server on screen on"/>
+ <CheckBoxPreference android:title="Device administration" android:summaryOn="Device administration is enabled." android:key="admin" android:summaryOff="Device administration is disabled. Some commands require device administration."/>
+ <CheckBoxPreference android:key="foreground" android:title="Run service in foreground" android:summaryOn="The FonBot service is kept in foreground. " android:summaryOff="The FonBot service is not kept in foreground."/>
<CheckBoxPreference android:key="system" android:summaryOff="FonBot is not a system app. Changing this requires root and busybox" android:title="System App" android:summaryOn="FonBot is a system app. Changing this requires root and busybox"/>
<EditTextPreference android:dialogTitle="Server hostname" android:title="Server hostname" android:dialogMessage="Server hostname. Do not change unless you know what you are doing!" android:key="hostname" android:defaultValue="fonbot.ieval.ro" android:summary="fonbot.ieval.ro"/>
<EditTextPreference android:dialogMessage="Server port. Do not change unless you know what you are doing!" android:dialogTitle="Server port" android:title="Server port" android:summary="443" android:defaultValue="443" android:key="port"/>
-</PreferenceScreen>
\ No newline at end of file
+</PreferenceScreen>
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);
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));
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
}
}
+ /**
+ * Runnable that continously long polls the server for commands
+ *
+ * @author Marius Gavrilescu <marius@ieval.ro>
+ */
+ 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
*/
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);
}
/** 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.
*
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)
+++ /dev/null
-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 <http://www.gnu.org/licenses/>.
- */
-
-/**
- * Service that responds to GCM events
- *
- * @author Marius Gavrilescu <marius@ieval.ro>
- */
-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
- }
-}
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;
*/
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));
}
/**
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;
*/
/**
- * 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 <marius@ieval.ro>
*/
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);
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.net.ConnectivityManager;
/*
* Copyright © 2013 Marius Gavrilescu
} 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));
}
}
/** 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;
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
*
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: