Skip to content

Commit 6e66498

Browse files
committed
Merge branch 'workaccount-store-cross-profile' into workaccount-store
2 parents 0ae381d + bbac09b commit 6e66498

File tree

21 files changed

+563
-68
lines changed

21 files changed

+563
-68
lines changed

play-services-auth-workaccount/core/src/main/kotlin/com/google/android/gms/auth/account/authenticator/WorkAccountAuthenticator.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import org.microg.gms.auth.AuthConstants
1919
import org.microg.gms.common.PackageUtils
2020
import org.microg.gms.auth.AuthRequest
2121
import org.microg.gms.auth.AuthResponse
22+
import org.microg.gms.auth.workaccount.WorkProfileSettings
2223
import java.io.IOException
2324
import kotlin.jvm.Throws
2425

@@ -38,7 +39,14 @@ class WorkAccountAuthenticator(val context: Context) : AbstractAccountAuthentica
3839
requiredFeatures: Array<out String>?,
3940
options: Bundle
4041
): Bundle? {
41-
if (
42+
43+
if (!WorkProfileSettings(context).allowCreateWorkAccount) {
44+
return Bundle().apply {
45+
putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION)
46+
putString(AccountManager.KEY_ERROR_MESSAGE, context.getString(R.string.auth_work_authenticator_disabled_error)
47+
)
48+
}
49+
} else if (
4250
!options.containsKey(KEY_ACCOUNT_CREATION_TOKEN)
4351
|| options.getString(KEY_ACCOUNT_CREATION_TOKEN) == null
4452
|| options.getInt(AccountManager.KEY_CALLER_UID) != android.os.Process.myUid()) {

play-services-auth-workaccount/core/src/main/kotlin/org/microg/gms/auth/workaccount/WorkAccountService.kt

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,17 @@ class WorkAccountServiceImpl(val context: Context) : IWorkAccountService.Stub()
110110
null
111111
)
112112
Thread {
113-
future.result.let { result ->
114-
if (result.containsKey(AccountManager.KEY_ERROR_CODE)) {
115-
Log.w(TAG, "could not add work account due to network error: ${result.getString(AccountManager.KEY_ERROR_MESSAGE)}")
116-
} else callback?.onAccountAdded(
117-
Account(
118-
result.getString(AccountManager.KEY_ACCOUNT_NAME),
119-
result.getString(AccountManager.KEY_ACCOUNT_TYPE)
113+
try {
114+
future.result.let { result ->
115+
callback?.onAccountAdded(
116+
Account(
117+
result.getString(AccountManager.KEY_ACCOUNT_NAME),
118+
result.getString(AccountManager.KEY_ACCOUNT_TYPE)
119+
)
120120
)
121-
)
121+
}
122+
} catch (e: Exception) {
123+
Log.e(TAG, "could not add work account with error message: ${e.message}")
122124
}
123125
}.start()
124126
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.microg.gms.auth.workaccount
2+
3+
import android.content.ContentValues
4+
import android.content.Context
5+
import android.database.Cursor
6+
import org.microg.gms.settings.SettingsContract
7+
8+
class WorkProfileSettings(private val context: Context) {
9+
private fun <T> getSettings(vararg projection: String, f: (Cursor) -> T): T =
10+
SettingsContract.getSettings(
11+
context,
12+
SettingsContract.WorkProfile.getContentUri(context),
13+
projection,
14+
f
15+
)
16+
17+
private fun setSettings(v: ContentValues.() -> Unit) =
18+
SettingsContract.setSettings(context, SettingsContract.WorkProfile.getContentUri(context), v)
19+
20+
var allowCreateWorkAccount: Boolean
21+
get() = getSettings(SettingsContract.WorkProfile.CREATE_WORK_ACCOUNT) { c -> c.getInt(0) != 0 }
22+
set(value) = setSettings { put(SettingsContract.WorkProfile.CREATE_WORK_ACCOUNT, value) }
23+
}

play-services-auth-workaccount/core/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77

88
<string name="auth_work_authenticator_label">Managed Google for Work account</string>
99
<string name="auth_work_authenticator_add_manual_error">This type of account is created automatically by your profile administrator if needed.</string>
10+
<string name="auth_work_authenticator_disabled_error">Creating a work account is disabled in microG settings.</string>
1011

1112
</resources>

play-services-base/core/src/main/AndroidManifest.xml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,43 @@
33
~ SPDX-FileCopyrightText: 2020, microG Project Team
44
~ SPDX-License-Identifier: Apache-2.0
55
-->
6-
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
6+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
7+
xmlns:tools="http://schemas.android.com/tools">
78

89
<permission android:name="${applicationId}.permission.READ_SETTINGS"
910
android:protectionLevel="signature" />
1011
<permission android:name="${applicationId}.permission.WRITE_SETTINGS"
1112
android:protectionLevel="signature" />
1213

14+
<uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
15+
tools:ignore="ProtectedPermissions" />
16+
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"
17+
tools:ignore="ProtectedPermissions" />
18+
1319
<application>
1420
<provider
1521
android:name="org.microg.gms.settings.SettingsProvider"
1622
android:authorities="${applicationId}.microg.settings"
1723
android:exported="true"
24+
android:grantUriPermissions="true"
1825
android:readPermission="${applicationId}.permission.READ_SETTINGS"
1926
android:writePermission="${applicationId}.permission.WRITE_SETTINGS" />
27+
28+
<activity
29+
android:name="org.microg.gms.crossprofile.CrossProfileSendActivity"
30+
android:exported="false"
31+
tools:targetApi="30" />
32+
33+
<activity
34+
android:name="org.microg.gms.crossprofile.CrossProfileRequestActivity"
35+
android:exported="false"
36+
tools:targetApi="30" />
37+
38+
<receiver android:name="org.microg.gms.crossprofile.UserInitReceiver"
39+
android:exported="false">
40+
<intent-filter>
41+
<action android:name="android.intent.action.USER_INITIALIZE" />
42+
</intent-filter>
43+
</receiver>
2044
</application>
2145
</manifest>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package org.microg.gms.crossprofile
2+
3+
import android.app.Activity
4+
import android.content.Intent
5+
import android.content.pm.CrossProfileApps
6+
import android.os.Build
7+
import android.os.Bundle
8+
import android.os.UserManager
9+
import android.util.Log
10+
import androidx.annotation.RequiresApi
11+
import org.microg.gms.settings.SettingsContract.CROSS_PROFILE_PERMISSION
12+
import org.microg.gms.settings.SettingsContract.CROSS_PROFILE_SHARED_PREFERENCES_NAME
13+
import androidx.core.content.edit
14+
15+
/**
16+
* Two-step process:
17+
* 1. request to hear back from `CrossProfileRequestActivity`
18+
* 2. receive resulting URI as intent data
19+
*
20+
* This dance so complicated because Android platform does not offer better APIs that only need
21+
* `INTERACT_ACROSS_PROFILES`, an appops permission (and not `INTERACT_ACROSS_USERS`, a
22+
* privileged|system permission).
23+
*/
24+
@RequiresApi(Build.VERSION_CODES.R)
25+
class CrossProfileRequestActivity : Activity() {
26+
27+
override fun onCreate(savedInstanceState: Bundle?) {
28+
super.onCreate(savedInstanceState)
29+
30+
// Check that we are work profile
31+
val userManager = getSystemService(UserManager::class.java)
32+
if (!userManager.isManagedProfile) {
33+
Log.w(CrossProfileSendActivity.TAG, "I was asked to send a cross-profile request, but I am not on a work profile!")
34+
finish()
35+
return
36+
}
37+
38+
val crossProfileApps = getSystemService(CrossProfileApps::class.java)
39+
40+
val targetProfiles = crossProfileApps.targetUserProfiles
41+
42+
if (!crossProfileApps.canInteractAcrossProfiles() || targetProfiles.isEmpty()) {
43+
Log.w(
44+
TAG, "I am supposed to send a cross-profile request, but the prerequisites are not met: " +
45+
"can interact = ${crossProfileApps.canInteractAcrossProfiles()}, " +
46+
"#targetProfiles = ${targetProfiles.size}")
47+
finish()
48+
return
49+
}
50+
51+
val intent = Intent(this, CrossProfileSendActivity::class.java)
52+
53+
Log.d(TAG, "asking for cross-profile URI")
54+
crossProfileApps.startActivity(
55+
intent,
56+
targetProfiles.first(),
57+
// if this parameter is provided, it works like `startActivityForResult` (with requestCode 0)
58+
this
59+
)
60+
61+
// finish only after receiving result
62+
}
63+
64+
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
65+
Log.d(TAG, data?.data.toString())
66+
67+
val uri = data?.data
68+
if (uri == null) {
69+
Log.w(TAG, "expected to receive data, but intent did not contain any.")
70+
finish()
71+
return
72+
}
73+
74+
contentResolver.takePersistableUriPermission(uri, 0)
75+
76+
val preferences = getSharedPreferences(CROSS_PROFILE_SHARED_PREFERENCES_NAME, MODE_PRIVATE)
77+
Log.i(TAG, "storing work URI")
78+
preferences.edit { putString(CROSS_PROFILE_PERMISSION, uri.toString()) }
79+
80+
finish()
81+
}
82+
83+
companion object {
84+
const val TAG = "GmsCrossProfileRequest"
85+
}
86+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package org.microg.gms.crossprofile
2+
3+
import android.app.Activity
4+
import android.content.Intent
5+
import android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
6+
import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
7+
import android.content.pm.CrossProfileApps
8+
import android.os.Build
9+
import android.os.Bundle
10+
import android.os.UserManager
11+
import android.util.Log
12+
import androidx.annotation.RequiresApi
13+
import androidx.core.net.toUri
14+
import org.microg.gms.settings.SettingsContract.getAuthority
15+
16+
@RequiresApi(Build.VERSION_CODES.R)
17+
class CrossProfileSendActivity : Activity() {
18+
override fun onCreate(savedInstanceState: Bundle?) {
19+
super.onCreate(savedInstanceState)
20+
21+
// Check that we are primary profile
22+
val userManager = getSystemService(UserManager::class.java)
23+
if (userManager.isManagedProfile) {
24+
Log.w(TAG, "Cross-profile send request was received on work profile!")
25+
finish()
26+
return
27+
}
28+
29+
// Check prerequisites
30+
val crossProfileApps = getSystemService(CrossProfileApps::class.java)
31+
val targetProfiles = crossProfileApps.targetUserProfiles
32+
33+
if (!crossProfileApps.canInteractAcrossProfiles() || targetProfiles.isEmpty()) {
34+
Log.w(
35+
TAG, "received cross-profile request, but I believe I cannot answer, as prerequisites are not met: " +
36+
"can interact = ${crossProfileApps.canInteractAcrossProfiles()}, " +
37+
"#targetProfiles = ${targetProfiles.size}. Note that this is expected during initial setup of a work profile.")
38+
}
39+
40+
// Respond
41+
Log.d(TAG, "responding to cross-profile request")
42+
43+
setResult(1, Intent().apply {
44+
setData("content://${getAuthority(this@CrossProfileSendActivity)}".toUri())
45+
addFlags(FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
46+
})
47+
finish()
48+
}
49+
50+
companion object {
51+
const val TAG = "GmsCrossProfileSend"
52+
}
53+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.microg.gms.crossprofile
2+
3+
import android.annotation.SuppressLint
4+
import android.content.BroadcastReceiver
5+
import android.content.Context
6+
import android.content.Intent
7+
import android.os.Build
8+
import android.os.UserManager
9+
import android.util.Log
10+
11+
class UserInitReceiver : BroadcastReceiver() {
12+
@SuppressLint("UnsafeProtectedBroadcastReceiver") // exported="false"
13+
override fun onReceive(context: Context, intent: Intent?) {
14+
15+
// Check that we are work profile
16+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
17+
val userManager = context.getSystemService(UserManager::class.java)
18+
if (userManager.isManagedProfile) {
19+
Log.d(TAG, "A new managed profile is being initialized; telling `CrossProfileRequestActivity` to request access to main profile's data.")
20+
// CrossProfileActivity will check whether permissions are present
21+
context.startActivity(
22+
Intent(context, CrossProfileRequestActivity::class.java).apply {
23+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
24+
}
25+
)
26+
} else {
27+
Log.d(TAG, "A new user is being initialized, but it is not a managed profile. Not connecting data")
28+
}
29+
}
30+
}
31+
32+
companion object {
33+
const val TAG = "GmsUserInit"
34+
}
35+
}

0 commit comments

Comments
 (0)