From c62109805a102bc084a13fa4f36ddf589ccb21ca Mon Sep 17 00:00:00 2001 From: Fynn Godau Date: Sun, 15 Sep 2024 17:32:37 +0200 Subject: [PATCH] Fetch bulk details for apps retrieved from policy Nonfunctional attempt. --- vending-app/src/main/AndroidManifest.xml | 4 +- .../vending/billing/core/GooglePlayApi.kt | 1 + .../java/org/microg/vending/enterprise/App.kt | 2 +- .../vending/enterprise/EnterpriseApp.kt | 2 +- .../org/microg/vending/ui/VendingActivity.kt | 117 ++++++++++++++---- .../kotlin/com/android/vending/extensions.kt | 2 +- .../src/main/proto/BulkDetailsRequest.proto | 15 +++ .../src/main/proto/BulkDetailsResponse.proto | 29 +++++ .../src/main/proto/RequestHeader.proto | 2 +- vending-app/src/main/proto/SplitInstall.proto | 2 + vending-app/src/main/res/drawable/ic_work.xml | 5 + vending-app/src/main/res/values/strings.xml | 1 + 12 files changed, 153 insertions(+), 29 deletions(-) create mode 100644 vending-app/src/main/proto/BulkDetailsRequest.proto create mode 100644 vending-app/src/main/proto/BulkDetailsResponse.proto create mode 100644 vending-app/src/main/res/drawable/ic_work.xml diff --git a/vending-app/src/main/AndroidManifest.xml b/vending-app/src/main/AndroidManifest.xml index 5588ca0f2b..023cc56063 100644 --- a/vending-app/src/main/AndroidManifest.xml +++ b/vending-app/src/main/AndroidManifest.xml @@ -187,7 +187,9 @@ android:name="com.google.android.finsky.splitinstallservice.SplitInstallManager$InstallResultReceiver" android:exported="true"/> + android:exported="true" + android:theme="@style/Theme.Material3.DayNight" + android:label="@string/vending_activity_name"> diff --git a/vending-app/src/main/java/org/microg/vending/billing/core/GooglePlayApi.kt b/vending-app/src/main/java/org/microg/vending/billing/core/GooglePlayApi.kt index 410640bcdb..025ea9d1a2 100644 --- a/vending-app/src/main/java/org/microg/vending/billing/core/GooglePlayApi.kt +++ b/vending-app/src/main/java/org/microg/vending/billing/core/GooglePlayApi.kt @@ -11,6 +11,7 @@ class GooglePlayApi { const val URL_GET_PURCHASE_HISTORY = "$URL_FDFE/inAppPurchaseHistory" const val URL_AUTH_PROOF_TOKENS = "https://www.googleapis.com/reauth/v1beta/users/me/reauthProofTokens" const val URL_DETAILS = "$URL_FDFE/details" + const val URL_BULK_DETAILS = "$URL_FDFE/bulkDetails" const val URL_PURCHASE = "$URL_FDFE/purchase" const val URL_ENTERPRISE_CLIENT_POLICY = "$URL_FDFE/getEnterpriseClientPolicy" } diff --git a/vending-app/src/main/java/org/microg/vending/enterprise/App.kt b/vending-app/src/main/java/org/microg/vending/enterprise/App.kt index ffafdd50b3..744be08abf 100644 --- a/vending-app/src/main/java/org/microg/vending/enterprise/App.kt +++ b/vending-app/src/main/java/org/microg/vending/enterprise/App.kt @@ -4,7 +4,7 @@ open class App( val packageName: String, val displayName: String, val state: State, - val iconUrl: String + val iconUrl: String? ) { enum class State { /** diff --git a/vending-app/src/main/java/org/microg/vending/enterprise/EnterpriseApp.kt b/vending-app/src/main/java/org/microg/vending/enterprise/EnterpriseApp.kt index 87bce6ca8d..d31169e07e 100644 --- a/vending-app/src/main/java/org/microg/vending/enterprise/EnterpriseApp.kt +++ b/vending-app/src/main/java/org/microg/vending/enterprise/EnterpriseApp.kt @@ -6,6 +6,6 @@ class EnterpriseApp( packageName: String, displayName: String, state: State, - iconUrl: String, + iconUrl: String?, val policy: AppInstallPolicy, ) : App(packageName, displayName, state, iconUrl) \ No newline at end of file diff --git a/vending-app/src/main/java/org/microg/vending/ui/VendingActivity.kt b/vending-app/src/main/java/org/microg/vending/ui/VendingActivity.kt index d8d17747b5..a6f51415a9 100644 --- a/vending-app/src/main/java/org/microg/vending/ui/VendingActivity.kt +++ b/vending-app/src/main/java/org/microg/vending/ui/VendingActivity.kt @@ -5,25 +5,46 @@ import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.android.vending.R import com.android.vending.buildRequestHeaders import com.android.volley.VolleyError +import com.google.android.finsky.BulkDetailsRequest +import com.google.android.finsky.DetailsRequest import com.google.android.finsky.GoogleApiResponse import kotlinx.coroutines.runBlocking import org.microg.gms.profile.ProfileManager +import org.microg.gms.ui.TAG import org.microg.vending.billing.AuthManager -import org.microg.vending.billing.TAG +import org.microg.vending.billing.core.GooglePlayApi.Companion.URL_BULK_DETAILS +import org.microg.vending.billing.core.GooglePlayApi.Companion.URL_DETAILS import org.microg.vending.billing.core.GooglePlayApi.Companion.URL_ENTERPRISE_CLIENT_POLICY import org.microg.vending.billing.core.HttpClient import org.microg.vending.billing.createDeviceEnvInfo +import org.microg.vending.billing.proto.ResponseWrapper import org.microg.vending.enterprise.App import org.microg.vending.enterprise.EnterpriseApp +import java.io.IOException class VendingActivity : ComponentActivity() { @@ -31,7 +52,9 @@ class VendingActivity : ComponentActivity() { var apps: MutableList = mutableStateListOf() var networkState by mutableStateOf(NetworkState.ACTIVE) + @OptIn(ExperimentalMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() super.onCreate(savedInstanceState) ProfileManager.ensureInitialized(this) @@ -40,51 +63,97 @@ class VendingActivity : ComponentActivity() { val account = am.getAccountsByType("com.google.work").first()!! Thread { runBlocking { - val authData = AuthManager.getAuthData(this@VendingActivity, account) - val deviceInfo = createDeviceEnvInfo(this@VendingActivity) - if (deviceInfo == null || authData == null) { - Log.e(TAG, "Unable to open play store when deviceInfo = $deviceInfo and authData = $authData") - return@runBlocking - } + try { + val authData = AuthManager.getAuthData(this@VendingActivity, account) + val deviceInfo = createDeviceEnvInfo(this@VendingActivity) + if (deviceInfo == null || authData == null) { + Log.e(TAG, "Unable to open play store when deviceInfo = $deviceInfo and authData = $authData") + networkState = NetworkState.ERROR + return@runBlocking + } - val headers = buildRequestHeaders(authData.authToken, authData.gsfId.toLong(16)) - .plus("content-type" to "application/x-protobuf") - val client = HttpClient(this@VendingActivity) + val headers = buildRequestHeaders(authData.authToken, authData.gsfId.toLong(16)) + val client = HttpClient(this@VendingActivity) - try { val apps = client.post( url = URL_ENTERPRISE_CLIENT_POLICY, - headers = headers, + headers = headers.plus("content-type" to "application/x-protobuf"), adapter = GoogleApiResponse.ADAPTER ).response?.enterpriseClientPolicyResult?.policy?.apps?.filter { it.packageName != null && it.policy != null } - ?.map { + + if (apps == null) { + Log.e(TAG, "unexpected network response: missing expected fields") + networkState = NetworkState.ERROR + return@runBlocking + } + + val details = client.post( + url = URL_BULK_DETAILS, + headers = headers, + adapter = GoogleApiResponse.ADAPTER, + payload = BulkDetailsRequest( + apps.map { + DetailsRequest( + packageName = it.packageName!!, + versionCode = 0, + unknown0 = 0 + ) + } + )).response?.bulkDetailsResponse?.details?.mapNotNull { it.metadata } + ?.filter { it.packageName != null && it.displayName != null }?.map { app -> EnterpriseApp( - it.packageName!!, - "Display name placeholder", + app.packageName!!, + app.displayName!!, App.State.NOT_INSTALLED, - "https://i.ytimg.com/vi/IWZFLZ1mvc4/hqdefault.jpg", - it.policy!! + app.icon.lastOrNull()?.url, + apps.find { it.packageName!! == app.packageName }!!.policy!!, ) } + this@VendingActivity.apps.apply { clear() - apps?.let { addAll(it) } + details?.let { addAll(it) } } networkState = NetworkState.PASSIVE + } catch (e: IOException) { + networkState = NetworkState.ERROR } catch (e: VolleyError) { networkState = NetworkState.ERROR } } }.start() - - setContent { MaterialTheme { - Column { - Text(account.name) - NetworkStateComponent(networkState, { TODO("reload") }) { - EnterpriseListComponent(apps) + Scaffold( + topBar = { + TopAppBar( + title = { + Row { + Icon( + painterResource(R.drawable.ic_work), + contentDescription = null, + Modifier.align(Alignment.CenterVertically), + tint = LocalContentColor.current + ) + Text(stringResource(R.string.vending_activity_name), + Modifier + .align(Alignment.CenterVertically) + .padding(start = 8.dp) + ) + } + }, + colors = TopAppBarDefaults.smallTopAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + titleContentColor = MaterialTheme.colorScheme.primary + ) + ) + } + ) { innerPadding -> + Column(Modifier.padding(innerPadding)) { + NetworkStateComponent(networkState, { TODO("reload") }) { + EnterpriseListComponent(apps) + } } } } diff --git a/vending-app/src/main/kotlin/com/android/vending/extensions.kt b/vending-app/src/main/kotlin/com/android/vending/extensions.kt index 5b010d2c32..34fee97b25 100644 --- a/vending-app/src/main/kotlin/com/android/vending/extensions.kt +++ b/vending-app/src/main/kotlin/com/android/vending/extensions.kt @@ -60,7 +60,7 @@ fun buildRequestHeaders(auth: String, androidId: Long, language: List ?= DeviceMeta.Builder().android( AndroidVersionMeta.Builder().androidSdk(Build.VERSION.SDK_INT).buildNumber(Build.ID).androidVersion(Build.VERSION.RELEASE).unknown(0).build() ).unknown1( - UnknownByte12.Builder().bytes(ByteString.EMPTY).build() + UnknownByte12.Builder().bytes(ByteString.EMPTY).build().toString() ).unknown2(1).build() ).userAgent( UserAgent.Builder().deviceName(Build.DEVICE).deviceHardware(Build.HARDWARE).deviceModelName(Build.MODEL).finskyVersion(FINSKY_VERSION) diff --git a/vending-app/src/main/proto/BulkDetailsRequest.proto b/vending-app/src/main/proto/BulkDetailsRequest.proto new file mode 100644 index 0000000000..6797a70b23 --- /dev/null +++ b/vending-app/src/main/proto/BulkDetailsRequest.proto @@ -0,0 +1,15 @@ +syntax = "proto2"; + +option java_package = "com.google.android.finsky"; +option java_multiple_files = true; + + +message BulkDetailsRequest { + repeated DetailsRequest requests = 8; +} + +message DetailsRequest { + required string packageName = 1; + optional uint32 versionCode = 2; + required uint32 unknown0 = 3; // = 0 +} \ No newline at end of file diff --git a/vending-app/src/main/proto/BulkDetailsResponse.proto b/vending-app/src/main/proto/BulkDetailsResponse.proto new file mode 100644 index 0000000000..6450d4b3a2 --- /dev/null +++ b/vending-app/src/main/proto/BulkDetailsResponse.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +option java_package = "com.google.android.finsky"; +option java_multiple_files = true; + +message BulkAppDetailsResponse { + repeated AppDetail details = 1; +} + +message AppDetail { + optional AppMetadata metadata = 1; +} + +message AppMetadata { + optional string packageName = 1; // duplicated at ID 2 + optional string displayName = 5; + optional string author = 6; + repeated Icon icon = 10; +} + +message Icon { + optional Resolution resolution = 2; + string url = 5; +} + +message Resolution { + optional uint32 width = 3; + optional uint32 height = 4; +} \ No newline at end of file diff --git a/vending-app/src/main/proto/RequestHeader.proto b/vending-app/src/main/proto/RequestHeader.proto index 4fb678a558..2dea2e09d4 100644 --- a/vending-app/src/main/proto/RequestHeader.proto +++ b/vending-app/src/main/proto/RequestHeader.proto @@ -43,7 +43,7 @@ message RequestLanguagePackage { message DeviceMeta { optional AndroidVersionMeta android = 1; - optional UnknownByte12 unknown1 = 2; + optional string unknown1 = 2; // inconsistent observations; a field of type "UnknownByte12" was observed as well optional uint32 unknown2 = 3; // observed value: 1 } diff --git a/vending-app/src/main/proto/SplitInstall.proto b/vending-app/src/main/proto/SplitInstall.proto index cb2f5ea491..abb4dcca9f 100644 --- a/vending-app/src/main/proto/SplitInstall.proto +++ b/vending-app/src/main/proto/SplitInstall.proto @@ -3,6 +3,7 @@ option java_package = "com.google.android.finsky"; option java_multiple_files = true; import "EnterpriseClientPolicy.proto"; +import "BulkDetailsResponse.proto"; message GoogleApiResponse { optional ApiResponse response = 1; @@ -16,6 +17,7 @@ message UnknownType { message ApiResponse { optional TocResponse tocApi = 6; + optional BulkAppDetailsResponse bulkDetailsResponse = 19; optional SplitResponse splitReqResult = 21; optional EnterpriseClientPolicyResponse enterpriseClientPolicyResult = 135; // optional SyncApiResp syncResult = 183; diff --git a/vending-app/src/main/res/drawable/ic_work.xml b/vending-app/src/main/res/drawable/ic_work.xml new file mode 100644 index 0000000000..2f9846c48b --- /dev/null +++ b/vending-app/src/main/res/drawable/ic_work.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/vending-app/src/main/res/values/strings.xml b/vending-app/src/main/res/values/strings.xml index 41cb93d525..4616d10f39 100644 --- a/vending-app/src/main/res/values/strings.xml +++ b/vending-app/src/main/res/values/strings.xml @@ -17,6 +17,7 @@ Sign In Ignore + Work app store Installation required Your administrator requires that you install these apps on your managed device. Your device is missing mandatory apps chosen by your administrator.