Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.DS_Store
jniLibs
NDK


# All below taken from: https://github.com/github/gitignore/blob/13c64104e96bbadc8c81b5e499386af443cca66b/Android.gitignore
Expand Down
112 changes: 112 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ before_install:

script:
- ./gradlew clean lint test
- ./gradlew connectedAndroidTest || true


20 changes: 12 additions & 8 deletions mobile/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 27
compileSdkVersion 28
defaultConfig {
applicationId "net.activitywatch.android"
minSdkVersion 24
targetSdkVersion 27
versionCode 2
versionName "0.1.0"
targetSdkVersion 28
versionCode 5
versionName "0.2.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
Expand All @@ -30,11 +30,15 @@ android {

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
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 "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:cardview-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1'
implementation 'com.android.support:recyclerview-v7:28.0.0'
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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4

import org.junit.Test
import org.junit.Before
import org.junit.runner.RunWith

import org.junit.Assert.*
import java.lang.Thread.sleep
import java.time.Instant

/**
* Instrumented test, which will execute on an Android device.
Expand All @@ -19,6 +22,28 @@ class ExampleInstrumentedTest {
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getTargetContext()
assertEquals("net.activitywatch.aw_android", appContext.packageName)
assertEquals("net.activitywatch.android", appContext.packageName)
}

@Test
fun getBuckets() {
// TODO: Clear test buckets before test
val appContext = InstrumentationRegistry.getTargetContext()
val ri = RustInterface(appContext)
val bucketId = "test-${Math.random()}"
val oldLen = ri.getBucketsJSON().length()
ri.createBucket("""{"id": "$bucketId", "type": "test", "hostname": "test", "client": "test"}""")
assertEquals(ri.getBucketsJSON().length(), oldLen + 1)
}

@Test
fun createHeartbeat() {
val appContext = InstrumentationRegistry.getTargetContext()
val ri = RustInterface(appContext)
val bucketId = "test-${Math.random()}"
ri.createBucket("""{"id": "$bucketId", "type": "test", "hostname": "test", "client": "test"}""")
ri.heartbeat(bucketId, """{"timestamp": "${Instant.now()}", "duration": 0, "data": {"key": "value"}}""")
sleep(10)
assertEquals(1, ri.getEventsJSON(bucketId).length())
}
}
8 changes: 5 additions & 3 deletions mobile/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="net.activitywatch.android">

<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions"/>
<uses-permission
android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions"/>

<application
android:allowBackup="true"
Expand Down
104 changes: 41 additions & 63 deletions mobile/src/main/java/net/activitywatch/android/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
package net.activitywatch.android

import android.app.usage.UsageEvents
import android.app.usage.UsageStatsManager
import android.content.Context
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.design.widget.NavigationView
import android.support.v4.view.GravityCompat
import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar_main.*
import android.content.pm.PackageManager
import android.app.AppOpsManager
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.provider.Settings
import kotlinx.android.synthetic.main.content_main.*
import android.support.v4.app.Fragment
import android.util.Log
import net.activitywatch.android.fragments.Bucket
import net.activitywatch.android.fragments.BucketListFragment
import net.activitywatch.android.fragments.TestFragment


class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener, BucketListFragment.OnListFragmentInteractionListener {

private val TAG = "MainActivity"

fun getVersion(): String {
return packageManager.getPackageInfo(packageName, 0).versionName;
val version: String
get() {
return packageManager.getPackageInfo(packageName, 0).versionName
}

override fun onListFragmentInteraction(item: Bucket?) {
Log.w(TAG, "Bucket onInteraction listener not implemented")
}

override fun onAttachFragment(fragment: Fragment) {
if (fragment is BucketListFragment) {
fragment.onAttach(this)
}
}

override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -42,54 +49,9 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte

nav_view.setNavigationItemSelectedListener(this)

button.setOnClickListener {
queryUsage()
}
}

private fun queryUsage() {
val usageIsAllowed = isUsageAllowed()

if (usageIsAllowed) {
// Get UsageStatsManager stuff
val usm: UsageStatsManager = 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.")
startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
}
}

private fun isUsageAllowed(): Boolean {
// https://stackoverflow.com/questions/27215013/check-if-my-application-has-usage-access-enabled
val applicationInfo: ApplicationInfo = try {
packageManager.getApplicationInfo(packageName, 0)
} catch (e: PackageManager.NameNotFoundException) {
Log.e(TAG, e.toString())
return false
}

val appOpsManager = 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
val firstFragment = TestFragment()
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, firstFragment).commit()
}

override fun onBackPressed() {
Expand Down Expand Up @@ -121,11 +83,14 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
}

override fun onNavigationItemSelected(item: MenuItem): Boolean {
var fragmentClass: Class<out Fragment>? = null
// Handle navigation view item clicks here.
when (item.itemId) {
R.id.nav_dashboard -> {
Snackbar.make(coordinator_layout, "The dashboard button was clicked, but it's not yet implemented!", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
fragmentClass = TestFragment::class.java
}
R.id.nav_buckets -> {
fragmentClass = BucketListFragment::class.java
}
R.id.nav_settings -> {
Snackbar.make(coordinator_layout, "The settings button was clicked, but it's not yet implemented!", Snackbar.LENGTH_LONG)
Expand All @@ -141,6 +106,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_container, fragment).commit()
}

drawer_layout.closeDrawer(GravityCompat.START)
return true
}
Expand Down
Loading