// Android cross-app SSO scaffold. // // All suite apps are signed with the SAME key, so a ContentProvider guarded by // a signature-level permission lets them share the OIDC session that ulti-core // persists. One app (e.g. UltiMail) hosts the provider; siblings read/write // through it. Backed by EncryptedSharedPreferences (androidx.security:security-crypto). // // Alternative: a custom AccountManager account type ("space.ulti.suite") with a // shared authenticator — heavier, but integrates with system account settings. // // Manifest (host app), plus a matching in every sibling: // // package space.ulti.suite.sso import android.content.ContentProvider import android.content.ContentValues import android.content.Context import android.database.Cursor import android.database.MatrixCursor import android.net.Uri import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey class SharedSessionProvider : ContentProvider() { override fun onCreate(): Boolean = true private fun prefs(ctx: Context) = EncryptedSharedPreferences.create( ctx, "ulti_shared_session", MasterKey.Builder(ctx).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(), EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, ) // query(content://space.ulti.suite.session/) -> single-row "value". override fun query( uri: Uri, projection: Array?, selection: String?, selectionArgs: Array?, sortOrder: String?, ): Cursor { val key = uri.lastPathSegment ?: "" val value = prefs(context!!).getString(key, null) return MatrixCursor(arrayOf("value")).apply { addRow(arrayOf(value)) } } // insert with values{ key, value } (value=null deletes). override fun insert(uri: Uri, values: ContentValues?): Uri? { val key = values?.getAsString("key") ?: return null val value = values.getAsString("value") prefs(context!!).edit().apply { if (value == null) remove(key) else putString(key, value) }.apply() return uri } override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { val key = uri.lastPathSegment ?: return 0 prefs(context!!).edit().remove(key).apply() return 1 } override fun update( uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?, ): Int = insert(uri, values)?.let { 1 } ?: 0 override fun getType(uri: Uri): String = "vnd.android.cursor.item/ulti.session" }