From 48b5d85904d159af22242831600979e277ccb56c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20K=C3=BChrer?= <20903852+christxph@users.noreply.github.com> Date: Thu, 4 Apr 2019 21:25:25 +0200 Subject: [PATCH] Cleanup Android project (Minor refactorings, etc.) (#1244) * (Android) Get rid of double bangs by using Kotlin view binding Instead of holding a nullable reference to the WebView, we are now accessing the WebView using the view binding utility of Kotlin's Android Extensions. Further reading: https://kotlinlang.org/docs/tutorials/android-plugin.html * (Android) Enable WebView debugging in debug builds This enables debugging the app's WebView using Chrome's DevTools. https://developers.google.com/web/tools/chrome-devtools/remote-debugging/webviews * (Android) Make MainActivity.kt adhere to common Kotlin conventions * (Android) Update dependencies and improve formatting of Gradle files This updates the Kotlin plugin to 1.3.21 and the Gradle plugin to 3.3.2 * (Android) Remove unnecessary ConstraintLayout container Layout files should generally have as few nested layers as possible, because every layer affects the performance. * (Android) Use JSONObject class to construct a JSON string It is way safer to construct a JSON string using classes that are meant for doing that, instead of concatenating raw strings. * (Android) Suppress JavaScript lint warning * (Android) Use Kotlin string templates instead of concatenating strings * (Android) Add missing SuppressLint import --- android/app/build.gradle | 4 +- .../org/mozilla/firefoxsend/MainActivity.kt | 206 +++++++++--------- .../app/src/main/res/layout/activity_main.xml | 12 +- android/build.gradle | 11 +- 4 files changed, 110 insertions(+), 123 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 5d27365d..471a3a21 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,7 +1,5 @@ apply plugin: 'com.android.application' - apply plugin: 'kotlin-android' - apply plugin: 'kotlin-android-extensions' android { @@ -31,7 +29,7 @@ dependencies { androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation 'com.github.delight-im:Android-AdvancedWebView:v3.0.0' - implementation "org.mozilla.components:service-firefox-accounts:${rootProject.ext.android_components_version}" + implementation "org.mozilla.components:service-firefox-accounts:$android_components_version" } task generateAndLinkBundle(type: Exec, description: 'Generate the android.js bundle and link it into the assets directory') { diff --git a/android/app/src/main/java/org/mozilla/firefoxsend/MainActivity.kt b/android/app/src/main/java/org/mozilla/firefoxsend/MainActivity.kt index 257b326f..21ed9ea3 100644 --- a/android/app/src/main/java/org/mozilla/firefoxsend/MainActivity.kt +++ b/android/app/src/main/java/org/mozilla/firefoxsend/MainActivity.kt @@ -1,39 +1,39 @@ package org.mozilla.firefoxsend - -import android.support.v7.app.AppCompatActivity -import android.os.Bundle -import im.delight.android.webview.AdvancedWebView -import android.graphics.Bitmap -import android.content.Intent import android.annotation.SuppressLint import android.content.ComponentName +import android.content.Intent +import android.graphics.Bitmap import android.net.Uri -import android.webkit.WebView -import android.webkit.WebMessage -import android.util.Log +import android.os.Bundle +import android.support.v7.app.AppCompatActivity import android.util.Base64 +import android.util.Log import android.view.View -import android.webkit.ConsoleMessage -import android.webkit.JavascriptInterface -import android.webkit.WebChromeClient +import android.webkit.* +import im.delight.android.webview.AdvancedWebView +import kotlinx.android.synthetic.main.activity_main.* import mozilla.components.service.fxa.Config import mozilla.components.service.fxa.FirefoxAccount -import mozilla.components.service.fxa.Profile import mozilla.components.service.fxa.FxaResult +import org.json.JSONObject internal class LoggingWebChromeClient : WebChromeClient() { override fun onConsoleMessage(cm: ConsoleMessage): Boolean { - Log.w("CONTENT", String.format("%s @ %d: %s", + Log.d(TAG, String.format("%s @ %d: %s", cm.message(), cm.lineNumber(), cm.sourceId())) return true } + + companion object { + private const val TAG = "CONTENT" + } } class WebAppInterface(private val mContext: MainActivity) { @JavascriptInterface fun beginOAuthFlow() { - mContext.beginOAuthFlow(); + mContext.beginOAuthFlow() } @JavascriptInterface @@ -43,176 +43,176 @@ class WebAppInterface(private val mContext: MainActivity) { } class MainActivity : AppCompatActivity(), AdvancedWebView.Listener { - private var mWebView: AdvancedWebView? = null + private var mToShare: String? = null private var mToCall: String? = null private var mAccount: FirefoxAccount? = null + @SuppressLint("SetJavaScriptEnabled") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - // https://developers.google.com/web/tools/chrome-devtools/remote-debugging/webviews - // WebView.setWebContentsDebuggingEnabled(true); // TODO only dev builds + WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG) + webView.apply { + setListener(this@MainActivity, this@MainActivity) + addJavascriptInterface(WebAppInterface(this@MainActivity), JS_INTERFACE_NAME) + setLayerType(View.LAYER_TYPE_HARDWARE, null) + webChromeClient = LoggingWebChromeClient() - mWebView = findViewById(R.id.webview) as AdvancedWebView - mWebView!!.setListener(this, this) - mWebView!!.setWebChromeClient(LoggingWebChromeClient()) - mWebView!!.addJavascriptInterface(WebAppInterface(this), "Android") - mWebView!!.setLayerType(View.LAYER_TYPE_HARDWARE, null); + settings.apply { + userAgentString = "Send Android" + allowUniversalAccessFromFileURLs = true + javaScriptEnabled = true + } + } - val webSettings = mWebView!!.getSettings() - webSettings.setUserAgentString("Send Android") - webSettings.setAllowUniversalAccessFromFileURLs(true) - webSettings.setJavaScriptEnabled(true) - - val intent = getIntent() - val action = intent.getAction() - val type = intent.getType() - - if (Intent.ACTION_SEND.equals(action) && type != null) { - if (type.equals("text/plain")) { + val type = intent.type + if (Intent.ACTION_SEND == intent.action && type != null) { + if (type == "text/plain") { val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT) - Log.w("INTENT", "text/plain " + sharedText) + Log.d(TAG_INTENT, "text/plain $sharedText") mToShare = "data:text/plain;base64," + Base64.encodeToString(sharedText.toByteArray(), 16).trim() } else if (type.startsWith("image/")) { val imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as Uri - Log.w("INTENT", "image/ " + imageUri) + Log.d(TAG_INTENT, "image/ $imageUri") mToShare = "data:text/plain;base64," + Base64.encodeToString(imageUri.path.toByteArray(), 16).trim() } } - mWebView!!.loadUrl("file:///android_asset/android.html") - + webView.loadUrl("file:///android_asset/android.html") } fun beginOAuthFlow() { - Config.release().then(fun (value: Config): FxaResult { + Config.release().then { value -> mAccount = FirefoxAccount(value, "20f7931c9054d833", "https://send.firefox.com/fxa/android-redirect.html") - mAccount?.beginOAuthFlow(arrayOf("profile", "https://identity.mozilla.com/apps/send"), true)?.then(fun (url: String): FxaResult { - Log.w("CONFIG", "GOT A URL " + url) - this@MainActivity.runOnUiThread({ - mWebView!!.loadUrl(url) - }) - return FxaResult.fromValue(Unit) - }) - Log.w("CONFIG", "CREATED FIREFOXACCOUNT") - return FxaResult.fromValue(Unit) - }) + mAccount?.beginOAuthFlow(arrayOf("profile", "https://identity.mozilla.com/apps/send"), true) + ?.then { url -> + Log.d(TAG_CONFIG, "GOT A URL $url") + this@MainActivity.runOnUiThread { + webView.loadUrl(url) + } + FxaResult.fromValue(Unit) + } + Log.d(TAG_CONFIG, "CREATED FIREFOXACCOUNT") + FxaResult.fromValue(Unit) + } } fun shareUrl(url: String) { - val shareIntent = Intent() - shareIntent.action = Intent.ACTION_SEND - shareIntent.type = "text/plain" - shareIntent.putExtra(Intent.EXTRA_TEXT, url) + val shareIntent = Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, url) + } + + val components = arrayOf(ComponentName(applicationContext, MainActivity::class.java)) val chooser = Intent.createChooser(shareIntent, "") - chooser.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, arrayOf(ComponentName(applicationContext, MainActivity::class.java))) + .putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, components) + startActivity(chooser) } - @SuppressLint("NewApi") override fun onResume() { super.onResume() - mWebView!!.onResume() - // ... + webView.onResume() } - @SuppressLint("NewApi") override fun onPause() { - mWebView!!.onPause() - // ... + webView.onPause() super.onPause() } override fun onDestroy() { - mWebView!!.onDestroy() - // ... + webView.onDestroy() super.onDestroy() } override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { super.onActivityResult(requestCode, resultCode, intent) - mWebView!!.onActivityResult(requestCode, resultCode, intent) - // ... + webView.onActivityResult(requestCode, resultCode, intent) } override fun onBackPressed() { - if (!mWebView!!.onBackPressed()) { + if (!webView.onBackPressed()) { return } - // ... super.onBackPressed() } override fun onPageStarted(url: String, favicon: Bitmap?) { if (url.startsWith("https://send.firefox.com/fxa/android-redirect.html")) { // We load this here so the user doesn't see the android-redirect.html page - mWebView!!.loadUrl("file:///android_asset/android.html") + webView.loadUrl("file:///android_asset/android.html") - val parsed = Uri.parse(url) - val code = parsed.getQueryParameter("code") - val state = parsed.getQueryParameter("state") - - code?.let { code -> - state?.let { state -> + val uri = Uri.parse(url) + uri.getQueryParameter("code")?.let { code -> + uri.getQueryParameter("state")?.let { state -> mAccount?.completeOAuthFlow(code, state)?.whenComplete { info -> - //displayAndPersistProfile(code, state) - val profile = mAccount?.getProfile(false)?.then(fun (profile: Profile): FxaResult { - val accessToken = info.accessToken - val keys = info.keys - val avatar = profile.avatar - val displayName = profile.displayName - val email = profile.email - val uid = profile.uid - val toPass = "{\"accessToken\": \"${accessToken}\", \"keys\": '${keys}', \"avatar\": \"${avatar}\", \"displayName\": \"${displayName}\", \"email\": \"${email}\", \"uid\": \"${uid}\"}" - mToCall = "finishLogin(${toPass})" - this@MainActivity.runOnUiThread({ + mAccount?.getProfile(false)?.then { profile -> + val profileJsonPayload = JSONObject() + .put("accessToken", info.accessToken) + .put("keys", info.keys) + .put("avatar", profile.avatar) + .put("displayName", profile.displayName) + .put("email", profile.email) + .put("uid", profile.uid).toString() + mToCall = "finishLogin($profileJsonPayload)" + this@MainActivity.runOnUiThread { // Clear the history so that the user can't use the back button to see broken pages // that were inserted into the history by the login process. - mWebView!!.clearHistory() + webView.clearHistory() // We also reload this here because we need to make sure onPageFinished runs after mToCall has been set. // We can't guarantee that onPageFinished wasn't already called at this point. - mWebView!!.loadUrl("file:///android_asset/android.html") - }) - - - return FxaResult.fromValue(Unit) - }) + webView.loadUrl("file:///android_asset/android.html") + } + FxaResult.fromValue(Unit) + } } } } } - Log.w("MAIN", "onPageStarted"); + Log.d(TAG_MAIN, "onPageStarted") } override fun onPageFinished(url: String) { - Log.w("MAIN", "onPageFinished") + Log.d(TAG_MAIN, "onPageFinished") if (mToShare != null) { - Log.w("INTENT", mToShare) + Log.d(TAG_INTENT, mToShare) - mWebView?.postWebMessage(WebMessage(mToShare), Uri.EMPTY) + webView.postWebMessage(WebMessage(mToShare), Uri.EMPTY) mToShare = null } if (mToCall != null) { - this@MainActivity.runOnUiThread({ - mWebView?.evaluateJavascript(mToCall, fun (value: String) { + this@MainActivity.runOnUiThread { + webView.evaluateJavascript(mToCall) { mToCall = null - }) - }) + } + } } } override fun onPageError(errorCode: Int, description: String, failingUrl: String) { - Log.w("MAIN", "onPageError " + description) + Log.d(TAG_MAIN, "onPageError($errorCode, $description, $failingUrl)") } - override fun onDownloadRequested(url: String, suggestedFilename: String, mimeType: String, contentLength: Long, contentDisposition: String, userAgent: String) { - Log.w("MAIN", "onDownloadRequested") + override fun onDownloadRequested(url: String, + suggestedFilename: String, + mimeType: String, + contentLength: Long, + contentDisposition: String, + userAgent: String) { + Log.d(TAG_MAIN, "onDownloadRequested") } override fun onExternalPageRequest(url: String) { - Log.w("MAIN", "onExternalPageRequest") + Log.d(TAG_MAIN, "onExternalPageRequest($url)") } + companion object { + private const val TAG_MAIN = "MAIN" + private const val TAG_INTENT = "INTENT" + private const val TAG_CONFIG = "CONFIG" + private const val JS_INTERFACE_NAME = "Android" + } } diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index 28f9b732..d2b2ba3f 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -1,13 +1,7 @@ - - - - \ No newline at end of file + tools:context=".MainActivity" /> \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index a20018c7..bae18a8d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -8,20 +8,15 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.3.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.20" - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath 'com.android.tools.build:gradle:3.3.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.21" } } allprojects { repositories { google() - maven { - url "https://maven.mozilla.org/maven2" - } + maven { url "https://maven.mozilla.org/maven2" } jcenter() maven { url "https://jitpack.io" } }