mirror of
https://github.com/TeamVanced/VancedMicroG
synced 2025-01-12 12:25:48 +01:00
parent
dcefc8dd7b
commit
31b6acd10e
@ -36,4 +36,5 @@ dependencies {
|
|||||||
api project(':play-services-basement')
|
api project(':play-services-basement')
|
||||||
api project(':play-services-cast-api')
|
api project(':play-services-cast-api')
|
||||||
api project(':play-services-cast-framework-api')
|
api project(':play-services-cast-framework-api')
|
||||||
|
api project(':play-services-iid-api')
|
||||||
}
|
}
|
||||||
|
37
play-services-iid-api/build.gradle
Normal file
37
play-services-iid-api/build.gradle
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion androidCompileSdk
|
||||||
|
buildToolsVersion "$androidBuildVersionTools"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
versionName version
|
||||||
|
minSdkVersion androidMinSdk
|
||||||
|
targetSdkVersion androidTargetSdk
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = 1.8
|
||||||
|
targetCompatibility = 1.8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api project(':play-services-basement')
|
||||||
|
}
|
34
play-services-iid-api/gradle.properties
Normal file
34
play-services-iid-api/gradle.properties
Normal 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
POM_NAME=Play Services Internal IID API
|
||||||
|
POM_DESCRIPTION=Interfaces and objects for IPC between Play Services Library and Play Services Core
|
||||||
|
|
||||||
|
POM_PACKAGING=aar
|
||||||
|
|
||||||
|
POM_URL=https://github.com/microg/android_external_GmsApi
|
||||||
|
|
||||||
|
POM_SCM_URL=https://github.com/microg/android_external_GmsApi
|
||||||
|
POM_SCM_CONNECTION=scm:git@github.com:microg/android_external_GmsApi.git
|
||||||
|
POM_SCM_DEV_CONNECTION=scm:git@github.com:microg/android_external_GmsApi.git
|
||||||
|
|
||||||
|
POM_LICENCE_NAME=The Apache Software License, Version 2.0
|
||||||
|
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||||
|
POM_LICENCE_DIST=repo
|
||||||
|
|
||||||
|
POM_DEVELOPER_ID=mar-v-in
|
||||||
|
POM_DEVELOPER_NAME=Marvin W
|
||||||
|
|
18
play-services-iid-api/src/main/AndroidManifest.xml
Normal file
18
play-services-iid-api/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2013-2017 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 package="org.microg.gms.iid.api"/>
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.google.android.gms.iid;
|
||||||
|
|
||||||
|
import android.os.Message;
|
||||||
|
|
||||||
|
interface IMessengerCompat {
|
||||||
|
void send(in Message message);
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013-2017 microG Project Team
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.iid;
|
||||||
|
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.os.Messenger;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.os.Build.VERSION_CODES.LOLLIPOP;
|
||||||
|
|
||||||
|
public class MessengerCompat implements Parcelable {
|
||||||
|
private Messenger messenger;
|
||||||
|
private IMessengerCompat messengerCompat;
|
||||||
|
|
||||||
|
public MessengerCompat(IBinder binder) {
|
||||||
|
if (SDK_INT >= LOLLIPOP) {
|
||||||
|
messenger = new Messenger(binder);
|
||||||
|
} else {
|
||||||
|
messengerCompat = IMessengerCompat.Stub.asInterface(binder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessengerCompat(Handler handler) {
|
||||||
|
if (SDK_INT >= LOLLIPOP) {
|
||||||
|
messenger = new Messenger(handler);
|
||||||
|
} else {
|
||||||
|
messengerCompat = new IMessengerCompatImpl(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof MessengerCompat && ((MessengerCompat) o).getBinder().equals(getBinder());
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBinder getBinder() {
|
||||||
|
return messenger != null ? messenger.getBinder() : messengerCompat.asBinder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getBinder().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(Message message) throws RemoteException {
|
||||||
|
if (messenger != null) messenger.send(message);
|
||||||
|
else messengerCompat.send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeStrongBinder(getBinder());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<MessengerCompat> CREATOR = new Creator<MessengerCompat>() {
|
||||||
|
@Override
|
||||||
|
public MessengerCompat createFromParcel(Parcel source) {
|
||||||
|
IBinder binder = source.readStrongBinder();
|
||||||
|
return binder != null ? new MessengerCompat(binder) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessengerCompat[] newArray(int size) {
|
||||||
|
return new MessengerCompat[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static class IMessengerCompatImpl extends IMessengerCompat.Stub {
|
||||||
|
private final Handler handler;
|
||||||
|
|
||||||
|
public IMessengerCompatImpl(Handler handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(Message message) throws RemoteException {
|
||||||
|
message.arg2 = Binder.getCallingUid();
|
||||||
|
handler.dispatchMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
play-services-iid/build.gradle
Normal file
47
play-services-iid/build.gradle
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 androidCompileSdk()
|
||||||
|
buildToolsVersion "$androidBuildVersionTools"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
versionName getMyVersionName()
|
||||||
|
minSdkVersion androidMinSdk()
|
||||||
|
targetSdkVersion androidTargetSdk()
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api project(':play-services-base')
|
||||||
|
api project(':play-services-iid-api')
|
||||||
|
}
|
34
play-services-iid/gradle.properties
Normal file
34
play-services-iid/gradle.properties
Normal 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
POM_NAME=Play Services IID Library
|
||||||
|
POM_DESCRIPTION=The Play Services Library module to access the InstanceID API
|
||||||
|
|
||||||
|
POM_PACKAGING=aar
|
||||||
|
|
||||||
|
POM_URL=https://github.com/microg/android_external_GmsLib
|
||||||
|
|
||||||
|
POM_SCM_URL=https://github.com/microg/android_external_GmsLib
|
||||||
|
POM_SCM_CONNECTION=scm:git@github.com:microg/android_external_GmsLib.git
|
||||||
|
POM_SCM_DEV_CONNECTION=scm:git@github.com:microg/android_external_GmsLib.git
|
||||||
|
|
||||||
|
POM_LICENCE_NAME=The Apache Software License, Version 2.0
|
||||||
|
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||||
|
POM_LICENCE_DIST=repo
|
||||||
|
|
||||||
|
POM_DEVELOPER_ID=mar-v-in
|
||||||
|
POM_DEVELOPER_NAME=Marvin W
|
||||||
|
|
24
play-services-iid/src/main/AndroidManifest.xml
Normal file
24
play-services-iid/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2013-2017 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 package="org.microg.gms.iid"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<!-- Permissions required for IID -->
|
||||||
|
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
@ -0,0 +1,275 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013-2017 microG Project Team
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.iid;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Looper;
|
||||||
|
import 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
|
||||||
|
* exponential backoff when retrying requests
|
||||||
|
*/
|
||||||
|
public static final String ERROR_BACKOFF = "RETRY_LATER";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocking methods must not be called on the main thread.
|
||||||
|
*/
|
||||||
|
public static final String ERROR_MAIN_THREAD = "MAIN_THREAD";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tokens can't be generated. Only devices with Google Play are supported.
|
||||||
|
*/
|
||||||
|
public static final String ERROR_MISSING_INSTANCEID_SERVICE = "MISSING_INSTANCEID_SERVICE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device cannot read the response, or there was a server error.
|
||||||
|
* Application should retry the request later using exponential backoff
|
||||||
|
* and retry (on each subsequent failure increase delay before retrying).
|
||||||
|
*/
|
||||||
|
public static final String ERROR_SERVICE_NOT_AVAILABLE = GcmConstants.ERROR_SERVICE_NOT_AVAILABLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeout waiting for a response.
|
||||||
|
*/
|
||||||
|
public static final String ERROR_TIMEOUT = "TIMEOUT";
|
||||||
|
|
||||||
|
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 {
|
||||||
|
deleteToken("*", "*");
|
||||||
|
creationTime = 0;
|
||||||
|
storeInstance.delete(subtype + "|");
|
||||||
|
keyPair = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revokes access to a scope (action) for an entity previously
|
||||||
|
* authorized by {@link com.google.android.gms.iid.InstanceID#getToken(java.lang.String, java.lang.String)}.
|
||||||
|
* <p/>
|
||||||
|
* Do not call this function on the main thread.
|
||||||
|
*
|
||||||
|
* @param authorizedEntity Entity that must no longer have access.
|
||||||
|
* @param scope Action that entity is no longer authorized to perform.
|
||||||
|
* @throws IOException if the request fails.
|
||||||
|
*/
|
||||||
|
public void deleteToken(String authorizedEntity, String scope) throws IOException {
|
||||||
|
deleteToken(authorizedEntity, scope, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PublicApi(exclude = true)
|
||||||
|
public void deleteToken(String authorizedEntity, String scope, Bundle extras) throws IOException {
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns time when instance ID was created.
|
||||||
|
*
|
||||||
|
* @return Time when instance ID was created (milliseconds since Epoch).
|
||||||
|
*/
|
||||||
|
public long getCreationTime() {
|
||||||
|
if (creationTime == 0) {
|
||||||
|
String s = storeInstance.get(subtype, "cre");
|
||||||
|
if (s != null) {
|
||||||
|
creationTime = Long.parseLong(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return creationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a stable identifier that uniquely identifies the app instance.
|
||||||
|
*
|
||||||
|
* @return The identifier for the application instance.
|
||||||
|
*/
|
||||||
|
public String getId() {
|
||||||
|
return sha1KeyPair(getKeyPair());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance of this class.
|
||||||
|
*
|
||||||
|
* @return InstanceID instance.
|
||||||
|
*/
|
||||||
|
public static InstanceID getInstance(Context context) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a token that authorizes an Entity (example: cloud service) to perform
|
||||||
|
* an action on behalf of the application identified by Instance ID.
|
||||||
|
* <p/>
|
||||||
|
* This is similar to an OAuth2 token except, it applies to the
|
||||||
|
* application instance instead of a user.
|
||||||
|
* <p/>
|
||||||
|
* Do not call this function on the main thread.
|
||||||
|
*
|
||||||
|
* @param authorizedEntity Entity authorized by the token.
|
||||||
|
* @param scope Action authorized for authorizedEntity.
|
||||||
|
* @param extras additional parameters specific to each token scope.
|
||||||
|
* Bundle keys starting with 'GCM.' and 'GOOGLE.' are
|
||||||
|
* reserved.
|
||||||
|
* @return a token that can identify and authorize the instance of the
|
||||||
|
* application on the device.
|
||||||
|
* @throws IOException if the request fails.
|
||||||
|
*/
|
||||||
|
public String getToken(String authorizedEntity, String scope, Bundle extras) throws IOException {
|
||||||
|
if (Looper.getMainLooper() == Looper.myLooper()) throw new IOException(ERROR_MAIN_THREAD);
|
||||||
|
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a token that authorizes an Entity (example: cloud service) to perform
|
||||||
|
* an action on behalf of the application identified by Instance ID.
|
||||||
|
* <p/>
|
||||||
|
* This is similar to an OAuth2 token except, it applies to the
|
||||||
|
* application instance instead of a user.
|
||||||
|
* <p/>
|
||||||
|
* Do not call this function on the main thread.
|
||||||
|
*
|
||||||
|
* @param authorizedEntity Entity authorized by the token.
|
||||||
|
* @param scope Action authorized for authorizedEntity.
|
||||||
|
* @return a token that can identify and authorize the instance of the
|
||||||
|
* application on the device.
|
||||||
|
* @throws IOException if the request fails.
|
||||||
|
*/
|
||||||
|
public String getToken(String authorizedEntity, String scope) throws IOException {
|
||||||
|
return getToken(authorizedEntity, scope, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013-2017 microG Project Team
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.gms.iid;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.Message;
|
||||||
|
import 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;
|
||||||
|
import static org.microg.gms.gcm.GcmConstants.EXTRA_FROM;
|
||||||
|
import static org.microg.gms.gcm.GcmConstants.EXTRA_GSF_INTENT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class to handle Instance ID service notifications on token
|
||||||
|
* refresh.
|
||||||
|
* <p/>
|
||||||
|
* Any app using Instance ID or GCM must include a class extending
|
||||||
|
* InstanceIDListenerService and implement {@link com.google.android.gms.iid.InstanceIDListenerService#onTokenRefresh()}.
|
||||||
|
* <p/>
|
||||||
|
* Include the following in the manifest:
|
||||||
|
* <pre>
|
||||||
|
* <service android:name=".YourInstanceIDListenerService" android:exported="false">
|
||||||
|
* <intent-filter>
|
||||||
|
* <action android:name="com.google.android.gms.iid.InstanceID"/>
|
||||||
|
* </intent-filter>
|
||||||
|
* </service></pre>
|
||||||
|
* Do not export this service. Instead, keep it private to prevent other apps
|
||||||
|
* accessing your service.
|
||||||
|
*/
|
||||||
|
public class InstanceIDListenerService extends Service {
|
||||||
|
|
||||||
|
private BroadcastReceiver registrationReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
handleIntent(intent);
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private MessengerCompat messengerCompat = new MessengerCompat(new Handler(Looper.getMainLooper()) {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
handleIntent((Intent) msg.obj);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private int counter = 0;
|
||||||
|
private int startId = -1;
|
||||||
|
|
||||||
|
private void handleIntent(Intent intent) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
if (intent != null && ACTION_INSTANCE_ID.equals(intent.getAction())) {
|
||||||
|
return messengerCompat.getBinder();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCreate() {
|
||||||
|
IntentFilter filter = new IntentFilter(ACTION_C2DM_REGISTRATION);
|
||||||
|
filter.addCategory(getPackageName());
|
||||||
|
registerReceiver(registrationReceiver, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDestroy() {
|
||||||
|
unregisterReceiver(registrationReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
synchronized (this) {
|
||||||
|
this.counter++;
|
||||||
|
if (startId > this.startId) this.startId = startId;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (intent != null) {
|
||||||
|
if (ACTION_INSTANCE_ID.equals(intent.getAction()) && intent.hasExtra(EXTRA_GSF_INTENT)) {
|
||||||
|
startService((Intent) intent.getParcelableExtra(EXTRA_GSF_INTENT));
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleIntent(intent);
|
||||||
|
|
||||||
|
if (intent.hasExtra(EXTRA_FROM))
|
||||||
|
WakefulBroadcastReceiver.completeWakefulIntent(intent);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
return START_NOT_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the system determines that the tokens need to be refreshed. The application
|
||||||
|
* should call getToken() and send the tokens to all application servers.
|
||||||
|
* <p/>
|
||||||
|
* This will not be called very frequently, it is needed for key rotation and to handle special
|
||||||
|
* cases.
|
||||||
|
* <p/>
|
||||||
|
* The system will throttle the refresh event across all devices to avoid overloading
|
||||||
|
* application servers with token updates.
|
||||||
|
*/
|
||||||
|
public void onTokenRefresh() {
|
||||||
|
// To be overwritten
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stop() {
|
||||||
|
synchronized (this) {
|
||||||
|
counter--;
|
||||||
|
if (counter <= 0) {
|
||||||
|
stopSelf(startId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,423 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013-2017 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013-2017 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();
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ include ':play-services-basement'
|
|||||||
include ':play-services-api'
|
include ':play-services-api'
|
||||||
include ':play-services-cast-api'
|
include ':play-services-cast-api'
|
||||||
include ':play-services-cast-framework-api'
|
include ':play-services-cast-framework-api'
|
||||||
|
include ':play-services-iid-api'
|
||||||
|
|
||||||
include ':play-services-base'
|
include ':play-services-base'
|
||||||
include ':play-services-tasks'
|
include ':play-services-tasks'
|
||||||
|
Loading…
Reference in New Issue
Block a user