diff --git a/play-services-core-proto/src/main/proto/help.proto b/play-services-core-proto/src/main/proto/help.proto new file mode 100644 index 0000000000..3ed086b79a --- /dev/null +++ b/play-services-core-proto/src/main/proto/help.proto @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2023 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +option java_package = "org.microg.gms.googlehelp"; + +option java_multiple_files = true; + +message RequestContent { + optional CallerAppInfo appInfo = 1; + optional DeviceInfo deviceInfo = 2; + optional RequestBody body = 3; + optional string host = 4; +} + +message ResponseContentWarp { + optional ResponseContent content = 1; +} + +message ResponseContent { + optional AnswerInfo info = 1; + optional uint32 theme = 2; +} + +message AnswerInfo { + optional string answerId = 1; + optional string answerTitle = 2; + optional string answerUrl = 3; + optional uint32 type = 5; +} + +message CallerAppInfo { + optional string packageName = 1; + optional string version = 2; +} + +message DeviceInfo { + optional string language = 1; + optional string name = 2; + optional string version = 3; + optional string code = 7; + optional string timeZone = 9; +} + +message RequestBody { + optional string appContext = 3; + optional string session = 4; + optional uint32 gmsVersionCode = 12; + optional string gmsVersionName = 13; + optional uint32 type = 26; + optional string ap = 28; +} \ No newline at end of file diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index a882f9fc4a..3df2cc125e 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -604,6 +604,7 @@ diff --git a/play-services-core/src/main/kotlin/org/microg/gms/googlehelp/ui/GoogleHelpRedirectActivity.kt b/play-services-core/src/main/kotlin/org/microg/gms/googlehelp/ui/GoogleHelpRedirectActivity.kt index 27f5667e35..915c261003 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/googlehelp/ui/GoogleHelpRedirectActivity.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/googlehelp/ui/GoogleHelpRedirectActivity.kt @@ -6,33 +6,118 @@ package org.microg.gms.googlehelp.ui import android.content.Intent +import android.net.Uri import android.os.Bundle +import android.os.Parcel +import android.os.Parcelable.Creator import android.util.Log import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.android.volley.NetworkResponse +import com.android.volley.Request +import com.android.volley.Response +import com.android.volley.VolleyError +import com.android.volley.toolbox.Volley import com.google.android.gms.googlehelp.GoogleHelp +import com.google.android.gms.googlehelp.InProductHelp +import org.microg.gms.googlehelp.CallerAppInfo +import org.microg.gms.googlehelp.DeviceInfo +import org.microg.gms.googlehelp.RequestBody +import org.microg.gms.googlehelp.RequestContent +import org.microg.gms.googlehelp.ResponseContentWarp +import java.util.Locale +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine private const val TAG = "GoogleHelpRedirect" +private const val GOOGLE_HELP_KEY = "EXTRA_GOOGLE_HELP" +private const val PRODUCT_HELP_KEY = "EXTRA_IN_PRODUCT_HELP" + +private const val HELP_URL = "https://www.google.com/tools/feedback/mobile/help-suggestions" class GoogleHelpRedirectActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val callingPackage = callingActivity?.packageName ?: return finish() - val intent = intent ?: return finish() - val googleHelp = intent.getParcelableExtra(EXTRA_GOOGLE_HELP) ?: return finish() - Log.d(TAG, "Using GoogleHelp: $googleHelp") - val uri = googleHelp.uri ?: return finish() - // TODO: Not all Google apps send proper URI values, as these are in fact not used by Google's original implementation. - // As a work-around we should get the proper URL by retrieving top_level_topic_url:$callingPackage - // from https://www.google.com/tools/feedback/mobile/get-configurations endpoint. - // Long-term best is to not redirect to web but instead implement the thing properly, allowing us also to show - // option items, do proper theming for better integration, etc. - Log.d(TAG, "Open $uri for $callingPackage/${googleHelp.appContext} in Browser") - // noinspection UnsafeImplicitIntentLaunch - startActivity(Intent(Intent.ACTION_VIEW, uri)) - finish() + Log.d(TAG, "onCreate begin") + if (intent == null) { + Log.d(TAG, "onCreate intent is null") + finish() + return + } + val callingPackage = callingPackage ?: callingActivity?.packageName ?: return finish() + Log.d(TAG, "onCreate callingPackage: $callingPackage") + val googleHelp = intent.getParcelableExtra(GOOGLE_HELP_KEY) + var inProductHelp: InProductHelp? = null + if (googleHelp == null) { + inProductHelp = getParcelableFromIntent(intent, PRODUCT_HELP_KEY, InProductHelp.CREATOR) + } + + lifecycleScope.launchWhenCreated { + Log.d(TAG, "onCreate: googleHelp: ${googleHelp ?: inProductHelp?.googleHelp}") + val searchId = googleHelp?.appContext ?: inProductHelp?.googleHelp?.appContext + val answerUrl = runCatching { requestHelpLink(callingPackage, searchId).content?.info?.answerUrl }.getOrNull() + Log.d(TAG, "requestHelpLink answerUrl: $answerUrl") + val url = answerUrl ?: googleHelp?.uri?.toString() ?: inProductHelp?.googleHelp?.uri?.toString() ?: return@launchWhenCreated finish() + Log.d(TAG, "Open $url for $callingPackage in Browser") + val targetIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + val resolveInfoList = packageManager.queryIntentActivities(targetIntent, 0) + Log.d(TAG, "resolveInfoList: $resolveInfoList") + if (resolveInfoList.isNotEmpty()) { + startActivity(targetIntent) + } + finish() + } + } + + private fun getParcelableFromIntent(intent: Intent, key: String?, creator: Creator): T? { + try { + val data = intent.getByteArrayExtra(key) + if (data != null) { + val parcel = Parcel.obtain() + parcel.unmarshall(data, 0, data.size) + parcel.setDataPosition(0) + val result = creator.createFromParcel(parcel) + parcel.recycle() + return result + } + } catch (e: Exception) { + Log.e(TAG, "Error deserializing InProductHelp", e) + } + return null } - companion object { - const val EXTRA_GOOGLE_HELP = "EXTRA_GOOGLE_HELP" + private suspend fun requestHelpLink(callingPackage: String, searchId: String?) = suspendCoroutine { sus -> + Volley.newRequestQueue(this.applicationContext).add(object : Request(Method.POST, HELP_URL, { + Log.d(TAG, "requestHelpLink: ", it) + sus.resumeWithException(it) + }) { + + override fun deliverResponse(response: ResponseContentWarp) { + Log.d(TAG, "requestHelpLink response: $response") + sus.resume(response) + } + + override fun getBody(): ByteArray { + return RequestContent.Builder().apply { + appInfo = CallerAppInfo.Builder().apply { packageName = callingPackage }.build() + deviceInfo = DeviceInfo.Builder().apply { language = Locale.getDefault().language }.build() + body = RequestBody.Builder().apply { appContext = searchId }.build() + }.build().also { + Log.d(TAG, "requestBody: $it") + }.encode() + } + + override fun getBodyContentType(): String = "application/x-protobuf" + + override fun parseNetworkResponse(response: NetworkResponse): Response { + return try { + Response.success(ResponseContentWarp.ADAPTER.decode(response.data), null) + } catch (e: Exception) { + Response.error(VolleyError(e)) + } + } + }) } } \ No newline at end of file