Big restructure and add GCM and IID client

This commit is contained in:
Marvin W 2016-08-07 18:13:17 +02:00
parent d6d32cc203
commit 3bb89224dc
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
44 changed files with 2604 additions and 216 deletions

177
LICENSE Normal file
View File

@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -15,16 +15,7 @@
*/
// 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 = getMyVersionName()
}

2
extern/GmsApi vendored

@ -1 +1 @@
Subproject commit 6aa110657beec0b3e6c26d1030943e0b97683335
Subproject commit be6af2eeb76ba4bcc1700285f2ba87f217ab6ac2

View File

@ -19,15 +19,24 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0'
classpath 'com.android.tools.build:gradle:2.1.2'
}
}
apply plugin: 'com.android.library'
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)
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
buildToolsVersion "23.0.3"
defaultConfig {
versionName getMyVersionName()
@ -39,6 +48,7 @@ android {
}
dependencies {
compile 'com.android.support:support-v4:23.3.0'
compile project(':play-services-api')
compile 'com.android.support:support-v4:23.4.0'
compile project(':play-services-common-api')
compile project(':play-services-tasks')
}

View File

@ -15,10 +15,19 @@
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.gms">
<manifest package="com.google.android.gms"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk android:minSdkVersion="9" />
<uses-sdk android:minSdkVersion="9"/>
<application />
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
<application>
<activity
android:name="com.google.android.gms.common.api.GoogleApiActivity"
android:exported="false"
android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
</application>
</manifest>

View File

@ -20,9 +20,12 @@ import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.text.TextUtils;
import com.google.android.gms.common.api.GoogleApiClient;
import java.util.Arrays;
/**
* Contains all possible error codes for when a client fails to connect to Google Play services.
* These error codes are used by {@link GoogleApiClient.OnConnectionFailedListener}.
@ -128,8 +131,18 @@ public class ConnectionResult {
@Deprecated
public static final int DRIVE_EXTERNAL_STORAGE_REQUIRED = 1500;
private final PendingIntent pendingIntent;
private final int statusCode;
private final PendingIntent pendingIntent;
private final String message;
/**
* Creates a connection result.
*
* @param statusCode The status code.
*/
public ConnectionResult(int statusCode) {
this(statusCode, null);
}
/**
* Creates a connection result.
@ -138,8 +151,89 @@ public class ConnectionResult {
* @param pendingIntent A pending intent that will resolve the issue when started, or null.
*/
public ConnectionResult(int statusCode, PendingIntent pendingIntent) {
this(statusCode, pendingIntent, getStatusString(statusCode));
}
/**
* Creates a connection result.
*
* @param statusCode The status code.
* @param pendingIntent A pending intent that will resolve the issue when started, or null.
* @param message An additional error message for the connection result, or null.
*/
public ConnectionResult(int statusCode, PendingIntent pendingIntent, String message) {
this.statusCode = statusCode;
this.pendingIntent = pendingIntent;
this.message = message;
}
static String getStatusString(int statusCode) {
switch (statusCode) {
case -1:
return "UNKNOWN";
case 0:
return "SUCCESS";
case 1:
return "SERVICE_MISSING";
case 2:
return "SERVICE_VERSION_UPDATE_REQUIRED";
case 3:
return "SERVICE_DISABLED";
case 4:
return "SIGN_IN_REQUIRED";
case 5:
return "INVALID_ACCOUNT";
case 6:
return "RESOLUTION_REQUIRED";
case 7:
return "NETWORK_ERROR";
case 8:
return "INTERNAL_ERROR";
case 9:
return "SERVICE_INVALID";
case 10:
return "DEVELOPER_ERROR";
case 11:
return "LICENSE_CHECK_FAILED";
case 13:
return "CANCELED";
case 14:
return "TIMEOUT";
case 15:
return "INTERRUPTED";
case 16:
return "API_UNAVAILABLE";
case 17:
return "SIGN_IN_FAILED";
case 18:
return "SERVICE_UPDATING";
case 19:
return "SERVICE_MISSING_PERMISSION";
case 20:
return "RESTRICTED_PROFILE";
case 21:
return "API_VERSION_UPDATE_REQUIRED";
case 42:
return "UPDATE_ANDROID_WEAR";
case 99:
return "UNFINISHED";
case 1500:
return "DRIVE_EXTERNAL_STORAGE_REQUIRED";
default:
return "UNKNOWN_ERROR_CODE(" + statusCode + ")";
}
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof ConnectionResult)) {
return false;
} else {
ConnectionResult r = (ConnectionResult)o;
return statusCode == r.statusCode && pendingIntent == null ? r.pendingIntent == null : pendingIntent.equals(r.pendingIntent) && TextUtils.equals(message, r.message);
}
}
/**
@ -151,6 +245,15 @@ public class ConnectionResult {
return statusCode;
}
/**
* Returns an error message for connection result.
*
* @return the message
*/
public String getErrorMessage() {
return message;
}
/**
* A pending intent to resolve the connection failure. This intent can be started with
* {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} to
@ -162,6 +265,11 @@ public class ConnectionResult {
return pendingIntent;
}
@Override
public int hashCode() {
return Arrays.hashCode(new Object[]{statusCode, pendingIntent, message});
}
/**
* Returns {@code true} if calling {@link #startResolutionForResult(Activity, int)} will start
* any intents requiring user interaction.
@ -196,8 +304,7 @@ public class ConnectionResult {
public void startResolutionForResult(Activity activity, int requestCode) throws
IntentSender.SendIntentException {
if (hasResolution()) {
activity.startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, null,
0, 0, 0);
activity.startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, null, 0, 0, 0);
}
}
}

View File

@ -0,0 +1,290 @@
/*
* 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.common;
import android.app.Activity;
import android.app.Dialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import org.microg.gms.common.Constants;
import org.microg.gms.common.PublicApi;
import static com.google.android.gms.common.ConnectionResult.INTERNAL_ERROR;
import static com.google.android.gms.common.ConnectionResult.INVALID_ACCOUNT;
import static com.google.android.gms.common.ConnectionResult.NETWORK_ERROR;
import static com.google.android.gms.common.ConnectionResult.RESOLUTION_REQUIRED;
import static com.google.android.gms.common.ConnectionResult.SERVICE_DISABLED;
import static com.google.android.gms.common.ConnectionResult.SERVICE_INVALID;
import static com.google.android.gms.common.ConnectionResult.SERVICE_MISSING;
import static com.google.android.gms.common.ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED;
import static com.google.android.gms.common.ConnectionResult.SIGN_IN_REQUIRED;
import static com.google.android.gms.common.ConnectionResult.SUCCESS;
@PublicApi
public class GoogleApiAvailability {
private static final String TAG = "GmsApiAvailability";
/**
* Package name for Google Play services.
*/
public static final String GOOGLE_PLAY_SERVICES_PACKAGE = Constants.GMS_PACKAGE_NAME;
/**
* Google Play services client library version (declared in library's AndroidManifest.xml android:versionCode).
*/
public static final int GOOGLE_PLAY_SERVICES_VERSION_CODE = Constants.MAX_REFERENCE_VERSION;
private static GoogleApiAvailability instance;
private GoogleApiAvailability() {
}
/**
* Returns the singleton instance of GoogleApiAvailability.
*/
public static GoogleApiAvailability getInstance() {
if (instance == null) {
synchronized (GoogleApiAvailability.class) {
if (instance == null) {
instance = new GoogleApiAvailability();
}
}
}
return instance;
}
/**
* Returns a dialog to address the provided errorCode. The returned dialog displays a localized
* message about the error and upon user confirmation (by tapping on dialog) will direct them
* to the Play Store if Google Play services is out of date or missing, or to system settings
* if Google Play services is disabled on the device.
*
* @param activity parent activity for creating the dialog, also used for identifying language to display dialog in.
* @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call.
* If errorCode is {@link ConnectionResult#SUCCESS} then null is returned.
* @param requestCode The requestCode given when calling startActivityForResult.
*/
public Dialog getErrorDialog(Activity activity, int errorCode, int requestCode) {
return getErrorDialog(activity, errorCode, requestCode, null);
}
/**
* Returns a dialog to address the provided errorCode. The returned dialog displays a localized
* message about the error and upon user confirmation (by tapping on dialog) will direct them
* to the Play Store if Google Play services is out of date or missing, or to system settings
* if Google Play services is disabled on the device.
*
* @param activity parent activity for creating the dialog, also used for identifying language to display dialog in.
* @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call.
* If errorCode is {@link ConnectionResult#SUCCESS} then null is returned.
* @param requestCode The requestCode given when calling startActivityForResult.
* @param cancelListener The {@link DialogInterface.OnCancelListener} to invoke if the dialog is canceled.
*/
public Dialog getErrorDialog(Activity activity, int errorCode, int requestCode, DialogInterface.OnCancelListener cancelListener) {
// TODO
return null;
}
/**
* Returns a PendingIntent to address the provided connection failure.
* <p/>
* If {@link ConnectionResult#hasResolution()} is true, then {@link ConnectionResult#getResolution()}
* will be returned. Otherwise, the returned PendingIntent will direct the user to either the
* Play Store if Google Play services is out of date or missing, or system settings if Google
* Play services is disabled on the device.
*
* @param context parent context for creating the PendingIntent.
* @param result the connection failure. If successful or the error is not resolvable by the user, null is returned.
*/
public PendingIntent getErrorResolutionPendingIntent(Context context, ConnectionResult result) {
if (result.hasResolution()) {
return result.getResolution();
}
return getErrorResolutionPendingIntent(context, result.getErrorCode(), 0);
}
/**
* Returns a PendingIntent to address the provided errorCode. It will direct the user to either
* the Play Store if Google Play services is out of date or missing, or system settings if
* Google Play services is disabled on the device.
*
* @param context parent context for creating the PendingIntent.
* @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call.
* If errorCode is {@link ConnectionResult#SUCCESS} then null is returned.
* @param requestCode The requestCode given when calling startActivityForResult.
*/
public PendingIntent getErrorResolutionPendingIntent(Context context, int errorCode, int requestCode) {
// TODO
return null;
}
/**
* Returns a human-readable string of the error code returned from {@link #isGooglePlayServicesAvailable(Context)}.
*/
public final String getErrorString(int errorCode) {
return ConnectionResult.getStatusString(errorCode);
}
/**
* Verifies that Google Play services is installed and enabled on this device, and that the
* version installed on this device is no older than the one required by this client.
*
* @return status code indicating whether there was an error. Can be one of following in
* {@link ConnectionResult}: SUCCESS, SERVICE_MISSING, SERVICE_UPDATING,
* SERVICE_VERSION_UPDATE_REQUIRED, SERVICE_DISABLED, SERVICE_INVALID
*/
public int isGooglePlayServicesAvailable(Context context) {
Log.d(TAG, "As we can't know right now if the later desired feature is available, " +
"we just pretend it to be.");
return SUCCESS;
}
/**
* Determines whether an error can be resolved via user action. If true, proceed by calling
* {@link #getErrorDialog(Activity, int, int)} and showing the dialog.
*
* @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)}, or
* returned to your application via {@link OnConnectionFailedListener#onConnectionFailed(ConnectionResult)}
* @return true if the error is resolvable with {@link #getErrorDialog(Activity, int, int)}
*/
public final boolean isUserResolvableError(int errorCode) {
switch (errorCode) {
case SERVICE_MISSING:
case SERVICE_VERSION_UPDATE_REQUIRED:
case SERVICE_DISABLED:
case SERVICE_INVALID:
return true;
case SIGN_IN_REQUIRED:
case INVALID_ACCOUNT:
case RESOLUTION_REQUIRED:
case NETWORK_ERROR:
case INTERNAL_ERROR:
default:
return false;
}
}
/**
* Attempts to make Google Play services available on this device. If Play Services is already
* available, the returned {@link Task} may complete immediately.
* <p/>
* If it is necessary to display UI in order to complete this request (e.g. sending the user
* to the Google Play store) the passed {@link Activity} will be used to display this UI.
* <p/>
* It is recommended to call this method from {@link Activity#onCreate(Bundle)}.
* If the passed {@link Activity} completes before the returned {@link Task} completes, the
* Task will fail with a {@link java.util.concurrent.CancellationException}.
* <p/>
* This method must be called from the main thread.
*
* @return A {@link Task}. If this Task completes without throwing an exception, Play Services
* is available on this device.
*/
public Task<Void> makeGooglePlayServicesAvailable(Activity activity) {
int status = isGooglePlayServicesAvailable(activity);
if (status == SUCCESS) {
return Tasks.forResult(null);
}
// TODO
return Tasks.forResult(null);
}
/**
* Displays a DialogFragment for an error code returned by {@link #isGooglePlayServicesAvailable(Context)}.
*
* @param activity parent activity for creating the dialog, also used for identifying language to display dialog in.
* @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call.
* If errorCode is {@link ConnectionResult#SUCCESS} then null is returned.
* @param requestCode The requestCode given when calling startActivityForResult.
* @return true if the dialog is shown, false otherwise
* @throws RuntimeException if API level is below 11 and activity is not a {@link FragmentActivity}.
* @see ErrorDialogFragment
* @see SupportErrorDialogFragmet
*/
public boolean showErrorDialogFragment(Activity activity, int errorCode, int requestCode) {
return showErrorDialogFragment(activity, errorCode, requestCode, null);
}
/**
* Displays a DialogFragment for an error code returned by {@link #isGooglePlayServicesAvailable(Context)}.
*
* @param activity parent activity for creating the dialog, also used for identifying language to display dialog in.
* @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call.
* If errorCode is {@link ConnectionResult#SUCCESS} then null is returned.
* @param requestCode The requestCode given when calling startActivityForResult.
* @param cancelListener The {@link DialogInterface.OnCancelListener} to invoke if the dialog is canceled.
* @return true if the dialog is shown, false otherwise
* @throws RuntimeException if API level is below 11 and activity is not a {@link FragmentActivity}.
* @see ErrorDialogFragment
* @see SupportErrorDialogFragmet
*/
public boolean showErrorDialogFragment(Activity activity, int errorCode, int requestCode, DialogInterface.OnCancelListener cancelListener) {
Dialog dialog = getErrorDialog(activity, errorCode, requestCode, cancelListener);
if (dialog == null) {
return false;
} else {
// TODO
return false;
}
}
/**
* Displays a notification for an error code returned from
* {@link #isGooglePlayServicesAvailable(Context)}, if it is resolvable by the user.
* <p/>
* This method is similar to {@link #getErrorDialog(int, android.app.Activity, int)}, but is
* provided for background tasks that cannot or should not display dialogs.
*
* @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call.
* If errorCode is {@link ConnectionResult#SUCCESS} then null is returned.
* @param context used for identifying language to display dialog in as well as accessing the
* {@link android.app.NotificationManager}.
*/
public void showErrorNotification(Context context, int errorCode) {
if (errorCode == RESOLUTION_REQUIRED) {
Log.e(TAG, "showErrorNotification(context, errorCode) is called for RESOLUTION_REQUIRED when showErrorNotification(context, result) should be called");
}
if (isUserResolvableError(errorCode)) {
GooglePlayServicesUtil.showErrorNotification(errorCode, context);
}
}
/**
* Displays a notification for a connection failure, if it is resolvable by the user.
*
* @param context The calling context used to display the notification.
* @param result The connection failure. If successful or the error is not resolvable by the
* user, no notification is shown.
*/
public void showErrorNotification(Context context, ConnectionResult result) {
PendingIntent pendingIntent = getErrorResolutionPendingIntent(context, result);
if (pendingIntent != null) {
// TODO
}
}
}

View File

@ -23,6 +23,7 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.util.Log;
import org.microg.gms.common.Constants;
@ -35,69 +36,199 @@ import org.microg.gms.common.PublicApi;
* <p/>
* TODO: methods :)
*/
@PublicApi
public class GooglePlayServicesUtil {
private static final String TAG = "GooglePlayServicesUtil";
public static final String GMS_ERROR_DIALOG = "GooglePlayServicesErrorDialog";
/**
* Package name for Google Play services.
*/
@Deprecated
public static final String GOOGLE_PLAY_SERVICES_PACKAGE = Constants.GMS_PACKAGE_NAME;
/**
* Google Play services client library version (declared in library's AndroidManifest.xml android:versionCode).
*/
@Deprecated
public static final int GOOGLE_PLAY_SERVICES_VERSION_CODE = Constants.MAX_REFERENCE_VERSION;
/**
* Package name for Google Play Store.
*/
public static final String GOOGLE_PLAY_STORE_PACKAGE = "com.android.vending";
/**
* Returns a dialog to address the provided errorCode. The returned dialog displays a localized
* message about the error and upon user confirmation (by tapping on dialog) will direct them
* to the Play Store if Google Play services is out of date or missing, or to system settings
* if Google Play services is disabled on the device.
*
* @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call.
* If errorCode is {@link ConnectionResult#SUCCESS} then null is returned.
* @param activity parent activity for creating the dialog, also used for identifying
* language to display dialog in.
* @param requestCode The requestCode given when calling startActivityForResult.
*/
@Deprecated
public static Dialog getErrorDialog(int errorCode, Activity activity, int requestCode) {
return null; // TODO
return getErrorDialog(errorCode, activity, requestCode, null);
}
public static Dialog getErrorDialog(int errorCode, Activity activity, int requestCode,
DialogInterface.OnCancelListener cancelListener) {
return null; // TODO
/**
* Returns a dialog to address the provided errorCode. The returned dialog displays a localized
* message about the error and upon user confirmation (by tapping on dialog) will direct them
* to the Play Store if Google Play services is out of date or missing, or to system settings
* if Google Play services is disabled on the device.
*
* @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call.
* If errorCode is {@link ConnectionResult#SUCCESS} then null is returned.
* @param activity parent activity for creating the dialog, also used for identifying
* language to display dialog in.
* @param requestCode The requestCode given when calling startActivityForResult.
* @param cancelListener The {@link DialogInterface.OnCancelListener} to invoke if the dialog
* is canceled.
*/
@Deprecated
public static Dialog getErrorDialog(int errorCode, Activity activity, int requestCode, DialogInterface.OnCancelListener cancelListener) {
return GoogleApiAvailability.getInstance().getErrorDialog(activity, errorCode, requestCode, cancelListener);
}
/**
* Returns a PendingIntent to address the provided errorCode. It will direct them to one of the
* following places to either the Play Store if Google Play services is out of date or missing,
* or system settings if Google Play services is disabled on the device.
*
* @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call.
* If errorCode is {@link ConnectionResult#SUCCESS} then null is returned.
* @param activity parent context for creating the PendingIntent.
* @param requestCode The requestCode given when calling startActivityForResult.
*/
@Deprecated
public static PendingIntent getErrorPendingIntent(int errorCode, Activity activity,
int requestCode) {
int requestCode) {
return null; // TODO
}
/**
* Returns a human-readable string of the error code returned from {@link #isGooglePlayServicesAvailable(Context)}.
*/
@Deprecated
public static String getErrorString(int errorCode) {
return null; // TODO
}
/**
* Returns the open source software license information for the Google Play services
* application, or null if Google Play services is not available on this device.
*/
@Deprecated
public static String getOpenSourceSoftwareLicenseInfo(Context context) {
return null; // TODO
}
/**
* This gets the Context object of the Buddy APK. This loads the Buddy APK code from the Buddy
* APK into memory. This returned context can be used to create classes and obtain resources
* defined in the Buddy APK.
*
* @return The Context object of the Buddy APK or null if the Buddy APK is not installed on the device.
*/
public static Context getRemoteContext(Context context) {
return null; // TODO
}
/**
* This gets the Resources object of the Buddy APK.
*
* @return The Resources object of the Buddy APK or null if the Buddy APK is not installed on the device.
*/
public static Resources getRemoteResources(Context context) {
return null; // TODO
}
/**
* Verifies that Google Play services is installed and enabled on this device, and that the
* version installed on this device is no older than the one required by this client.
*
* @return status code indicating whether there was an error. Can be one of following in
* {@link ConnectionResult}: SUCCESS, SERVICE_MISSING, SERVICE_VERSION_UPDATE_REQUIRED,
* SERVICE_DISABLED, SERVICE_INVALID
*/
@Deprecated
public static int isGooglePlayServicesAvailable(Context context) {
Log.d(TAG, "As we can't know right now if the later desired feature is available, " +
"we just pretend it to be.");
return ConnectionResult.SUCCESS;
}
@Deprecated
public static boolean isGoogleSignedUid(PackageManager packageManager, int uid) {
return false; // TODO
}
/**
* Determines whether an error is user-recoverable. If true, proceed by calling
* {@link #getErrorDialog(int, Activity, int)} and showing the dialog.
*
* @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)}, or
* returned to your application via {@link com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener#onConnectionFailed(ConnectionResult)}
* @return true if the error is recoverable with {@link #getErrorDialog(int, Activity, int)}
*/
@Deprecated
public static boolean isUserRecoverableError(int errorCode) {
return false; // TODO
}
public static boolean showErrorDialogFragment(int errorCode, Activity activity,
int requestCode) {
/**
* Display a DialogFragment for an error code returned by {@link #isGooglePlayServicesAvailable(Context)}.
*
* @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call.
* If errorCode is {@link ConnectionResult#SUCCESS} then null is returned.
* @param activity parent activity for creating the dialog, also used for identifying
* language to display dialog in.
* @param requestCode The requestCode given when calling startActivityForResult.
* @return true if the dialog is shown, false otherwise
* @throws RuntimeException if API level is below 11 and activity is not a {@link android.support.v4.app.FragmentActivity}.
*/
@Deprecated
public static boolean showErrorDialogFragment(int errorCode, Activity activity, int requestCode) {
return showErrorDialogFragment(errorCode, activity, requestCode, null);
}
@Deprecated
public static boolean showErrorDialogFragment(int errorCode, Activity activity, Fragment fragment, int requestCode, DialogInterface.OnCancelListener cancelListener) {
return false; // TODO
}
public static boolean showErrorDialogFragment(int errorCode, Activity activity,
Fragment fragment, int requestCode, DialogInterface.OnCancelListener cancelListener) {
return false; // TODO
}
public static boolean showErrorDialogFragment(int errorCode, Activity activity, int requestCode,
DialogInterface.OnCancelListener cancelListener) {
return false; // TODO
/**
* @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call.
* If errorCode is {@link ConnectionResult#SUCCESS} then null is returned.
* @param activity parent activity for creating the dialog, also used for identifying
* language to display dialog in.
* @param requestCode The requestCode given when calling startActivityForResult.
* @param cancelListener The {@link DialogInterface.OnCancelListener} to invoke if the dialog
* is canceled.
* @return true if the dialog is shown, false otherwise.
* @throws RuntimeException if API level is below 11 and activity is not a {@link android.support.v4.app.FragmentActivity}.
*/
@Deprecated
public static boolean showErrorDialogFragment(int errorCode, Activity activity, int requestCode, DialogInterface.OnCancelListener cancelListener) {
return showErrorDialogFragment(errorCode, activity, null, requestCode, cancelListener);
}
/**
* Displays a notification relevant to the provided error code. This method is similar to
* {@link #getErrorDialog(int, android.app.Activity, int)}, but is provided for background
* tasks that cannot or shouldn't display dialogs.
*
* @param errorCode error code returned by {@link #isGooglePlayServicesAvailable(Context)} call.
* If errorCode is {@link ConnectionResult#SUCCESS} then null is returned.
* @param context used for identifying language to display dialog in as well as accessing the
* {@link android.app.NotificationManager}.
*/
@Deprecated
public static void showErrorNotification(int errorCode, Context context) {
// TODO
}

View File

@ -1,115 +0,0 @@
/*
* 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.
*/
package com.google.android.gms.gcm;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import java.io.IOException;
public class GoogleCloudMessaging {
/**
* The GCM {@link #register(String...)} and {@link #unregister()} methods are blocking. You
* should not run them in the main thread or in broadcast receivers.
*/
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 = "SERVICE_NOT_AVAILABLE";
/**
* Returned by {@link #getMessageType(Intent)} to indicate that the server deleted some
* pending messages because they were collapsible.
*/
public static final String MESSAGE_TYPE_DELETED = "deleted_messages";
/**
* Returned by {@link #getMessageType(Intent)} to indicate a regular message.
*/
public static final String MESSAGE_TYPE_MESSAGE = "gcm";
/**
* Returned by {@link #getMessageType(Intent)} to indicate a send error. The intent includes
* the message ID of the message and an error code.
*/
public static final String MESSAGE_TYPE_SEND_ERROR = "send_error";
/**
* Return the singleton instance of GCM.
*/
public static synchronized GoogleCloudMessaging getInstance(Context context) {
return null;
}
/**
* Must be called when your application is done using GCM, to release internal resources.
*/
public void close() {
}
/**
* 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:
* <ul>
* <li>{@link #MESSAGE_TYPE_MESSAGE}regular message from your server.</li>
* <li>{@link #MESSAGE_TYPE_DELETED}special status message indicating that some messages have been collapsed by GCM.</li>
* <li>{@link #MESSAGE_TYPE_SEND_ERROR}special status message indicating that there were errors sending one of the messages.</li>
* </ul>
* 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.
*
* @param intent
* @return
*/
public String getMessageType(Intent intent) {
return null;
}
public String register(String... senderIds) {
return null;
}
public void send(String to, String msgId, long timeToLive, Bundle data) {
}
public void send(String to, String msgId, Bundle data) {
}
/**
* Unregister the application. Calling unregister() stops any messages from the server.
* This is a blocking callyou 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 your registration ID, which you should never change
* unnecessarily. A better approach is to simply have your server stop sending messages.
* Only use unregister if you want to change your sender ID.
*
* @throws IOException if we can't connect to server to unregister.
*/
public void unregister() throws IOException {
}
}

1
play-services-cast-api Symbolic link
View File

@ -0,0 +1 @@
extern/GmsApi/play-services-cast-api/

View File

@ -19,15 +19,24 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0'
classpath 'com.android.tools.build:gradle:2.1.2'
}
}
apply plugin: 'com.android.library'
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)
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
buildToolsVersion "23.0.3"
defaultConfig {
versionName getMyVersionName()
@ -40,4 +49,5 @@ android {
dependencies {
compile project(':play-services-base')
compile project(':play-services-cast-api')
}

1
play-services-common-api Symbolic link
View File

@ -0,0 +1 @@
extern/GmsApi/play-services-common-api/

View File

@ -19,15 +19,24 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0'
classpath 'com.android.tools.build:gradle:2.1.2'
}
}
apply plugin: 'com.android.library'
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)
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
buildToolsVersion "23.0.3"
defaultConfig {
versionName getMyVersionName()
@ -39,5 +48,6 @@ android {
}
dependencies {
compile project(':play-services-base')
compile project(':play-services-iid')
// compile project(':play-services-measurement')
}

View File

@ -20,5 +20,12 @@
<uses-sdk android:minSdkVersion="9" />
<!-- Permissions required for GCM -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- Move to play-services-measurement -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application />
</manifest>

View File

@ -90,8 +90,9 @@ public class GcmPubSub {
if (TextUtils.isEmpty(registrationToken))
throw new IllegalArgumentException("No registration token!");
if (TextUtils.isEmpty(topic) || !topicPattern.matcher(topic).matches())
throw new IllegalArgumentException("Invalid topic!");
throw new IllegalArgumentException("Invalid topic: " + topic);
if (extras == null) extras = new Bundle();
extras.putString(EXTRA_TOPIC, topic);
instanceId.getToken(registrationToken, topic, extras);
}
@ -110,7 +111,7 @@ public class GcmPubSub {
*/
public void unsubscribe(String registrationToken, String topic) throws IOException {
if (TextUtils.isEmpty(topic) || !topicPattern.matcher(topic).matches())
throw new IllegalArgumentException("Invalid topic!");
throw new IllegalArgumentException("Invalid topic: " + topic);
Bundle extras = new Bundle();
extras.putString(EXTRA_TOPIC, topic);

View File

@ -16,9 +16,25 @@
package com.google.android.gms.gcm;
import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.support.v4.content.WakefulBroadcastReceiver;
import android.util.Base64;
import android.util.Log;
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_RAWDATA;
import static org.microg.gms.gcm.GcmConstants.EXTRA_RAWDATA_BASE64;
import static org.microg.gms.gcm.GcmConstants.GCMID_INSTANCE_ID;
import static org.microg.gms.gcm.GcmConstants.GCMID_REFRESH;
/**
* <code>WakefulBroadcastReceiver</code> that receives GCM messages and delivers them to an
@ -41,9 +57,71 @@ import android.support.v4.content.WakefulBroadcastReceiver;
* services. This prevents other apps from invoking the broadcast receiver.
*/
public class GcmReceiver extends WakefulBroadcastReceiver {
private static final String TAG = "GcmReceiver";
public void onReceive(Context context, Intent intent) {
throw new UnsupportedOperationException();
sanitizeIntent(context, intent);
enforceIntentClassName(context, intent);
sendIntent(context, intent);
if (getResultCode() == 0) setResultCodeIfOrdered(-1);
}
private void sanitizeIntent(Context context, Intent intent) {
intent.setComponent(null);
intent.setPackage(context.getPackageName());
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
intent.removeCategory(context.getPackageName());
}
String from = intent.getStringExtra(EXTRA_FROM);
if (ACTION_C2DM_REGISTRATION.equals(intent.getAction()) || GCMID_INSTANCE_ID.equals(from) || GCMID_REFRESH.equals(from)) {
intent.setAction(ACTION_INSTANCE_ID);
}
String base64encoded = intent.getStringExtra(EXTRA_RAWDATA_BASE64);
if (base64encoded != null) {
intent.putExtra(EXTRA_RAWDATA, Base64.decode(base64encoded, Base64.DEFAULT));
intent.removeExtra(EXTRA_RAWDATA_BASE64);
}
}
private void enforceIntentClassName(Context context, Intent intent) {
ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent, 0);
if (resolveInfo == null || resolveInfo.serviceInfo == null) {
Log.e(TAG, "Failed to resolve target intent service, skipping classname enforcement");
return;
}
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
if (!context.getPackageName().equals(serviceInfo.packageName) || serviceInfo.name == null) {
Log.e(TAG, "Error resolving target intent service, skipping classname enforcement. Resolved service was: " + serviceInfo.packageName + "/" + serviceInfo.name);
return;
}
intent.setClassName(context, serviceInfo.name.startsWith(".") ? (context.getPackageName() + serviceInfo.name) : serviceInfo.name);
}
private void sendIntent(Context context, Intent intent) {
setResultCodeIfOrdered(500);
try {
ComponentName startedComponent;
if (context.checkCallingOrSelfPermission(Manifest.permission.WAKE_LOCK) == PackageManager.PERMISSION_GRANTED) {
startedComponent = startWakefulService(context, intent);
} else {
Log.d(TAG, "Missing wake lock permission, service start may be delayed");
startedComponent = context.startService(intent);
}
if (startedComponent == null) {
Log.e(TAG, "Error while delivering the message: ServiceIntent not found.");
setResultCodeIfOrdered(404);
} else {
setResultCodeIfOrdered(-1);
}
} catch (SecurityException e) {
Log.e(TAG, "Error while delivering the message to the serviceIntent", e);
setResultCodeIfOrdered(401);
}
}
private void setResultCodeIfOrdered(int code) {
if (isOrderedBroadcast()) {
setResultCode(code);
}
}
}

View File

@ -16,7 +16,6 @@
package com.google.android.gms.gcm;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@ -26,21 +25,23 @@ import android.text.TextUtils;
import com.google.android.gms.iid.InstanceID;
import org.microg.gms.common.PublicApi;
import org.microg.gms.gcm.CloudMessagingRpc;
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_ERROR;
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_REGISTRATION_ID;
import static org.microg.gms.gcm.GcmConstants.EXTRA_SENDER;
import static org.microg.gms.gcm.GcmConstants.EXTRA_SENDER_LEGACY;
import static org.microg.gms.gcm.GcmConstants.EXTRA_SEND_FROM;
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
@ -123,43 +124,33 @@ public class GoogleCloudMessaging {
@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 static GoogleCloudMessaging instance;
private CloudMessagingRpc rpc;
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;
public synchronized void close() {
instance = null;
rpc.close();
}
/**
* Return the singleton instance of GCM.
*/
public static GoogleCloudMessaging getInstance(Context context) {
if (INSTANCE == null) {
INSTANCE = new GoogleCloudMessaging();
INSTANCE.context = context.getApplicationContext();
if (instance == null) {
instance = new GoogleCloudMessaging();
instance.context = context.getApplicationContext();
instance.rpc = new CloudMessagingRpc(instance.context);
}
return INSTANCE;
return instance;
}
/**
@ -204,16 +195,23 @@ public class GoogleCloudMessaging {
@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);
String sender = sb.toString();
if (isLegacyFallback()) {
Bundle extras = new Bundle();
extras.putString(EXTRA_SENDER_LEGACY, sender);
return InstanceID.getInstance(context).getToken(sb.toString(), INSTANCE_ID_SCOPE, extras);
} else {
Bundle extras = new Bundle();
extras.putString(EXTRA_SENDER, sender);
return rpc.handleRegisterMessageResult(rpc.sendRegisterMessageBlocking(extras));
}
}
/**
@ -242,16 +240,27 @@ public class GoogleCloudMessaging {
*/
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);
if (isLegacyFallback()) {
Bundle extras = new Bundle();
for (String key : data.keySet()) {
Object o = extras.get(key);
if (o instanceof String) {
extras.putString("gcm." + key, (String) o);
}
}
extras.putString(EXTRA_SEND_TO, to);
extras.putString(EXTRA_MESSAGE_ID, msgId);
InstanceID.getInstance(context).requestToken("GCM", "upstream", extras);
} else {
Bundle extras = data != null ? new Bundle(data) : new Bundle();
extras.putString(EXTRA_SEND_TO, to);
extras.putString(EXTRA_SEND_FROM, getFrom(to));
extras.putString(EXTRA_MESSAGE_ID, msgId);
extras.putLong(EXTRA_TTL, timeToLive);
extras.putInt(EXTRA_DELAY, -1);
rpc.sendGcmMessage(extras);
}
}
/**
@ -298,4 +307,16 @@ public class GoogleCloudMessaging {
InstanceID.getInstance(context).deleteInstanceID();
}
private boolean isLegacyFallback() {
String gcmPackageName = CloudMessagingRpc.getGcmPackageName(context);
return gcmPackageName != null && gcmPackageName.endsWith(".gsf");
}
private String getFrom(String to) {
int i = to.indexOf('@');
if (i > 0) {
to = to.substring(0, i);
}
return InstanceID.getInstance(context).getStore().get("", to, INSTANCE_ID_SCOPE);
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
public class PendingCallback implements Parcelable {
private final IBinder binder;
public PendingCallback(IBinder binder) {
this.binder = binder;
}
private PendingCallback(Parcel in) {
this.binder = in.readStrongBinder();
}
public IBinder getBinder() {
return binder;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStrongBinder(binder);
}
public static final Creator<PendingCallback> CREATOR = new Creator<PendingCallback>() {
@Override
public PendingCallback createFromParcel(Parcel source) {
return new PendingCallback(source);
}
@Override
public PendingCallback[] newArray(int size) {
return new PendingCallback[size];
}
};
}

View File

@ -0,0 +1,165 @@
/*
* 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 org.microg.gms.gcm;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;
import com.google.android.gms.iid.InstanceID;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.google.android.gms.gcm.GoogleCloudMessaging.ERROR_SERVICE_NOT_AVAILABLE;
import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME;
import static org.microg.gms.common.Constants.GSF_PACKAGE_NAME;
import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_REGISTER;
import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_REGISTRATION;
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_ERROR;
import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSAGE_ID;
import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSENGER;
import static org.microg.gms.gcm.GcmConstants.EXTRA_REGISTRATION_ID;
import static org.microg.gms.gcm.GcmConstants.EXTRA_UNREGISTERED;
import static org.microg.gms.gcm.GcmConstants.PERMISSION_GTALK;
import static org.microg.gms.gcm.GcmConstants.PERMISSION_RECEIVE;
public class CloudMessagingRpc {
private static final AtomicInteger messageIdCounter = new AtomicInteger(1);
private static String gcmPackageName;
private final BlockingQueue<Intent> messengerResponseQueue = new LinkedBlockingQueue<Intent>();
private final Messenger messenger = new Messenger(new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg == null || !(msg.obj instanceof Intent)) {
// Invalid message -> drop
return;
}
Intent intent = (Intent) msg.obj;
if (ACTION_C2DM_REGISTRATION.equals(intent.getAction())) {
messengerResponseQueue.add(intent);
}
}
});
/**
* 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 CloudMessagingRpc(Context context) {
this.context = context;
}
public static String getGcmPackageName(Context context) {
if (gcmPackageName != null) {
return gcmPackageName;
}
PackageManager packageManager = context.getPackageManager();
for (ResolveInfo resolveInfo : packageManager.queryIntentServices(new Intent(ACTION_C2DM_REGISTER), 0)) {
if (packageManager.checkPermission(PERMISSION_RECEIVE, resolveInfo.serviceInfo.packageName) == PERMISSION_GRANTED) {
return gcmPackageName = resolveInfo.serviceInfo.packageName;
}
}
try {
ApplicationInfo appInfo = packageManager.getApplicationInfo(GMS_PACKAGE_NAME, 0);
return gcmPackageName = appInfo.packageName;
} catch (PackageManager.NameNotFoundException ignored) {
}
try {
ApplicationInfo appInfo = packageManager.getApplicationInfo(GSF_PACKAGE_NAME, 0);
return gcmPackageName = appInfo.packageName;
} catch (PackageManager.NameNotFoundException ex3) {
return null;
}
}
public void close() {
// Cancel the authentication
if (selfAuthIntent != null) {
selfAuthIntent.cancel();
selfAuthIntent = null;
}
}
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;
}
public Intent sendRegisterMessageBlocking(Bundle extras) throws IOException {
sendRegisterMessage(extras);
Intent resultIntent;
try {
resultIntent = messengerResponseQueue.poll(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new IOException(e.getMessage());
}
if (resultIntent == null) {
throw new IOException(ERROR_SERVICE_NOT_AVAILABLE);
}
return resultIntent;
}
private void sendRegisterMessage(Bundle extras) {
Intent intent = new Intent(ACTION_C2DM_REGISTER);
intent.setPackage(getGcmPackageName(context));
extras.putString(EXTRA_MESSAGE_ID, "google.rpc" + messageIdCounter.getAndIncrement());
intent.putExtras(extras);
intent.putExtra(EXTRA_MESSENGER, messenger);
intent.putExtra(EXTRA_APP, getSelfAuthIntent());
context.startService(intent);
}
public void sendGcmMessage(Bundle extras) {
Intent intent = new Intent(ACTION_GCM_SEND);
intent.setPackage(GMS_PACKAGE_NAME);
intent.putExtras(extras);
intent.putExtra(EXTRA_APP, getSelfAuthIntent());
context.sendOrderedBroadcast(intent, PERMISSION_GTALK);
}
public String handleRegisterMessageResult(Intent resultIntent) throws IOException {
if (resultIntent == null) throw new IOException(InstanceID.ERROR_SERVICE_NOT_AVAILABLE);
String result = resultIntent.getStringExtra(EXTRA_REGISTRATION_ID);
if (result == null) result = resultIntent.getStringExtra(EXTRA_UNREGISTERED);
if (result != null) return result;
result = resultIntent.getStringExtra(EXTRA_ERROR);
throw new IOException(result != null ? result : InstanceID.ERROR_SERVICE_NOT_AVAILABLE);
}
}

1
play-services-iid-api Symbolic link
View File

@ -0,0 +1 @@
extern/GmsApi/play-services-iid-api

View File

@ -0,0 +1,53 @@
/*
* 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.1.2'
}
}
apply plugin: 'com.android.library'
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)
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
versionName getMyVersionName()
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
}
}
dependencies {
compile project(':play-services-base')
compile project(':play-services-iid-api')
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.microg.gms.iid">
<uses-sdk android:minSdkVersion="9" />
<!-- Permissions required for IID -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.INTERNET" />
<application />
</manifest>

View File

@ -19,23 +19,46 @@ package com.google.android.gms.iid;
import android.content.Context;
import android.os.Bundle;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import org.microg.gms.common.PublicApi;
import org.microg.gms.gcm.GcmConstants;
import org.microg.gms.iid.InstanceIdRpc;
import org.microg.gms.iid.InstanceIdStore;
import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import static org.microg.gms.gcm.GcmConstants.EXTRA_DELETE;
import static org.microg.gms.gcm.GcmConstants.EXTRA_SCOPE;
import static org.microg.gms.gcm.GcmConstants.EXTRA_SENDER;
import static org.microg.gms.gcm.GcmConstants.EXTRA_SUBSCIPTION;
import static org.microg.gms.gcm.GcmConstants.EXTRA_SUBTYPE;
/**
* Instance ID provides a unique identifier for each app instance and a mechanism
* to authenticate and authorize actions (for example, sending a GCM message).
* <p/>
* Instance ID is stable but may become invalid, if:
* [...]
* <ul>
* <li>App deletes Instance ID</li>
* <li>Device is factory reset</li>
* <li>User uninstalls the app</li>
* <li>User clears app data</li>
* </ul>
* 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)}.
*/
@PublicApi
public class InstanceID {
/**
* Error returned when failed requests are retried too often. Use
@ -65,13 +88,31 @@ public class InstanceID {
*/
public static final String ERROR_TIMEOUT = "TIMEOUT";
private static final int RSA_KEY_SIZE = 2048;
private static final String TAG = "InstanceID";
private static InstanceIdStore storeInstance;
private static InstanceIdRpc rpc;
private static Map<String, InstanceID> instances = new HashMap<String, InstanceID>();
private final String subtype;
private KeyPair keyPair;
private long creationTime;
private InstanceID(String subtype) {
this.subtype = subtype == null ? "" : subtype;
}
/**
* Resets Instance ID and revokes all tokens.
*
* @throws IOException
*/
public void deleteInstanceID() throws IOException {
throw new UnsupportedOperationException();
deleteToken("*", "*");
creationTime = 0;
storeInstance.delete(subtype + "|");
keyPair = null;
}
/**
@ -85,12 +126,25 @@ public class InstanceID {
* @throws IOException if the request fails.
*/
public void deleteToken(String authorizedEntity, String scope) throws IOException {
deleteToken(authorizedEntity, scope, new Bundle());
deleteToken(authorizedEntity, scope, null);
}
@PublicApi(exclude = true)
public void deleteToken(String authorizedEntity, String scope, Bundle extras) throws IOException {
throw new UnsupportedOperationException();
if (Looper.getMainLooper() == Looper.myLooper()) throw new IOException(ERROR_MAIN_THREAD);
storeInstance.delete(subtype, authorizedEntity, scope);
if (extras == null) extras = new Bundle();
extras.putString(EXTRA_SENDER, authorizedEntity);
extras.putString(EXTRA_SUBSCIPTION, authorizedEntity);
extras.putString(EXTRA_DELETE, "1");
extras.putString("X-" + EXTRA_DELETE, "1");
extras.putString(EXTRA_SUBTYPE, TextUtils.isEmpty(subtype) ? authorizedEntity : subtype);
extras.putString("X-" + EXTRA_SUBTYPE, TextUtils.isEmpty(subtype) ? authorizedEntity : subtype);
if (scope != null) extras.putString(EXTRA_SCOPE, scope);
rpc.handleRegisterMessageResult(rpc.sendRegisterMessageBlocking(extras, getKeyPair()));
}
/**
@ -99,7 +153,13 @@ public class InstanceID {
* @return Time when instance ID was created (milliseconds since Epoch).
*/
public long getCreationTime() {
throw new UnsupportedOperationException();
if (creationTime == 0) {
String s = storeInstance.get(subtype, "cre");
if (s != null) {
creationTime = Long.parseLong(s);
}
}
return creationTime;
}
/**
@ -108,7 +168,7 @@ public class InstanceID {
* @return The identifier for the application instance.
*/
public String getId() {
throw new UnsupportedOperationException();
return sha1KeyPair(getKeyPair());
}
/**
@ -117,7 +177,17 @@ public class InstanceID {
* @return InstanceID instance.
*/
public static InstanceID getInstance(Context context) {
throw new UnsupportedOperationException();
String subtype = "";
if (storeInstance == null) {
storeInstance = new InstanceIdStore(context.getApplicationContext());
rpc = new InstanceIdRpc(context.getApplicationContext());
}
InstanceID instance = instances.get(subtype);
if (instance == null) {
instance = new InstanceID(subtype);
instances.put(subtype, instance);
}
return instance;
}
/**
@ -163,4 +233,43 @@ public class InstanceID {
return getToken(authorizedEntity, scope, null);
}
@PublicApi(exclude = true)
public InstanceIdStore getStore() {
return storeInstance;
}
@PublicApi(exclude = true)
public String requestToken(String authorizedEntity, String scope, Bundle extras) {
throw new UnsupportedOperationException();
}
private synchronized KeyPair getKeyPair() {
if (keyPair == null) {
keyPair = storeInstance.getKeyPair(subtype);
if (keyPair == null) {
try {
KeyPairGenerator rsaGenerator = KeyPairGenerator.getInstance("RSA");
rsaGenerator.initialize(RSA_KEY_SIZE);
keyPair = rsaGenerator.generateKeyPair();
creationTime = System.currentTimeMillis();
storeInstance.put(subtype, keyPair, creationTime);
} catch (NoSuchAlgorithmException e) {
Log.w(TAG, e);
}
}
}
return keyPair;
}
@PublicApi(exclude = true)
public static String sha1KeyPair(KeyPair keyPair) {
try {
byte[] digest = MessageDigest.getInstance("SHA1").digest(keyPair.getPublic().getEncoded());
digest[0] = (byte) (112 + (0xF & digest[0]) & 0xFF);
return Base64.encodeToString(digest, 0, 8, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
} catch (NoSuchAlgorithmException e) {
Log.w(TAG, e);
return null;
}
}
}

View File

@ -25,8 +25,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import com.google.android.gms.gcm.GcmReceiver;
import android.support.v4.content.WakefulBroadcastReceiver;
import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_REGISTRATION;
import static org.microg.gms.gcm.GcmConstants.ACTION_INSTANCE_ID;
@ -104,7 +103,8 @@ public class InstanceIDListenerService extends Service {
handleIntent(intent);
if (intent.hasExtra(EXTRA_FROM)) GcmReceiver.completeWakefulIntent(intent);
if (intent.hasExtra(EXTRA_FROM))
WakefulBroadcastReceiver.completeWakefulIntent(intent);
}
} finally {
stop();

View File

@ -0,0 +1,423 @@
/*
* 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 org.microg.gms.iid;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import com.google.android.gms.iid.InstanceID;
import com.google.android.gms.iid.MessengerCompat;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.google.android.gms.iid.InstanceID.ERROR_BACKOFF;
import static com.google.android.gms.iid.InstanceID.ERROR_MISSING_INSTANCEID_SERVICE;
import static com.google.android.gms.iid.InstanceID.ERROR_SERVICE_NOT_AVAILABLE;
import static com.google.android.gms.iid.InstanceID.ERROR_TIMEOUT;
import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME;
import static org.microg.gms.common.Constants.GSF_PACKAGE_NAME;
import static org.microg.gms.common.Constants.MAX_REFERENCE_VERSION;
import static org.microg.gms.gcm.GcmConstants.ACTION_C2DM_REGISTER;
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_APP;
import static org.microg.gms.gcm.GcmConstants.EXTRA_APP_ID;
import static org.microg.gms.gcm.GcmConstants.EXTRA_APP_VERSION_CODE;
import static org.microg.gms.gcm.GcmConstants.EXTRA_APP_VERSION_NAME;
import static org.microg.gms.gcm.GcmConstants.EXTRA_CLIENT_VERSION;
import static org.microg.gms.gcm.GcmConstants.EXTRA_ERROR;
import static org.microg.gms.gcm.GcmConstants.EXTRA_GMS_VERSION;
import static org.microg.gms.gcm.GcmConstants.EXTRA_GSF_INTENT;
import static org.microg.gms.gcm.GcmConstants.EXTRA_IS_MESSENGER2;
import static org.microg.gms.gcm.GcmConstants.EXTRA_KID;
import static org.microg.gms.gcm.GcmConstants.EXTRA_MESSENGER;
import static org.microg.gms.gcm.GcmConstants.EXTRA_OS_VERSION;
import static org.microg.gms.gcm.GcmConstants.EXTRA_PUBLIC_KEY;
import static org.microg.gms.gcm.GcmConstants.EXTRA_REGISTRATION_ID;
import static org.microg.gms.gcm.GcmConstants.EXTRA_SIGNATURE;
import static org.microg.gms.gcm.GcmConstants.EXTRA_UNREGISTERED;
import static org.microg.gms.gcm.GcmConstants.EXTRA_USE_GSF;
import static org.microg.gms.gcm.GcmConstants.PERMISSION_RECEIVE;
public class InstanceIdRpc {
private static final String TAG = "InstanceID/Rpc";
private static final int BLOCKING_WAIT_TIME = 30000;
private static String iidPackageName;
private static int lastRequestId;
private static int retryCount;
private static Map<String, Object> blockingResponses = new HashMap<String, Object>();
private long nextAttempt;
private int interval;
private Context context;
private PendingIntent selfAuthToken;
private Messenger messenger;
private Messenger myMessenger;
private MessengerCompat messengerCompat;
public InstanceIdRpc(Context context) {
this.context = context;
}
public static String getIidPackageName(Context context) {
if (iidPackageName != null) {
return iidPackageName;
}
PackageManager packageManager = context.getPackageManager();
for (ResolveInfo resolveInfo : packageManager.queryIntentServices(new Intent(ACTION_C2DM_REGISTER), 0)) {
if (packageManager.checkPermission(PERMISSION_RECEIVE, resolveInfo.serviceInfo.packageName) == PERMISSION_GRANTED) {
return iidPackageName = resolveInfo.serviceInfo.packageName;
}
}
try {
ApplicationInfo appInfo = packageManager.getApplicationInfo(GMS_PACKAGE_NAME, 0);
return iidPackageName = appInfo.packageName;
} catch (PackageManager.NameNotFoundException ignored) {
}
try {
ApplicationInfo appInfo = packageManager.getApplicationInfo(GSF_PACKAGE_NAME, 0);
return iidPackageName = appInfo.packageName;
} catch (PackageManager.NameNotFoundException ex3) {
Log.w(TAG, "Both Google Play Services and legacy GSF package are missing");
return null;
}
}
private static int getGmsVersionCode(final Context context) {
final PackageManager packageManager = context.getPackageManager();
try {
return packageManager.getPackageInfo(getIidPackageName(context), 0).versionCode;
} catch (PackageManager.NameNotFoundException ex) {
return -1;
}
}
private static int getSelfVersionCode(final Context context) {
try {
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
} catch (PackageManager.NameNotFoundException neverHappens) {
return 0;
}
}
private static String getSelfVersionName(final Context context) {
try {
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException neverHappens) {
return null;
}
}
void initialize() {
if (myMessenger != null) return;
getIidPackageName(context);
myMessenger = new Messenger(new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg == null) {
return;
}
if (msg.obj instanceof Intent) {
Intent intent = (Intent) msg.obj;
intent.setExtrasClassLoader(MessengerCompat.class.getClassLoader());
if (intent.hasExtra(EXTRA_MESSENGER)) {
Parcelable messengerCandidate = intent.getParcelableExtra(EXTRA_MESSENGER);
if (messengerCandidate instanceof MessengerCompat) {
messengerCompat = (MessengerCompat) messengerCandidate;
} else if (messengerCandidate instanceof Messenger) {
messenger = (Messenger) messengerCandidate;
}
}
handleResponseInternal(intent);
} else {
Log.w(TAG, "Dropping invalid message");
}
}
});
}
public void handleResponseInternal(Intent resultIntent) {
if (resultIntent == null) return;
if (!ACTION_C2DM_REGISTRATION.equals(resultIntent.getAction()) && !ACTION_INSTANCE_ID.equals(resultIntent.getAction()))
return;
String result = resultIntent.getStringExtra(EXTRA_REGISTRATION_ID);
if (result == null) result = resultIntent.getStringExtra(EXTRA_UNREGISTERED);
if (result == null) {
handleError(resultIntent);
return;
}
retryCount = 0;
nextAttempt = 0;
interval = 0;
String requestId = null;
if (result.startsWith("|")) {
// parse structured response
String[] split = result.split("\\|");
if (!"ID".equals(split[1])) {
Log.w(TAG, "Unexpected structured response " + result);
}
requestId = split[2];
if (split.length > 4) {
if ("SYNC".equals(split[3])) {
// TODO: sync
} else if("RST".equals(split[3])) {
// TODO: rst
resultIntent.removeExtra(EXTRA_REGISTRATION_ID);
return;
}
}
result = split[split.length-1];
if (result.startsWith(":"))
result = result.substring(1);
resultIntent.putExtra(EXTRA_REGISTRATION_ID, result);
}
setResponse(requestId, resultIntent);
}
private void handleError(Intent resultIntent) {
String error = resultIntent.getStringExtra("error");
if (error == null) return;
String requestId = null;
if (error.startsWith("|")) {
// parse structured error message
String[] split = error.split("\\|");
if (!"ID".equals(split[1])) {
Log.w(TAG, "Unexpected structured response " + error);
}
if (split.length > 2) {
requestId = split[2];
error = split[3];
if (error.startsWith(":"))
error = error.substring(1);
} else {
error = "UNKNOWN";
}
resultIntent.putExtra("error", error);
}
setResponse(requestId, resultIntent);
long retryAfter = resultIntent.getLongExtra("Retry-After", 0);
if (retryAfter > 0) {
interval = (int) (retryAfter * 1000);
nextAttempt = SystemClock.elapsedRealtime() + interval;
Log.d(TAG, "Server requested retry delay: " + interval);
} else if (ERROR_SERVICE_NOT_AVAILABLE.equals(error) || "AUTHENTICATION_FAILED".equals(error)
&& GSF_PACKAGE_NAME.equals(getIidPackageName(context))) {
retryCount++;
if (retryCount < 3) return;
if (retryCount == 3) interval = 1000 + new Random().nextInt(1000);
interval = interval * 2;
nextAttempt = SystemClock.elapsedRealtime() + interval;
Log.d(TAG, "Setting retry delay to " + interval);
}
}
private synchronized PendingIntent getSelfAuthToken() {
if (selfAuthToken == null) {
Intent intent = new Intent();
intent.setPackage("com.google.example.invalidpackage");
selfAuthToken = PendingIntent.getBroadcast(context, 0, intent, 0);
}
return selfAuthToken;
}
private static synchronized String getRequestId() {
return Integer.toString(lastRequestId++);
}
private void sendRegisterMessage(Bundle data, KeyPair keyPair, String requestId) throws IOException {
long elapsedRealtime = SystemClock.elapsedRealtime();
if (nextAttempt != 0 && elapsedRealtime <= nextAttempt) {
Log.w(TAG, "Had to wait for " + interval + ", that's still " + (nextAttempt - elapsedRealtime));
throw new IOException(ERROR_BACKOFF);
}
initialize();
if (iidPackageName == null) {
throw new IOException(ERROR_MISSING_INSTANCEID_SERVICE);
}
Intent intent = new Intent(ACTION_C2DM_REGISTER);
intent.setPackage(iidPackageName);
data.putString(EXTRA_GMS_VERSION, Integer.toString(getGmsVersionCode(context)));
data.putString(EXTRA_OS_VERSION, Integer.toString(Build.VERSION.SDK_INT));
data.putString(EXTRA_APP_VERSION_CODE, Integer.toString(getSelfVersionCode(context)));
data.putString(EXTRA_APP_VERSION_NAME, getSelfVersionName(context));
data.putString(EXTRA_CLIENT_VERSION, "iid-" + MAX_REFERENCE_VERSION);
data.putString(EXTRA_APP_ID, InstanceID.sha1KeyPair(keyPair));
String pub = base64encode(keyPair.getPublic().getEncoded());
data.putString(EXTRA_PUBLIC_KEY, pub);
data.putString(EXTRA_SIGNATURE, sign(keyPair, context.getPackageName(), pub));
intent.putExtras(data);
intent.putExtra(EXTRA_APP, getSelfAuthToken());
sendRequest(intent, requestId);
}
private static String sign(KeyPair keyPair, String... payload) {
byte[] bytes;
try {
bytes = TextUtils.join("\n", payload).getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "Unable to encode", e);
return null;
}
PrivateKey privateKey = keyPair.getPrivate();
try {
Signature signature = Signature.getInstance(privateKey instanceof RSAPrivateKey ? "SHA256withRSA" : "SHA256withECDSA");
signature.initSign(privateKey);
signature.update(bytes);
return base64encode(signature.sign());
} catch (GeneralSecurityException e) {
Log.e(TAG, "Unable to sign", e);
return null;
}
}
private static String base64encode(byte[] bytes) {
return Base64.encodeToString(bytes, Base64.URL_SAFE + Base64.NO_PADDING + Base64.NO_WRAP);
}
private void sendRequest(Intent intent, String requestId) {
intent.putExtra(EXTRA_KID, "|ID|" + requestId + "|");
intent.putExtra("X-" + EXTRA_KID, "|ID|" + requestId + "|");
Log.d(TAG, "Sending " + intent.getExtras());
if (messenger != null) {
intent.putExtra(EXTRA_MESSENGER, myMessenger);
Message msg = Message.obtain();
msg.obj = intent;
try {
messenger.send(msg);
return;
} catch (RemoteException e) {
Log.d(TAG, "Messenger failed, falling back to service");
}
}
boolean useGsf = iidPackageName.endsWith(".gsf");
if (intent.hasExtra(EXTRA_USE_GSF))
useGsf = "1".equals(intent.getStringExtra(EXTRA_USE_GSF));
if (useGsf) {
Intent holder = new Intent(ACTION_INSTANCE_ID);
holder.setPackage(context.getPackageName());
holder.putExtra(EXTRA_GSF_INTENT, intent);
context.startService(holder);
} else {
intent.putExtra(EXTRA_MESSENGER, myMessenger);
intent.putExtra(EXTRA_IS_MESSENGER2, "1");
if (messengerCompat != null) {
Message msg = Message.obtain();
msg.obj = intent;
try {
messengerCompat.send(msg);
return;
} catch (RemoteException e) {
Log.d(TAG, "Messenger failed, falling back to service");
}
}
context.startService(intent);
}
}
public Intent sendRegisterMessageBlocking(Bundle data, KeyPair keyPair) throws IOException {
Intent intent = sendRegisterMessageBlockingInternal(data, keyPair);
if (intent != null && intent.hasExtra(EXTRA_MESSENGER)) {
// Now with a messenger
intent = sendRegisterMessageBlockingInternal(data, keyPair);
}
return intent;
}
private Intent sendRegisterMessageBlockingInternal(Bundle data, KeyPair keyPair) throws IOException {
ConditionVariable cv = new ConditionVariable();
String requestId = getRequestId();
synchronized (InstanceIdRpc.class) {
blockingResponses.put(requestId, cv);
}
sendRegisterMessage(data, keyPair, requestId);
cv.block(BLOCKING_WAIT_TIME);
synchronized (InstanceIdRpc.class) {
Object res = blockingResponses.remove(requestId);
if (res instanceof Intent) {
return (Intent) res;
} else if (res instanceof String) {
throw new IOException((String) res);
}
Log.w(TAG, "No response " + res);
throw new IOException(ERROR_TIMEOUT);
}
}
public String handleRegisterMessageResult(Intent resultIntent) throws IOException {
if (resultIntent == null) throw new IOException(ERROR_SERVICE_NOT_AVAILABLE);
String result = resultIntent.getStringExtra(EXTRA_REGISTRATION_ID);
if (result == null) result = resultIntent.getStringExtra(EXTRA_UNREGISTERED);
if (result != null) return result;
result = resultIntent.getStringExtra(EXTRA_ERROR);
throw new IOException(result != null ? result : ERROR_SERVICE_NOT_AVAILABLE);
}
private void setResponse(String requestId, Object response) {
if (requestId == null) {
for (String r : blockingResponses.keySet()) {
setResponse(r, response);
}
}
Object old = blockingResponses.get(requestId);
blockingResponses.put(requestId, response);
if (old instanceof ConditionVariable) {
((ConditionVariable) old).open();
} else if (old instanceof Messenger) {
Message msg = Message.obtain();
msg.obj = response;
try {
((Messenger) old).send(msg);
} catch (RemoteException e) {
Log.w(TAG, "Failed to send response", e);
}
}
}
}

View File

@ -0,0 +1,111 @@
/*
* 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 org.microg.gms.iid;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;
import android.util.Log;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class InstanceIdStore {
private static final String TAG = "InstanceID/Store";
private static final String PREF_NAME = "com.google.android.gms.appid";
private Context context;
private SharedPreferences sharedPreferences;
public InstanceIdStore(Context context) {
this.context = context;
this.sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
public synchronized String get(String key) {
return sharedPreferences.getString(key, null);
}
public String get(String subtype, String key) {
return get(subtype + "|S|" + key);
}
public String get(String subtype, String authorizedEntity, String scope) {
return get(subtype + "|T|" + authorizedEntity + "|" + scope);
}
public KeyPair getKeyPair(String subtype) {
String pub = get(subtype, "|P|");
String priv = get(subtype, "|K|");
if (pub == null || priv == null) {
return null;
}
try {
byte[] pubKey = Base64.decode(pub, Base64.URL_SAFE);
byte[] privKey = Base64.decode(priv, Base64.URL_SAFE);
KeyFactory rsaFactory = KeyFactory.getInstance("RSA");
return new KeyPair(rsaFactory.generatePublic(new X509EncodedKeySpec(pubKey)), rsaFactory.generatePrivate(new PKCS8EncodedKeySpec(privKey)));
} catch (Exception e) {
Log.w(TAG, "Invalid key stored " + e);
return null;
}
}
public synchronized void put(String key, String value) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(key, value);
editor.apply();
}
public void put(String subtype, String key, String value) {
put(subtype + "|S|" + key, value);
}
public void put(String subtype, String authorizedEntity, String scope, String value) {
put(subtype + "|T|" + authorizedEntity + "|" + scope, value);
}
public synchronized void put(String subtype, KeyPair keyPair, long timestamp) {
put(subtype, "|P|", Base64.encodeToString(keyPair.getPublic().getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING));
put(subtype, "|K|", Base64.encodeToString(keyPair.getPrivate().getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING));
put(subtype, "cre", Long.toString(timestamp));
}
public synchronized void delete() {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.apply();
}
public synchronized void delete(String prefix) {
SharedPreferences.Editor editor = sharedPreferences.edit();
for (String key : sharedPreferences.getAll().keySet()) {
if (key.startsWith(prefix)) {
editor.remove(key);
}
}
editor.apply();
}
public synchronized void delete(String subtype, String authorizedEntity, String scope) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove(subtype + "|T|" + authorizedEntity + "|" + scope);
editor.apply();
}
}

1
play-services-location-api Symbolic link
View File

@ -0,0 +1 @@
extern/GmsApi/play-services-location-api/

View File

@ -19,15 +19,24 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0'
classpath 'com.android.tools.build:gradle:2.1.2'
}
}
apply plugin: 'com.android.library'
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)
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
buildToolsVersion "23.0.3"
defaultConfig {
versionName getMyVersionName()
@ -40,4 +49,5 @@ android {
dependencies {
compile project(':play-services-base')
compile project(':play-services-location-api')
}

View File

@ -0,0 +1,52 @@
/*
* 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.1.2'
}
}
apply plugin: 'com.android.library'
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)
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
versionName getMyVersionName()
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
}
}
dependencies {
compile project(':play-services-common-api')
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.microg.gms.tasks">
<uses-sdk android:minSdkVersion="9" />
<application />
</manifest>

View File

@ -0,0 +1,68 @@
/*
* 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.tasks;
import org.microg.gms.common.PublicApi;
/**
* A function that is called to continue execution after completion of a {@link Task}.
*
* @see Task#continueWith(Continuation)
* @see Task#continueWithTask(Continuation)
*/
@PublicApi
public interface Continuation<TResult, TContinuationResult> {
/**
* Returns the result of applying this Continuation to {@code task}.
* <p/>
* To propagate failure from the completed Task call {@link Task#getResult()} and allow the
* {@link RuntimeExecutionException} to propagate. The RuntimeExecutionException will be
* unwrapped such that the Task returned by {@link Task#continueWith(Continuation)} or
* {@link Task#continueWithTask(Continuation)} fails with the original exception.
* <p/>
* To suppress specific failures call {@link Task#getResult(Class)} and catch the exception
* types of interest:
* <pre>task.continueWith(new Continuation<String, String>() {
* @Override
* public String then(Task<String> task) {
* try {
* return task.getResult(IOException.class);
* } catch (FileNotFoundException e) {
* return "Not found";
* } catch (IOException e) {
* return "Read failed";
* }
* }
* }</pre>
* <p/>
* To suppress all failures guard any calls to {@link Task#getResult()} with {@link Task#isSuccessful()}:
* <pre>task.continueWith(new Continuation<String, String>() {
* @Override
* public String then(Task<String> task) {
* if (task.isSuccessful()) {
* return task.getResult();
* } else {
* return DEFAULT_VALUE;
* }
* }
* }</pre>
*
* @param task the completed Task. Never null
* @throws Exception if the result couldn't be produced
*/
TContinuationResult then(Task<TResult> task);
}

View File

@ -0,0 +1,34 @@
/*
* 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.tasks;
import org.microg.gms.common.PublicApi;
/**
* Listener called when a {@link Task} completes.
*
* @see Task#addOnCompleteListener(OnCompleteListener)
*/
@PublicApi
public interface OnCompleteListener<TResult> {
/**
* Called when the Task completes.
*
* @param task the completed Task. Never null
*/
void onComplete(Task<TResult> task);
}

View File

@ -0,0 +1,35 @@
/*
* 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.tasks;
import org.microg.gms.common.PublicApi;
/**
* Listener called when a {@link Task} fails with an exception.
*
* @see Task#addOnFailureListener(OnFailureListener)
*/
@PublicApi
public interface OnFailureListener {
/**
* Called when the Task fails with an exception.
*
* @param e the exception that caused the Task to fail. Never null
*/
void onFailure(Exception e);
}

View File

@ -0,0 +1,34 @@
/*
* 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.tasks;
import org.microg.gms.common.PublicApi;
/**
* Listener called when a {@link Task} completes successfully.
*
* @see Task#addOnSuccessListener(OnSuccessListener)
*/
@PublicApi
public interface OnSuccessListener<TResult> {
/**
* Called when the {@link Task} completes successfully.
*
* @param result the result of the Task
*/
void onSuccess(TResult result);
}

View File

@ -0,0 +1,33 @@
/*
* 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.tasks;
import org.microg.gms.common.PublicApi;
import java.util.concurrent.ExecutionException;
/**
* Runtime version of {@link ExecutionException}.
*
* @see Task#getResult(Class)
*/
@PublicApi
public class RuntimeExecutionException extends RuntimeException {
public RuntimeExecutionException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,224 @@
/*
* 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.tasks;
import android.app.Activity;
import org.microg.gms.common.PublicApi;
import java.util.concurrent.Executor;
/**
* Represents an asynchronous operation.
*/
@PublicApi
public abstract class Task<TResult> {
public Task() {
}
/**
* Adds a listener that is called when the Task completes.
* <p/>
* The listener will be called on main application thread. If the Task is already complete, a
* call to the listener will be immediately scheduled. If multiple listeners are added, they
* will be called in the order in which they were added.
*
* @return this Task
*/
public Task<TResult> addOnCompleteListener(OnCompleteListener<TResult> listener) {
throw new UnsupportedOperationException("addOnCompleteListener is not implemented");
}
/**
* Adds an Activity-scoped listener that is called when the Task completes.
* <p/>
* The listener will be called on main application thread. If the Task is already complete, a
* call to the listener will be immediately scheduled. If multiple listeners are added, they
* will be called in the order in which they were added.
* <p/>
* The listener will be automatically removed during {@link Activity#onStop()}.
*
* @return this Task
*/
public Task<TResult> addOnCompleteListener(Activity activity, OnCompleteListener<TResult> listener) {
throw new UnsupportedOperationException("addOnCompleteListener is not implemented");
}
/**
* Adds a listener that is called when the Task completes.
* <p/>
* If the Task is already complete, a call to the listener will be immediately scheduled. If
* multiple listeners are added, they will be called in the order in which they were added.
*
* @param executor the executor to use to call the listener
* @return this Task
*/
public Task<TResult> addOnCompleteListener(Executor executor, OnCompleteListener<TResult> listener) {
throw new UnsupportedOperationException("addOnCompleteListener is not implemented");
}
/**
* Adds an Activity-scoped listener that is called if the Task fails.
* <p/>
* The listener will be called on main application thread. If the Task has already failed, a
* call to the listener will be immediately scheduled. If multiple listeners are added, they
* will be called in the order in which they were added.
* <p/>
* The listener will be automatically removed during {@link Activity#onStop()}.
*
* @return this Task
*/
public abstract Task<TResult> addOnFailureListener(Activity activity, OnFailureListener listener);
/**
* Adds an Activity-scoped listener that is called if the Task fails.
* <p/>
* The listener will be called on main application thread. If the Task has already failed, a
* call to the listener will be immediately scheduled. If multiple listeners are added, they
* will be called in the order in which they were added.
*
* @return this Task
*/
public abstract Task<TResult> addOnFailureListener(OnFailureListener listener);
/**
* Adds a listener that is called if the Task fails.
* <p/>
* If the Task has already failed, a call to the listener will be immediately scheduled. If
* multiple listeners are added, they will be called in the order in which they were added.
*
* @param executor the executor to use to call the listener
* @return this Task
*/
public abstract Task<TResult> addOnFailureListener(Executor executor, OnFailureListener listener);
/**
* Adds a listener that is called if the Task completes successfully.
* <p/>
* If multiple listeners are added, they will be called in the order in which they were added. If
* the Task has already completed successfully, a call to the listener will be immediately scheduled.
*
* @param executor the executor to use to call the listener
* @return this Task
*/
public abstract Task<TResult> addOnSuccessListener(Executor executor, OnSuccessListener<? super TResult> listener);
/**
* Adds a listener that is called if the Task completes successfully.
* <p/>
* The listener will be called on the main application thread. If the Task has already
* completed successfully, a call to the listener will be immediately scheduled. If multiple
* listeners are added, they will be called in the order in which they were added.
*
* @return this Task
*/
public abstract Task<TResult> addOnSuccessListener(OnSuccessListener<? super TResult> listener);
/**
* Adds an Activity-scoped listener that is called if the Task completes successfully.
* <p/>
* The listener will be called on the main application thread. If the Task has already
* completed successfully, a call to the listener will be immediately scheduled. If multiple
* listeners are added, they will be called in the order in which they were added.
* <p/>
* The listener will be automatically removed during {@link Activity#onStop()}.
*
* @return this Task
*/
public abstract Task<TResult> addOnSuccessListener(Activity activity, OnSuccessListener<? super TResult> listener);
/**
* Returns a new Task that will be completed with the result of applying the specified
* Continuation to this Task.
* <p/>
* The Continuation will be called on the main application thread.
*
* @see Continuation#then(Task)
*/
public <TContinuationResult> Task<TContinuationResult> continueWith(Continuation<TResult, TContinuationResult> continuation) {
throw new UnsupportedOperationException("continueWith is not implemented");
}
/**
* Returns a new Task that will be completed with the result of applying the specified Continuation to this Task.
*
* @param executor the executor to use to call the Continuation
* @see Continuation#then(Task)
*/
public <TContinuationResult> Task<TContinuationResult> continueWith(Executor executor, Continuation<TResult, TContinuationResult> continuation) {
throw new UnsupportedOperationException("continueWith is not implemented");
}
/**
* Returns a new Task that will be completed with the result of applying the specified
* Continuation to this Task.
* <p/>
* The Continuation will be called on the main application thread.
*
* @see Continuation#then(Task)
*/
public <TContinuationResult> Task<TContinuationResult> continueWithTask(Continuation<TResult, Task<TContinuationResult>> continuation) {
throw new UnsupportedOperationException("continueWithTask is not implemented");
}
/**
* Returns a new Task that will be completed with the result of applying the specified Continuation to this Task.
*
* @param executor the executor to use to call the Continuation
* @see Continuation#then(Task)
*/
public <TContinuationResult> Task<TContinuationResult> continueWithTask(Executor executor, Continuation<TResult, Task<TContinuationResult>> var2) {
throw new UnsupportedOperationException("continueWithTask is not implemented");
}
/**
* Returns the exception that caused the Task to fail. Returns {@code null} if the Task is not
* yet complete, or completed successfully.
*/
public abstract Exception getException();
/**
* Gets the result of the Task, if it has already completed.
*
* @throws IllegalStateException if the Task is not yet complete
* @throws RuntimeExecutionException if the Task failed with an exception
*/
public abstract TResult getResult();
/**
* Gets the result of the Task, if it has already completed.
*
* @throws IllegalStateException if the Task is not yet complete
* @throws X if the Task failed with an exception of type X
* @throws RuntimeExecutionException if the Task failed with an exception that was not of type X
*/
public abstract <X extends Throwable> TResult getResult(Class<X> exceptionType) throws X;
/**
* Returns {@code true} if the Task is complete; {@code false} otherwise.
*/
public abstract boolean isComplete();
/**
* Returns {@code true} if the Task has completed successfully; {@code false} otherwise.
*/
public abstract boolean isSuccessful();
}

View File

@ -0,0 +1,52 @@
/*
* 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.tasks;
import org.microg.gms.common.PublicApi;
/**
* Provides the ability to create an incomplete {@link Task} and later complete it by either
* calling {@link #setResult(TResult)} or {@link #setException(Exception)}.
*/
@PublicApi
public class TaskCompletionSource<TResult> {
public TaskCompletionSource() {
}
/**
* Returns the Task.
*/
public Task<TResult> getTask() {
return null;
}
/**
* Completes the Task with the specified exception.
* @throws IllegalStateException if the Task is already complete
*/
public void setException(Exception e) {
}
/**
* Completes the Task with the specified result.
* @throws IllegalStateException if the Task is already complete
*/
public void setResult(TResult result) {
}
}

View File

@ -0,0 +1,116 @@
/*
* 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.tasks;
import org.microg.gms.common.PublicApi;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* {@link Task} utility methods.
*/
@PublicApi
public final class Tasks {
/**
* Blocks until the specified Task is complete.
*
* @return the Task's result
* @throws ExecutionException if the Task fails
* @throws InterruptedException if an interrupt occurs while waiting for the Task to complete
* @throws TimeoutException if the specified timeout is reached before the Task completes
*/
public static <TResult> TResult await(Task<TResult> task, long timeout, TimeUnit unit) {
// TODO
return null;
}
/**
* Blocks until the specified Task is complete.
*
* @return the Task's result
* @throws ExecutionException if the Task fails
* @throws InterruptedException if an interrupt occurs while waiting for the Task to complete
*/
public static <TResult> TResult await(Task<TResult> task) {
// TODO
return null;
}
/**
* Returns a Task that will be completed with the result of the specified Callable.
* <p/>
* The Callable will be called on the main application thread.
*/
public static <TResult> Task<TResult> call(Callable<TResult> callable) {
// TODO
return null;
}
/**
* Returns a Task that will be completed with the result of the specified Callable.
*
* @param executor the Executor to use to call the Callable
*/
public static <TResult> Task<TResult> call(Executor executor, Callable<TResult> callable) {
// TODO
return null;
}
/**
* Returns a completed Task with the specified exception.
*/
public static <TResult> Task<TResult> forException(Exception e) {
// TODO
return null;
}
/**
* Returns a completed Task with the specified result.
*/
public static <TResult> Task<TResult> forResult(TResult result) {
// TODO
return null;
}
/**
* Returns a Task that completes successfully when all of the specified Tasks complete
* successfully. Does not accept nulls.
*
* @throws NullPointerException if any of the provided Tasks are null
*/
public static Task<Void> whenAll(Collection<? extends Task<?>> tasks) {
// TODO
return null;
}
/**
* Returns a Task that completes successfully when all of the specified Tasks complete
* successfully. Does not accept nulls.
*
* @throws NullPointerException if any of the provided Tasks are null
*/
public static Task<Void> whenAll(Task<?>... tasks) {
return whenAll(Arrays.asList(tasks));
}
}

1
play-services-wearable-api Symbolic link
View File

@ -0,0 +1 @@
extern/GmsApi/play-services-wearable-api/

View File

@ -19,15 +19,24 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0'
classpath 'com.android.tools.build:gradle:2.1.2'
}
}
apply plugin: 'com.android.library'
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)
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
buildToolsVersion "23.0.3"
defaultConfig {
versionName getMyVersionName()
@ -40,4 +49,5 @@ android {
dependencies {
compile project(':play-services-base')
compile project(':play-services-wearable-api')
}

View File

@ -33,7 +33,7 @@ public class DataEventBuffer extends DataBuffer<DataEvent> implements Result {
@PublicApi(exclude = true)
public DataEventBuffer(DataHolder dataHolder) {
super(dataHolder);
status = new Status(dataHolder.statusCode);
status = new Status(dataHolder.getStatusCode());
}
@Override

View File

@ -30,7 +30,7 @@ public class DataItemBuffer extends DataBuffer<DataItem> implements Result {
@PublicApi(exclude = true)
public DataItemBuffer(DataHolder dataHolder) {
super(dataHolder);
status = new Status(dataHolder.statusCode);
status = new Status(dataHolder.getStatusCode());
}
@Override

View File

@ -19,12 +19,21 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0'
classpath 'com.android.tools.build:gradle:2.1.0'
}
}
apply plugin: 'com.android.library'
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)
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"

View File

@ -16,12 +16,19 @@
include ':safe-parcel'
include ':play-services-cast-api'
include ':play-services-common-api'
include ':play-services-iid-api'
include ':play-services-location-api'
include ':play-services-wearable-api'
include ':play-services-api'
include ':play-services-base'
include ':play-services-cast'
include ':play-services-gcm'
include ':play-services-iid'
include ':play-services-location'
include ':play-services-tasks'
include ':play-services-wearable'
include ':play-services'