diff --git a/android/stores/.eslintrc.yaml b/android/.eslintrc.yaml similarity index 100% rename from android/stores/.eslintrc.yaml rename to android/.eslintrc.yaml diff --git a/android/android.iml b/android/android.iml index 8dac7322..eff6737c 100644 --- a/android/android.iml +++ b/android/android.iml @@ -1,13 +1,5 @@ - - - - - - diff --git a/android/android.js b/android/android.js index 4fc7c85e..f7f32d4f 100644 --- a/android/android.js +++ b/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` - ${header(state, emit)} - - - - ${main(state, emit)} + + + + ${header(state, emit)} + ${main(state, emit)} `; 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)); diff --git a/android/app/app.iml b/android/app/app.iml index bfc58132..1fc6f7ac 100644 --- a/android/app/app.iml +++ b/android/app/app.iml @@ -51,19 +51,19 @@ - - + + - + - + @@ -112,31 +112,40 @@ - - + + + - + + - - + + + + - + + + + + + @@ -146,9 +155,11 @@ + + @@ -162,21 +173,26 @@ + + + + - + + - + - + diff --git a/android/app/build.gradle b/android/app/build.gradle index fe209ee1..4f597e68 100644 --- a/android/app/build.gradle +++ b/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') { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 786540c6..e3086ea3 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -8,7 +8,6 @@ diff --git a/android/app/src/main/java/com/mozilla/send/sendandroid/MainActivity.kt b/android/app/src/main/java/com/mozilla/send/sendandroid/MainActivity.kt index 70f90964..d48ccf26 100644 --- a/android/app/src/main/java/com/mozilla/send/sendandroid/MainActivity.kt +++ b/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(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 { + mAccount = FirefoxAccount(value, "12cc4070a481bc73", "fxaclient://android.redirect") + 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) + }) } @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 { + 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(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) { diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 5885930d..0eb88fe3 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,7 +1,7 @@ -