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 @@ --> + package="org.microg.gms.cast"> diff --git a/play-services-gcm/build.gradle b/play-services-gcm/build.gradle new file mode 100644 index 00000000..b3a6c833 --- /dev/null +++ b/play-services-gcm/build.gradle @@ -0,0 +1,43 @@ +/* + * Copyright 2013-2015 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. + */ + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.0.0' + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + versionName getMyVersionName() + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_6 + } +} + +dependencies { + compile project(':play-services-base') +} \ No newline at end of file diff --git a/play-services-gcm/src/main/AndroidManifest.xml b/play-services-gcm/src/main/AndroidManifest.xml new file mode 100644 index 00000000..baaa3ad3 --- /dev/null +++ b/play-services-gcm/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + diff --git a/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmListenerService.java b/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmListenerService.java new file mode 100644 index 00000000..8a14ff78 --- /dev/null +++ b/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmListenerService.java @@ -0,0 +1,195 @@ +/* + * 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.app.Service; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.IBinder; +import android.support.v4.os.AsyncTaskCompat; +import android.util.Log; + +import org.microg.gms.common.PublicApi; + +import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_RECEIVE; +import static org.microg.gms.gcm.GcmConstants.ACTION_NOTIFICATION_OPEN; +import static org.microg.gms.gcm.GcmConstants.EXTRA_ERROR; +import static org.microg.gms.gcm.GcmConstants.EXTRA_FROM; +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_PENDING_INTENT; +import static org.microg.gms.gcm.GcmConstants.MESSAGE_TYPE_DELETED_MESSAGE; +import static org.microg.gms.gcm.GcmConstants.MESSAGE_TYPE_GCM; +import static org.microg.gms.gcm.GcmConstants.MESSAGE_TYPE_SEND_ERROR; +import static org.microg.gms.gcm.GcmConstants.MESSAGE_TYPE_SEND_EVENT; + +/** + * Base class for communicating with Google Cloud Messaging. + *

+ * It also provides functionality such as automatically displaying + * notifications when requested by app server. + *

+ * Override base class methods to handle any events required by the application. + * Methods are invoked asynchronously. + *

+ * Include the following in the manifest: + *

+ * 
+ *     
+ *         
+ *     
+ * 
+ */ +@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() { + @Override + protected Void doInBackground(Void... params) { + handleC2dmMessage(intent); + return null; + } + }); + } else { + Log.w(TAG, "Unknown intent action: " + intent.getAction()); + + } + + return START_REDELIVER_INTENT; + } else { + finishCounter(); + return START_NOT_STICKY; + } + } + + private void handleC2dmMessage(Intent intent) { + try { + String messageType = intent.getStringExtra(EXTRA_MESSAGE_TYPE); + if (messageType == null || MESSAGE_TYPE_GCM.equals(messageType)) { + String from = intent.getStringExtra(EXTRA_FROM); + Bundle data = intent.getExtras(); + data.remove(EXTRA_MESSAGE_TYPE); + data.remove("android.support.content.wakelockid"); // WakefulBroadcastReceiver.EXTRA_WAKE_LOCK_ID + data.remove(EXTRA_FROM); + onMessageReceived(from, data); + } else if (MESSAGE_TYPE_DELETED_MESSAGE.equals(messageType)) { + onDeletedMessages(); + } else if (MESSAGE_TYPE_SEND_EVENT.equals(messageType)) { + onMessageSent(intent.getStringExtra(EXTRA_MESSAGE_ID)); + } else if (MESSAGE_TYPE_SEND_ERROR.equals(messageType)) { + onSendError(intent.getStringExtra(EXTRA_MESSAGE_ID), intent.getStringExtra(EXTRA_ERROR)); + } else { + Log.w(TAG, "Unknown message type: " + messageType); + } + finishCounter(); + } finally { + GcmReceiver.completeWakefulIntent(intent); + } + } + + private void handlePendingNotification(Intent intent) { + PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT); + if (pendingIntent != null) { + try { + pendingIntent.send(); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Notification cancelled", e); + } + } else { + Log.w(TAG, "Notification was null"); + } + } + + private void finishCounter() { + synchronized (lock) { + this.counter--; + if (counter == 0) { + stopSelfResult(startId); + } + } + } + +} \ No newline at end of file diff --git a/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmNetworkManager.java b/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmNetworkManager.java new file mode 100644 index 00000000..3c2dc39b --- /dev/null +++ b/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmNetworkManager.java @@ -0,0 +1,244 @@ +/* + * 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.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.text.TextUtils; + +import java.util.List; + +import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME; +import static org.microg.gms.gcm.GcmConstants.ACTION_SCHEDULE; +import static org.microg.gms.gcm.GcmConstants.ACTION_TASK_READY; +import static org.microg.gms.gcm.GcmConstants.EXTRA_COMPONENT; +import static org.microg.gms.gcm.GcmConstants.EXTRA_SCHEDULER_ACTION; +import static org.microg.gms.gcm.GcmConstants.EXTRA_TAG; +import static org.microg.gms.gcm.GcmConstants.SCHEDULER_ACTION_CANCEL; +import static org.microg.gms.gcm.GcmConstants.SCHEDULER_ACTION_CANCEL_ALL; +import static org.microg.gms.gcm.GcmConstants.SCHEDULER_ACTION_SCHEDULE; + +/** + * Class to create apps with robust "send-to-sync", which is the mechanism to sync data with + * servers where new information is available. + *

+ * You can use the API to schedule network-oriented tasks, and let Google Play services batch + * network operations across the system. This greatly simplifies the implementation of common + * patterns, such as waiting for network connectivity, network retries, and backoff. + *

+ * Tasks must be scheduled based on an execution window in time. During this execution window + * the scheduler will use its discretion in picking an optimal execution time, based on network + * availability (whether the device has connectivity), network activity (whether packages are + * actively being transferred). and load (how many other pending tasks are available for + * execution at that point in time). If none of these factors are influential, the + * scheduler will always wait until the end of the specified window. + *

+ * To receive the notification from the scheduler that a task is ready to be executed, your + * client app must implement a {@link com.google.android.gms.gcm.GcmTaskService} and filter + * on the action {@link com.google.android.gms.gcm.GcmTaskService#SERVICE_ACTION_EXECUTE_TASK}. + *

+ * Note that tags of arbitrary length are not allowed; if the tag you + * provide is greater than 100 characters an exception will be thrown when you try to create your + * {@link com.google.android.gms.gcm.Task} object. + *

+ * The service should be protected by the permission + * com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE which is used by Google Play + * Services. This prevents other code from invoking the broadcast receiver. Here is an excerpt from + * a sample manifest: + *

+ * 
+ *     
+ *        
+ *     
+ * 
+ * 
+ * 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 -e tag + * 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 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 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)}

+ * + * @param task Task constructed using {@link com.google.android.gms.gcm.Task.Builder}. Can be + * an instance of {@link com.google.android.gms.gcm.PeriodicTask} or + * {@link com.google.android.gms.gcm.OneoffTask}. + */ + public void schedule(Task task) { + validateService(task.getServiceName()); + Intent scheduleIntent = createScheduleIntent(); + if (scheduleIntent != null) { + Bundle extras = scheduleIntent.getExtras(); + extras.putString(EXTRA_SCHEDULER_ACTION, SCHEDULER_ACTION_SCHEDULE); + task.toBundle(extras); + scheduleIntent.putExtras(extras); + context.sendBroadcast(scheduleIntent); + } + } + + private Intent createScheduleIntent() { + if (!packageExists(GMS_PACKAGE_NAME)) return null; + Intent scheduleIntent = new Intent(ACTION_SCHEDULE); + scheduleIntent.setPackage(GMS_PACKAGE_NAME); + scheduleIntent.putExtra("app", PendingIntent.getBroadcast(context, 0, new Intent(), 0)); + return scheduleIntent; + } + + private boolean packageExists(String packageName) { + try { + PackageManager pm = context.getPackageManager(); + pm.getPackageInfo(packageName, 0); + return true; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + private void validateService(String serviceName) { + if (serviceName == null) throw new NullPointerException("No service provided"); + Intent taskIntent = new Intent(ACTION_TASK_READY); + taskIntent.setPackage(context.getPackageName()); + PackageManager pm = context.getPackageManager(); + List serviceResolves = pm.queryIntentServices(taskIntent, 0); + if (serviceResolves == null || serviceResolves.isEmpty()) + throw new IllegalArgumentException("No service found"); + for (ResolveInfo info : serviceResolves) { + if (serviceName.equals(info.serviceInfo.name)) return; + } + throw new IllegalArgumentException("Service not supported."); + } +} diff --git a/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmPubSub.java b/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmPubSub.java new file mode 100644 index 00000000..5ef87164 --- /dev/null +++ b/play-services-gcm/src/main/java/com/google/android/gms/gcm/GcmPubSub.java @@ -0,0 +1,120 @@ +/* + * 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.os.Bundle; +import android.text.TextUtils; + +import com.google.android.gms.iid.InstanceID; + +import java.io.IOException; +import java.util.regex.Pattern; + +import static org.microg.gms.gcm.GcmConstants.EXTRA_TOPIC; + +/** + * GcmPubSub provides a publish-subscribe model for sending GCM topic messages. + *

+ * An app can subscribe to different topics defined by the + * developer. The app server can then send messages to the subscribed devices + * without having to maintain topic-subscribers mapping. Topics do not + * need to be explicitly created before subscribing or publishing—they + * are automatically created when publishing or subscribing. + *

+ * 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)} or + * {@link com.google.android.gms.gcm.GcmNetworkManager#cancelAllTasks(java.lang.Class)} 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 Creator CREATOR = new Creator() { + @Override + public OneoffTask createFromParcel(Parcel source) { + return new OneoffTask(source); + } + + @Override + public OneoffTask[] newArray(int size) { + return new OneoffTask[size]; + } + }; + + public static class Builder extends Task.Builder { + private long windowStart = -1; + private long windowEnd = -1; + + public Builder() { + this.isPersisted = false; + } + + public OneoffTask build() { + return new OneoffTask(this); + } + + /** + * Mandatory setter for creating a one-off task. You specify the earliest point in + * time in the future from which your task might start executing, as well as the + * latest point in time in the future at which your task must have executed. + * + * @param windowStartDelaySeconds Earliest point from which your task is eligible to + * run. + * @param windowEndDelaySeconds Latest point at which your task must be run. + */ + public OneoffTask.Builder setExecutionWindow(long windowStartDelaySeconds, long windowEndDelaySeconds) { + this.windowEnd = windowEndDelaySeconds; + this.windowStart = windowStartDelaySeconds; + return this; + } + + /** + * Optional setter for specifying any extra parameters necessary for the task. + */ + public OneoffTask.Builder setExtras(Bundle extras) { + this.extras = extras; + return this; + } + + /** + * Optional setter to specify whether this task should be persisted across reboots.. + * 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 OneoffTask.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 OneoffTask.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 OneoffTask.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 OneoffTask.Builder setService(Class 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 OneoffTask.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 OneoffTask.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/PeriodicTask.java b/play-services-gcm/src/main/java/com/google/android/gms/gcm/PeriodicTask.java new file mode 100644 index 00000000..60864bfb --- /dev/null +++ b/play-services-gcm/src/main/java/com/google/android/gms/gcm/PeriodicTask.java @@ -0,0 +1,235 @@ +/* + * 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 periodic task is one that will recur at the specified interval, without needing to be + * rescheduled. + * Schedule a task that will recur until the user calls one of + * {@link com.google.android.gms.gcm.GcmNetworkManager#cancelAllTasks(java.lang.Class)}, or + * {@link com.google.android.gms.gcm.GcmNetworkManager#cancelTask(java.lang.String, java.lang.Class)} with + * an identifying tag. + *

+ * Periodic tasks will not be scheduled if their period is below a certain minimum + * (currently 30 seconds). + */ +@PublicApi +public class PeriodicTask extends com.google.android.gms.gcm.Task { + + protected long mFlexInSeconds; + + protected long mIntervalInSeconds; + + private PeriodicTask(Builder builder) { + super(builder); + this.mIntervalInSeconds = builder.periodInSeconds; + this.mFlexInSeconds = Math.min(builder.flexInSeconds, mIntervalInSeconds); + } + + private PeriodicTask(Parcel source) { + super(source); + mIntervalInSeconds = source.readLong(); + mFlexInSeconds = Math.min(source.readLong(), mIntervalInSeconds); + } + + + /** + * @return The number of seconds before the end of the period returned via + * {@link com.google.android.gms.gcm.PeriodicTask#getPeriod()} that this periodic task can be executed early. + */ + public long getFlex() { + return mFlexInSeconds; + } + + /** + * @return The period for this task. The number of seconds between subsequent executions. + */ + public long getPeriod() { + return mIntervalInSeconds; + } + + /** + * 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("period", this.mIntervalInSeconds); + bundle.putLong("period_flex", this.mFlexInSeconds); + } + + public String toString() { + return super.toString() + " period=" + this.getPeriod() + " flex=" + this.getFlex(); + } + + public void writeToParcel(Parcel parcel, int flags) { + super.writeToParcel(parcel, flags); + parcel.writeLong(this.mIntervalInSeconds); + parcel.writeLong(this.mFlexInSeconds); + } + + public static final Creator CREATOR = new Creator() { + @Override + public PeriodicTask createFromParcel(Parcel source) { + return new PeriodicTask(source); + } + + @Override + public PeriodicTask[] newArray(int size) { + return new PeriodicTask[size]; + } + }; + + public static class Builder extends com.google.android.gms.gcm.Task.Builder { + private long flexInSeconds = -1; + private long periodInSeconds = -1; + + public Builder() { + isPersisted = true; + } + + public PeriodicTask build() { + return new PeriodicTask(this); + } + + /** + * Optional setter for specifying any extra parameters necessary for the task. + */ + public PeriodicTask.Builder setExtras(Bundle extras) { + this.extras = extras; + return this; + } + + /** + * Optional setter for specifying how close to the end of the period set in + * {@link com.google.android.gms.gcm.PeriodicTask.Builder#setPeriod(long)} you are willing to execute. + *

+ * For example, specifying a period of 30 seconds, with a flex value of 10 seconds + * will allow the scheduler to determine the best moment between the 20th and 30th + * second at which to execute your task. + */ + public PeriodicTask.Builder setFlex(long flexInSeconds) { + this.flexInSeconds = flexInSeconds; + return this; + } + + /** + * Mandatory setter for creating a periodic task. This specifies that you would like + * this task to recur at most once every mIntervalInSeconds. + *

+ * 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 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 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 { public GoogleLocationManagerClient(Context context, GoogleApiClient.ConnectionCallbacks @@ -37,7 +37,7 @@ public abstract class GoogleLocationManagerClient extends GmsClient