Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Work app store #2553

Open
wants to merge 61 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
34ccbe2
SplitInstallService Additions
DaVinci9196 Aug 19, 2024
5e6f01e
add permission
DaVinci9196 Aug 19, 2024
1962f33
ISplitInstallServiceCallback added
DaVinci9196 Aug 19, 2024
c9bfbc0
remove Phenotype / Experiment
DaVinci9196 Aug 21, 2024
796c3ab
Merge branch 'microg:master' into split_install_service
DaVinci9196 Aug 22, 2024
e8a0058
Optimizing the code
DaVinci9196 Aug 22, 2024
b8d2b20
SplitInstallService add call super.onBind
DaVinci9196 Aug 22, 2024
6cdd488
Fixed the issue that multiple versions of language packs could not be…
DaVinci9196 Aug 26, 2024
36651db
Formatting Code
DaVinci9196 Aug 27, 2024
ff837d9
Formatting Code
DaVinci9196 Aug 27, 2024
8a68044
Execute processing suggestions
DaVinci9196 Sep 12, 2024
5a9f1b3
cleanup
DaVinci9196 Sep 12, 2024
7df6f93
Location/Huawei: Fix permission notification cancelling
mar-v-in Aug 22, 2024
bc57d78
Added the automatic login function for Games (#2435)
DaVinci9196 Aug 22, 2024
a7a2584
Location: Make sure current interval and configured interval stay in …
mar-v-in Aug 22, 2024
c5b7383
Create Work Account service
fynngodau Sep 20, 2023
686ab6d
Introduce WorkAccountAuthenticatorService
jonathanklee Aug 14, 2024
96e6b0c
Work account POC
fynngodau Aug 27, 2024
7f4f252
Work profile: use correct AIDL
fynngodau Aug 27, 2024
762fa53
Call account creation when app is using API to create work account
fynngodau Sep 1, 2024
999e1d3
Work account: Fix rough edges
fynngodau Sep 1, 2024
3af3991
Work account: Add security checks
fynngodau Sep 1, 2024
5e2c9d3
Work account: Redeem token when signing in to account
fynngodau Sep 2, 2024
3128c06
Add license identifiers and remove unwanted changes
fynngodau Sep 2, 2024
30f4858
Work account: Fix `NewApi` lint failure
fynngodau Sep 2, 2024
482038a
Deduplicate work account service declaration
fynngodau Sep 13, 2024
5a39c65
POC: Test activity in companion app to show client policy
fynngodau Sep 14, 2024
0db6eed
Improve visuals of vending activity
fynngodau Sep 15, 2024
c621098
Fetch bulk details for apps retrieved from policy
fynngodau Sep 15, 2024
1e17cc1
Load app details using getItems
fynngodau Sep 16, 2024
8e5ca13
Cleanup: Remove nonfunctional bulkDetails request
fynngodau Sep 16, 2024
20e04fe
UI: Add download button
fynngodau Sep 16, 2024
e99cbd3
Fetch app download URLs
fynngodau Sep 20, 2024
80c9c52
Add app installation support
fynngodau Sep 21, 2024
7ab6cf1
Add app uninstallation support
fynngodau Sep 21, 2024
c3398b9
Refactor SplitInstallManager: data types
fynngodau Sep 21, 2024
3edeb83
Refactor SplitInstallManager: structural pt. 1
fynngodau Sep 21, 2024
23d4459
`FLAG_MUTABLE` instead of `FLAG_IMMUTABLE`
fynngodau Sep 21, 2024
e434c8c
Refactor SplitInstallManager: structural pt. 2
fynngodau Sep 21, 2024
73f1a3a
Refactor SplitInstallManager: structural pt. 3
fynngodau Sep 22, 2024
3b8509d
Work vending: Show installation success without reload
fynngodau Sep 22, 2024
68ef487
Use cache dir instead of files dir
fynngodau Sep 22, 2024
f257e83
Enable work app store only after adding a work account
fynngodau Sep 22, 2024
1887759
Fix lint
fynngodau Sep 22, 2024
85bd1c9
Fix split installs
fynngodau Sep 22, 2024
2e1dfec
Organize vending protobuf files
fynngodau Sep 25, 2024
ec18cdb
Replace volley with ktor + okhttp
fynngodau Sep 25, 2024
09acd1a
Install packages directly to improve performance
fynngodau Sep 25, 2024
9ee71fb
Add progress bar to work app store
fynngodau Sep 26, 2024
352260d
Reimplement caching in `HttpClient`
fynngodau Sep 26, 2024
2109e46
Discard session after exception to clear up storage
fynngodau Sep 26, 2024
c237146
Notifications upon install progress
fynngodau Oct 1, 2024
f39a889
Deduplicate auth-related files
fynngodau Oct 19, 2024
c976df1
Clean up constants
fynngodau Oct 19, 2024
f14124c
Merge master branch into workaccount-store
fynngodau Oct 19, 2024
655cc6f
Fix crash when no apps in enterprise policy
fynngodau Oct 19, 2024
4f0665c
Fix build
fynngodau Oct 19, 2024
a6dc3b3
Purchase apps before downloading in work store
fynngodau Oct 19, 2024
b2cec33
Support additional strange behaviors
fynngodau Oct 19, 2024
04afa56
Show info: wait before work app store can be used
fynngodau Oct 21, 2024
ed745dc
Apply review
fynngodau Oct 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ buildscript {

ext.slf4jVersion = '1.7.36'
ext.volleyVersion = '1.2.1'
ext.wireVersion = '4.8.0'
ext.okHttpVersion = '4.12.0'
ext.ktorVersion = '2.3.12'
ext.wireVersion = '4.9.9'

ext.androidBuildGradleVersion = '8.2.2'

Expand Down
38 changes: 38 additions & 0 deletions play-services-auth-workaccount/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2023 e foundation
* SPDX-License-Identifier: Apache-2.0
*/

apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
apply plugin: 'signing'

android {
namespace "com.google.android.gms.auth.workaccount"

compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"

buildFeatures {
aidl = true
}

defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}

compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}

}

apply from: '../gradle/publish-android.gradle'

description = 'microG implementation of managed work account support'

dependencies {
}
45 changes: 45 additions & 0 deletions play-services-auth-workaccount/core/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: 2023 e foundation
* SPDX-License-Identifier: Apache-2.0
*/

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

dependencies {
api project(':play-services-auth-workaccount')
api project(':play-services-auth')
implementation project(':play-services-base-core')

implementation "androidx.appcompat:appcompat:$appcompatVersion"
}

android {
namespace "com.google.android.gms.auth.workaccount"

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
}

lintOptions {
disable 'MissingTranslation'
}
}
49 changes: 49 additions & 0 deletions play-services-auth-workaccount/core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2023 e foundation
~ SPDX-License-Identifier: Apache-2.0
-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission
android:name="android.permission.AUTHENTICATE_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission
android:name="android.permission.GET_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission
android:name="android.permission.MANAGE_ACCOUNTS"
android:maxSdkVersion="22" />

<application>

<service android:name="org.microg.gms.auth.workaccount.WorkAccountService"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.auth.account.workaccount.START" />
</intent-filter>
</service>

<service
android:name="com.google.android.gms.auth.account.authenticator.WorkAccountAuthenticatorService"
android:process=":persistent"
android:enabled="false"
android:exported="false">

<intent-filter>
<action android:name="android.accounts.AccountAuthenticator"/>
</intent-filter>

<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/auth_work_authenticator"/>

<meta-data
android:name="android.accounts.AccountAuthenticator.customTokens"
android:value="1"/>

</service>

</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* SPDX-FileCopyrightText: 2024 e foundation
* SPDX-License-Identifier: Apache-2.0
*/

package com.google.android.gms.auth.account.authenticator

import android.accounts.AbstractAccountAuthenticator
import android.accounts.Account
import android.accounts.AccountAuthenticatorResponse
import android.accounts.AccountManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import com.google.android.gms.auth.workaccount.R
import org.microg.gms.auth.AuthConstants
import org.microg.gms.common.PackageUtils
import org.microg.gms.auth.AuthRequest
import org.microg.gms.auth.AuthResponse
import java.io.IOException

class WorkAccountAuthenticator(val context: Context) : AbstractAccountAuthenticator(context) {

override fun editProperties(
response: AccountAuthenticatorResponse,
accountType: String?
): Bundle {
TODO("Not yet implemented: editProperties")
}

override fun addAccount(
response: AccountAuthenticatorResponse,
accountType: String,
authTokenType: String?,
requiredFeatures: Array<out String>?,
options: Bundle
): Bundle? {
if (
!options.containsKey(KEY_ACCOUNT_CREATION_TOKEN)
|| options.getString(KEY_ACCOUNT_CREATION_TOKEN) == null
|| options.getInt(AccountManager.KEY_CALLER_UID) != android.os.Process.myUid()) {
Log.e(TAG,
"refusing to add account without creation token or from external app: " +
"could have been manually initiated by user (not supported) " +
"or by unauthorized app (not allowed)"
)

// TODO: The error message is not automatically displayed by the settings app as of now.
// We can consider showing the error message through a popup instead.

return Bundle().apply {
putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION)
putString(AccountManager.KEY_ERROR_MESSAGE, context.getString(R.string.auth_work_authenticator_add_manual_error)
)
}
}

val oauthToken: String = options.getString(KEY_ACCOUNT_CREATION_TOKEN)!!

try {
val authResponse = AuthRequest().fromContext(context)
.appIsGms()
.callerIsGms()
.service("ac2dm")
.token(oauthToken).isAccessToken()
.addAccount()
.getAccountId()
.droidguardResults(null)
.response

val accountManager = AccountManager.get(context)
if (accountManager.addAccountExplicitly(
Account(authResponse.email, AuthConstants.WORK_ACCOUNT_TYPE),
authResponse.token, Bundle().apply {
// Work accounts have no SID / LSID ("BAD_COOKIE") and no first/last name.
if (authResponse.accountId.isNotBlank()) {
putString(KEY_GOOGLE_USER_ID, authResponse.accountId)
}
putString(AuthConstants.KEY_ACCOUNT_CAPABILITIES, authResponse.capabilities)
putString(AuthConstants.KEY_ACCOUNT_SERVICES, authResponse.services)
if (authResponse.services != "android") {
Log.i(TAG, "unexpected 'services' value ${authResponse.services} (usually 'android')")
}
}
)) {

// Notify vending package
context.sendBroadcast(
Intent(WORK_ACCOUNT_CHANGED_BOARDCAST).setPackage("com.android.vending")
)

// Report successful creation to caller
response.onResult(Bundle().apply {
putString(AccountManager.KEY_ACCOUNT_NAME, authResponse.email)
putString(AccountManager.KEY_ACCOUNT_TYPE, AuthConstants.WORK_ACCOUNT_TYPE)
})
}
fynngodau marked this conversation as resolved.
Show resolved Hide resolved

} catch (exception: Exception) {
response.onResult(Bundle().apply {
putInt(
AccountManager.KEY_ERROR_CODE,
AccountManager.ERROR_CODE_NETWORK_ERROR
)
putString(AccountManager.KEY_ERROR_MESSAGE, exception.message)
})
}

/* Note: as is not documented, `null` must only be returned after `response.onResult` was
* already called, hence forcing the requests to be synchronous. They are still async to
* the caller's main thread because AccountManager forces potentially blocking operations,
* like waiting for a response upon `addAccount`, not to be on the main thread.
*/
return null
}

override fun confirmCredentials(
response: AccountAuthenticatorResponse?,
account: Account?,
options: Bundle?
): Bundle {
return Bundle().apply {
putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true)
}
}

override fun getAuthToken(
response: AccountAuthenticatorResponse?,
account: Account,
authTokenType: String?,
options: Bundle?
): Bundle {
try {
val authResponse: AuthResponse =
AuthRequest().fromContext(context)
.source("android")
.app(
context.packageName,
PackageUtils.firstSignatureDigest(context, context.packageName)
)
.email(account.name)
.token(AccountManager.get(context).getPassword(account))
.service(authTokenType)
.delegation(0, null)
// .oauth2Foreground(oauth2Foreground)
// .oauth2Prompt(oauth2Prompt)
// .oauth2IncludeProfile(includeProfile)
// .oauth2IncludeEmail(includeEmail)
// .itCaveatTypes(itCaveatTypes)
// .tokenRequestOptions(tokenRequestOptions)
.systemPartition(true)
.hasPermission(true)
// .putDynamicFiledMap(dynamicFields)
.appIsGms()
.callerIsApp()
.response

return Bundle().apply {
putString(AccountManager.KEY_ACCOUNT_NAME, account.name)
putString(AccountManager.KEY_ACCOUNT_TYPE, account.type)
putString(AccountManager.KEY_AUTHTOKEN, authResponse.auth)
}
} catch (e: IOException) {
return Bundle().apply {
putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_NETWORK_ERROR)
putString(AccountManager.KEY_ERROR_MESSAGE, e.message)
}
}
}

override fun getAuthTokenLabel(authTokenType: String?): String {
TODO("Not yet implemented: getAuthTokenLabel")
}

override fun updateCredentials(
response: AccountAuthenticatorResponse?,
account: Account?,
authTokenType: String?,
options: Bundle?
): Bundle {
TODO("Not yet implemented: updateCredentials")
}

override fun hasFeatures(
response: AccountAuthenticatorResponse?,
account: Account?,
features: Array<out String>
): Bundle {
Log.i(TAG, "Queried features: " + features.joinToString(", "))
return Bundle().apply {
putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)
}
}

/**
* Prevent accidental deletion, unlike GMS. The account can only be removed through client apps;
* ideally, it would only be removed by the app that requested it to be created / the DPC
* manager, though this is not enforced. On API 21, the account can also be removed by hand
* because `removeAccountExplicitly` is not available on API 21.
*/
override fun getAccountRemovalAllowed(
response: AccountAuthenticatorResponse?,
account: Account?
): Bundle {
return Bundle().apply {
putBoolean(AccountManager.KEY_BOOLEAN_RESULT,
Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1
)
}
}

companion object {
const val TAG = "WorkAccAuthenticator"

const val WORK_ACCOUNT_CHANGED_BOARDCAST = "org.microg.vending.WORK_ACCOUNT_CHANGED"

const val KEY_ACCOUNT_CREATION_TOKEN = "creationToken"
private const val KEY_GOOGLE_USER_ID = AuthConstants.GOOGLE_USER_ID
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2024 e foundation
* SPDX-License-Identifier: Apache-2.0
*/

package com.google.android.gms.auth.account.authenticator

import android.accounts.AccountManager
import android.app.Service
import android.content.Intent
import android.os.IBinder

class WorkAccountAuthenticatorService : Service() {
private val authenticator by lazy { WorkAccountAuthenticator(this) }

override fun onBind(intent: Intent): IBinder? {
if (intent.action == AccountManager.ACTION_AUTHENTICATOR_INTENT) {
return authenticator.iBinder
}
return null
}
}
Loading