Repo Created
This commit is contained in:
parent
eb305e2886
commit
a8c22c65db
4784 changed files with 329907 additions and 2 deletions
45
play-services-auth-workaccount/core/build.gradle
Normal file
45
play-services-auth-workaccount/core/build.gradle
Normal file
|
|
@ -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 "org.microg.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'
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?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" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
|
||||
|
||||
<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>
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* 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.VERSION.SDK_INT
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import org.microg.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 org.microg.gms.auth.workaccount.WorkProfileSettings
|
||||
import java.io.IOException
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
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 (!WorkProfileSettings(context).allowCreateWorkAccount) {
|
||||
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_disabled_error)
|
||||
)
|
||||
}
|
||||
} else 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 {
|
||||
tryAddAccount(oauthToken, response)
|
||||
} 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
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun tryAddAccount(
|
||||
oauthToken: String,
|
||||
response: AccountAuthenticatorResponse
|
||||
) {
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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, SDK_INT < 22)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 e foundation
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.auth.workaccount
|
||||
|
||||
import android.accounts.Account
|
||||
import android.accounts.AccountManager
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.Bundle
|
||||
import android.os.Parcel
|
||||
import android.util.Log
|
||||
import com.google.android.gms.auth.account.IWorkAccountCallback
|
||||
import com.google.android.gms.auth.account.IWorkAccountService
|
||||
import com.google.android.gms.auth.account.authenticator.WorkAccountAuthenticator.Companion.KEY_ACCOUNT_CREATION_TOKEN
|
||||
import com.google.android.gms.auth.account.authenticator.WorkAccountAuthenticator.Companion.WORK_ACCOUNT_CHANGED_BOARDCAST
|
||||
import com.google.android.gms.auth.account.authenticator.WorkAccountAuthenticatorService
|
||||
import com.google.android.gms.common.Feature
|
||||
import com.google.android.gms.common.api.CommonStatusCodes
|
||||
import com.google.android.gms.common.internal.ConnectionInfo
|
||||
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.auth.AuthConstants
|
||||
import org.microg.gms.common.GmsService
|
||||
import org.microg.gms.common.PackageUtils
|
||||
|
||||
private const val TAG = "GmsWorkAccountService"
|
||||
|
||||
class WorkAccountService : BaseService(TAG, GmsService.WORK_ACCOUNT) {
|
||||
override fun handleServiceRequest(
|
||||
callback: IGmsCallbacks,
|
||||
request: GetServiceRequest,
|
||||
service: GmsService
|
||||
) {
|
||||
val packageName = PackageUtils.getAndCheckCallingPackage(this, request.packageName)
|
||||
val policyManager = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
|
||||
val authorized = policyManager.isDeviceAdminApp(packageName)
|
||||
|
||||
if (authorized) {
|
||||
callback.onPostInitCompleteWithConnectionInfo(
|
||||
CommonStatusCodes.SUCCESS,
|
||||
WorkAccountServiceImpl(this),
|
||||
ConnectionInfo().apply {
|
||||
features = arrayOf(Feature("work_account_client_is_whitelisted", 1))
|
||||
})
|
||||
} else {
|
||||
// Return mock response, don't tell client that it is whitelisted
|
||||
callback.onPostInitCompleteWithConnectionInfo(
|
||||
CommonStatusCodes.SUCCESS,
|
||||
UnauthorizedWorkAccountServiceImpl(),
|
||||
ConnectionInfo().apply {
|
||||
features = emptyArray()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun DevicePolicyManager.isDeviceAdminApp(packageName: String?): Boolean {
|
||||
if (packageName == null) return false
|
||||
return if (SDK_INT >= 21) {
|
||||
isDeviceOwnerApp(packageName) || isProfileOwnerApp(packageName)
|
||||
} else {
|
||||
isDeviceOwnerApp(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
class WorkAccountServiceImpl(val context: Context) : IWorkAccountService.Stub() {
|
||||
|
||||
val packageManager: PackageManager = context.packageManager
|
||||
val accountManager: AccountManager = AccountManager.get(context)
|
||||
|
||||
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
|
||||
Log.d(TAG, "$code, $data, $reply, $flags")
|
||||
return super.onTransact(code, data, reply, flags)
|
||||
}
|
||||
|
||||
override fun setWorkAuthenticatorEnabled(enabled: Boolean) {
|
||||
Log.d(TAG, "setWorkAuthenticatorEnabled with $enabled")
|
||||
|
||||
val componentName = ComponentName(
|
||||
context,
|
||||
WorkAccountAuthenticatorService::class.java
|
||||
)
|
||||
packageManager.setComponentEnabledSetting(
|
||||
componentName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
}
|
||||
|
||||
override fun addWorkAccount(
|
||||
callback: IWorkAccountCallback?,
|
||||
token: String?
|
||||
) {
|
||||
Log.d(TAG, "addWorkAccount with token $token")
|
||||
val future = accountManager.addAccount(
|
||||
AuthConstants.WORK_ACCOUNT_TYPE,
|
||||
null,
|
||||
null,
|
||||
Bundle().apply { putString(KEY_ACCOUNT_CREATION_TOKEN, token) },
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
Thread {
|
||||
try {
|
||||
future.result.let { result ->
|
||||
callback?.onAccountAdded(
|
||||
Account(
|
||||
result.getString(AccountManager.KEY_ACCOUNT_NAME)!!,
|
||||
result.getString(AccountManager.KEY_ACCOUNT_TYPE)!!
|
||||
)
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "could not add work account with error message: ${e.message}")
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
override fun removeWorkAccount(
|
||||
callback: IWorkAccountCallback?,
|
||||
account: Account?
|
||||
) {
|
||||
Log.d(TAG, "removeWorkAccount with account ${account?.name}")
|
||||
account?.let {
|
||||
if (SDK_INT >= 22) {
|
||||
|
||||
val success = accountManager.removeAccountExplicitly(it)
|
||||
|
||||
// Notify vending package
|
||||
context.sendBroadcast(
|
||||
Intent(WORK_ACCOUNT_CHANGED_BOARDCAST).setPackage("com.android.vending")
|
||||
)
|
||||
|
||||
callback?.onAccountRemoved(success)
|
||||
} else {
|
||||
val future = accountManager.removeAccount(it, null, null)
|
||||
Thread {
|
||||
future.result.let { result ->
|
||||
callback?.onAccountRemoved(result)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UnauthorizedWorkAccountServiceImpl : IWorkAccountService.Stub() {
|
||||
override fun setWorkAuthenticatorEnabled(enabled: Boolean) {
|
||||
throw SecurityException("client not admin, yet tried to enable work authenticator")
|
||||
}
|
||||
|
||||
override fun addWorkAccount(callback: IWorkAccountCallback?, token: String?) {
|
||||
throw SecurityException("client not admin, yet tried to add work account")
|
||||
}
|
||||
|
||||
override fun removeWorkAccount(callback: IWorkAccountCallback?, account: Account?) {
|
||||
throw SecurityException("client not admin, yet tried to remove work account")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 e foundation
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.microg.gms.auth.workaccount
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import org.microg.gms.settings.SettingsContract
|
||||
|
||||
class WorkProfileSettings(private val context: Context) {
|
||||
private fun <T> getSettings(vararg projection: String, f: (Cursor) -> T): T =
|
||||
SettingsContract.getSettings(
|
||||
context,
|
||||
SettingsContract.WorkProfile.getContentUri(context),
|
||||
projection,
|
||||
f
|
||||
)
|
||||
|
||||
private fun setSettings(v: ContentValues.() -> Unit) =
|
||||
SettingsContract.setSettings(context, SettingsContract.WorkProfile.getContentUri(context), v)
|
||||
|
||||
var allowCreateWorkAccount: Boolean
|
||||
get() = getSettings(SettingsContract.WorkProfile.CREATE_WORK_ACCOUNT) { c -> c.getInt(0) != 0 }
|
||||
set(value) = setSettings { put(SettingsContract.WorkProfile.CREATE_WORK_ACCOUNT, value) }
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<!--
|
||||
~ SPDX-FileCopyrightText: Android Open Source Project
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#4285F4" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||
|
||||
<path android:fillColor="@android:color/white" android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM14,6h-4L10,4h4v2z"/>
|
||||
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: 2024 e foundation
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
|
||||
<string name="auth_work_authenticator_label">Managed Google for Work account</string>
|
||||
<string name="auth_work_authenticator_add_manual_error">This type of account is created automatically by your profile administrator if needed.</string>
|
||||
<string name="auth_work_authenticator_disabled_error">Creating a work account is disabled in microG settings.</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: 2024 e foundation
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:label="@string/auth_work_authenticator_label"
|
||||
android:icon="@drawable/ic_briefcase"
|
||||
android:accountType="com.google.work"
|
||||
android:smallIcon="@drawable/ic_briefcase"
|
||||
android:customTokens="true"/>
|
||||
Loading…
Add table
Add a link
Reference in a new issue