diff --git a/build.gradle b/build.gradle
index 49dfafa1..c6c27343 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,7 +15,16 @@
*/
// Top-level build file where you can add configuration options common to all sub-projects/modules.
+String getMyVersionName() {
+ def stdout = new ByteArrayOutputStream()
+ if (rootProject.file("gradlew").exists())
+ exec { commandLine 'git', 'describe', '--tags', '--always', '--dirty'; standardOutput = stdout }
+ else // automatic build system, don't tag dirty
+ exec { commandLine 'git', 'describe', '--tags', '--always'; standardOutput = stdout }
+ return stdout.toString().trim().substring(1)
+}
+
subprojects {
group = 'org.microg'
- version = '1.0-SNAPSHOT'
+ version = getMyVersionName()
}
diff --git a/extern/GmsApi b/extern/GmsApi
index f0ec7e60..6aa11065 160000
--- a/extern/GmsApi
+++ b/extern/GmsApi
@@ -1 +1 @@
-Subproject commit f0ec7e606f3e6e3a219eb87266c4dae1828b5083
+Subproject commit 6aa110657beec0b3e6c26d1030943e0b97683335
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 426c09a0..90babf29 100755
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -19,4 +19,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip
diff --git a/play-services-base/build.gradle b/play-services-base/build.gradle
index fe05a441..52ff3bd9 100644
--- a/play-services-base/build.gradle
+++ b/play-services-base/build.gradle
@@ -19,23 +19,26 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.3.0'
- classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
+ classpath 'com.android.tools.build:gradle:2.0.0'
}
}
apply plugin: 'com.android.library'
-apply plugin: 'com.github.dcendents.android-maven'
android {
compileSdkVersion 23
- buildToolsVersion "23.0.1"
+ buildToolsVersion "23.0.2"
+
+ defaultConfig {
+ versionName getMyVersionName()
+ }
+
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
}
}
dependencies {
- compile 'com.android.support:support-v4:23.0.1'
+ compile 'com.android.support:support-v4:23.3.0'
compile project(':play-services-api')
}
diff --git a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java
index c3e44885..7140ca96 100644
--- a/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java
+++ b/play-services-base/src/main/java/com/google/android/gms/common/api/GoogleApiClient.java
@@ -26,7 +26,7 @@ import android.view.View;
import com.google.android.gms.common.ConnectionResult;
-import org.microg.gms.common.Constants;
+import org.microg.gms.auth.AuthConstants;
import org.microg.gms.common.PublicApi;
import org.microg.gms.common.api.GoogleApiClientImpl;
@@ -422,7 +422,7 @@ public interface GoogleApiClient {
* Specify that the default account should be used when connecting to services.
*/
public Builder useDefaultAccount() {
- this.accountName = Constants.DEFAULT_ACCOUNT;
+ this.accountName = AuthConstants.DEFAULT_ACCOUNT;
return this;
}
}
diff --git a/play-services-cast/build.gradle b/play-services-cast/build.gradle
index 2d7747bc..f900225d 100644
--- a/play-services-cast/build.gradle
+++ b/play-services-cast/build.gradle
@@ -19,17 +19,20 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.3.0'
- classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
+ classpath 'com.android.tools.build:gradle:2.0.0'
}
}
apply plugin: 'com.android.library'
-apply plugin: 'com.github.dcendents.android-maven'
android {
compileSdkVersion 23
- buildToolsVersion "23.0.1"
+ buildToolsVersion "23.0.2"
+
+ defaultConfig {
+ versionName getMyVersionName()
+ }
+
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
}
diff --git a/play-services-cast/src/main/AndroidManifest.xml b/play-services-cast/src/main/AndroidManifest.xml
index bb22a334..2031102a 100644
--- a/play-services-cast/src/main/AndroidManifest.xml
+++ b/play-services-cast/src/main/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
+ *
+ */
+@PublicApi
+public abstract class GcmListenerService extends Service {
+ private static final String TAG = "GcmListenerService";
+
+ private final Object lock = new Object();
+ private int startId;
+ private int counter = 0;
+
+ public final IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ /**
+ * Called when GCM server deletes pending messages due to exceeded
+ * storage limits, for example, when the device cannot be reached
+ * for an extended period of time.
+ *
+ * It is recommended to retrieve any missing messages directly from the
+ * app server.
+ */
+ public void onDeletedMessages() {
+ // To be overwritten
+ }
+
+ /**
+ * Called when a message is received.
+ *
+ * @param from describes message sender.
+ * @param data message data as String key/value pairs.
+ */
+ public void onMessageReceived(String from, Bundle data) {
+ // To be overwritten
+ }
+
+ /**
+ * Called when an upstream message has been successfully sent to the
+ * GCM connection server.
+ *
+ * @param msgId of the upstream message sent using
+ * {@link com.google.android.gms.gcm.GoogleCloudMessaging#send(java.lang.String, java.lang.String, android.os.Bundle)}.
+ */
+ public void onMessageSent(String msgId) {
+ // To be overwritten
+ }
+
+ /**
+ * Called when there was an error sending an upstream message.
+ *
+ * @param msgId of the upstream message sent using
+ * {@link com.google.android.gms.gcm.GoogleCloudMessaging#send(java.lang.String, java.lang.String, android.os.Bundle)}.
+ * @param error description of the error.
+ */
+ public void onSendError(String msgId, String error) {
+ // To be overwritten
+ }
+
+ public final int onStartCommand(final Intent intent, int flags, int startId) {
+ synchronized (lock) {
+ this.startId = startId;
+ this.counter++;
+ }
+
+ if (intent != null) {
+ if (ACTION_NOTIFICATION_OPEN.equals(intent.getAction())) {
+ handlePendingNotification(intent);
+ finishCounter();
+ GcmReceiver.completeWakefulIntent(intent);
+ } else if (ACTION_C2DM_RECEIVE.equals(intent.getAction())) {
+ AsyncTaskCompat.executeParallel(new AsyncTask
+ *
+ * An execution contains the tag identifier which your client app provides. This identifies
+ * one "task", or piece of work, that you mean to perform. Consider the tag to be the key to which
+ * your task logic is paired.
+ *
+ * // Schedule a task to occur between five and fifteen seconds from now:
+ * OneoffTask myTask = new OneoffTask.Builder()
+ * .setService(MyGcmTaskService.class)
+ * .setExecutionWindow(
+ * 5 * DateUtil.MINUTE_IN_SECONDS, 15 * DateUtil.MINUTE_IN_SECONDS)
+ * .setTag("test-upload")
+ * .build();
+ * GcmNetworkManager.get(this).schedule(myTask);
+ * ...
+ * // Implement service logic to be notified when the task elapses:
+ * MyUploadService extends GcmTaskService {
+ *
+ * @Override public int onRunTask(TaskParams params) {
+ * // Do some upload work.
+ * return GcmNetworkManager.RESULT_SUCCESS;
+ * }
+ * }
+ *
+ * To help in debugging your tasks, run
+ * adb shell dumpsys activity service GcmService --endpoints [...]
+ * If you want to execute your task immediately (for debugging) you can execute tasks from the
+ * command line via:
+ * adb shell am broadcast -a "com.google.android.gms.gcm.ACTION_TRIGGER_TASK" \
+ * -e component
+ * Where COMPONENT_NAME: The full
+ * {@link ComponentName#flattenToString()} returned for your implementation of
+ * {@link com.google.android.gms.gcm.GcmTaskService}.
+ * TAG: the tag you want to have land in
+ * {@link com.google.android.gms.gcm.GcmTaskService#onRunTask(com.google.android.gms.gcm.TaskParams)}
+ * Example usage for the gradle target GcmTestProxy service:
+ * adb shell am broadcast -a "com.google.android.gms.gcm.ACTION_TRIGGER_TASK" \
+ * -e component "com.google.android.gms.gcm.test.proxy/.internal.nstest.TestNetworkTaskService" \
+ * -e tag "upload"
+ * This is only available if the device is a test-keys build. This will replace any
+ * previously scheduled task with the same tag! This will have especially awkward effects
+ * if your original task was a periodic, because the debug task is scheduled as a one-off.
+ */
+public class GcmNetworkManager {
+ /**
+ * Indicates a task has failed, but not to reschedule.
+ */
+ public static final int RESULT_FAILURE = 2;
+
+ /**
+ * Indicates a task has failed to execute, and must be retried with back-off.
+ */
+ public static final int RESULT_RESCHEDULE = 1;
+
+ /**
+ * Indicates a task has successfully been executed, and can be removed from the queue.
+ */
+ public static final int RESULT_SUCCESS = 0;
+
+ private static GcmNetworkManager INSTANCE;
+
+ private final Context context;
+
+ private GcmNetworkManager(Context context) {
+ this.context = context;
+ }
+
+ /**
+ * Cancels all tasks previously scheduled against the provided GcmTaskService. Note that a
+ * cancel will have no effect on an in-flight task.
+ *
+ * @param gcmTaskService The endpoint for which you want to cancel all outstanding tasks.
+ */
+ public void cancelAllTasks(Class extends GcmTaskService> gcmTaskService) {
+ validateService(gcmTaskService.getName());
+ Intent scheduleIntent = createScheduleIntent();
+ if (scheduleIntent != null) {
+ scheduleIntent.putExtra(EXTRA_SCHEDULER_ACTION, SCHEDULER_ACTION_CANCEL_ALL);
+ scheduleIntent.putExtra(EXTRA_COMPONENT, new ComponentName(context, gcmTaskService));
+ context.sendBroadcast(scheduleIntent);
+ }
+ }
+
+ /**
+ * Cancel a task, specified by tag. Note that a cancel will have no effect on an in-flight
+ * task.
+ *
+ * @param tag The tag to uniquely identify this task on this endpoint.
+ * @param gcmTaskService The endpoint for which you want to cancel all outstanding tasks.
+ */
+ public void cancelTask(String tag, Class extends GcmTaskService> gcmTaskService) {
+ if (TextUtils.isEmpty(tag) || tag.length() < 100) throw new IllegalArgumentException("tag invalid");
+ validateService(gcmTaskService.getName());
+ Intent scheduleIntent = createScheduleIntent();
+ if (scheduleIntent != null) {
+ scheduleIntent.putExtra(EXTRA_SCHEDULER_ACTION, SCHEDULER_ACTION_CANCEL);
+ scheduleIntent.putExtra(EXTRA_TAG, tag);
+ scheduleIntent.putExtra(EXTRA_COMPONENT, new ComponentName(context, gcmTaskService));
+ context.sendBroadcast(scheduleIntent);
+ }
+ }
+
+ /**
+ * Use this function to access the GcmNetworkManager API.
+ *
+ * @param context Context of the calling app.
+ * @return GcmNetworkManager object.
+ */
+ public static GcmNetworkManager getInstance(Context context) {
+ synchronized (GcmNetworkManager.class) {
+ if (INSTANCE == null) {
+ INSTANCE = new GcmNetworkManager(context);
+ }
+ return INSTANCE;
+ }
+ }
+
+ /**
+ * Entry point to schedule a task with the network manager.
+ *
+ * If GooglePlayServices is unavailable (upgrading, missing, etc). This call will fail silently.
+ * You should wrap it in a call to {@link com.google.android.gms.common.GooglePlayServicesUtil#isGooglePlayServicesAvailable(android.content.Context)}
+ * String topic = "/topics/myTopic"; + * String registrationToken = InstanceID.getInstance(context) + * .getToken(SENDER_ID, GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); + * GcmPubSub.getInstance(context).subscribe(registrationToken, topic, null); + * // Messages published to the topic will be received as regular GCM messages + * // with 'from' set to "/topics/myTopic"+ * To publish to a topic, see + * GCM server documentation. + */ +public class GcmPubSub { + + private static final Pattern topicPattern = Pattern.compile("/topics/[a-zA-Z0-9-_.~%]{1,900}"); + private static GcmPubSub INSTANCE; + + private final InstanceID instanceId; + + public GcmPubSub(Context context) { + this.instanceId = InstanceID.getInstance(context); + } + + /** + * Returns an instance of GCM PubSub. + * + * @return GcmPubSub instance + */ + public static synchronized GcmPubSub getInstance(Context context) { + if (INSTANCE == null) { + INSTANCE = new GcmPubSub(context); + } + return INSTANCE; + } + + /** + * Subscribes an app instance to a topic, enabling it to receive messages + * sent to that topic. + * + * The topic sender must be authorized to send messages to the + * app instance. To authorize it, call {@link com.google.android.gms.iid.InstanceID#getToken(java.lang.String, java.lang.String)} + * with the sender ID and {@link com.google.android.gms.gcm.GoogleCloudMessaging#INSTANCE_ID_SCOPE} + * + * Do not call this function on the main thread. + * + * @param registrationToken {@link com.google.android.gms.iid.InstanceID} token that authorizes topic + * sender to send messages to the app instance. + * @param topic developer defined topic name. + * Must match the following regular expression: + * "/topics/[a-zA-Z0-9-_.~%]{1,900}". + * @param extras (optional) additional information. + * @throws IOException if the request fails. + */ + public void subscribe(String registrationToken, String topic, Bundle extras) throws IOException { + if (TextUtils.isEmpty(registrationToken)) + throw new IllegalArgumentException("No registration token!"); + if (TextUtils.isEmpty(topic) || !topicPattern.matcher(topic).matches()) + throw new IllegalArgumentException("Invalid topic!"); + + extras.putString(EXTRA_TOPIC, topic); + instanceId.getToken(registrationToken, topic, extras); + } + + /** + * Unsubscribes an app instance from a topic, stopping it from receiving + * any further messages sent to that topic. + * + * Do not call this function on the main thread. + * + * @param registrationToken {@link com.google.android.gms.iid.InstanceID} token + * for the same sender and scope that was previously + * used for subscribing to the topic. + * @param topic from which to stop receiving messages. + * @throws IOException if the request fails. + */ + public void unsubscribe(String registrationToken, String topic) throws IOException { + if (TextUtils.isEmpty(topic) || !topicPattern.matcher(topic).matches()) + throw new IllegalArgumentException("Invalid topic!"); + + Bundle extras = new Bundle(); + extras.putString(EXTRA_TOPIC, topic); + instanceId.deleteToken(registrationToken, topic, extras); + } + +} \ No newline at end of file diff --git a/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmReceiver.java b/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmReceiver.java new file mode 100644 index 00000000..0d79190f --- /dev/null +++ b/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmReceiver.java @@ -0,0 +1,49 @@ +/* + * Copyright 2013-2016 microG Project Team + * + * 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.gms.gcm; + +import android.content.Context; +import android.content.Intent; +import android.support.v4.content.WakefulBroadcastReceiver; + +/** + *
WakefulBroadcastReceiver
that receives GCM messages and delivers them to an
+ * application-specific {@link com.google.android.gms.gcm.GcmListenerService} subclass.
+ *
+ * This receiver should be declared in your application's manifest file as follows:
+ *
+ * + *+ * The+ * + * + *+ * + * + *
com.google.android.c2dm.permission.SEND
permission is held by Google Play
+ * services. This prevents other apps from invoking the broadcast receiver.
+ */
+public class GcmReceiver extends WakefulBroadcastReceiver {
+
+ public void onReceive(Context context, Intent intent) {
+ throw new UnsupportedOperationException();
+ }
+
+}
\ No newline at end of file
diff --git a/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmTaskService.java b/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmTaskService.java
new file mode 100644
index 00000000..d431b0df
--- /dev/null
+++ b/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmTaskService.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2013-2016 microG Project Team
+ *
+ * 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.gms.gcm;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.os.PowerManager;
+import android.util.Log;
+
+import org.microg.gms.common.PublicApi;
+import org.microg.gms.gcm.GcmConstants;
+
+/**
+ * Implemented by the client application to provide an endpoint for the {@link com.google.android.gms.gcm.GcmNetworkManager}
+ * to call back to when a task is ready to be executed.
+ *
+ * Clients must add this service to their manifest and implement
+ * {@link com.google.android.gms.gcm.GcmTaskService#onRunTask(com.google.android.gms.gcm.TaskParams)}.
+ * This service must provide an {@link IntentFilter} on the action
+ * {@link com.google.android.gms.gcm.GcmTaskService#SERVICE_ACTION_EXECUTE_TASK}. Here's an example:
+ * + *+ * The return value of onRunTask(TaskParams) will determine what the manager does with subsequent + * executions of this task. Specifically you can return {@link com.google.android.gms.gcm.GcmNetworkManager#RESULT_RESCHEDULE} + * to have this task be re-executed again shortly subject to exponential back-off. Returning + * {@link com.google.android.gms.gcm.GcmNetworkManager#RESULT_FAILURE} for a periodic task will only affect the executing + * instance of the task, and future tasks will be executed as normal. + * + * Once a task is running it will not be cancelled, however a newly scheduled task with the same + * tag will not be executed until the active task has completed. This newly scheduled task will + * replace the previous task, regardless of whether the previous task returned + * {@link com.google.android.gms.gcm.GcmNetworkManager#RESULT_RESCHEDULE}. + * + * Bear in mind that your service may receive multiple calls from the scheduler at once + * (specifically if you've made multiple schedule requests that overlap). If this is the case, your + * implementation of {@link com.google.android.gms.gcm.GcmTaskService#onRunTask(com.google.android.gms.gcm.TaskParams)} must be thread-safe. + * + * The scheduler will hold a {@link PowerManager.WakeLock} for your service, however + * after three minutes of execution if your task has not returned it will be considered to + * have timed out, and the wakelock will be released. Rescheduling your task at this point + * will have no effect. + * If you suspect your task will run longer than this you should start your own service + * explicitly or use some other mechanism; this API is intended for relatively quick network + * operations. + * + * Your task will run at priority Process.THREAD_PRIORITY_BACKGROUND. If this + * is not appropriate, you should start your own service with suitably + * conditioned threads. + */ +@PublicApi +public abstract class GcmTaskService extends Service { + private static final String TAG = "GcmTaskService"; + + /** + * Action broadcast by the GcmNetworkManager to the requesting package when + * a scheduled task is ready for execution. + */ + public static final String SERVICE_ACTION_EXECUTE_TASK = GcmConstants.ACTION_TASK_READY; + + /** + * Action that a {@link com.google.android.gms.gcm.GcmTaskService} is started with when the service needs to initialize + * its tasks. + */ + public static final String SERVICE_ACTION_INITIALIZE = GcmConstants.ACTION_TASK_INITIALZE; + + /** + * You must protect your service with this permission to avoid being bound to by an + * application other than Google Play Services. + */ + public static final String SERVICE_PERMISSION = GcmConstants.PERMISSION_NETWORK_TASK; + + public IBinder onBind(Intent intent) { + return null; + } + + /** + * When your package is removed or updated, all of its network tasks are cleared by the + * GcmNetworkManager. You can override this method to reschedule them in the case of an + * updated package. This is not called when your application is first installed. + * + * This is called on your application's main thread. + */ + public void onInitializeTasks() { + // To be overwritten + } + + /** + * Override this function to provide the logic for your task execution. + * + * @param params Parameters provided at schedule time with + * {@link com.google.android.gms.gcm.OneoffTask.Builder#setTag(java.lang.String)} + * @return One of {@link com.google.android.gms.gcm.GcmNetworkManager#RESULT_SUCCESS}, + * {@link com.google.android.gms.gcm.GcmNetworkManager#RESULT_RESCHEDULE}, or + * {@link com.google.android.gms.gcm.GcmNetworkManager#RESULT_FAILURE}. + */ + public abstract int onRunTask(TaskParams params); + + /** + * Receives the command to begin doing work, for which it spawns another thread. + */ + public int onStartCommand(Intent intent, int flags, int startId) { + intent.setExtrasClassLoader(PendingCallback.class.getClassLoader()); + if (SERVICE_ACTION_EXECUTE_TASK.equals(intent.getAction())) { + String tag = intent.getStringExtra("tag"); + Parcelable callback = intent.getParcelableExtra("callback"); + Bundle extras = intent.getBundleExtra("extras"); + if (callback == null || !(callback instanceof PendingCallback)) { + Log.w(TAG, tag + ": Invalid callback!"); + return START_NOT_STICKY; + } + + // TODO ensure single instance + + // TODO run task in new thread + } else if (SERVICE_ACTION_INITIALIZE.equals(intent.getAction())) { + this.onInitializeTasks(); + + // TODO ensure single instance + } + + return START_NOT_STICKY; + } + +} diff --git a/play-services-gcm/src/main/java/com/google/android/gms/gcm/GoogleCloudMessaging.java b/play-services-gcm/src/main/java/com/google/android/gms/gcm/GoogleCloudMessaging.java new file mode 100644 index 00000000..4cd0bdf4 --- /dev/null +++ b/play-services-gcm/src/main/java/com/google/android/gms/gcm/GoogleCloudMessaging.java @@ -0,0 +1,301 @@ +/* + * Copyright 2013-2016 microG Project Team + * + * 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.gms.gcm; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Looper; +import android.text.TextUtils; + +import com.google.android.gms.iid.InstanceID; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.gcm.GcmConstants; + +import java.io.IOException; + +import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; +import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_RECEIVE; +import static org.microg.gms.gcm.GcmConstants.ACTION_GCM_SEND; +import static org.microg.gms.gcm.GcmConstants.EXTRA_APP; +import static org.microg.gms.gcm.GcmConstants.EXTRA_DELAY; +import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSAGE_ID; +import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSAGE_TYPE; +import static org.microg.gms.gcm.GcmConstants.EXTRA_SENDER_LEGACY; +import static org.microg.gms.gcm.GcmConstants.EXTRA_SEND_TO; +import static org.microg.gms.gcm.GcmConstants.EXTRA_TTL; +import static org.microg.gms.gcm.GcmConstants.PERMISSION_GTALK; + +/** + * GoogleCloudMessaging (GCM) enables apps to communicate with their app servers + * using simple messages. + * + * To send or receive messages, the app must get a + * registrationToken from {@link com.google.android.gms.iid.InstanceID#getToken(java.lang.String, java.lang.String)}, which authorizes an + * app server to send messages to an app instance. Pass sender ID and + * {@link com.google.android.gms.gcm.GoogleCloudMessaging#INSTANCE_ID_SCOPE} as parameters to the method. + * A sender ID is a project number acquired from the API console, as described in + * Getting Started. + * + * In order to receive GCM messages, declare {@link com.google.android.gms.gcm.GcmReceiver} + * and an implementation of {@link com.google.android.gms.gcm.GcmListenerService} in the app manifest. + * {@link com.google.android.gms.gcm.GcmReceiver} will pass the incoming messages to the implementation + * of {@link com.google.android.gms.gcm.GcmListenerService}. To process messages, override base class + * methods to handle any events required by the application. + * + * Client apps can send upstream messages back to the app server using the XMPP-based + * Cloud Connection Server, + * For example: + * + * gcm.send(SENDER_ID + "@gcm.googleapis.com", id, data); + * See Implementing GCM Client on Android for more details. + */ +@PublicApi +public class GoogleCloudMessaging { + /** + * The GCM {@link com.google.android.gms.gcm.GoogleCloudMessaging#register(java.lang.String...)} and {@link com.google.android.gms.gcm.GoogleCloudMessaging#unregister()} methods are + * blocking. Blocking methods must not be called on the main thread. + */ + public static final String ERROR_MAIN_THREAD = "MAIN_THREAD"; + + /** + * 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 = GcmConstants.ERROR_SERVICE_NOT_AVAILABLE; + + /** + * Specifies scope used in obtaining GCM registrationToken when calling + * {@link com.google.android.gms.iid.InstanceID#getToken(java.lang.String, java.lang.String)} + */ + public static final String INSTANCE_ID_SCOPE = GcmConstants.INSTANCE_ID_SCOPE_GCM; + + /** + * Returned by {@link com.google.android.gms.gcm.GoogleCloudMessaging#getMessageType(android.content.Intent)} to indicate that the server deleted + * some pending messages because they exceeded the storage limits. The + * application should contact the server to retrieve the discarded messages. + * + * @deprecated Instead implement {@link com.google.android.gms.gcm.GcmListenerService#onDeletedMessages()} + */ + @Deprecated + public static final String MESSAGE_TYPE_DELETED = GcmConstants.MESSAGE_TYPE_DELETED_MESSAGE; + + /** + * Returned by {@link com.google.android.gms.gcm.GoogleCloudMessaging#getMessageType(android.content.Intent)} to indicate a regular message. + * + * @deprecated Instead implement {@link com.google.android.gms.gcm.GcmListenerService#onMessageReceived(java.lang.String, android.os.Bundle)} + */ + @Deprecated + public static final String MESSAGE_TYPE_MESSAGE = GcmConstants.MESSAGE_TYPE_GCM; + + /** + * Returned by {@link com.google.android.gms.gcm.GoogleCloudMessaging#getMessageType(android.content.Intent)} to indicate a send error. + * The intent includes the message ID of the message and an error code. + * + * @deprecated Instead implement {@link com.google.android.gms.gcm.GcmListenerService#onSendError(java.lang.String, java.lang.String)} + */ + @Deprecated + public static final String MESSAGE_TYPE_SEND_ERROR = GcmConstants.MESSAGE_TYPE_SEND_ERROR; + + /** + * Returned by {@link com.google.android.gms.gcm.GoogleCloudMessaging#getMessageType(android.content.Intent)} to indicate a sent message has been received by the GCM + * server. The intent includes the message ID of the message. + * + * @deprecated Instead implement {@link com.google.android.gms.gcm.GcmListenerService#onMessageSent(java.lang.String)} + */ + @Deprecated + public static final String MESSAGE_TYPE_SEND_EVENT = GcmConstants.MESSAGE_TYPE_SEND_EVENT; + + private static GoogleCloudMessaging INSTANCE; + /** + * Due to it's nature of being a monitored reference, Intents can be used to authenticate a package source. + */ + private PendingIntent selfAuthIntent; + private Context context; + + public GoogleCloudMessaging() { + throw new UnsupportedOperationException(); + } + + /** + * Must be called when your application is done using GCM, to release + * internal resources. + */ + public void close() { + throw new UnsupportedOperationException(); + } + + private PendingIntent getSelfAuthIntent() { + if (selfAuthIntent == null) { + Intent intent = new Intent(); + intent.setPackage("com.google.example.invalidpackage"); + selfAuthIntent = PendingIntent.getBroadcast(context, 0, intent, 0); + } + return selfAuthIntent; + } + + /** + * Return the singleton instance of GCM. + */ + public static GoogleCloudMessaging getInstance(Context context) { + if (INSTANCE == null) { + INSTANCE = new GoogleCloudMessaging(); + INSTANCE.context = context.getApplicationContext(); + } + return INSTANCE; + } + + /** + * Return the message type from an intent passed into a client app's broadcast receiver. There + * are two general categories of messages passed from the server: regular GCM messages, + * and special GCM status messages. + * + * The possible types are: + * {@link #MESSAGE_TYPE_MESSAGE}, {@link #MESSAGE_TYPE_DELETED}, {@link #MESSAGE_TYPE_SEND_EVENT} and {@link #MESSAGE_TYPE_SEND_ERROR} + * + * You can use this method to filter 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. + * + * @return The message type or null if the intent is not a GCM intent + */ + public String getMessageType(Intent intent) throws IOException { + if (intent == null || !ACTION_C2DM_RECEIVE.equals(intent.getAction())) return null; + if (!intent.hasExtra(EXTRA_MESSAGE_TYPE)) return MESSAGE_TYPE_MESSAGE; + return intent.getStringExtra(EXTRA_MESSAGE_TYPE); + } + + /** + * Register the application for GCM and return the registration ID. You must call this once, + * when your application is installed, and send the returned registration ID to the server. + * + * Repeated calls to this method will return the original registration ID. + * + * If you want to modify the list of senders, you must call+ * + *+ * + *+ *
unregister()
first.
+ *
+ * Most applications use a single sender ID. You may use multiple senders if different
+ * servers may send messages to the app or for testing.
+ *
+ * @param senderIds list of project numbers or Google accounts identifying who is allowed to
+ * send messages to this application.
+ * @return registration id
+ * @throws IOException
+ * @deprecated Instead, for GCM registration, use
+ * {@link com.google.android.gms.iid.InstanceID#getToken(java.lang.String, java.lang.String)}.
+ * Set authorizedEntity to a sender ID and scope to {@link com.google.android.gms.gcm.GoogleCloudMessaging#INSTANCE_ID_SCOPE}.
+ */
+ @Deprecated
+ public String register(String... senderIds) throws IOException {
+ if (Looper.getMainLooper() == Looper.myLooper()) throw new IOException(ERROR_MAIN_THREAD);
+ if (senderIds == null || senderIds.length == 0) throw new IllegalArgumentException("No sender ids");
+ StringBuilder sb = new StringBuilder(senderIds[0]);
+ for (int i = 1; i < senderIds.length; i++) {
+ sb.append(',').append(senderIds[i]);
+ }
+ // This seems to be a legacy variant
+ // TODO: Implement latest version
+ Bundle extras = new Bundle();
+ extras.putString(EXTRA_SENDER_LEGACY, sb.toString());
+ return InstanceID.getInstance(context).getToken(sb.toString(), INSTANCE_ID_SCOPE, extras);
+ }
+
+ /**
+ * Send an upstream ("device to cloud") message. You can only use the upstream feature
+ * if your GCM implementation uses the XMPP-based
+ * Cloud Connection Server.
+ *
+ * The current limits for max storage time and number of outstanding messages per
+ * application are documented in the
+ * GCM Developers Guide.
+ *
+ * @param to string identifying the receiver of the message in the format of
+ * SENDER_ID@gcm.googleapis.com
. The SENDER_ID
should be one of the sender
+ * IDs used when calling {@link com.google.android.gms.iid.InstanceID#getToken(java.lang.String, java.lang.String)}
+ * @param msgId ID of the message. This is generated by the application. It must be
+ * unique for each message. This allows error callbacks and debugging.
+ * @param timeToLive If 0, we'll attempt to send immediately and return an
+ * error if we're not connected. Otherwise, the message will be queued.
+ * As for server-side messages, we don't return an error if the message has been
+ * dropped because of TTL—this can happen on the server side, and it would require
+ * extra communication.
+ * @param data key/value pairs to be sent. Values must be String, any other type will
+ * be ignored.
+ * @throws IllegalArgumentException
+ * @throws IOException
+ */
+ public void send(String to, String msgId, long timeToLive, Bundle data) throws IOException {
+ if (TextUtils.isEmpty(to)) throw new IllegalArgumentException("Invalid 'to'");
+ Intent intent = new Intent(ACTION_GCM_SEND);
+ intent.setPackage(GMS_PACKAGE_NAME);
+ if (data != null) intent.putExtras(data);
+ intent.putExtra(EXTRA_APP, getSelfAuthIntent());
+ intent.putExtra(EXTRA_SEND_TO, to);
+ intent.putExtra(EXTRA_MESSAGE_ID, msgId);
+ intent.putExtra(EXTRA_TTL, timeToLive);
+ intent.putExtra(EXTRA_DELAY, -1);
+ //intent.putExtra(EXTRA_SEND_FROM, TODO)
+ context.sendOrderedBroadcast(intent, PERMISSION_GTALK);
+ }
+
+ /**
+ * Send an upstream ("device to cloud") message. You can only use the upstream feature
+ * if your GCM implementation uses the XMPP-based
+ * Cloud Connection Server.
+ *
+ * When there is an active connection the message will be sent immediately, otherwise the
+ * message will be queued for the maximum interval.
+ *
+ * @param to string identifying the receiver of the message in the format of
+ * SENDER_ID@gcm.googleapis.com
. The SENDER_ID
should be one of the sender
+ * IDs used when calling {@link com.google.android.gms.iid.InstanceID#getToken(java.lang.String, java.lang.String)}
+ * @param msgId ID of the message. This is generated by the application. It must be
+ * unique for each message. This allows error callbacks and debugging.
+ * @param data key/value pairs to be sent. Values must be String—any other type will
+ * be ignored.
+ * @throws IllegalArgumentException
+ * @throws IOException
+ */
+ public void send(String to, String msgId, Bundle data) throws IOException {
+ send(to, msgId, -1, data);
+ }
+
+ /**
+ * Unregister the application. Calling unregister()
stops any
+ * messages from the server. This is a blocking call—you shouldn't call
+ * it from the UI thread.
+ *
+ * You should rarely (if ever) need to call this method. Not only is it
+ * expensive in terms of resources, but it invalidates all your registration IDs
+ * returned from register() or subscribe(). This should not be done
+ * unnecessarily. A better approach is to simply have your server stop
+ * sending messages.
+ *
+ * @throws IOException if we can't connect to server to unregister.
+ * @deprecated Instead use
+ * {@link com.google.android.gms.iid.InstanceID#deleteToken(java.lang.String, java.lang.String)} or
+ * {@link com.google.android.gms.iid.InstanceID#deleteInstanceID()}.
+ */
+ @Deprecated
+ public void unregister() throws IOException {
+ if (Looper.getMainLooper() == Looper.myLooper()) throw new IOException(ERROR_MAIN_THREAD);
+ InstanceID.getInstance(context).deleteInstanceID();
+ }
+
+}
\ No newline at end of file
diff --git a/play-services-gcm/src/main/java/com/google/android/gms/gcm/OneoffTask.java b/play-services-gcm/src/main/java/com/google/android/gms/gcm/OneoffTask.java
new file mode 100644
index 00000000..ed1fe8a3
--- /dev/null
+++ b/play-services-gcm/src/main/java/com/google/android/gms/gcm/OneoffTask.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2013-2016 microG Project Team
+ *
+ * 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.gms.gcm;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.microg.gms.common.PublicApi;
+
+/**
+ * A task that will execute once,at some point within the specified window.
+ * If one of {@link com.google.android.gms.gcm.GcmNetworkManager#cancelTask(java.lang.String, java.lang.Class extends com.google.android.gms.gcm.GcmTaskService>)} or
+ * {@link com.google.android.gms.gcm.GcmNetworkManager#cancelAllTasks(java.lang.Class extends com.google.android.gms.gcm.GcmTaskService>)} is called before this
+ * executes it will be cancelled.
+ *
+ * Note that you can request a one-off task to be executed at any point in the future, but to
+ * prevent abuse the scheduler will only set an alarm at a minimum of 30 seconds in the
+ * future. Your task can still be run earlier than this if some network event occurs to wake up
+ * the scheduler.
+ */
+@PublicApi
+public class OneoffTask extends com.google.android.gms.gcm.Task {
+ private final long windowStart;
+ private final long windowEnd;
+
+ private OneoffTask(Builder builder) {
+ super(builder);
+ this.windowStart = builder.windowStart;
+ this.windowEnd = builder.windowEnd;
+ }
+
+ private OneoffTask(Parcel source) {
+ super(source);
+ this.windowStart = source.readLong();
+ this.windowEnd = source.readLong();
+ }
+
+ /**
+ * @return The number of seconds from now by which this task must have executed.
+ */
+ public long getWindowEnd() {
+ return windowEnd;
+ }
+
+ /**
+ * @return The number of seconds from now at which this task is eligible for execution.
+ */
+ public long getWindowStart() {
+ return windowStart;
+ }
+
+ /**
+ * Insert the task object into the provided bundle for IPC. Use #fromBundle to recreate the
+ * object on the other side.
+ */
+ public void toBundle(Bundle bundle) {
+ super.toBundle(bundle);
+ bundle.putLong("window_start", this.windowStart);
+ bundle.putLong("window_end", this.windowEnd);
+ }
+
+ public String toString() {
+ return super.toString()
+ + " windowStart=" + this.getWindowStart()
+ + " windowEnd=" + this.getWindowEnd();
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ super.writeToParcel(parcel, flags);
+ parcel.writeLong(this.windowStart);
+ parcel.writeLong(this.windowEnd);
+ }
+
+ public static final CreatormIntervalInSeconds.
+ *
+ * By default you have no control over where within this period the task will execute.
+ * If you want to restrict the task to run within a certain timeframe from the end of
+ * the period, use {@link com.google.android.gms.gcm.PeriodicTask.Builder#setFlex(long)}
+ */
+ public PeriodicTask.Builder setPeriod(long periodInSeconds) {
+ this.periodInSeconds = periodInSeconds;
+ return this;
+ }
+
+ /**
+ * Optional setter to specify whether this task should be persisted across reboots. This
+ * defaults to true for periodic tasks,
+ *
+ * Callers must hold the permission
+ * android.Manifest.permission.RECEIVE_BOOT_COMPLETED, otherwise this setter is
+ * ignored.
+ *
+ * @param isPersisted True if this task should be persisted across device reboots.
+ */
+ public PeriodicTask.Builder setPersisted(boolean isPersisted) {
+ this.isPersisted = isPersisted;
+ return this;
+ }
+
+ /**
+ * Set the network state your task requires to run. If the specified network is
+ * unavailable your task will not be executed until it becomes available.
+ *
+ * The default for either a periodic or one-off task is
+ * {@link com.google.android.gms.gcm.Task#NETWORK_STATE_CONNECTED}. Note that changing this to
+ * {@link com.google.android.gms.gcm.Task#NETWORK_STATE_ANY} means there is no guarantee that data will be available
+ * when your task executes.
+ *
+ * In addition, the only guarantee for connectivity is at the moment of execution - it is
+ * possible for the device to lose data shortly after your task begins executing.
+ */
+ public PeriodicTask.Builder setRequiredNetwork(int requiredNetworkState) {
+ this.requiredNetworkState = requiredNetworkState;
+ return this;
+ }
+
+ /**
+ * Set whether your task requires that the device be connected to power in order to
+ * execute.
+ *
+ * Use this to defer nonessential operations whenever possible. Note that if you set this
+ * field and the device is not connected to power your task will not run
+ * until the device is plugged in.
+ *
+ * One way to deal with your task not executing until the constraint is met is to schedule
+ * another task without the constraints that is subject to some deadline that you can abide.
+ * This task would be responsible for executing your fallback logic.
+ */
+ public PeriodicTask.Builder setRequiresCharging(boolean requiresCharging) {
+ this.requiresCharging = requiresCharging;
+ return this;
+ }
+
+ /**
+ * Set whichever {@link com.google.android.gms.gcm.GcmTaskService} you implement to execute the logic for this task.
+ * + * @param gcmTaskService Endpoint against which you're scheduling this task. + */ + public PeriodicTask.Builder setService(Class extends GcmTaskService> gcmTaskService) { + this.gcmTaskService = gcmTaskService.getName(); + return this; + } + + /** + * Mandatory setter for specifying the tag identifer for this task. This tag will be + * returned at execution time to your endpoint. See + * {@link com.google.android.gms.gcm.GcmTaskService#onRunTask(com.google.android.gms.gcm.TaskParams)} + * + * Maximum tag length is 100. + * + * @param tag String identifier for this task. Consecutive schedule calls for the same + * tag will update any preexisting task with the same tag. + */ + public PeriodicTask.Builder setTag(String tag) { + this.tag = tag; + return this; + } + + /** + * Optional setter to specify whether this task should override any preexisting tasks + * with the same tag. This defaults to false, which means that a new task will not + * override an existing one. + * + * @param updateCurrent True to update the current task with the parameters of the new. + * Default false. + */ + public PeriodicTask.Builder setUpdateCurrent(boolean updateCurrent) { + this.updateCurrent = updateCurrent; + return this; + } + } +} \ No newline at end of file diff --git a/play-services-gcm/src/main/java/com/google/android/gms/gcm/Task.java b/play-services-gcm/src/main/java/com/google/android/gms/gcm/Task.java new file mode 100644 index 00000000..0126ff40 --- /dev/null +++ b/play-services-gcm/src/main/java/com/google/android/gms/gcm/Task.java @@ -0,0 +1,271 @@ +/* + * Copyright 2013-2016 microG Project Team + * + * 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.gms.gcm; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import org.microg.gms.common.PublicApi; + +/** + * Encapsulates the parameters of a task that you will schedule on the + * {@link com.google.android.gms.gcm.GcmNetworkManager}. + * + * Construct instances of either {@link com.google.android.gms.gcm.PeriodicTask} or + * {@link com.google.android.gms.gcm.OneoffTask} with the desired parameters/behaviour and + * schedule them using {@link com.google.android.gms.gcm.GcmNetworkManager#schedule(com.google.android.gms.gcm.Task)}. + */ +@PublicApi +public abstract class Task implements Parcelable { + + /** + *The maximum size allowed for extras bundle in bytes. + *
+ */ + public static final int EXTRAS_LIMIT_BYTES = 10240; + + /** + *Specify using {@link com.google.android.gms.gcm.Task.Builder#setRequiredNetwork(int)} + * that your task will execute [...] of whether network is available. + *
+ */ + public static final int NETWORK_STATE_ANY = 2; + + /** + *Specify using {@link com.google.android.gms.gcm.Task.Builder#setRequiredNetwork(int)} + * that your task will only execute if [...] sort of data connection is available - + * either metered or unmetered. This is the default.
+ */ + public static final int NETWORK_STATE_CONNECTED = 0; + + /** + *Specify using {@link com.google.android.gms.gcm.Task.Builder#setRequiredNetwork(int)} + * that your task will only execute if there is an unmetered network connection available. + *
+ */ + public static final int NETWORK_STATE_UNMETERED = 1; + + protected static final long UNINITIALIZED = -1; + + private final String serviceName; + private final String tag; + private final boolean updateCurrent; + private final boolean persisted; + private final int requiredNetwork; + private final boolean requiresCharging; + private final Bundle extras; + + Task(Builder builder) { + this.serviceName = builder.gcmTaskService; + this.tag = builder.tag; + this.updateCurrent = builder.updateCurrent; + this.persisted = builder.isPersisted; + this.requiredNetwork = builder.requiredNetworkState; + this.requiresCharging = builder.requiresCharging; + this.extras = builder.extras; + } + + Task(Parcel in) { + this.serviceName = in.readString(); + this.tag = in.readString(); + this.updateCurrent = in.readInt() == 1; + this.persisted = in.readInt() == 1; + this.requiredNetwork = NETWORK_STATE_ANY; + this.requiresCharging = false; + this.extras = null; + } + + public int describeContents() { + return 0; + } + + /** + * @return The extra parameters for the task set by the client. + */ + public Bundle getExtras() { + return extras; + } + + /** + * If the specified network is unavailable, your task will not be run until + * it is. + * + * @return The network type that this task requires in order to run. See the NETWORK_TYPE_* + * flavours for an explanation of what this value can be. + */ + public int getRequiredNetwork() { + return requiredNetwork; + } + + /** + * If the device is not charging and this is set to true, your task will not be run + * until it is. + * + * @return Whether or not this task depends on the device being connected to power in order to + * execute. + */ + public boolean getRequiresCharging() { + return requiresCharging; + } + + /** + * @return The {@link com.google.android.gms.gcm.GcmTaskService} component that this task + * will execute on. + */ + public String getServiceName() { + return serviceName; + } + + /** + * @return The String identifier for this task, that is returned to + * {@link com.google.android.gms.gcm.GcmTaskService#onRunTask(com.google.android.gms.gcm.TaskParams)} + * when this task executes. + */ + public String getTag() { + return tag; + } + + /** + * @return Whether this task will be persisted across devices restarts or Google Play Services + * crashes. + */ + public boolean isPersisted() { + return persisted; + } + + /** + * @return Whether or not this task will update a pre-existing task in the scheduler queue. + */ + public boolean isUpdateCurrent() { + return updateCurrent; + } + + public void toBundle(Bundle bundle) { + bundle.putString("tag", this.tag); + bundle.putBoolean("update_current", this.updateCurrent); + bundle.putBoolean("persisted", this.persisted); + bundle.putString("service", this.serviceName); + bundle.putInt("requiredNetwork", this.requiredNetwork); + bundle.putBoolean("requiresCharging", this.requiresCharging); + bundle.putBundle("retryStrategy", null); // TODO + bundle.putBundle("extras", this.extras); + } + + public void writeToParcel(Parcel parcel, int i) { + parcel.writeString(serviceName); + parcel.writeString(tag); + parcel.writeInt(updateCurrent ? 1 : 0); + parcel.writeInt(persisted ? 1 : 0); + } + + + /** + *Builder object to construct these tasks before sending them to the network manager. Use + * either {@link com.google.android.gms.gcm.PeriodicTask.Builder} or + * {@link com.google.android.gms.gcm.Task.Builder}
+ */ + public abstract static class Builder { + protected Bundle extras; + protected String gcmTaskService; + protected boolean isPersisted; + protected int requiredNetworkState; + protected boolean requiresCharging; + protected String tag; + protected boolean updateCurrent; + + public Builder() { + throw new UnsupportedOperationException(); + } + + public abstract Task build(); + + /** + * Optional setter for specifying any extra parameters necessary for the task. + */ + public abstract Task.Builder setExtras(Bundle extras); + + /** + * Optional setter to specify whether this task should be persisted across reboots. This + * defaults to true for periodic tasks, and is not supported for one-off tasks. + * + * Callers must hold the permission + * android.Manifest.permission.RECEIVE_BOOT_COMPLETED, otherwise this setter is + * ignored. + * + * @param isPersisted True if this task should be persisted across device reboots. + */ + public abstract Task.Builder setPersisted(boolean isPersisted); + + /** + * Set the network state your task requires to run. If the specified network is + * unavailable your task will not be executed until it becomes available. + * + * The default for either a periodic or one-off task is + * {@link com.google.android.gms.gcm.Task#NETWORK_STATE_CONNECTED}. Note that changing this to + * {@link com.google.android.gms.gcm.Task#NETWORK_STATE_ANY} means there is no guarantee that data will be available + * when your task executes. + * + * In addition, the only guarantee for connectivity is at the moment of execution - it is + * possible for the device to lose data shortly after your task begins executing. + */ + public abstract Task.Builder setRequiredNetwork(int requiredNetworkState); + + /** + * Set whether your task requires that the device be connected to power in order to + * execute. + * + * Use this to defer nonessential operations whenever possible. Note that if you set this + * field and the device is not connected to power your task will not run + * until the device is plugged in. + * + * One way to deal with your task not executing until the constraint is met is to schedule + * another task without the constraints that is subject to some deadline that you can abide. + * This task would be responsible for executing your fallback logic. + */ + public abstract Task.Builder setRequiresCharging(boolean requiresCharging); + + /** + * Set whichever {@link com.google.android.gms.gcm.GcmTaskService} you implement to execute the logic for this task. + * + * @param gcmTaskService Endpoint against which you're scheduling this task. + */ + public abstract Task.Builder setService(Class extends GcmTaskService> gcmTaskService); + + /** + * Mandatory setter for specifying the tag identifer for this task. This tag will be + * returned at execution time to your endpoint. See + * {@link com.google.android.gms.gcm.GcmTaskService#onRunTask(com.google.android.gms.gcm.TaskParams)} + * + * Maximum tag length is 100. + * + * @param tag String identifier for this task. Consecutive schedule calls for the same tag + * will update any preexisting task with the same tag. + */ + public abstract Task.Builder setTag(String tag); + + /** + * Optional setter to specify whether this task should override any preexisting tasks with + * the same tag. This defaults to false, which means that a new task will not override an + * existing one. + * + * @param updateCurrent True to update the current task with the parameters of the new. + * Default false. + */ + public abstract Task.Builder setUpdateCurrent(boolean updateCurrent); + } +} \ No newline at end of file diff --git a/play-services-gcm/src/main/java/com/google/android/gms/gcm/TaskParams.java b/play-services-gcm/src/main/java/com/google/android/gms/gcm/TaskParams.java new file mode 100644 index 00000000..b3b0a99f --- /dev/null +++ b/play-services-gcm/src/main/java/com/google/android/gms/gcm/TaskParams.java @@ -0,0 +1,49 @@ +/* + * Copyright 2013-2016 microG Project Team + * + * 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.gms.gcm; + +import android.os.Bundle; + +import org.microg.gms.common.PublicApi; + +/** + * Container of parameters handed off to the client app in + * {@link com.google.android.gms.gcm.GcmTaskService#onRunTask(com.google.android.gms.gcm.TaskParams)}. + */ +@PublicApi +public class TaskParams { + private final String tag; + private final Bundle extras; + + public TaskParams(String tag) { + this(tag, null); + } + + public TaskParams(String tag, Bundle extras) { + this.tag = tag; + this.extras = extras; + } + + public Bundle getExtras() { + return extras; + } + + public String getTag() { + return tag; + } + +} diff --git a/play-services-gcm/src/main/java/com/google/android/gms/iid/InstanceID.java b/play-services-gcm/src/main/java/com/google/android/gms/iid/InstanceID.java new file mode 100644 index 00000000..c4f0cb34 --- /dev/null +++ b/play-services-gcm/src/main/java/com/google/android/gms/iid/InstanceID.java @@ -0,0 +1,166 @@ +/* + * Copyright 2013-2016 microG Project Team + * + * 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.gms.iid; + +import android.content.Context; +import android.os.Bundle; +import android.os.Looper; + +import org.microg.gms.common.PublicApi; +import org.microg.gms.gcm.GcmConstants; + +import java.io.IOException; + +/** + * Instance ID provides a unique identifier for each app instance and a mechanism + * to authenticate and authorize actions (for example, sending a GCM message). + * + * Instance ID is stable but may become invalid, if: + * [...] + * If Instance ID has become invalid, the app can call {@link com.google.android.gms.iid.InstanceID#getId()} + * to request a new Instance ID. + * To prove ownership of Instance ID and to allow servers to access data or + * services associated with the app, call {@link com.google.android.gms.iid.InstanceID#getToken(java.lang.String, java.lang.String)}. + */ +public class InstanceID { + /** + * Error returned when failed requests are retried too often. Use + * exponential backoff when retrying requests + */ + public static final String ERROR_BACKOFF = "RETRY_LATER"; + + /** + * Blocking methods must not be called on the main thread. + */ + public static final String ERROR_MAIN_THREAD = "MAIN_THREAD"; + + /** + * Tokens can't be generated. Only devices with Google Play are supported. + */ + public static final String ERROR_MISSING_INSTANCEID_SERVICE = "MISSING_INSTANCEID_SERVICE"; + + /** + * The device cannot read the response, or there was a server error. + * Application should retry the request later using exponential backoff + * and retry (on each subsequent failure increase delay before retrying). + */ + public static final String ERROR_SERVICE_NOT_AVAILABLE = GcmConstants.ERROR_SERVICE_NOT_AVAILABLE; + + /** + * Timeout waiting for a response. + */ + public static final String ERROR_TIMEOUT = "TIMEOUT"; + + /** + * Resets Instance ID and revokes all tokens. + * + * @throws IOException + */ + public void deleteInstanceID() throws IOException { + throw new UnsupportedOperationException(); + } + + /** + * Revokes access to a scope (action) for an entity previously + * authorized by {@link com.google.android.gms.iid.InstanceID#getToken(java.lang.String, java.lang.String)}. + * + * Do not call this function on the main thread. + * + * @param authorizedEntity Entity that must no longer have access. + * @param scope Action that entity is no longer authorized to perform. + * @throws IOException if the request fails. + */ + public void deleteToken(String authorizedEntity, String scope) throws IOException { + deleteToken(authorizedEntity, scope, new Bundle()); + } + + @PublicApi(exclude = true) + public void deleteToken(String authorizedEntity, String scope, Bundle extras) throws IOException { + throw new UnsupportedOperationException(); + } + + /** + * Returns time when instance ID was created. + * + * @return Time when instance ID was created (milliseconds since Epoch). + */ + public long getCreationTime() { + throw new UnsupportedOperationException(); + } + + /** + * Returns a stable identifier that uniquely identifies the app instance. + * + * @return The identifier for the application instance. + */ + public String getId() { + throw new UnsupportedOperationException(); + } + + /** + * Returns an instance of this class. + * + * @return InstanceID instance. + */ + public static InstanceID getInstance(Context context) { + throw new UnsupportedOperationException(); + } + + /** + * Returns a token that authorizes an Entity (example: cloud service) to perform + * an action on behalf of the application identified by Instance ID. + * + * This is similar to an OAuth2 token except, it applies to the + * application instance instead of a user. + * + * Do not call this function on the main thread. + * + * @param authorizedEntity Entity authorized by the token. + * @param scope Action authorized for authorizedEntity. + * @param extras additional parameters specific to each token scope. + * Bundle keys starting with 'GCM.' and 'GOOGLE.' are + * reserved. + * @return a token that can identify and authorize the instance of the + * application on the device. + * @throws IOException if the request fails. + */ + public String getToken(String authorizedEntity, String scope, Bundle extras) throws IOException { + if (Looper.getMainLooper() == Looper.myLooper()) throw new IOException(ERROR_MAIN_THREAD); + + throw new UnsupportedOperationException(); + } + + /** + * Returns a token that authorizes an Entity (example: cloud service) to perform + * an action on behalf of the application identified by Instance ID. + * + * This is similar to an OAuth2 token except, it applies to the + * application instance instead of a user. + * + * Do not call this function on the main thread. + * + * @param authorizedEntity Entity authorized by the token. + * @param scope Action authorized for authorizedEntity. + * @return a token that can identify and authorize the instance of the + * application on the device. + * @throws IOException if the request fails. + */ + public String getToken(String authorizedEntity, String scope) throws IOException { + return getToken(authorizedEntity, scope, null); + } + +} \ No newline at end of file diff --git a/play-services-gcm/src/main/java/com/google/android/gms/iid/InstanceIDListenerService.java b/play-services-gcm/src/main/java/com/google/android/gms/iid/InstanceIDListenerService.java new file mode 100644 index 00000000..cd9a80ff --- /dev/null +++ b/play-services-gcm/src/main/java/com/google/android/gms/iid/InstanceIDListenerService.java @@ -0,0 +1,138 @@ +/* + * Copyright 2013-2016 microG Project Team + * + * 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.gms.iid; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; + +import com.google.android.gms.gcm.GcmReceiver; + +import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_REGISTRATION; +import static org.microg.gms.gcm.GcmConstants.ACTION_INSTANCE_ID; +import static org.microg.gms.gcm.GcmConstants.EXTRA_FROM; +import static org.microg.gms.gcm.GcmConstants.EXTRA_GSF_INTENT; + +/** + * Base class to handle Instance ID service notifications on token + * refresh. + * + * Any app using Instance ID or GCM must include a class extending + * InstanceIDListenerService and implement {@link com.google.android.gms.iid.InstanceIDListenerService#onTokenRefresh()}. + * + * Include the following in the manifest: + *+ *+ * Do not export this service. Instead, keep it private to prevent other apps + * accessing your service. + */ +public class InstanceIDListenerService extends Service { + + private BroadcastReceiver registrationReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + handleIntent(intent); + stop(); + } + }; + private MessengerCompat messengerCompat = new MessengerCompat(new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + handleIntent((Intent) msg.obj); + } + }); + + private int counter = 0; + private int startId = -1; + + private void handleIntent(Intent intent) { + // TODO + } + + public IBinder onBind(Intent intent) { + if (intent != null && ACTION_INSTANCE_ID.equals(intent.getAction())) { + return messengerCompat.getBinder(); + } + return null; + } + + public void onCreate() { + IntentFilter filter = new IntentFilter(ACTION_C2DM_REGISTRATION); + filter.addCategory(getPackageName()); + registerReceiver(registrationReceiver, filter); + } + + public void onDestroy() { + unregisterReceiver(registrationReceiver); + } + + public int onStartCommand(Intent intent, int flags, int startId) { + synchronized (this) { + this.counter++; + if (startId > this.startId) this.startId = startId; + } + try { + if (intent != null) { + if (ACTION_INSTANCE_ID.equals(intent.getAction()) && intent.hasExtra(EXTRA_GSF_INTENT)) { + startService((Intent) intent.getParcelableExtra(EXTRA_GSF_INTENT)); + return START_STICKY; + } + + handleIntent(intent); + + if (intent.hasExtra(EXTRA_FROM)) GcmReceiver.completeWakefulIntent(intent); + } + } finally { + stop(); + } + return START_NOT_STICKY; + } + + /** + * Called when the system determines that the tokens need to be refreshed. The application + * should call getToken() and send the tokens to all application servers. + * + * This will not be called very frequently, it is needed for key rotation and to handle special + * cases. + * + * The system will throttle the refresh event across all devices to avoid overloading + * application servers with token updates. + */ + public void onTokenRefresh() { + // To be overwritten + } + + private void stop() { + synchronized (this) { + counter--; + if (counter <= 0) { + stopSelf(startId); + } + } + } + +} \ No newline at end of file diff --git a/play-services-location/build.gradle b/play-services-location/build.gradle index be40ce7f..b3a6c833 100644 --- a/play-services-location/build.gradle +++ b/play-services-location/build.gradle @@ -19,17 +19,20 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.3.0' - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' + classpath 'com.android.tools.build:gradle:2.0.0' } } apply plugin: 'com.android.library' -apply plugin: 'com.github.dcendents.android-maven' android { compileSdkVersion 23 - buildToolsVersion "23.0.1" + buildToolsVersion "23.0.2" + + defaultConfig { + versionName getMyVersionName() + } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_6 } diff --git a/play-services-location/src/main/java/com/google/android/gms/location/FusedLocationProviderApi.java b/play-services-location/src/main/java/com/google/android/gms/location/FusedLocationProviderApi.java index 572a6ae1..14a92352 100644 --- a/play-services-location/src/main/java/com/google/android/gms/location/FusedLocationProviderApi.java +++ b/play-services-location/src/main/java/com/google/android/gms/location/FusedLocationProviderApi.java @@ -23,11 +23,11 @@ import android.os.Looper; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.PendingResult; -import org.microg.gms.common.Constants; +import org.microg.gms.location.LocationConstants; public interface FusedLocationProviderApi { String KEY_LOCATION_CHANGED = "com.google.android.location.LOCATION"; - String KEY_MOCK_LOCATION = Constants.KEY_MOCK_LOCATION; + String KEY_MOCK_LOCATION = LocationConstants.KEY_MOCK_LOCATION; Location getLastLocation(GoogleApiClient client); diff --git a/play-services-location/src/main/java/org/microg/gms/location/GoogleLocationManagerClient.java b/play-services-location/src/main/java/org/microg/gms/location/GoogleLocationManagerClient.java index b012cf97..55713f02 100644 --- a/play-services-location/src/main/java/org/microg/gms/location/GoogleLocationManagerClient.java +++ b/play-services-location/src/main/java/org/microg/gms/location/GoogleLocationManagerClient.java @@ -27,7 +27,7 @@ import com.google.android.gms.location.internal.IGoogleLocationManagerService; import org.microg.gms.common.Constants; import org.microg.gms.common.GmsClient; -import org.microg.gms.common.Services; +import org.microg.gms.common.GmsService; public abstract class GoogleLocationManagerClient extends GmsClient+ * + * + *+ *