Browse Source

Implement the mechanics of fxa login on android, but don't show ui fo… (#1000)

* Implement the mechanics of fxa login on android, but don't show ui for it yet. Also, scopedKeys are not yet implemented.

* Hopefully fix the package-lock conflict?

* WIP on android scoped keys

* Finish implementing login.

* created android/user.js to handle android logins
Donovan Preston 6 years ago
parent
commit
cab6f1bafb

+ 0 - 0
android/stores/.eslintrc.yaml → android/.eslintrc.yaml


+ 0 - 8
android/android.iml

@@ -1,13 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <module external.linked.project.id="android" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
-  <component name="FacetManager">
-    <facet type="java-gradle" name="Java-Gradle">
-      <configuration>
-        <option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
-        <option name="BUILDABLE" value="false" />
-      </configuration>
-    </facet>
-  </component>
   <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
     <exclude-output />
     <content url="file://$MODULE_DIR$">

+ 19 - 8
android/android.js

@@ -1,4 +1,4 @@
-/* global window */
+/* global window, navigator */
 
 window.LIMITS = {
   ANON: {
@@ -27,14 +27,18 @@ const locale = require('../common/locales');
 const home = require('../app/ui/home');
 const app = choo();
 
+if (navigator.userAgent === 'Send Android') {
+  assets.setPrefix('/android_asset');
+}
+
 function body(main) {
   return function(state, emit) {
     return html`<body class="flex flex-col items-center font-sans bg-blue-lightest md:h-screen md:bg-grey-lightest">
-    ${header(state, emit)}
-    <a id="hamburger" class="absolute pin-t pin-r z-50" href="#" onclick=${clickPreferences}>
-      <img src=${assets.get('preferences.png')} />
-    </a>
-    ${main(state, emit)}
+      <a id="hamburger" class="absolute pin-t pin-r z-50" href="#" onclick=${clickPreferences}>
+        <img src=${assets.get('preferences.png')} />
+      </a>
+      ${header(state, emit)}
+      ${main(state, emit)}
     </body>`;
 
     function clickPreferences(event) {
@@ -44,15 +48,22 @@ function body(main) {
   };
 }
 
+app.use(require('./stores/state').default);
 app.use((state, emitter) => {
   state.translate = locale.getTranslator();
-  state.capabilities = {}; //TODO
+  state.capabilities = {
+    account: true
+  }; //TODO
+
+  window.finishLogin = async function(accountInfo) {
+    await state.user.finishLogin(accountInfo);
+    emitter.emit('render');
+  };
 
   // for debugging
   window.appState = state;
   window.appEmit = emitter.emit.bind(emitter);
 });
-app.use(require('./stores/state').default);
 app.use(require('../app/fileManager').default);
 app.use(require('./stores/intents').default);
 app.route('/', body(home));

+ 29 - 13
android/app/app.iml

@@ -51,19 +51,19 @@
     </facet>
   </component>
   <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7">
-    <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
-    <output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
+    <output url="file://$MODULE_DIR$/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes" />
+    <output-test url="file://$MODULE_DIR$/build/intermediates/javac/debugUnitTest/compileDebugUnitTestJavaWithJavac/classes" />
     <exclude-output />
     <content url="file://$MODULE_DIR$">
       <sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
-      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/not_namespaced_r_class_sources/debug/processDebugResources/r" isTestSource="false" generated="true" />
       <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
       <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
       <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
       <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
       <sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
       <sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
-      <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
+      <sourceFolder url="file://$MODULE_DIR$/build/generated/not_namespaced_r_class_sources/debugAndroidTest/processDebugAndroidTestResources/r" isTestSource="true" generated="true" />
       <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
       <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
       <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
@@ -112,31 +112,40 @@
       <sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
       <sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
       <sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
-      <excludeFolder url="file://$MODULE_DIR$/build/.DS_Store" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/annotation_processor_list" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/apk_list" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/build-info" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/check-libraries" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/check-manifest" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/checkDebugClasspath" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/compatible_screen_manifest" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-apk" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-main-apk-res" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaPrecompile" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant_run_main_apk_resources" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant_run_merged_manifests" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant_run_split_apk_resources" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/javac" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
-      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint_jar" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifest-checker" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/merged_assets" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/merged_manifests" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/prebuild" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/processed_res" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/resources" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/shader_assets" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/split-apk" />
+      <excludeFolder url="file://$MODULE_DIR$/build/intermediates/split_list" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/splits-support" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
       <excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
@@ -146,9 +155,11 @@
     </content>
     <orderEntry type="jdk" jdkName="Android API 27 Platform" jdkType="Android SDK" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Gradle: net.java.dev.jna:jna-4.5.2" level="project" />
     <orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test:runner-1.0.2" level="project" />
     <orderEntry type="library" name="Gradle: android.arch.lifecycle:common:1.1.0@jar" level="project" />
     <orderEntry type="library" name="Gradle: com.android.support:support-annotations:27.1.1@jar" level="project" />
+    <orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.2.61@jar" level="project" />
     <orderEntry type="library" name="Gradle: com.android.support:animated-vector-drawable-27.1.1" level="project" />
     <orderEntry type="library" name="Gradle: com.android.support:support-compat-27.1.1" level="project" />
     <orderEntry type="library" name="Gradle: android.arch.lifecycle:viewmodel-1.1.0" level="project" />
@@ -162,21 +173,26 @@
     <orderEntry type="library" name="Gradle: com.github.delight-im:Android-AdvancedWebView-v3.0.0" level="project" />
     <orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test.espresso:espresso-core-3.0.2" level="project" />
     <orderEntry type="library" scope="TEST" name="Gradle: javax.inject:javax.inject:1@jar" level="project" />
+    <orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-coroutines-android:0.23.4@jar" level="project" />
     <orderEntry type="library" name="Gradle: com.android.support:support-fragment-27.1.1" level="project" />
     <orderEntry type="library" scope="TEST" name="Gradle: junit:junit:4.12@jar" level="project" />
     <orderEntry type="library" name="Gradle: android.arch.core:runtime-1.1.0" level="project" />
+    <orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.61@jar" level="project" />
     <orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-core:1.3@jar" level="project" />
     <orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test:monitor-1.0.2" level="project" />
+    <orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.4@jar" level="project" />
     <orderEntry type="library" name="Gradle: com.android.support:appcompat-v7-27.1.1" level="project" />
+    <orderEntry type="library" name="Gradle: org.mozilla.components:service-firefox-accounts-0.26.0" level="project" />
     <orderEntry type="library" scope="TEST" name="Gradle: com.android.support.test.espresso:espresso-idling-resource-3.0.2" level="project" />
     <orderEntry type="library" name="Gradle: com.android.support.constraint:constraint-layout-solver:1.1.2@jar" level="project" />
-    <orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.2.60@jar" level="project" />
+    <orderEntry type="library" name="Gradle: org.mozilla.fxa_client:fxa_client-0.5.1" level="project" />
     <orderEntry type="library" name="Gradle: android.arch.lifecycle:livedata-core-1.1.0" level="project" />
+    <orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:kotlinx-coroutines-core-common:0.23.4@jar" level="project" />
     <orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-library:1.3@jar" level="project" />
     <orderEntry type="library" scope="TEST" name="Gradle: org.hamcrest:hamcrest-integration:1.3@jar" level="project" />
-    <orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60@jar" level="project" />
+    <orderEntry type="library" name="Gradle: org.jetbrains.kotlinx:atomicfu-common:0.10.3@jar" level="project" />
     <orderEntry type="library" name="Gradle: android.arch.core:common:1.1.0@jar" level="project" />
-    <orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.60@jar" level="project" />
+    <orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib-common:1.2.61@jar" level="project" />
     <orderEntry type="library" scope="TEST" name="Gradle: net.sf.kxml:kxml2:2.3.0@jar" level="project" />
     <orderEntry type="library" name="Gradle: android.arch.lifecycle:runtime-1.1.0" level="project" />
   </component>

+ 1 - 0
android/app/build.gradle

@@ -31,6 +31,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}"
 }
 
 task generateAndLinkBundle(type: Exec, description: 'Generate the android.js bundle and link it into the assets directory') {

+ 0 - 1
android/app/src/main/AndroidManifest.xml

@@ -8,7 +8,6 @@
     <application
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
-        android:label="@string/app_name"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
         android:theme="@style/AppTheme">

+ 88 - 7
android/app/src/main/java/com/mozilla/send/sendandroid/MainActivity.kt

@@ -5,6 +5,7 @@ import android.support.v7.app.AppCompatActivity
 import android.os.Bundle
 import im.delight.android.webview.AdvancedWebView
 import android.graphics.Bitmap
+import android.content.Context
 import android.content.Intent
 import android.annotation.SuppressLint
 import android.net.Uri
@@ -13,7 +14,13 @@ import android.webkit.WebMessage
 import android.util.Log
 import android.util.Base64
 import android.webkit.ConsoleMessage
+import android.webkit.JavascriptInterface
 import android.webkit.WebChromeClient
+import mozilla.components.service.fxa.Config
+import mozilla.components.service.fxa.FirefoxAccount
+import mozilla.components.service.fxa.OAuthInfo
+import mozilla.components.service.fxa.Profile
+import mozilla.components.service.fxa.FxaResult
 
 internal class LoggingWebChromeClient : WebChromeClient() {
     override fun onConsoleMessage(cm: ConsoleMessage): Boolean {
@@ -23,9 +30,18 @@ internal class LoggingWebChromeClient : WebChromeClient() {
     }
 }
 
+class WebAppInterface(private val mContext: MainActivity) {
+    @JavascriptInterface
+    fun beginOAuthFlow() {
+        mContext.beginOAuthFlow();
+    }
+}
+
 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
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -34,14 +50,17 @@ class MainActivity : AppCompatActivity(), AdvancedWebView.Listener {
         mWebView = findViewById<WebView>(R.id.webview) as AdvancedWebView
         mWebView!!.setListener(this, this)
         mWebView!!.setWebChromeClient(LoggingWebChromeClient())
+        mWebView!!.addJavascriptInterface(WebAppInterface(this), "Android")
 
         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 sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
@@ -51,12 +70,25 @@ class MainActivity : AppCompatActivity(), AdvancedWebView.Listener {
                 val imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as Uri
                 Log.w("INTENT", "image/ " + imageUri)
                 mToShare = "data:text/plain;base64," + Base64.encodeToString(imageUri.path.toByteArray(), 16).trim()
-
-                // TODO Currently this causes a Permission Denied error
-                // val stream = contentResolver.openInputStream(imageUri)
             }
         }
         mWebView!!.loadUrl("file:///android_asset/android.html")
+
+    }
+
+    fun beginOAuthFlow() {
+        Config.custom("https://send-fxa.dev.lcip.org").then(fun (value: Config): FxaResult<Unit> {
+            mAccount = FirefoxAccount(value, "12cc4070a481bc73", "fxaclient://android.redirect")
+            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)
+        })
     }
 
     @SuppressLint("NewApi")
@@ -94,7 +126,48 @@ class MainActivity : AppCompatActivity(), AdvancedWebView.Listener {
     }
 
     override fun onPageStarted(url: String, favicon: Bitmap?) {
+        if (url.startsWith("fxaclient")) {
+            // We load this here so the user doesn't see an ugly screen that says "can't handle fxaclient urls"...
+            mWebView!!.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 ->
+                    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({
+                                // But then we also reload this here because we need to make sure onPageFinished runs after mToCall has been set.
+                                // We can't guarantee that onPageFinished has already been called at this point.
+                                mWebView!!.loadUrl("file:///android_asset/android.html")
+                            })
+
+
+                            return FxaResult.fromValue(Unit)
+                        })
+
+                        // TODO get k from it.keys
+                        // TODO get profile from mAccount.getProfile
+                        // TODO get access_token
+
+                        //mToShare = "data:text/plain;base64," + Base64.encodeToString(toSend.toByteArray(), 16).trim()
+                    }
+                }
+            }
+        }
         Log.w("MAIN", "onPageStarted");
+        // account.completeOAuthFlow()
     }
 
     override fun onPageFinished(url: String) {
@@ -102,14 +175,22 @@ class MainActivity : AppCompatActivity(), AdvancedWebView.Listener {
         if (mToShare != null) {
             Log.w("INTENT", mToShare)
 
-            val webView = findViewById<WebView>(R.id.webview) as AdvancedWebView
-            webView.postWebMessage(WebMessage(mToShare), Uri.EMPTY)
+            mWebView?.postWebMessage(WebMessage(mToShare), Uri.EMPTY)
+            mToShare = null
+        }
+        if (mToCall != null) {
+            this@MainActivity.runOnUiThread({
+                mWebView?.evaluateJavascript(mToCall, fun (value: String) {
+                    // noop
+                })
+            })
+
+            mToCall = null
         }
-
     }
 
     override fun onPageError(errorCode: Int, description: String, failingUrl: String) {
-        Log.w("MAIN", "onPageError")
+        Log.w("MAIN", "onPageError " + description)
     }
 
     override fun onDownloadRequested(url: String, suggestedFilename: String, mimeType: String, contentLength: Long, contentDisposition: String, userAgent: String) {

+ 1 - 1
android/app/src/main/res/values/styles.xml

@@ -1,7 +1,7 @@
 <resources>
 
     <!-- Base application theme. -->
-    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
         <!-- Customize your theme here. -->
         <item name="colorPrimary">@color/colorPrimary</item>
         <item name="colorPrimaryDark">@color/colorPrimaryDark</item>

+ 5 - 1
android/build.gradle

@@ -2,12 +2,13 @@
 
 buildscript {
     ext.kotlin_version = '1.2.60'
+    ext.android_components_version = '0.26.0'
     repositories {
         google()
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.1.4'
+        classpath 'com.android.tools.build:gradle:3.2.1'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.60"
 
         // NOTE: Do not place your application dependencies here; they belong
@@ -18,6 +19,9 @@ buildscript {
 allprojects {
     repositories {
         google()
+        maven {
+            url "https://maven.mozilla.org/maven2"
+        }
         jcenter()
         maven { url "https://jitpack.io" }
     }

+ 1 - 1
android/generateAndLinkBundle.js

@@ -3,7 +3,7 @@ const path = require('path');
 
 child_process.execSync('npm run build');
 child_process.execSync(
-  `cp -R ${path.resolve(__dirname, '../dist')} ${path.resolve(
+  `cp -R ${path.resolve(__dirname, '../dist/*')} ${path.resolve(
     __dirname,
     'app/src/main/assets'
   )}`

Some files were not shown because too many files changed in this diff