Browse Source

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
Christoph Kührer 6 years ago
parent
commit
48b5d85904

+ 1 - 3
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') {

+ 104 - 104
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
-
-        mWebView = findViewById<WebView>(R.id.webview) as AdvancedWebView
-        mWebView!!.setListener(this, this)
-        mWebView!!.setWebChromeClient(LoggingWebChromeClient())
-        mWebView!!.addJavascriptInterface(WebAppInterface(this), "Android")
-        mWebView!!.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-
-        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()
+        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()
+
+            settings.apply {
+                userAgentString = "Send Android"
+                allowUniversalAccessFromFileURLs = true
+                javaScriptEnabled = true
+            }
+        }
 
-        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<Unit> {
+        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<Unit> {
-                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<Unit> {
-                            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"
+    }
 }

+ 3 - 9
android/app/src/main/res/layout/activity_main.xml

@@ -1,13 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
-<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
+<im.delight.android.webview.AdvancedWebView xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/webView"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context=".MainActivity">
-
-    <im.delight.android.webview.AdvancedWebView
-        android:id="@+id/webview"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-</android.support.constraint.ConstraintLayout>
+    tools:context=".MainActivity" />

+ 3 - 8
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" }
     }