Skip to content

Commit a669733

Browse files
authored
Settings: Add Javascript interactive content (#2654)
1 parent dcaa450 commit a669733

File tree

5 files changed

+160
-5
lines changed

5 files changed

+160
-5
lines changed

play-services-core/src/main/java/org/microg/gms/people/PeopleManager.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import android.content.ContentValues;
2121
import android.content.Context;
2222
import android.database.Cursor;
23+
import android.database.sqlite.SQLiteDatabase;
2324
import android.graphics.Bitmap;
2425
import android.graphics.BitmapFactory;
2526
import android.util.Log;
@@ -114,6 +115,17 @@ public static Bitmap getOwnerAvatarBitmap(Context context, String accountName, b
114115
return BitmapFactory.decodeFile(avaterFile.getPath());
115116
}
116117

118+
public static void updateOwnerAvatar(Context context, String accountName, String newAvatar) {
119+
try (DatabaseHelper databaseHelper = new DatabaseHelper(context); SQLiteDatabase db = databaseHelper.getWritableDatabase()) {
120+
ContentValues contentValues = new ContentValues();
121+
contentValues.put("avatar", newAvatar);
122+
int rowsAffected = db.update(DatabaseHelper.OWNERS_TABLE, contentValues, "account_name = ?", new String[]{accountName});
123+
Log.d(TAG, "updateOwnerAvatar affected: " + rowsAffected);
124+
} catch (Exception e) {
125+
Log.e(TAG, "Error updating avatar: " + e.getMessage());
126+
}
127+
}
128+
117129
public static String loadUserInfo(Context context, Account account) {
118130
try {
119131
URLConnection conn = new URL(USERINFO_URL).openConnection();

play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/MainActivity.kt

Lines changed: 137 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,26 @@ package org.microg.gms.accountsettings.ui
77

88
import android.accounts.Account
99
import android.accounts.AccountManager
10+
import android.content.Intent
1011
import android.os.Bundle
1112
import android.text.TextUtils
1213
import android.util.Log
1314
import android.view.View
15+
import android.webkit.JavascriptInterface
1416
import android.webkit.WebView
15-
import android.widget.FrameLayout
1617
import android.widget.ProgressBar
1718
import android.widget.RelativeLayout
1819
import android.widget.RelativeLayout.LayoutParams.MATCH_PARENT
1920
import android.widget.RelativeLayout.LayoutParams.WRAP_CONTENT
2021
import androidx.appcompat.app.AppCompatActivity
21-
import androidx.core.view.updateLayoutParams
22+
import androidx.lifecycle.lifecycleScope
23+
import kotlinx.coroutines.Dispatchers
24+
import kotlinx.coroutines.withContext
25+
import org.json.JSONException
26+
import org.json.JSONObject
2227
import org.microg.gms.auth.AuthConstants
2328
import org.microg.gms.common.Constants
29+
import org.microg.gms.people.PeopleManager
2430

2531
private const val TAG = "AccountSettings"
2632

@@ -96,6 +102,7 @@ private val SCREEN_ID_TO_URL = hashMapOf(
96102
10729 to "https://myaccount.google.com/data-and-privacy/data-visibility",
97103
10759 to "https://myaccount.google.com/address/home",
98104
10760 to "https://myaccount.google.com/address/work",
105+
14500 to "https://profilewidgets.google.com/alternate-profile/edit?interop=o&opts=sb",
99106
)
100107

101108
private val ALLOWED_WEB_PREFIXES = setOf(
@@ -115,7 +122,8 @@ private val ALLOWED_WEB_PREFIXES = setOf(
115122
"https://fit.google.com/privacy/settings",
116123
"https://maps.google.com/maps/timeline",
117124
"https://myadcenter.google.com/controls",
118-
"https://families.google.com/kidonboarding"
125+
"https://families.google.com/kidonboarding",
126+
"https://profilewidgets.google.com/alternate-profile/edit",
119127
)
120128

121129
private val ACTION_TO_SCREEN_ID = hashMapOf(
@@ -126,6 +134,8 @@ private val ACTION_TO_SCREEN_ID = hashMapOf(
126134

127135
class MainActivity : AppCompatActivity() {
128136
private lateinit var webView: WebView
137+
private var accountName: String? = null
138+
private var resultBundle: Bundle? = null
129139

130140
private fun getSelectedAccountName(): String? = null
131141

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

148158
val ignoreAccount = intent?.getBooleanExtra(EXTRA_IGNORE_ACCOUNT, false) ?: false
149-
val accountName = if (ignoreAccount) null else {
159+
accountName = if (ignoreAccount) null else {
150160
val accounts = AccountManager.get(this).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE)
151161
val accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME) ?: intent.getParcelableExtra<Account>("account")?.name ?: getSelectedAccountName()
152162
accounts.find { it.name.equals(accountName) }?.name
@@ -175,6 +185,7 @@ class MainActivity : AppCompatActivity() {
175185
webView = WebView(this).apply {
176186
layoutParams = RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
177187
visibility = View.INVISIBLE
188+
addJavascriptInterface(UiBridge(), "ocUi")
178189
}
179190
layout.addView(webView)
180191
setContentView(layout)
@@ -198,4 +209,126 @@ class MainActivity : AppCompatActivity() {
198209
super.onBackPressed()
199210
}
200211
}
212+
213+
private fun updateLocalAccountAvatar(newAvatarUrl: String?) {
214+
if (TextUtils.isEmpty(newAvatarUrl) || accountName == null) {
215+
return
216+
}
217+
lifecycleScope.launchWhenCreated {
218+
withContext(Dispatchers.IO) {
219+
PeopleManager.updateOwnerAvatar(this@MainActivity, accountName, newAvatarUrl)
220+
}
221+
}
222+
}
223+
224+
private inner class UiBridge {
225+
226+
@JavascriptInterface
227+
fun close() {
228+
Log.d(TAG, "close: ")
229+
val intent = Intent()
230+
if (resultBundle != null) {
231+
intent.putExtras(resultBundle!!)
232+
}
233+
setResult(RESULT_OK, intent)
234+
finish()
235+
}
236+
237+
@JavascriptInterface
238+
fun closeWithResult(resultJsonStr: String?) {
239+
Log.d(TAG, "closeWithResult: resultJsonStr -> $resultJsonStr")
240+
setResult(resultJsonStr)
241+
close()
242+
}
243+
244+
@JavascriptInterface
245+
fun goBackOrClose() {
246+
Log.d(TAG, "goBackOrClose: ")
247+
onBackPressed()
248+
}
249+
250+
@JavascriptInterface
251+
fun hideKeyboard() {
252+
Log.d(TAG, "hideKeyboard: ")
253+
}
254+
255+
@JavascriptInterface
256+
fun isCloseWithResultSupported(): Boolean {
257+
return true
258+
}
259+
260+
@JavascriptInterface
261+
fun isOpenHelpEnabled(): Boolean {
262+
return true
263+
}
264+
265+
@JavascriptInterface
266+
fun isOpenScreenEnabled(): Boolean {
267+
return true
268+
}
269+
270+
@JavascriptInterface
271+
fun isSetResultSupported(): Boolean {
272+
return true
273+
}
274+
275+
@JavascriptInterface
276+
fun open(str: String?) {
277+
Log.d(TAG, "open: str -> $str")
278+
}
279+
280+
@JavascriptInterface
281+
fun openHelp(str: String?) {
282+
Log.d(TAG, "openHelp: str -> $str")
283+
}
284+
285+
@JavascriptInterface
286+
fun openScreen(screenId: Int, str: String?) {
287+
Log.d(TAG, "openScreen: screenId -> $screenId str -> $str accountName -> $accountName")
288+
val intent = Intent(this@MainActivity, MainActivity::class.java).apply {
289+
putExtra(EXTRA_SCREEN_ID, screenId)
290+
putExtra(EXTRA_ACCOUNT_NAME, accountName)
291+
}
292+
startActivity(intent)
293+
}
294+
295+
@JavascriptInterface
296+
fun setBackStop() {
297+
Log.d(TAG, "setBackStop: ")
298+
webView.clearHistory()
299+
}
300+
301+
@JavascriptInterface
302+
fun setResult(resultJsonStr: String?) {
303+
Log.d(TAG, "setResult: resultJsonStr -> $resultJsonStr")
304+
val map = jsonToMap(resultJsonStr) ?: return
305+
if (map.containsKey(KEY_UPDATED_PHOTO_URL)) {
306+
updateLocalAccountAvatar(map[KEY_UPDATED_PHOTO_URL])
307+
}
308+
resultBundle = Bundle().apply {
309+
for ((key, value) in map) {
310+
putString("result.$key", value)
311+
}
312+
}
313+
}
314+
315+
private fun jsonToMap(jsonStr: String?): Map<String, String>? {
316+
val hashMap = HashMap<String, String>()
317+
if (!jsonStr.isNullOrEmpty()) {
318+
try {
319+
val jSONObject = JSONObject(jsonStr)
320+
val keys = jSONObject.keys()
321+
while (keys.hasNext()) {
322+
val next = keys.next()
323+
val obj = jSONObject[next]
324+
hashMap[next] = obj as String
325+
}
326+
} catch (e: JSONException) {
327+
Log.d(TAG, "Unable to parse result JSON string", e)
328+
return null
329+
}
330+
}
331+
return hashMap
332+
}
333+
}
201334
}

play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/WebViewHelper.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import androidx.webkit.WebViewClientCompat
2020
import kotlinx.coroutines.Dispatchers
2121
import kotlinx.coroutines.launch
2222
import kotlinx.coroutines.withContext
23+
import org.json.JSONObject
2324
import org.microg.gms.auth.AuthManager
2425
import org.microg.gms.common.Constants.GMS_PACKAGE_NAME
2526
import org.microg.gms.common.PackageUtils
@@ -126,5 +127,10 @@ class WebViewHelper(private val activity: AppCompatActivity, private val webView
126127
settings.useWideViewPort = false
127128
settings.setSupportZoom(false)
128129
settings.javaScriptCanOpenWindowsAutomatically = false
130+
settings.userAgentString = "${settings.userAgentString} ${
131+
String.format(Locale.getDefault(), "OcIdWebView (%s)", JSONObject().apply {
132+
put("os", "Android")
133+
}.toString())
134+
}"
129135
}
130136
}

play-services-core/src/main/kotlin/org/microg/gms/accountsettings/ui/extensions.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ const val EXTRA_THEME_CHOICE = "extra.themeChoice"
2323
const val EXTRA_SCREEN_MY_ACTIVITY_PRODUCT = "extra.screen.myactivityProduct"
2424
const val EXTRA_SCREEN_KID_ONBOARDING_PARAMS = "extra.screen.kidOnboardingParams"
2525

26+
const val KEY_UPDATED_PHOTO_URL = "updatedPhotoUrl"
27+
2628
const val OPTION_SCREEN_FLAVOR = "screenFlavor"

play-services-core/src/main/kotlin/org/microg/gms/ui/AccountsFragment.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ class AccountsFragment : PreferenceFragmentCompat() {
5353
}
5454

5555
private fun getCircleBitmapDrawable(bitmap: Bitmap?) =
56-
if (bitmap != null) RoundedBitmapDrawableFactory.create(resources, bitmap).also { it.isCircular = true } else null
56+
if (bitmap != null) RoundedBitmapDrawableFactory.create(resources, bitmap.let {
57+
Bitmap.createScaledBitmap(bitmap, 100, 100, true)
58+
}).also { it.isCircular = true } else null
5759

5860
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
5961
addPreferencesFromResource(R.xml.preferences_accounts)

0 commit comments

Comments
 (0)