Repo Created

This commit is contained in:
Fr4nz D13trich 2025-11-15 17:44:12 +01:00
parent eb305e2886
commit a8c22c65db
4784 changed files with 329907 additions and 2 deletions

View file

@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 2019 e Foundation
* SPDX-FileCopyrightText: 2024 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
apply plugin: 'com.android.library'
group = 'org.microg'
android {
namespace "com.google.android.gms.appinvite"
compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"
buildFeatures {
aidl = true
}
defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
// Dependencies from play-services-appinvite:18.0.0
api project(':play-services-base')
api project(':play-services-basement')
api project(':play-services-tasks')
// api project(':firebase-analytics')
api project(':firebase-dynamic-links')
}

View file

@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
apply plugin: 'com.android.library'
apply plugin: 'com.squareup.wire'
apply plugin: 'kotlin-android'
dependencies {
api project(':play-services-appinvite')
implementation project(':play-services-base-core')
implementation "androidx.appcompat:appcompat:$appcompatVersion"
implementation "com.android.volley:volley:$volleyVersion"
implementation "com.squareup.wire:wire-runtime:$wireVersion"
}
android {
namespace "org.microg.gms.appinvite"
compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"
defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
kotlinOptions {
jvmTarget = 1.8
}
}
wire {
kotlin {}
}

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ SPDX-FileCopyrightText: 2023 microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<service
android:name="org.microg.gms.appinivite.AppInviteService"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.appinvite.service.START" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
<activity
android:name="org.microg.gms.appinivite.AppInviteActivity"
android:excludeFromRecents="true"
android:process=":ui"
android:exported="true"
android:theme="@style/Theme.AppCompat.Light.Dialog.NoActionBar">
<intent-filter
android:priority="900"
android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https" />
<data android:scheme="http" />
<data android:host="*.app.goo.gl" />
<data android:pathPrefix="/" />
</intent-filter>
<intent-filter>
<action android:name="com.google.firebase.dynamiclinks.VIEW_DYNAMIC_LINK" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https" />
<data android:scheme="http" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -0,0 +1,123 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.appinivite
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.ViewGroup
import android.view.Window
import android.widget.ProgressBar
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.pm.PackageInfoCompat
import androidx.core.os.bundleOf
import androidx.core.view.setPadding
import androidx.lifecycle.lifecycleScope
import com.android.volley.toolbox.Volley
import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer
import com.google.firebase.dynamiclinks.internal.DynamicLinkData
import org.microg.gms.appinvite.MutateAppInviteLinkResponse
import org.microg.gms.appinivite.utils.DynamicLinkUtils
import org.microg.gms.utils.singleInstanceOf
private const val TAG = "AppInviteActivity"
private const val APPINVITE_DEEP_LINK = "com.google.android.gms.appinvite.DEEP_LINK"
private const val APPINVITE_INVITATION_ID = "com.google.android.gms.appinvite.INVITATION_ID"
private const val APPINVITE_OPENED_FROM_PLAY_STORE = "com.google.android.gms.appinvite.OPENED_FROM_PLAY_STORE"
private const val APPINVITE_REFERRAL_BUNDLE = "com.google.android.gms.appinvite.REFERRAL_BUNDLE"
private const val DYNAMIC_LINK_DATA = "com.google.firebase.dynamiclinks.DYNAMIC_LINK_DATA"
class AppInviteActivity : AppCompatActivity() {
private val queue by lazy { singleInstanceOf { Volley.newRequestQueue(applicationContext) } }
private val Int.px: Int get() = (this * resources.displayMetrics.density).toInt()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
setContentView(ProgressBar(this).apply {
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
setPadding(20.px)
isIndeterminate = true
})
val extras = intent.extras
extras?.keySet()
Log.d(TAG, "Intent: $intent $extras")
if (intent?.data == null) return finish()
lifecycleScope.launchWhenStarted {
val response = DynamicLinkUtils.requestLinkResponse(intent.data.toString(), queue) ?: return@launchWhenStarted redirectToBrowser()
open(response)
}
}
private fun redirectToBrowser() {
try {
startActivity(Intent(Intent.ACTION_VIEW).apply {
addCategory(Intent.CATEGORY_DEFAULT)
data = intent.data
})
} catch (e: Exception) {
Log.w(TAG, e)
}
finish()
}
private fun open(appInviteLink: MutateAppInviteLinkResponse) {
val minAppVersion = appInviteLink.data_?.app?.minAppVersion
val dynamicLinkData = DynamicLinkData(appInviteLink.metadata?.info?.url, appInviteLink.data_?.intentData,
(minAppVersion ?: 0).toInt(), System.currentTimeMillis(), null, null)
val linkPackageName = appInviteLink.data_?.packageName
val intent = Intent(Intent.ACTION_VIEW).apply {
addCategory(Intent.CATEGORY_DEFAULT)
data = appInviteLink.data_?.intentData?.let { Uri.parse(it) }
`package` = linkPackageName
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
putExtra(
APPINVITE_REFERRAL_BUNDLE, bundleOf(
APPINVITE_DEEP_LINK to appInviteLink,
APPINVITE_INVITATION_ID to "",
APPINVITE_OPENED_FROM_PLAY_STORE to false
)
)
putExtra(DYNAMIC_LINK_DATA, SafeParcelableSerializer.serializeToBytes(dynamicLinkData))
}
val fallbackIntent = Intent(Intent.ACTION_VIEW).apply {
addCategory(Intent.CATEGORY_DEFAULT)
data = appInviteLink.data_?.fallbackUrl?.let { Uri.parse(it) }
}
val installedVersionCode = runCatching {
if (linkPackageName != null) {
PackageInfoCompat.getLongVersionCode(packageManager.getPackageInfo(linkPackageName, 0))
} else {
null
}
}.getOrNull()
if (installedVersionCode != null && (minAppVersion == null || installedVersionCode >= minAppVersion)) {
val componentName = intent.resolveActivity(packageManager)
if (componentName == null) {
Log.w(TAG, "open resolve activity is null")
if (linkPackageName != null) {
val intentLaunch =
packageManager.getLaunchIntentForPackage(linkPackageName)
if (intentLaunch != null) {
intent.setComponent(intentLaunch.component)
}
}
}
startActivity(intent)
finish()
} else {
try {
startActivity(fallbackIntent)
} catch (e: Exception) {
Log.w(TAG, e)
}
finish()
}
}
}

View file

@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2019 e Foundation
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.appinivite
import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.os.Parcel
import android.os.RemoteException
import android.util.Log
import com.google.android.gms.appinvite.internal.IAppInviteCallbacks
import com.google.android.gms.appinvite.internal.IAppInviteService
import com.google.android.gms.common.api.Status
import com.google.android.gms.common.internal.GetServiceRequest
import com.google.android.gms.common.internal.IGmsCallbacks
import org.microg.gms.BaseService
import org.microg.gms.common.GmsService
import org.microg.gms.common.PackageUtils
import org.microg.gms.utils.warnOnTransactionIssues
private const val TAG = "AppInviteService"
class AppInviteService : BaseService(TAG, GmsService.APP_INVITE) {
override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) {
PackageUtils.getAndCheckCallingPackage(this, request.packageName)
callback.onPostInitComplete(0, AppInviteServiceImpl(this, request.packageName, request.extras), null)
}
}
class AppInviteServiceImpl(context: Context?, packageName: String?, extras: Bundle?) : IAppInviteService.Stub() {
override fun updateInvitationOnInstall(callback: IAppInviteCallbacks, invitationId: String) {
callback.onStatus(Status.SUCCESS)
}
override fun convertInvitation(callback: IAppInviteCallbacks, invitationId: String) {
callback.onStatus(Status.SUCCESS)
}
override fun getInvitation(callback: IAppInviteCallbacks) {
callback.onStatusIntent(Status(Activity.RESULT_CANCELED), null)
}
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags, TAG) { super.onTransact(code, data, reply, flags) }
}

View file

@ -0,0 +1,147 @@
/**
* SPDX-FileCopyrightText: 2024 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.appinivite.utils
import android.content.Context
import android.os.Build.VERSION.SDK_INT
import android.os.LocaleList
import com.android.volley.NetworkResponse
import com.android.volley.ParseError
import com.android.volley.Request
import com.android.volley.Request.Method.POST
import com.android.volley.RequestQueue
import com.android.volley.Response
import com.android.volley.VolleyError
import com.android.volley.toolbox.HttpHeaderParser
import com.android.volley.toolbox.JsonRequest
import com.squareup.wire.Message
import com.squareup.wire.ProtoAdapter
import kotlinx.coroutines.CompletableDeferred
import okio.ByteString.Companion.decodeHex
import org.json.JSONException
import org.json.JSONObject
import org.microg.gms.appinvite.ClientIdInfo
import org.microg.gms.appinvite.ClientPlatform
import org.microg.gms.appinvite.LinkInfo
import org.microg.gms.appinvite.MutateAppInviteLinkRequest
import org.microg.gms.appinvite.MutateAppInviteLinkResponse
import org.microg.gms.appinvite.MutateDataRequest
import org.microg.gms.appinvite.MutateDataResponseWithError
import org.microg.gms.appinvite.MutateOperation
import org.microg.gms.appinvite.MutateOperationId
import org.microg.gms.appinvite.SystemInfo
import org.microg.gms.common.Constants
import org.microg.gms.utils.digest
import org.microg.gms.utils.getCertificates
import org.microg.gms.utils.toHexString
import java.io.UnsupportedEncodingException
import java.nio.charset.Charset
import java.util.HashMap
import java.util.Locale
import kotlin.collections.firstOrNull
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
object DynamicLinkUtils {
suspend fun requestLinkResponse(linkUrl: String, queue: RequestQueue): MutateAppInviteLinkResponse? {
val request = ProtobufPostRequest(
"https://datamixer-pa.googleapis.com/v1/mutateonekey?alt=proto&key=AIzaSyAP-gfH3qvi6vgHZbSYwQ_XHqV_mXHhzIk", MutateOperation(
id = MutateOperationId.AppInviteLink, mutateRequest = MutateDataRequest(
appInviteLink = MutateAppInviteLinkRequest(
client = ClientIdInfo(
platform = ClientPlatform.Android,
packageName = Constants.GMS_PACKAGE_NAME,
signature = Constants.GMS_PACKAGE_SIGNATURE_SHA1.decodeHex().base64(),
language = Locale.getDefault().language
), link = LinkInfo(
invitationId = "", uri = linkUrl
), system = SystemInfo(
gms = SystemInfo.GmsInfo(
versionCode = Constants.GMS_VERSION_CODE
)
)
)
)
), MutateDataResponseWithError.ADAPTER
)
val response = try {
request.sendAndAwait(queue)
} catch (e: Exception) {
return null
}
if (response.errorStatus != null || response.dataResponse?.appInviteLink == null) return null
return response.dataResponse?.appInviteLink
}
suspend fun requestShortLinks(context: Context, packageName: String, apiKey: String, longDynamicLink: String, queue: RequestQueue) = suspendCoroutine<JSONObject> { con ->
queue.add(object : JsonRequest<JSONObject>(POST, "https://firebasedynamiclinks.googleapis.com/v1/shortLinks?key=$apiKey", JSONObject().apply {
put("longDynamicLink", longDynamicLink)
}.toString(), {
con.resume(it)
}, {
con.resumeWithException(RuntimeException(it))
}) {
override fun parseNetworkResponse(response: NetworkResponse): Response<JSONObject> {
return try {
val jsonString = String(response.data, Charset.forName(HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET)))
Response.success(JSONObject(jsonString), null)
} catch (e: UnsupportedEncodingException) {
Response.error(ParseError(e))
} catch (je: JSONException) {
Response.error(ParseError(je))
}
}
override fun getHeaders(): Map<String, String?> = mapOf(
"X-Android-Package" to packageName,
"X-Android-Cert" to context.packageManager.getCertificates(packageName).firstOrNull()?.digest("SHA1")?.toHexString()?.uppercase()
)
})
}
}
internal class ProtobufPostRequest<I : Message<I, *>, O>(url: String, private val i: I, private val oAdapter: ProtoAdapter<O>) : Request<O>(Method.POST, url, null) {
private val deferred = CompletableDeferred<O>()
override fun getHeaders(): Map<String, String> {
val headers = HashMap(super.getHeaders())
headers["Accept-Language"] = if (SDK_INT >= 24) LocaleList.getDefault().toLanguageTags() else Locale.getDefault().language
headers["X-Android-Package"] = Constants.GMS_PACKAGE_NAME
headers["X-Android-Cert"] = Constants.GMS_PACKAGE_SIGNATURE_SHA1
return headers
}
override fun getBody(): ByteArray = i.encode()
override fun getBodyContentType(): String = "application/x-protobuf"
override fun parseNetworkResponse(response: NetworkResponse): Response<O> {
return try {
Response.success(oAdapter.decode(response.data), null)
} catch (e: VolleyError) {
Response.error(e)
} catch (e: Exception) {
Response.error(VolleyError())
}
}
override fun deliverResponse(response: O) {
deferred.complete(response)
}
override fun deliverError(error: VolleyError) {
deferred.completeExceptionally(error)
}
suspend fun await(): O = deferred.await()
suspend fun sendAndAwait(queue: RequestQueue): O {
queue.add(this)
return await()
}
}

View file

@ -0,0 +1,99 @@
syntax = "proto2";
option java_package = "org.microg.gms.appinvite";
enum ClientPlatform {
Android = 1;
IOS = 2;
}
message ClientIdInfo {
optional ClientPlatform platform = 1;
optional string packageName = 3; // e.g. com.google.android.gms
optional string signature = 4; // Signing certificate sha-1 base64 with padding, e.g. WOHEEz90Qew9LCcCcKFIAtpHug4=
optional string language = 6; // e.g. en
}
message LinkInfo {
optional string invitationId = 1; // e.g. ""
optional string uri = 2;
}
message SystemInfo {
message GmsInfo {
optional uint32 versionCode = 1; // 212423054
}
optional GmsInfo gms = 1;
}
message MutateAppInviteLinkRequest {
optional ClientIdInfo client = 1;
optional LinkInfo link = 4;
optional SystemInfo system = 5;
}
message MutateDataRequest {
oneof request {
MutateAppInviteLinkRequest appInviteLink = 84453462;
}
}
message AppInviteLinkInfo {
optional int32 type = 1;
optional string url = 2;
optional string name = 3;
}
message AppInviteAppData {
optional string packageName = 1; // apn
optional uint64 minAppVersion = 2; // amv
optional string altPackageName = 3; //apn
}
message AppInviteLinkData {
optional string fallbackUrl = 1; // afl
optional string packageName = 2; // apn
optional string intentData = 3; // link
optional AppInviteAppData app = 6;
}
message AppInviteLinkMetadata {
optional string source = 2; // utm_source
optional string medium = 3; // utm_medium
optional string campaign = 4; // utm_campaign
optional string id = 5;
optional string appCode = 6;
optional AppInviteLinkInfo info = 8;
optional string sessionId = 9;
optional string domainUriPrefix = 10;
optional string content = 11; // utm_content
optional string term = 12; // utm_term
}
message MutateAppInviteLinkResponse {
optional AppInviteLinkData data = 1;
optional AppInviteLinkMetadata metadata = 4;
}
message MutateDataResponse {
oneof response {
MutateAppInviteLinkResponse appInviteLink = 84453462;
}
}
enum MutateOperationId {
AppInviteLink = 84453462;
}
message MutateOperation {
optional MutateOperationId id = 1; // 84453462
optional MutateDataRequest mutateRequest = 2;
}
message StatusProto {
optional int32 code = 1;
}
message MutateDataResponseWithError {
optional MutateDataResponse dataResponse = 1;
optional StatusProto errorStatus = 2;
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 e Foundation
~
~ 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 />

View file

@ -0,0 +1,11 @@
package com.google.android.gms.appinvite.internal;
import com.google.android.gms.common.api.Status;
import android.content.Intent;
interface IAppInviteCallbacks {
void onStatus(in Status status) = 0;
void onStatusIntent(in Status status, in Intent intent) = 1;
}

View file

@ -0,0 +1,14 @@
package com.google.android.gms.appinvite.internal;
import com.google.android.gms.appinvite.internal.IAppInviteCallbacks;
import com.google.android.gms.dynamic.IObjectWrapper;
import com.google.android.gms.common.api.Status;
interface IAppInviteService {
void updateInvitationOnInstall(IAppInviteCallbacks callback, String invitationId) = 0;
void convertInvitation(IAppInviteCallbacks callback, String invitationId) = 1;
void getInvitation(IAppInviteCallbacks callback) = 2;
}