Skip to content

OcWeb: Improve OC corresponding JS method #2879

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

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions play-services-core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@
android:name="android.permission.UPDATE_APP_OPS_STATS"
tools:ignore="ProtectedPermissions" />

<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature
android:name="android.hardware.camera"
android:required="false" />

<uses-sdk
tools:overrideLibrary="androidx.compose.ui.tooling,
androidx.compose.material3,
Expand Down Expand Up @@ -266,6 +271,15 @@
android:multiprocess="false"
android:readPermission="android.permission.SUBSCRIBED_FEEDS_READ"
android:writePermission="android.permission.SUBSCRIBED_FEEDS_WRITE" />
<provider
android:authorities="com.google.android.gms.fileprovider"
android:name="org.microg.gms.settings.GmsFileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths"/>
</provider>

<!-- Device Checkin -->

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* SPDX-FileCopyrightText: 2025 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.settings

import android.content.Context
import android.content.pm.ProviderInfo
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import android.os.CancellationSignal
import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.core.content.FileProvider
import java.io.FileNotFoundException

private const val TAG = "GmsFileProvider"

class GmsFileProvider : FileProvider() {
private val emptyProjection = arrayOfNulls<String>(0)
private var isInitializationFailed = false

override fun attachInfo(context: Context, info: ProviderInfo) {
try {
super.attachInfo(context, info)
} catch (e: Exception) {
isInitializationFailed = true
Log.e(TAG, "attachInfo error:${e.message}")
}
}

override fun onCreate(): Boolean {
return true
}

override fun getType(uri: Uri): String? {
if (isInitializationFailed) {
return null
}
return super.getType(uri)
}

override fun openFile(
uri: Uri, mode: String, signal: CancellationSignal?
): ParcelFileDescriptor? {
if (!isInitializationFailed) {
return super.openFile(uri, mode, signal)
}
throw FileNotFoundException("FileProvider creation failed")
}

override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
if (isInitializationFailed) {
return 0
}
return super.delete(uri, selection, selectionArgs)
}

override fun query(
uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?
): Cursor {
if (isInitializationFailed) {
return MatrixCursor(emptyProjection)
}
return super.query(uri, projection, selection, selectionArgs, sortOrder)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,46 @@ package org.microg.gms.accountsettings.ui

import android.accounts.Account
import android.accounts.AccountManager
import android.content.Intent
import android.graphics.Color
import android.graphics.Typeface
import android.os.Build.VERSION.SDK_INT
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.Gravity
import android.view.View
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.widget.ProgressBar
import android.widget.RelativeLayout
import android.widget.RelativeLayout.LayoutParams.MATCH_PARENT
import android.widget.RelativeLayout.LayoutParams.WRAP_CONTENT
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONException
import org.json.JSONObject
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import com.google.android.gms.R
import org.microg.gms.accountsettings.ui.bridge.OcAdvertisingIdBridge
import org.microg.gms.accountsettings.ui.bridge.OcAndroidIdBridge
import org.microg.gms.accountsettings.ui.bridge.OcAppBarBridge
import org.microg.gms.accountsettings.ui.bridge.OcAppPermissionsBridge
import org.microg.gms.accountsettings.ui.bridge.OcClientInfoBridge
import org.microg.gms.accountsettings.ui.bridge.OcConsistencyBridge
import org.microg.gms.accountsettings.ui.bridge.OcContactsBridge
import org.microg.gms.accountsettings.ui.bridge.OcFido2Bridge
import org.microg.gms.accountsettings.ui.bridge.OcFidoU2fBridge
import org.microg.gms.accountsettings.ui.bridge.OcFilePickerBridge
import org.microg.gms.accountsettings.ui.bridge.OcFolsomBridge
import org.microg.gms.accountsettings.ui.bridge.OcPermissionsBridge
import org.microg.gms.accountsettings.ui.bridge.OcPlayProtectBridge
import org.microg.gms.accountsettings.ui.bridge.OcTelephonyBridge
import org.microg.gms.accountsettings.ui.bridge.OcTrustAgentBridge
import org.microg.gms.accountsettings.ui.bridge.OcUdcBridge
import org.microg.gms.accountsettings.ui.bridge.OcUiBridge
import org.microg.gms.auth.AuthConstants
import org.microg.gms.common.Constants
import org.microg.gms.people.PeopleManager
import org.microg.gms.profile.ProfileManager
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors

private const val TAG = "AccountSettings"

Expand Down Expand Up @@ -62,7 +82,7 @@ private val SCREEN_ID_TO_URL = hashMapOf(
310 to "https://myaccount.google.com/reservations",
312 to "https://myaccount.google.com/accessibility",
313 to "https://myaccount.google.com/inputtools",
400 to "https://myaccount.google.com/security-checkup/",
400 to "https://myaccount.google.com/security-checkup/6",
401 to "https://myaccount.google.com/signinoptions/password",
403 to "https://myaccount.google.com/signinoptions/two-step-verification",
406 to "https://myaccount.google.com/signinoptions/rescuephone",
Expand Down Expand Up @@ -94,7 +114,7 @@ private val SCREEN_ID_TO_URL = hashMapOf(
10007 to "https://myaccount.google.com/payments-and-subscriptions",
10015 to "https://support.google.com/accounts",
10050 to "https://myaccount.google.com/profile",
10052 to "https://myaccount.google.com/embedded/family/create",
10052 to "https://myaccount.google.com/family/details",
10090 to "https://myaccount.google.com/profile/name",
10704 to "https://www.google.com/account/about",
10706 to "https://myaccount.google.com/profile/profiles-summary",
Expand Down Expand Up @@ -134,8 +154,7 @@ private val ACTION_TO_SCREEN_ID = hashMapOf(

class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView
private var accountName: String? = null
private var resultBundle: Bundle? = null
private val executor: ExecutorService = Executors.newSingleThreadExecutor()

private fun getSelectedAccountName(): String? = null

Expand All @@ -156,7 +175,7 @@ class MainActivity : AppCompatActivity() {
val callingPackage = intent?.getStringExtra(EXTRA_CALLING_PACKAGE_NAME) ?: callingActivity?.packageName ?: Constants.GMS_PACKAGE_NAME

val ignoreAccount = intent?.getBooleanExtra(EXTRA_IGNORE_ACCOUNT, false) ?: false
accountName = if (ignoreAccount) null else {
val accountName = if (ignoreAccount) null else {
val accounts = AccountManager.get(this).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE)
val accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME) ?: intent.getParcelableExtra<Account>("account")?.name ?: getSelectedAccountName()
accounts.find { it.name.equals(accountName) }?.name
Expand All @@ -175,18 +194,45 @@ class MainActivity : AppCompatActivity() {
} else { this }
}
val layout = RelativeLayout(this)
layout.addView(ProgressBar(this).apply {
val titleView = TextView(this).apply {
text = ContextCompat.getString(context, R.string.pref_accounts_title)
textSize = 20f
setTextColor(Color.BLACK)
maxLines = 1
ellipsize = TextUtils.TruncateAt.END
setTypeface(Typeface.create("sans-serif", Typeface.NORMAL))
layoutParams = Toolbar.LayoutParams(MATCH_PARENT, WRAP_CONTENT, Gravity.START)
}
val toolbar = Toolbar(this).apply {
id = View.generateViewId()
setBackgroundColor(Color.WHITE)
if (SDK_INT >= 21) {
backgroundTintList = null
}
layoutParams = RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply {
addRule(RelativeLayout.ALIGN_PARENT_TOP)
}
navigationIcon = ContextCompat.getDrawable(context, R.drawable.ic_arrow_close)
setNavigationOnClickListener { finish() }
addView(titleView)
}
val progressBar = ProgressBar(this).apply {
id = View.generateViewId()
layoutParams = RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
addRule(RelativeLayout.CENTER_HORIZONTAL)
addRule(RelativeLayout.CENTER_VERTICAL)
addRule(RelativeLayout.CENTER_IN_PARENT)
}
isIndeterminate = true
})
}
webView = WebView(this).apply {
layoutParams = RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
id = View.generateViewId()
layoutParams = RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT).apply {
addRule(RelativeLayout.BELOW, toolbar.id)
}
visibility = View.INVISIBLE
addJavascriptInterface(UiBridge(), "ocUi")
loadJsBridge(accountName, toolbar)
}
layout.addView(toolbar)
layout.addView(progressBar)
layout.addView(webView)
setContentView(layout)
WebViewHelper(this, webView, ALLOWED_WEB_PREFIXES).openWebView(screenUrl, accountName, callingPackage)
Expand All @@ -200,6 +246,9 @@ class MainActivity : AppCompatActivity() {

override fun onDestroy() {
super.onDestroy()
if (!executor.isShutdown) {
executor.shutdown()
}
}

override fun onBackPressed() {
Expand All @@ -210,125 +259,24 @@ class MainActivity : AppCompatActivity() {
}
}

private fun updateLocalAccountAvatar(newAvatarUrl: String?) {
if (TextUtils.isEmpty(newAvatarUrl) || accountName == null) {
return
}
lifecycleScope.launchWhenCreated {
withContext(Dispatchers.IO) {
PeopleManager.updateOwnerAvatar(this@MainActivity, accountName, newAvatarUrl)
}
}
}

private inner class UiBridge {

@JavascriptInterface
fun close() {
Log.d(TAG, "close: ")
val intent = Intent()
if (resultBundle != null) {
intent.putExtras(resultBundle!!)
}
setResult(RESULT_OK, intent)
finish()
}

@JavascriptInterface
fun closeWithResult(resultJsonStr: String?) {
Log.d(TAG, "closeWithResult: resultJsonStr -> $resultJsonStr")
setResult(resultJsonStr)
close()
}

@JavascriptInterface
fun goBackOrClose() {
Log.d(TAG, "goBackOrClose: ")
onBackPressed()
}

@JavascriptInterface
fun hideKeyboard() {
Log.d(TAG, "hideKeyboard: ")
}

@JavascriptInterface
fun isCloseWithResultSupported(): Boolean {
return true
}

@JavascriptInterface
fun isOpenHelpEnabled(): Boolean {
return true
}

@JavascriptInterface
fun isOpenScreenEnabled(): Boolean {
return true
}

@JavascriptInterface
fun isSetResultSupported(): Boolean {
return true
}

@JavascriptInterface
fun open(str: String?) {
Log.d(TAG, "open: str -> $str")
}

@JavascriptInterface
fun openHelp(str: String?) {
Log.d(TAG, "openHelp: str -> $str")
}

@JavascriptInterface
fun openScreen(screenId: Int, str: String?) {
Log.d(TAG, "openScreen: screenId -> $screenId str -> $str accountName -> $accountName")
val intent = Intent(this@MainActivity, MainActivity::class.java).apply {
putExtra(EXTRA_SCREEN_ID, screenId)
putExtra(EXTRA_ACCOUNT_NAME, accountName)
}
startActivity(intent)
}

@JavascriptInterface
fun setBackStop() {
Log.d(TAG, "setBackStop: ")
webView.clearHistory()
}

@JavascriptInterface
fun setResult(resultJsonStr: String?) {
Log.d(TAG, "setResult: resultJsonStr -> $resultJsonStr")
val map = jsonToMap(resultJsonStr) ?: return
if (map.containsKey(KEY_UPDATED_PHOTO_URL)) {
updateLocalAccountAvatar(map[KEY_UPDATED_PHOTO_URL])
}
resultBundle = Bundle().apply {
for ((key, value) in map) {
putString("result.$key", value)
}
}
}

private fun jsonToMap(jsonStr: String?): Map<String, String>? {
val hashMap = HashMap<String, String>()
if (!jsonStr.isNullOrEmpty()) {
try {
val jSONObject = JSONObject(jsonStr)
val keys = jSONObject.keys()
while (keys.hasNext()) {
val next = keys.next()
val obj = jSONObject[next]
hashMap[next] = obj as String
}
} catch (e: JSONException) {
Log.d(TAG, "Unable to parse result JSON string", e)
return null
}
}
return hashMap
}
private fun WebView.loadJsBridge(accountName: String?, toolbar: Toolbar) {
ProfileManager.ensureInitialized(this@MainActivity)
addJavascriptInterface(OcUiBridge(this@MainActivity, accountName, this, executor), OcUiBridge.NAME)
addJavascriptInterface(OcConsistencyBridge(), OcConsistencyBridge.NAME)
addJavascriptInterface(OcAppBarBridge(toolbar, this), OcAppBarBridge.NAME)
addJavascriptInterface(OcPlayProtectBridge(this), OcPlayProtectBridge.NAME)
addJavascriptInterface(OcTrustAgentBridge(this), OcTrustAgentBridge.NAME)
addJavascriptInterface(OcPermissionsBridge(this), OcPermissionsBridge.NAME)
addJavascriptInterface(OcFido2Bridge(this), OcFido2Bridge.NAME)
addJavascriptInterface(OcClientInfoBridge(), OcClientInfoBridge.NAME)
addJavascriptInterface(OcTelephonyBridge(), OcTelephonyBridge.NAME)
addJavascriptInterface(OcUdcBridge(this), OcUdcBridge.NAME)
addJavascriptInterface(OcAdvertisingIdBridge(this@MainActivity), OcAdvertisingIdBridge.NAME)
addJavascriptInterface(OcAndroidIdBridge(this@MainActivity), OcAndroidIdBridge.NAME)
addJavascriptInterface(OcAppPermissionsBridge(this), OcAppPermissionsBridge.NAME)
addJavascriptInterface(OcFolsomBridge(), OcFolsomBridge.NAME)
addJavascriptInterface(OcFidoU2fBridge(this), OcFidoU2fBridge.NAME)
addJavascriptInterface(OcContactsBridge(this), OcContactsBridge.NAME)
addJavascriptInterface(OcFilePickerBridge(this@MainActivity, this, executor), OcFilePickerBridge.NAME)
}
}
Loading