From 02a34ef23f84fe86fae97b4a8d4eb2cdeefe9d2b Mon Sep 17 00:00:00 2001 From: Miguel Rochefort Date: Wed, 1 Apr 2020 20:58:44 -0400 Subject: [PATCH] Add ChromeWatcher To squash --- mobile/src/main/AndroidManifest.xml | 9 ++ .../android/watcher/ChromeWatcher.kt | 136 ++++++++++++++++++ mobile/src/main/res/values/strings.xml | 1 + .../res/xml/accessibility_service_config.xml | 8 ++ 4 files changed, 154 insertions(+) create mode 100644 mobile/src/main/java/net/activitywatch/android/watcher/ChromeWatcher.kt create mode 100644 mobile/src/main/res/xml/accessibility_service_config.xml diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 80757c33..4cc5df2c 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -38,5 +38,14 @@ + + + + + + + \ No newline at end of file diff --git a/mobile/src/main/java/net/activitywatch/android/watcher/ChromeWatcher.kt b/mobile/src/main/java/net/activitywatch/android/watcher/ChromeWatcher.kt new file mode 100644 index 00000000..997b63aa --- /dev/null +++ b/mobile/src/main/java/net/activitywatch/android/watcher/ChromeWatcher.kt @@ -0,0 +1,136 @@ +package net.activitywatch.android.watcher + +import android.accessibilityservice.AccessibilityService +import android.annotation.TargetApi +import android.app.AlarmManager +import android.app.AppOpsManager +import android.app.PendingIntent +import android.app.usage.UsageEvents +import android.app.usage.UsageStatsManager +import android.content.Context +import android.content.Intent +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.os.* +import android.provider.Settings +import android.support.annotation.RequiresApi +import android.util.Log +import android.view.accessibility.AccessibilityEvent +import android.view.accessibility.AccessibilityNodeInfo +import android.widget.Toast +import net.activitywatch.android.R +import net.activitywatch.android.RustInterface +import net.activitywatch.android.models.Event +import org.json.JSONObject +import org.threeten.bp.DateTimeUtils +import org.threeten.bp.Duration +import org.threeten.bp.Instant +import java.net.URL +import java.text.ParseException +import java.text.SimpleDateFormat + +class ChromeWatcher : AccessibilityService() { + + private val TAG = "ChromeWatcher" + private val bucket_id = "aw-watcher-android-web-chrome" + + private var ri : RustInterface? = null + + var lastUrlTimestamp : Instant? = null + var lastUrl : String? = null + var lastTitle : String? = null + + override fun onCreate() { + ri = RustInterface(applicationContext) + ri?.createBucketHelper(bucket_id, "web.tab.current") + } + + override fun onAccessibilityEvent(event: AccessibilityEvent) { + + // TODO: This method is called very often, which might affect performance. Future optimizations needed. + + // Only track Chrome and System events + if (event.packageName != "com.android.chrome" && event.packageName != "com.android.systemui") { + onUrl(null) + return + } + + try { + if (event != null && event.source != null) { + // Get URL + val urlBars = event.source.findAccessibilityNodeInfosByViewId("com.android.chrome:id/url_bar") + if (urlBars.any()) { + val newUrl = "http://" + urlBars[0].text.toString() // TODO: We can't access the URI scheme, so we assume HTTP. + onUrl(newUrl) + } + + // Get title + var webView = findWebView(event.source) + if (webView != null) { + lastTitle = webView.text.toString() + Log.i(TAG, "Title: ${lastTitle}") + } + } + } + catch(ex : Exception) { + Log.e(TAG, ex.message) + } + } + + fun findWebView(info : AccessibilityNodeInfo) : AccessibilityNodeInfo? { + if(info == null) + return null + + if(info.className == "android.webkit.WebView" && info.text != null) + return info + + for (i in 0 until info.childCount) { + val child = info.getChild(i) + val webView = findWebView(child) + if (webView != null) { + return webView + } + if(child != null){ + child.recycle() + } + } + + return null + } + + fun onUrl(newUrl : String?) { + Log.i(TAG, "Url: ${newUrl}") + if (newUrl != lastUrl) { // URL changed + if (lastUrl != null) { + // Log last URL and title as a completed browser event. + // We wait for the event to complete (marked by a change in URL) to ensure that + // we had a chance to receive the title of the page, which often only arrives after + // the page loads completely and/or the user interacts with the page. + logBrowserEvent(lastUrl!!, lastTitle ?: "", lastUrlTimestamp!!) + } + + lastUrlTimestamp = Instant.ofEpochMilli(System.currentTimeMillis()) + lastUrl = newUrl + lastTitle = null + } + } + + fun logBrowserEvent(url: String, title: String, lastUrlTimestamp : Instant) { + val now = Instant.ofEpochMilli(System.currentTimeMillis()) + val start = lastUrlTimestamp + val end = now + val duration = Duration.between(start, end) + + val data = JSONObject() + data.put("url", url) + data.put("title", title) + data.put("audible", false) // TODO + data.put("incognito", false) // TODO + + ri?.heartbeatHelper(bucket_id, start, duration.seconds.toDouble(), data, 1.0) + } + + override fun onInterrupt() { + TODO("not implemented") + } +} \ No newline at end of file diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 31e08916..8de16d54 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -108,6 +108,7 @@ "\nIf you\'re new to ActivityWatch, you should also check out the desktop version of ActivityWatch." Click me to log data! + Allows ActivityWatch to read the URL and title from your browser. Hello blank fragment diff --git a/mobile/src/main/res/xml/accessibility_service_config.xml b/mobile/src/main/res/xml/accessibility_service_config.xml new file mode 100644 index 00000000..a0825521 --- /dev/null +++ b/mobile/src/main/res/xml/accessibility_service_config.xml @@ -0,0 +1,8 @@ + + \ No newline at end of file