From 9f64ffa628b4711efdcdca572f6ce5b12e2d18e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Tue, 25 Dec 2018 18:56:02 +0100 Subject: [PATCH 1/6] added basic working version of calling aw-server-rust code --- .../net/activitywatch/android/MainActivity.kt | 11 +++++ .../activitywatch/android/RustGreetings.kt | 9 ++++ scripts/setup-rust-with-ndk.sh | 44 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 mobile/src/main/java/net/activitywatch/android/RustGreetings.kt create mode 100755 scripts/setup-rust-with-ndk.sh diff --git a/mobile/src/main/java/net/activitywatch/android/MainActivity.kt b/mobile/src/main/java/net/activitywatch/android/MainActivity.kt index f02cfe1b..3d7e3bbd 100644 --- a/mobile/src/main/java/net/activitywatch/android/MainActivity.kt +++ b/mobile/src/main/java/net/activitywatch/android/MainActivity.kt @@ -20,11 +20,17 @@ import android.content.Intent import android.content.pm.ApplicationInfo import android.provider.Settings import kotlinx.android.synthetic.main.content_main.* +import android.widget.TextView + class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { private val TAG = "MainActivity" + init { + System.loadLibrary("aw_server"); + } + fun getVersion(): String { return packageManager.getPackageInfo(packageName, 0).versionName; } @@ -45,6 +51,11 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte button.setOnClickListener { queryUsage() } + + + val g = RustGreetings() + val r = g.sayHello("world") + Log.w(TAG, r) } private fun queryUsage() { diff --git a/mobile/src/main/java/net/activitywatch/android/RustGreetings.kt b/mobile/src/main/java/net/activitywatch/android/RustGreetings.kt new file mode 100644 index 00000000..aebce1a4 --- /dev/null +++ b/mobile/src/main/java/net/activitywatch/android/RustGreetings.kt @@ -0,0 +1,9 @@ +package net.activitywatch.android + +class RustGreetings { + private external fun greeting(pattern: String): String + + fun sayHello(to: String): String { + return greeting(to) + } +} \ No newline at end of file diff --git a/scripts/setup-rust-with-ndk.sh b/scripts/setup-rust-with-ndk.sh new file mode 100755 index 00000000..f6d8748f --- /dev/null +++ b/scripts/setup-rust-with-ndk.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Based on https://mozilla.github.io/firefox-browser-architecture/experiments/2017-09-21-rust-on-android.html + +project_path="/home/erb/Programming/activitywatch/other/aw-android/" + +export ANDROID_HOME=/home/$USER/Android/Sdk +export NDK_HOME=$ANDROID_HOME/ndk-bundle + +# curl https://sh.rustup.rs -sSf | sh + +mkdir -p NDK +${NDK_HOME}/build/tools/make_standalone_toolchain.py --api 28 --arch arm64 --install-dir $project_path/NDK/arm64 +${NDK_HOME}/build/tools/make_standalone_toolchain.py --api 28 --arch arm --install-dir $project_path/NDK/arm +${NDK_HOME}/build/tools/make_standalone_toolchain.py --api 28 --arch x86 --install-dir $project_path/NDK/x86 + + +# TODO: Check first that ~/.cargo/config doesn't already exist +# TODO: +project_path='/home/erb/Programming/activitywatch/other/aw-android' +echo " +[target.aarch64-linux-android] +ar = '$project_path/NDK/arm64/bin/aarch64-linux-android-ar' +linker = '$project_path/NDK/arm64/bin/aarch64-linux-android-clang' + +[target.armv7-linux-androideabi] +ar = '$project_path/NDK/arm/bin/arm-linux-androideabi-ar' +linker = '$project_path/NDK/arm/bin/arm-linux-androideabi-clang' + +[target.i686-linux-android] +ar = '$project_path/NDK/x86/bin/i686-linux-android-ar' +linker = '$project_path/NDK/x86/bin/i686-linux-android-clang' +" > ~/.cargo/config + +rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android + +mkdir -p $project_path/mobile/src/main/jniLibs/x86 +mkdir -p $project_path/mobile/src/main/jniLibs/arm64 +mkdir -p $project_path/mobile/src/main/jniLibs/armeabi + +# Some more steps after this is done: +# - Build aw-server-rust using its compile-android.sh script +# - Copy/link the built libraries into the mobile/src/main/jniLibs folder +# - Build and test the app! From bcfb0c7812aaa39fa7754aa7979687bcc52989b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Sat, 5 Jan 2019 16:37:31 +0100 Subject: [PATCH 2/6] added code for creating buckets using aw-server-rust --- .../net/activitywatch/android/MainActivity.kt | 8 +++--- .../activitywatch/android/RustGreetings.kt | 28 ++++++++++++++++++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/mobile/src/main/java/net/activitywatch/android/MainActivity.kt b/mobile/src/main/java/net/activitywatch/android/MainActivity.kt index 3d7e3bbd..cb458c2c 100644 --- a/mobile/src/main/java/net/activitywatch/android/MainActivity.kt +++ b/mobile/src/main/java/net/activitywatch/android/MainActivity.kt @@ -50,12 +50,12 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte button.setOnClickListener { queryUsage() + testRust() } + } - - val g = RustGreetings() - val r = g.sayHello("world") - Log.w(TAG, r) + private fun testRust() { + RustGreetings(applicationContext).test() } private fun queryUsage() { diff --git a/mobile/src/main/java/net/activitywatch/android/RustGreetings.kt b/mobile/src/main/java/net/activitywatch/android/RustGreetings.kt index aebce1a4..b675a170 100644 --- a/mobile/src/main/java/net/activitywatch/android/RustGreetings.kt +++ b/mobile/src/main/java/net/activitywatch/android/RustGreetings.kt @@ -1,9 +1,35 @@ package net.activitywatch.android -class RustGreetings { +import android.content.Context +import android.util.Log +import org.json.JSONObject + +class RustGreetings constructor(context: Context) { private external fun greeting(pattern: String): String + private external fun setAndroidDataDir(path: String) + private external fun getBucketsJSONString(): String + private external fun createBucket(bucket: String): String + + private val TAG = "RustGreetings" + + init { + setAndroidDataDir(context.filesDir.absolutePath) + } fun sayHello(to: String): String { return greeting(to) } + + fun getBuckets(): JSONObject { + return JSONObject(getBucketsJSONString()) + } + + fun test() { + val r = sayHello("world") + Log.w(TAG, r) + Log.w(TAG, getBuckets().toString(2)) + val msg = createBucket("""{"id": "test", "type": "test", "hostname": "test", "client": "test"}""") + Log.w(TAG, msg) + Log.w(TAG, getBuckets().toString(2)) + } } \ No newline at end of file From 012bf12dfd49caec43be1507e6edbe35b88a4cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Bj=C3=A4reholt?= Date: Sun, 6 Jan 2019 21:20:26 +0100 Subject: [PATCH 3/6] lots of changes: fragments, refactored UsageStats stuff to seperate class, now actually creating heartbeats in buckets --- .gitignore | 2 + mobile/build.gradle | 5 +- mobile/src/main/AndroidManifest.xml | 8 +- .../net/activitywatch/android/MainActivity.kt | 95 +++++++---------- .../activitywatch/android/RustGreetings.kt | 35 ------ .../activitywatch/android/RustInterface.kt | 46 ++++++++ .../android/UsageStatsWatcher.kt | 61 +++++++++++ .../android/fragments/BucketFragment.kt | 100 ++++++++++++++++++ .../fragments/MyBucketRecyclerViewAdapter.kt | 66 ++++++++++++ .../android/fragments/TestFragment.kt | 50 +++++++++ .../android/models/BucketsContent.kt | 57 ++++++++++ .../android/models/DummyContent.kt | 65 ++++++++++++ .../android/models/TestViewModel.kt | 7 ++ mobile/src/main/res/layout/content_main.xml | 35 +----- .../src/main/res/layout/fragment_bucket.xml | 20 ++++ .../main/res/layout/fragment_bucket_list.xml | 14 +++ mobile/src/main/res/layout/test_fragment.xml | 36 +++++++ .../main/res/menu/activity_main_drawer.xml | 1 + mobile/src/main/res/values/dimens.xml | 2 + mobile/src/main/res/values/strings.xml | 90 ++++++++++++++++ 20 files changed, 666 insertions(+), 129 deletions(-) delete mode 100644 mobile/src/main/java/net/activitywatch/android/RustGreetings.kt create mode 100644 mobile/src/main/java/net/activitywatch/android/RustInterface.kt create mode 100644 mobile/src/main/java/net/activitywatch/android/UsageStatsWatcher.kt create mode 100644 mobile/src/main/java/net/activitywatch/android/fragments/BucketFragment.kt create mode 100644 mobile/src/main/java/net/activitywatch/android/fragments/MyBucketRecyclerViewAdapter.kt create mode 100644 mobile/src/main/java/net/activitywatch/android/fragments/TestFragment.kt create mode 100644 mobile/src/main/java/net/activitywatch/android/models/BucketsContent.kt create mode 100644 mobile/src/main/java/net/activitywatch/android/models/DummyContent.kt create mode 100644 mobile/src/main/java/net/activitywatch/android/models/TestViewModel.kt create mode 100644 mobile/src/main/res/layout/fragment_bucket.xml create mode 100644 mobile/src/main/res/layout/fragment_bucket_list.xml create mode 100644 mobile/src/main/res/layout/test_fragment.xml diff --git a/.gitignore b/.gitignore index 67c2d736..d00e50bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .DS_Store +jniLibs +NDK # All below taken from: https://github.com/github/gitignore/blob/13c64104e96bbadc8c81b5e499386af443cca66b/Android.gitignore diff --git a/mobile/build.gradle b/mobile/build.gradle index c46808eb..f6d0670b 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -30,11 +30,14 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support:support-v4:27.1.1' implementation 'com.android.support:design:27.1.1' implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'com.jakewharton.threetenabp:threetenabp:1.0.3' + implementation 'com.android.support:recyclerview-v7:27.1.1' + implementation 'android.arch.lifecycle:extensions:1.1.1' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 23ae700f..10d59308 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -1,9 +1,11 @@ - - + ? = null // Handle navigation view item clicks here. when (item.itemId) { R.id.nav_dashboard -> { + fragmentClass = TestFragment::class.java Snackbar.make(coordinator_layout, "The dashboard button was clicked, but it's not yet implemented!", Snackbar.LENGTH_LONG) .setAction("Action", null).show() } + R.id.nav_buckets -> { + fragmentClass = BucketFragment::class.java + } R.id.nav_settings -> { Snackbar.make(coordinator_layout, "The settings button was clicked, but it's not yet implemented!", Snackbar.LENGTH_LONG) .setAction("Action", null).show() @@ -152,6 +118,19 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte } } + val fragment: Fragment? = try { + fragmentClass?.newInstance() + } catch (e: Exception) { + e.printStackTrace() + null + } + + if(fragment != null) { + // Insert the fragment by replacing any existing fragment + val fragmentManager = supportFragmentManager + fragmentManager.beginTransaction().replace(R.id.fragment, fragment).commit() + } + drawer_layout.closeDrawer(GravityCompat.START) return true } diff --git a/mobile/src/main/java/net/activitywatch/android/RustGreetings.kt b/mobile/src/main/java/net/activitywatch/android/RustGreetings.kt deleted file mode 100644 index b675a170..00000000 --- a/mobile/src/main/java/net/activitywatch/android/RustGreetings.kt +++ /dev/null @@ -1,35 +0,0 @@ -package net.activitywatch.android - -import android.content.Context -import android.util.Log -import org.json.JSONObject - -class RustGreetings constructor(context: Context) { - private external fun greeting(pattern: String): String - private external fun setAndroidDataDir(path: String) - private external fun getBucketsJSONString(): String - private external fun createBucket(bucket: String): String - - private val TAG = "RustGreetings" - - init { - setAndroidDataDir(context.filesDir.absolutePath) - } - - fun sayHello(to: String): String { - return greeting(to) - } - - fun getBuckets(): JSONObject { - return JSONObject(getBucketsJSONString()) - } - - fun test() { - val r = sayHello("world") - Log.w(TAG, r) - Log.w(TAG, getBuckets().toString(2)) - val msg = createBucket("""{"id": "test", "type": "test", "hostname": "test", "client": "test"}""") - Log.w(TAG, msg) - Log.w(TAG, getBuckets().toString(2)) - } -} \ No newline at end of file diff --git a/mobile/src/main/java/net/activitywatch/android/RustInterface.kt b/mobile/src/main/java/net/activitywatch/android/RustInterface.kt new file mode 100644 index 00000000..07b5c7ce --- /dev/null +++ b/mobile/src/main/java/net/activitywatch/android/RustInterface.kt @@ -0,0 +1,46 @@ +package net.activitywatch.android + +import android.content.Context +import android.util.Log +import org.json.JSONArray +import org.json.JSONObject +import org.threeten.bp.Instant + +class RustInterface constructor(context: Context) { + private val TAG = "RustGreetings" + + init { + setAndroidDataDir(context.filesDir.absolutePath) + } + + external fun greeting(pattern: String): String + external fun setAndroidDataDir(path: String) + external fun getBuckets(): String + external fun createBucket(bucket: String): String + external fun getEvents(bucket_id: String): String + external fun heartbeat(bucket_id: String, event: String): String + + fun sayHello(to: String): String { + return greeting(to) + } + + fun getBucketsJSON(): JSONObject { + return JSONObject(getBuckets()) + } + + fun getEventsJSON(bucket_id: String): JSONArray { + return JSONArray(getEvents(bucket_id)) + } + + fun test() { + Log.w(TAG, sayHello("Android")) + Log.w(TAG, createBucket("""{"id": "test", "type": "test", "hostname": "test", "client": "test"}""")) + Log.w(TAG, getBucketsJSON().toString(2)) + + val event = """{"timestamp": "${Instant.now()}", "duration": 0, "data": {"key": "value"}}""" + Log.w(TAG, event) + Log.w(TAG, heartbeat("test", event)) + Log.w(TAG, getBucketsJSON().toString(2)) + Log.w(TAG, getEventsJSON("test").toString(2)) + } +} \ No newline at end of file diff --git a/mobile/src/main/java/net/activitywatch/android/UsageStatsWatcher.kt b/mobile/src/main/java/net/activitywatch/android/UsageStatsWatcher.kt new file mode 100644 index 00000000..32dfbcce --- /dev/null +++ b/mobile/src/main/java/net/activitywatch/android/UsageStatsWatcher.kt @@ -0,0 +1,61 @@ +package net.activitywatch.android + +import android.app.AppOpsManager +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.provider.Settings +import android.util.Log + +class UsageStatsWatcher constructor(val context: Context) { + val TAG = "UsageStatsWatcher" + + private fun isUsageAllowed(): Boolean { + // https://stackoverflow.com/questions/27215013/check-if-my-application-has-usage-access-enabled + val applicationInfo: ApplicationInfo = try { + context.packageManager.getApplicationInfo(context.packageName, 0) + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, e.toString()) + return false + } + + val appOpsManager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager + val mode = appOpsManager.checkOpNoThrow( + AppOpsManager.OPSTR_GET_USAGE_STATS, + applicationInfo.uid, + applicationInfo.packageName + ) + return mode == AppOpsManager.MODE_ALLOWED + } + + fun queryUsage() { + val usageIsAllowed = isUsageAllowed() + + if (usageIsAllowed) { + // Get UsageStatsManager stuff + val usm: UsageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager + + // Print per application + val usageStats = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, 0, Long.MAX_VALUE) + Log.i(TAG, "usageStats.size=${usageStats.size}") + for(e in usageStats) { + Log.i(TAG, "${e.packageName}: ${e.totalTimeInForeground/1000}") + } + + // Print each event + val usageEvents = usm.queryEvents(0, Long.MAX_VALUE) + val eventOut = UsageEvents.Event() + while(usageEvents.hasNextEvent()) { + usageEvents.getNextEvent(eventOut) + Log.i(TAG, "timestamp=${eventOut.timeStamp}, ${eventOut.eventType}, ${eventOut.className}") + } + } else { + Log.w(TAG, "Was not allowed access to UsageStats, enable in settings.") + context.startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)) + } + } + +} \ No newline at end of file diff --git a/mobile/src/main/java/net/activitywatch/android/fragments/BucketFragment.kt b/mobile/src/main/java/net/activitywatch/android/fragments/BucketFragment.kt new file mode 100644 index 00000000..1a04cec6 --- /dev/null +++ b/mobile/src/main/java/net/activitywatch/android/fragments/BucketFragment.kt @@ -0,0 +1,100 @@ +package net.activitywatch.android.fragments + +import android.content.Context +import android.os.Bundle +import android.support.v4.app.Fragment +import android.support.v7.widget.GridLayoutManager +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import net.activitywatch.android.R +import net.activitywatch.android.models.BucketsContent + + +/** + * A fragment representing a list of Items. + * Activities containing this fragment MUST implement the + * [BucketFragment.OnListFragmentInteractionListener] interface. + */ +class BucketFragment : Fragment() { + + // TODO: Customize parameters + private var columnCount = 1 + + private var listener: OnListFragmentInteractionListener? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + arguments?.let { + columnCount = it.getInt(ARG_COLUMN_COUNT) + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate(R.layout.fragment_bucket_list, container, false) + + // Set the adapter + if (view is RecyclerView) { + with(view) { + layoutManager = when { + columnCount <= 1 -> LinearLayoutManager(context) + else -> GridLayoutManager(context, columnCount) + } + adapter = + MyBucketRecyclerViewAdapter(BucketsContent.ITEMS, listener) + } + } + return view + } + + override fun onAttach(context: Context) { + super.onAttach(context) + if (context is OnListFragmentInteractionListener) { + listener = context + } else { + throw RuntimeException(context.toString() + " must implement OnListFragmentInteractionListener") + } + } + + override fun onDetach() { + super.onDetach() + listener = null + } + + /** + * This interface must be implemented by activities that contain this + * fragment to allow an interaction in this fragment to be communicated + * to the activity and potentially other fragments contained in that + * activity. + * + * + * See the Android Training lesson + * [Communicating with Other Fragments](http://developer.android.com/training/basics/fragments/communicating.html) + * for more information. + */ + interface OnListFragmentInteractionListener { + // TODO: Update argument type and name + fun onListFragmentInteraction(item: Bucket?) + } + + companion object { + + // TODO: Customize parameter argument names + const val ARG_COLUMN_COUNT = "column-count" + + // TODO: Customize parameter initialization + @JvmStatic + fun newInstance(columnCount: Int) = + BucketFragment().apply { + arguments = Bundle().apply { + putInt(ARG_COLUMN_COUNT, columnCount) + } + } + } +} diff --git a/mobile/src/main/java/net/activitywatch/android/fragments/MyBucketRecyclerViewAdapter.kt b/mobile/src/main/java/net/activitywatch/android/fragments/MyBucketRecyclerViewAdapter.kt new file mode 100644 index 00000000..2327b9a0 --- /dev/null +++ b/mobile/src/main/java/net/activitywatch/android/fragments/MyBucketRecyclerViewAdapter.kt @@ -0,0 +1,66 @@ +package net.activitywatch.android.fragments + +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView + + +import net.activitywatch.android.fragments.BucketFragment.OnListFragmentInteractionListener + +import kotlinx.android.synthetic.main.fragment_bucket.view.* +import net.activitywatch.android.R +import org.json.JSONObject + +typealias Bucket = JSONObject + +/** + * [RecyclerView.Adapter] that can display a [DummyItem] and makes a call to the + * specified [OnListFragmentInteractionListener]. + * TODO: Replace the implementation with code for your data type. + */ +class MyBucketRecyclerViewAdapter( + private val mValues: List, + private val mListener: OnListFragmentInteractionListener? +) : RecyclerView.Adapter() { + + private val mOnClickListener: View.OnClickListener + + init { + mOnClickListener = View.OnClickListener { v -> + val item = v.tag as Bucket + // Notify the active callbacks interface (the activity, if the fragment is attached to + // one) that an item has been selected. + mListener?.onListFragmentInteraction(item) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.fragment_bucket, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = mValues[position] + holder.mIdView.text = item.getString("id") + holder.mContentView.text = item.getString("created") + + with(holder.mView) { + tag = item + setOnClickListener(mOnClickListener) + } + } + + override fun getItemCount(): Int = mValues.size + + inner class ViewHolder(val mView: View) : RecyclerView.ViewHolder(mView) { + val mIdView: TextView = mView.item_number + val mContentView: TextView = mView.content + + override fun toString(): String { + return super.toString() + " '" + mContentView.text + "'" + } + } +} diff --git a/mobile/src/main/java/net/activitywatch/android/fragments/TestFragment.kt b/mobile/src/main/java/net/activitywatch/android/fragments/TestFragment.kt new file mode 100644 index 00000000..7e11beb7 --- /dev/null +++ b/mobile/src/main/java/net/activitywatch/android/fragments/TestFragment.kt @@ -0,0 +1,50 @@ +package net.activitywatch.android.fragments + +import android.arch.lifecycle.ViewModelProviders +import android.os.Bundle +import android.support.v4.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import net.activitywatch.android.R +import net.activitywatch.android.RustInterface +import net.activitywatch.android.UsageStatsWatcher +import net.activitywatch.android.models.TestViewModel + + +class TestFragment : Fragment() { + + companion object { + fun newInstance() = TestFragment() + } + + private lateinit var viewModel: TestViewModel + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.test_fragment, container, false) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + viewModel = ViewModelProviders.of(this).get(TestViewModel::class.java) + // TODO: Use the ViewModel + + val button = view?.findViewById(R.id.button) as Button + button.setOnClickListener { + val ctx = activity + if(ctx != null) UsageStatsWatcher(ctx).queryUsage() + testRust() + } + } + + private fun testRust() { + val ctx = activity + if(ctx != null) { + RustInterface(ctx).test() + } + } +} diff --git a/mobile/src/main/java/net/activitywatch/android/models/BucketsContent.kt b/mobile/src/main/java/net/activitywatch/android/models/BucketsContent.kt new file mode 100644 index 00000000..25eacc17 --- /dev/null +++ b/mobile/src/main/java/net/activitywatch/android/models/BucketsContent.kt @@ -0,0 +1,57 @@ +package net.activitywatch.android.models + +import org.json.JSONObject +import java.util.ArrayList +import java.util.HashMap + +typealias Bucket = JSONObject + +/** + * Helper class for providing sample content for user interfaces created by + * Android template wizards. + * + * TODO: Replace all uses of this class before publishing your app. + */ +object BucketsContent { + + /** + * An array of sample (dummy) items. + */ + val ITEMS: MutableList = ArrayList() + + /** + * A map of sample (dummy) items, by ID. + */ + val ITEM_MAP: MutableMap = HashMap() + + private val COUNT = 25 + + init { + // Add some sample items. + for (i in 1..COUNT) { + addItem( + createDummyItem( + i + ) + ) + } + } + + private fun addItem(item: Bucket) { + ITEMS.add(item) + ITEM_MAP.put(item.getString("id"), item) + } + + private fun createDummyItem(position: Int): Bucket { + return Bucket("""{"id": "test $position", "created": "2019-01-01T16:20:00.1"}""") + } + + private fun makeDetails(position: Int): String { + val builder = StringBuilder() + builder.append("Details about Item: ").append(position) + for (i in 0..position - 1) { + builder.append("\nMore details information here.") + } + return builder.toString() + } +} diff --git a/mobile/src/main/java/net/activitywatch/android/models/DummyContent.kt b/mobile/src/main/java/net/activitywatch/android/models/DummyContent.kt new file mode 100644 index 00000000..fd184b5b --- /dev/null +++ b/mobile/src/main/java/net/activitywatch/android/models/DummyContent.kt @@ -0,0 +1,65 @@ +package net.activitywatch.android.models + +import java.util.ArrayList +import java.util.HashMap + +/** + * Helper class for providing sample content for user interfaces created by + * Android template wizards. + * + * TODO: Replace all uses of this class before publishing your app. + */ +object DummyContent { + + /** + * An array of sample (dummy) items. + */ + val ITEMS: MutableList = ArrayList() + + /** + * A map of sample (dummy) items, by ID. + */ + val ITEM_MAP: MutableMap = HashMap() + + private val COUNT = 25 + + init { + // Add some sample items. + for (i in 1..COUNT) { + addItem( + createDummyItem( + i + ) + ) + } + } + + private fun addItem(item: DummyItem) { + ITEMS.add(item) + ITEM_MAP.put(item.id, item) + } + + private fun createDummyItem(position: Int): DummyItem { + return DummyItem( + position.toString(), + "Item " + position, + makeDetails(position) + ) + } + + private fun makeDetails(position: Int): String { + val builder = StringBuilder() + builder.append("Details about Item: ").append(position) + for (i in 0..position - 1) { + builder.append("\nMore details information here.") + } + return builder.toString() + } + + /** + * A dummy item representing a piece of content. + */ + data class DummyItem(val id: String, val content: String, val details: String) { + override fun toString(): String = content + } +} diff --git a/mobile/src/main/java/net/activitywatch/android/models/TestViewModel.kt b/mobile/src/main/java/net/activitywatch/android/models/TestViewModel.kt new file mode 100644 index 00000000..2e75cdf3 --- /dev/null +++ b/mobile/src/main/java/net/activitywatch/android/models/TestViewModel.kt @@ -0,0 +1,7 @@ +package net.activitywatch.android.models + +import android.arch.lifecycle.ViewModel; + +class TestViewModel : ViewModel() { + // TODO: Implement the ViewModel +} diff --git a/mobile/src/main/res/layout/content_main.xml b/mobile/src/main/res/layout/content_main.xml index 58b5f011..e7cc4b0d 100644 --- a/mobile/src/main/res/layout/content_main.xml +++ b/mobile/src/main/res/layout/content_main.xml @@ -9,38 +9,9 @@ tools:showIn="@layout/app_bar_main" tools:context=".MainActivity"> - - - - -