diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fbcf181
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,87 @@
+# Built application files
+*.aar
+*.ap_
+*.aab
+
+# Files for the ART/Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+out/
+# Uncomment the following line in case you need and you don't have the release build type files in your app
+# release/
+
+# Gradle files
+.gradle/
+build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+# Android Studio Navigation editor temp files
+.navigation/
+
+# Android Studio captures folder
+captures/
+
+# IntelliJ
+*.iml
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+# Android Studio 3 in .gitignore file.
+.idea/caches
+.idea/modules.xml
+# Comment next line if keeping position of elements in Navigation Editor is relevant for you
+.idea/navEditor.xml
+
+# Keystore files
+# Uncomment the following lines if you do not want to check your keystore files in.
+#*.jks
+#*.keystore
+
+# External native build folder generated in Android Studio 2.2 and later
+.externalNativeBuild
+.cxx/
+
+# Google Services (e.g. APIs or Firebase)
+# google-services.json
+
+# Freeline
+freeline.py
+freeline/
+freeline_project_description.json
+
+# fastlane
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots
+fastlane/test_output
+fastlane/readme.md
+
+# Version control
+vcs.xml
+
+# lint
+lint/intermediates/
+lint/generated/
+lint/outputs/
+lint/tmp/
+# lint/reports/
+
+# Android Profiling
+*.hprof
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..3cc336b
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..a5f05cd
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..7bfef59
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index c9085f4..54f0909 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,24 @@
-# Revolut-Stock-List-Extract-Android
-Simple App to extract the list of ticker symbols from Revolut
+# Stock-List-Extract-Android
+Simple App to extract the list of ticker symbols from R
+
+## IF you need only the file: last updated at 2020.09.11
+file: [stock_list_1599869417144.csv](https://github.com/fknives/Stock-List-Extract-Android/blob/dev/stock_list_1599869417144.csv)
+
+## Usage:
+- install the application on your phone
+- Start the application
+- Click on Start Service
+- Enable Accessibility Service
+- Click on Start Service again
+- Notice a notification is shown
+- Navigate to R All Stocks List
+- Tap on the Notification
+- Notice the app starts to load from the screen (the notification is updated)
+- Wait until the notification disappears and the screen is scrolled until the end
+- Go back to this app
+- Notice it now shows all the tickers at the bottom and you can send the file to somewhere else
+
+## Notes:
+- the app does not have internet permission, so you don't have to worry that something is leaving your device that you do not want to
+- after finishing the sync the AccessibilityService is disabled (at least above api 24, if you have a solution below please let me know)
+- the file format is CSV meaning the tickers are separated by comma, easy to include into google sheets or other programs.
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..fce1a0d
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,62 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply from: "./config.gradle"
+
+android {
+ compileSdkVersion 29
+
+ defaultConfig {
+ buildConfigField "String", "CONFIG_COMPANY_IMG_ID", "\"$COMPANY_IMG_ID\""
+ buildConfigField "String", "CONFIG_COMPANY_NAME_ID", "\"$COMPANY_NAME_ID\""
+ buildConfigField "String", "CONFIG_COMPANY_TICKER_ID", "\"$COMPANY_TICKER_ID\""
+ buildConfigField "String", "CONFIG_COMPANY_SHARE_PRICE_ID", "\"$COMPANY_SHARE_PRICE_ID\""
+ buildConfigField "String", "CONFIG_COMPANY_CHANGE_PERCENT_ID", "\"$COMPANY_CHANGE_PERCENT_ID\""
+ buildConfigField "String", "CONFIG_RECYCLER_ID1", "\"$RECYCLER_ID1\""
+ buildConfigField "String", "CONFIG_RECYCLER_ID2", "\"$RECYCLER_ID2\""
+ applicationId "org.fknives.rstocklist"
+ minSdkVersion 21
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_1_8
+ }
+
+ buildFeatures {
+ viewBinding true
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: "libs", include: ["*.jar"])
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
+ implementation "androidx.core:core-ktx:1.3.1"
+ implementation "androidx.appcompat:appcompat:1.2.0"
+ implementation "androidx.constraintlayout:constraintlayout:2.0.1"
+ implementation "androidx.recyclerview:recyclerview:1.1.0"
+ implementation "com.google.android.material:material:1.2.1"
+
+ testImplementation "junit:junit:4.13"
+ androidTestImplementation "androidx.test.ext:junit:1.1.2"
+ androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0"
+
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3dc7cc9
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/fknives/rstocklist/BindingViewHolder.kt b/app/src/main/java/org/fknives/rstocklist/BindingViewHolder.kt
new file mode 100644
index 0000000..5dbfc2a
--- /dev/null
+++ b/app/src/main/java/org/fknives/rstocklist/BindingViewHolder.kt
@@ -0,0 +1,16 @@
+package org.fknives.rstocklist
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import androidx.viewbinding.ViewBinding
+
+class BindingViewHolder(
+ val binding: Binding
+) : RecyclerView.ViewHolder(binding.root) {
+
+ constructor(
+ parent: ViewGroup,
+ howToBind: (LayoutInflater, ViewGroup, Boolean) -> Binding
+ ) : this(howToBind(LayoutInflater.from(parent.context), parent, false))
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fknives/rstocklist/FileManager.kt b/app/src/main/java/org/fknives/rstocklist/FileManager.kt
new file mode 100644
index 0000000..7747f4e
--- /dev/null
+++ b/app/src/main/java/org/fknives/rstocklist/FileManager.kt
@@ -0,0 +1,49 @@
+package org.fknives.rstocklist
+
+import android.content.Context
+import androidx.annotation.MainThread
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+import java.io.File
+
+class FileManager private constructor(private val context: Context) {
+
+ private val fileDir get() = context.cacheDir
+ private var last = 0
+ private val stateFlow = MutableStateFlow(last)
+ val tickersWithLastLoadedAtFlow = stateFlow.map { loadTickersAndLastUpdatedAt() }
+
+ fun saveTickers(tickers: List) {
+ context.cacheDir.listFiles()?.forEach { it.delete() }
+ val fileIntoSave = File(context.cacheDir, "$STATIC_PART_OF_FILENAME${System.currentTimeMillis()}$FILE_EXTENSION")
+ fileIntoSave.writeText(tickers.firstOrNull().orEmpty())
+ tickers.drop(1).forEach {
+ fileIntoSave.appendText(",$it")
+ }
+ last = (last + 1) % 2
+ stateFlow.value = last
+ }
+
+ private fun loadTickersAndLastUpdatedAt(): Pair>? =
+ lastFile()?.let {
+ it.getTimestampFromFile() to it.readText().split(",")
+ }
+
+ private fun File.getTimestampFromFile(): Long =
+ name.drop(STATIC_PART_OF_FILENAME.length).dropLast(FILE_EXTENSION.length).toLongOrNull() ?: 0L
+
+ fun lastFile(): File? =
+ fileDir.listFiles()
+ ?.filter { it.name.contains(STATIC_PART_OF_FILENAME) }
+ ?.maxByOrNull { it.getTimestampFromFile() }
+
+ companion object {
+ private const val STATIC_PART_OF_FILENAME = "stock_list_"
+ private const val FILE_EXTENSION = ".csv"
+ private var fileManager: FileManager? = null
+
+ @MainThread
+ operator fun invoke(context: Context): FileManager =
+ fileManager ?: FileManager(context.applicationContext).also { fileManager = it }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fknives/rstocklist/MainActivity.kt b/app/src/main/java/org/fknives/rstocklist/MainActivity.kt
new file mode 100644
index 0000000..a0dd35a
--- /dev/null
+++ b/app/src/main/java/org/fknives/rstocklist/MainActivity.kt
@@ -0,0 +1,69 @@
+package org.fknives.rstocklist
+
+import android.content.Intent
+import android.os.Bundle
+import android.provider.Settings
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.FileProvider
+import androidx.core.view.isVisible
+import androidx.lifecycle.lifecycleScope
+import kotlinx.coroutines.flow.collect
+import org.fknives.rstocklist.appsync.SyncService
+import org.fknives.rstocklist.databinding.ActivityMainBinding
+import java.text.SimpleDateFormat
+
+
+class MainActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ val fileManager = FileManager(this)
+
+ binding.startServiceCta.setOnClickListener {
+ if (SyncService.canStart()) {
+ startService(NotificationService.getStartIntent(this))
+ } else {
+ startActivity(
+ Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
+ )
+ }
+ }
+
+ binding.shareFileCta.setOnClickListener {
+ val file = fileManager.lastFile() ?: return@setOnClickListener
+ val uri = FileProvider.getUriForFile(
+ this,
+ "org.fknives.rstocklist.fileprovider",
+ file
+ )
+ val sharingIntent = Intent(Intent.ACTION_SEND)
+ sharingIntent.type = "text/*"
+ sharingIntent.putExtra(Intent.EXTRA_STREAM, uri)
+ startActivity(Intent.createChooser(sharingIntent, resources.getText(R.string.send_to)))
+ }
+
+ val adapter = TickerAdapter()
+ binding.recycler.adapter = adapter
+
+ lifecycleScope.launchWhenCreated {
+ fileManager.tickersWithLastLoadedAtFlow.collect {
+ binding.lastUpdatedAt.isVisible = it != null
+ binding.shareFileCta.isVisible = it != null
+ it?.first?.let(SimpleDateFormat("YYYY-MM-dd hh:mm")::format)
+ ?.let { date -> getString(R.string.file_last_updated_at, date) }
+ ?.let(binding.lastUpdatedAt::setText)
+ adapter.submitList(it?.second.orEmpty())
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ stopService(NotificationService.getStartIntent(this))
+ SyncService.stop()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fknives/rstocklist/NotificationService.kt b/app/src/main/java/org/fknives/rstocklist/NotificationService.kt
new file mode 100644
index 0000000..02b9194
--- /dev/null
+++ b/app/src/main/java/org/fknives/rstocklist/NotificationService.kt
@@ -0,0 +1,84 @@
+package org.fknives.rstocklist
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.IBinder
+import androidx.core.app.NotificationCompat
+import org.fknives.rstocklist.appsync.SyncService
+
+class NotificationService : Service(), SyncService.EventListener {
+
+ override fun onCreate() {
+ super.onCreate()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val name = getString(R.string.start_service_channel_title)
+ val importance = NotificationManager.IMPORTANCE_HIGH
+ val mChannel = NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance)
+ mChannel.description = name
+ mChannel.enableLights(true)
+ val manager = (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
+ manager.createNotificationChannel(mChannel)
+ }
+
+ updateNotification("Click Me When on Ticker List Screen!")
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ intent?.getBooleanExtra(SYNC_STARTED, false)?.takeIf { it }?.let {
+ updateNotification("Syncing ...")
+ SyncService.listener = this
+ SyncService.start()
+ }
+ intent?.getIntExtra(PROGRESS, -1)?.takeIf { it >= 0 }?.let {
+ updateNotification("Processed: $it, syncing ...")
+ }
+ return START_NOT_STICKY
+ }
+
+ private fun updateNotification(text: String) {
+ val intent = Intent(this, NotificationService::class.java)
+ .putExtra(SYNC_STARTED, true)
+ val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_launcher_foreground)
+ .setContentTitle("Start syncing on Stock List")
+ .setContentText(text)
+ .setContentIntent(PendingIntent.getService(this, 0, intent, 0))
+ .build()
+
+ startForeground(1, notification)
+ }
+
+
+ override fun onBind(intent: Intent?): IBinder? = null
+
+ override fun onItemProcessed(index: Int) {
+ updateNotification("Processed: ${index + 1}, syncing ...")
+ }
+
+ override fun onItemProcessingFinished(items: List) {
+ FileManager(this).saveTickers(items)
+ updateNotification("Processed: ${items.size}.")
+
+ stopSelf()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ SyncService.stop()
+ }
+
+ companion object {
+ fun getStartIntent(context: Context): Intent =
+ Intent(context, NotificationService::class.java)
+
+ const val NOTIFICATION_CHANNEL_ID = "START_SERVICE_ID"
+ const val SYNC_STARTED = "SYNC_STARTED"
+ const val PROGRESS = "PROGRESS"
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fknives/rstocklist/TickerAdapter.kt b/app/src/main/java/org/fknives/rstocklist/TickerAdapter.kt
new file mode 100644
index 0000000..26c3d6f
--- /dev/null
+++ b/app/src/main/java/org/fknives/rstocklist/TickerAdapter.kt
@@ -0,0 +1,32 @@
+package org.fknives.rstocklist
+
+import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import org.fknives.rstocklist.databinding.ItemTickerBinding
+
+class TickerAdapter :
+ ListAdapter>(StringDiffUtilItem()) {
+
+ class StringDiffUtilItem : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: String, newItem: String): Boolean =
+ oldItem == newItem
+
+ override fun areContentsTheSame(oldItem: String, newItem: String): Boolean =
+ true
+
+ override fun getChangePayload(oldItem: String, newItem: String): Any? =
+ this
+
+ }
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): BindingViewHolder =
+ BindingViewHolder(parent, ItemTickerBinding::inflate)
+
+ override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {
+ holder.binding.ticker.text = getItem(position)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fknives/rstocklist/TickersWithLastLoadedTime.kt b/app/src/main/java/org/fknives/rstocklist/TickersWithLastLoadedTime.kt
new file mode 100644
index 0000000..06930c6
--- /dev/null
+++ b/app/src/main/java/org/fknives/rstocklist/TickersWithLastLoadedTime.kt
@@ -0,0 +1,3 @@
+package org.fknives.rstocklist
+
+data class TickersWithLastLoadedTime(val tickers: List, val lastLoadedAt: Long)
\ No newline at end of file
diff --git a/app/src/main/java/org/fknives/rstocklist/appsync/ParseTicker.kt b/app/src/main/java/org/fknives/rstocklist/appsync/ParseTicker.kt
new file mode 100644
index 0000000..394523e
--- /dev/null
+++ b/app/src/main/java/org/fknives/rstocklist/appsync/ParseTicker.kt
@@ -0,0 +1,34 @@
+package org.fknives.rstocklist.appsync
+
+import android.view.accessibility.AccessibilityNodeInfo
+import org.fknives.rstocklist.BuildConfig
+
+class ParseTicker {
+
+ operator fun invoke(accessibilityNodeInfo: AccessibilityNodeInfo): String? =
+ if (accessibilityNodeInfo.isStockViewGroup()) {
+ accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(BuildConfig.CONFIG_COMPANY_TICKER_ID)
+ .firstOrNull()
+ ?.text
+ ?.toString()
+ } else {
+ null
+ }
+
+ private fun AccessibilityNodeInfo.isStockViewGroup(): Boolean =
+ companyIds.all { hasChildWithId(it) }
+
+ companion object {
+ private val companyIds = listOf(
+ BuildConfig.CONFIG_COMPANY_IMG_ID,
+ BuildConfig.CONFIG_COMPANY_NAME_ID,
+ BuildConfig.CONFIG_COMPANY_TICKER_ID,
+ BuildConfig.CONFIG_COMPANY_SHARE_PRICE_ID,
+ BuildConfig.CONFIG_COMPANY_CHANGE_PERCENT_ID
+ )
+
+
+ private fun AccessibilityNodeInfo.hasChildWithId(id: String): Boolean =
+ findAccessibilityNodeInfosByViewId(id)?.isNotEmpty() == true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fknives/rstocklist/appsync/SyncService.kt b/app/src/main/java/org/fknives/rstocklist/appsync/SyncService.kt
new file mode 100644
index 0000000..240d54d
--- /dev/null
+++ b/app/src/main/java/org/fknives/rstocklist/appsync/SyncService.kt
@@ -0,0 +1,85 @@
+package org.fknives.rstocklist.appsync
+
+import android.accessibilityservice.AccessibilityService
+import android.view.accessibility.AccessibilityEvent
+import android.view.accessibility.AccessibilityNodeInfo
+import org.fknives.rstocklist.BuildConfig
+
+class SyncService : AccessibilityService() {
+
+ private var traverseRecyclerView: TraverseRecyclerView? = null
+ private val parseTicker = ParseTicker()
+
+ override fun onAccessibilityEvent(event: AccessibilityEvent?) {
+ if (syncState == SyncState.STOP){
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
+ disableSelf()
+ }
+ return
+ }
+ synchronized(this) {
+ isStarted = true
+ if (syncState == SyncState.NOT_STARTED) return
+ if (syncState == SyncState.RESET) {
+ syncState = SyncState.WORKING
+ traverseRecyclerView = object : TraverseRecyclerView(0) {
+ val tickers = mutableListOf()
+
+ override fun found(accessibilityNodeInfo: AccessibilityNodeInfo) {
+ parseTicker(accessibilityNodeInfo)?.let(tickers::add)
+ listener?.onItemProcessed(tickers.size - 1)
+ }
+
+ override fun finished() {
+ syncState = SyncState.NOT_STARTED
+ listener?.onItemProcessingFinished(tickers)
+ }
+
+ }
+ }
+ val recycler = findRecycler() ?: return
+ traverseRecyclerView?.next(recycler)
+ }
+ }
+
+ override fun onInterrupt() {
+ isStarted = false
+ }
+
+ private fun findRecycler() =
+ RECYCLER_VIEW_IDS.asSequence().mapNotNull {
+ rootInActiveWindow.findAccessibilityNodeInfosByViewId(it)?.firstOrNull()
+ }.firstOrNull()
+
+ enum class SyncState {
+ RESET, WORKING, NOT_STARTED, STOP
+ }
+
+ interface EventListener {
+ fun onItemProcessed(index: Int)
+
+ fun onItemProcessingFinished(items: List)
+ }
+
+ companion object {
+
+ private val RECYCLER_VIEW_IDS = listOf(
+ BuildConfig.CONFIG_RECYCLER_ID1,
+ BuildConfig.CONFIG_RECYCLER_ID2
+ )
+
+ private var isStarted: Boolean = false
+ private var syncState: SyncState = SyncState.NOT_STARTED
+ var listener : EventListener? = null
+
+ fun start() {
+ syncState = SyncState.RESET
+ }
+
+ fun stop() {
+ syncState = SyncState.STOP
+ }
+
+ fun canStart(): Boolean = isStarted
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/fknives/rstocklist/appsync/TraverseRecyclerView.kt b/app/src/main/java/org/fknives/rstocklist/appsync/TraverseRecyclerView.kt
new file mode 100644
index 0000000..47cb2bb
--- /dev/null
+++ b/app/src/main/java/org/fknives/rstocklist/appsync/TraverseRecyclerView.kt
@@ -0,0 +1,98 @@
+package org.fknives.rstocklist.appsync
+
+import android.view.accessibility.AccessibilityNodeInfo
+
+abstract class TraverseRecyclerView(private var index: Int) {
+
+ fun next(recyclerView: AccessibilityNodeInfo) {
+ val collectionRowCount = recyclerView.collectionInfo?.rowCount ?: return
+ if (collectionRowCount <= index) {
+ return finished()
+ }
+
+ val found = recyclerView.children
+ .firstOrNull { it.getRefreshedFlattenIndex() == index }
+ when {
+ found == null
+ && recyclerView.getSmallestFlatIndexOfChildren(index) > index
+ && recyclerView.canScrollBackward() -> {
+ recyclerView.scrollBackward()
+ }
+ found == null && recyclerView.canScrollForward() -> {
+ recyclerView.scrollForward()
+ }
+ found == null && index == collectionRowCount - 1 -> {
+ finished()
+ }
+ found == null -> {
+ if (recyclerView.canScrollBackward()) {
+ recyclerView.scrollBackward()
+ } else {
+ finished()
+ }
+ }
+ else -> {
+ index++
+ found(found)
+ }
+ }
+ }
+
+ protected abstract fun found(accessibilityNodeInfo: AccessibilityNodeInfo)
+
+ protected abstract fun finished()
+
+ companion object {
+
+ private fun AccessibilityNodeInfo.getRefreshedFlattenIndex(): Int? {
+ val maxColumnCount = parent.collectionInfo?.columnCount ?: return null
+ return apply { refresh() }
+ .collectionItemInfo
+ ?.getFlattenIndex(maxColumnCount)
+ }
+
+ private fun AccessibilityNodeInfo.getSmallestFlatIndexOfChildren(default: Int): Int =
+ children.mapNotNull { it.getFlattenIndex() }
+ .minOrDefault(default)
+
+ private val AccessibilityNodeInfo.children
+ get() = (0 until childCount).mapNotNull(::getChild)
+
+ private fun AccessibilityNodeInfo.getFlattenIndex(): Int? {
+ val maxColumnCount = parent.collectionInfo?.columnCount ?: return null
+ return collectionItemInfo?.getFlattenIndex(maxColumnCount)
+ }
+
+ private fun AccessibilityNodeInfo.CollectionItemInfo.getFlattenIndex(maxColumnCount: Int) =
+ rowIndex
+
+ private fun > List.minOrDefault(default: T) = minOrNull() ?: default
+
+ private fun AccessibilityNodeInfo.scrollBackward(delay: Long = 500): Boolean =
+ performActionThenDelayIfOk(delay) {
+ performAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD.id)
+ }
+
+ private fun AccessibilityNodeInfo.scrollForward(delay: Long = 500): Boolean =
+ performActionThenDelayIfOk(delay) {
+ performAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.id)
+ }
+
+ private inline fun performActionThenDelayIfOk(delay: Long, action: () -> Boolean): Boolean {
+ val result = action()
+ if (result) {
+ Thread.sleep(delay)
+ }
+ return result
+ }
+
+ private fun AccessibilityNodeInfo.canScrollBackward(): Boolean =
+ canDo(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD.id)
+
+ private fun AccessibilityNodeInfo.canScrollForward(): Boolean =
+ canDo(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.id)
+
+ private fun AccessibilityNodeInfo.canDo(id: Int): Boolean =
+ actionList?.any { it.id == id } == true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..a85b186
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..3a13f53
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..1696255
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_ticker.xml b/app/src/main/res/layout/item_ticker.xml
new file mode 100644
index 0000000..09121ae
--- /dev/null
+++ b/app/src/main/res/layout/item_ticker.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..f4bc9d9
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..f4bc9d9
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a571e60
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..61da551
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c41dd28
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..db5080a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..6dba46d
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..da31a87
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..15ac681
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..b216f2d
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..f25a419
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..e96783c
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..4faecfa
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #6200EE
+ #3700B3
+ #03DAC5
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..2634e9f
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 24dp
+ 16dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..62a2f59
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,18 @@
+
+ RStockList
+
+ This is a simple app to load all the stock tickers from Rev.\n
+ Clicking on the "Start Service" button will first navigate you to settings. You need to activate this app\'s AccessibilityService.
+ Next clicking the "Start Service" button will show a notification, then you need to navigate to Rev to the list of stocks.\n
+ At that point you should click Start Loading. Now the it will start to sync the data from Rev.\n
+ You should leave your phone for a few minutes.\n
+ When the sync finished the notification disappears and data is saved into a csv file.\n
+ You may use that file to import into Google Sheet or other platform where you want to verify and filter that data.\n
+ This file sharing is done from this screen, the tickers will be also shown\n
+
+ Start Service
+ File last updated at: %s
+ Send File
+ Start Service
+ Send to
+
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..fac9291
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 0000000..5501bba
--- /dev/null
+++ b/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/serviceconfig.xml b/app/src/main/res/xml/serviceconfig.xml
new file mode 100644
index 0000000..fd8feaa
--- /dev/null
+++ b/app/src/main/res/xml/serviceconfig.xml
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..0cbe343
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,26 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ ext.kotlin_version = "1.4.0"
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:4.0.1"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..4d15d01
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0e19c02
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Sep 11 21:30:17 EEST 2020
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..b88bc5a
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+include ':app'
+rootProject.name = "RStockList"
\ No newline at end of file
diff --git a/stock_list_1599869417144.csv b/stock_list_1599869417144.csv
new file mode 100644
index 0000000..241203a
--- /dev/null
+++ b/stock_list_1599869417144.csv
@@ -0,0 +1 @@
+TSLA,AAPL,AAL,AMZN,MSFT,GOOGL,BA,DIS,DAL,KO,AMD,NFLX,BABA,FB,CCL,XOM,BYND,BRK.B,MA,T,SPCE,NCLH,V,GILD,ZM,F,UBER,RCL,MMM,NVDA,MCD,BKNG,GE,BAC,ATVI,MRO,NIO,PFE,GOOG,GRPN,AMRX,INTC,SBUX,ABEV,SHOP,GPRO,JPM,ADBE,WORK,CSCO,FOXA,NKE,AXP,SNE,APA,LUV,JNJ,OXY,ABBV,AMC,WFC,IVR,TWTR,MUR,BTG,AR,ANF,MFA,IBM,AIG,FIT,UAA,ABT,UAL,M,CVX,APPS,SNAP,QCOM,GM,HOME,PYPL,WMT,ZNGA,JBLU,NYMT,PINS,SM,AA,EB,ERJ,GOL,C,HLT,LXRX,PEP,SPOT,PLUG,PG,SQ,ARNC,TEVA,PBR,CVE,CRM,GT,TRIP,GOLD,MPC,MGM,FCAU,GES,BB,MAR,EA,EBAY,BIDU,O,AL,DOCU,ZNH,MGI,GPS,TWOU,MO,EXPE,GS,LPL,CHS,HAL,ET,SPGI,ROKU,UMC,KHC,TXMD,LMT,NET,AM,RACE,HPQ,LYFT,IMGN,ARI,TGI,JMIA,SPG,COTY,CLNY,EQH,ENLC,WM,NEE,AXL,FDX,VZ,DBX,MRK,MTDR,BSBR,SLB,AUY,MDRX,BEP,UPS,OPK,CAT,AGNC,SPWR,LEVI,XRX,DELL,HOG,CARS,TM,HMY,FSLR,TSM,JD,BBAR,AMAT,TTM,NRZ,CX,BX,FL,BLK,BMY,BBD,HPE,ALXN,IRBT,MDT,DDOG,CVS,AES,SLCA,PM,FVRR,BAM,PTON,DPZ,BSX,OKE,IVZ,MU,COP,EGO,ADSK,REGI,CLDR,REGN,AEO,GME,DXC,ARR,WBA,ALLY,VRTX,NBL,OVV,NOV,COST,BRFS,HIMX,CZR,ANGI,CYH,ORCL,LULU,CIM,DVN,CMCSA,WDC,TWO,CAJ,KSS,HAS,DHC,PBF,QD,MUX,TWLO,CL,BIIB,CNDT,STNE,PSX,EPD,TME,KGC,HON,H,WU,NEM,OKTA,NBEV,AU,AKAM,ETSY,NDAQ,BSMX,GFI,RL,AVGO,RY,PTEN,WPX,HMC,AMGN,DE,CHL,LLY,ILMN,SHAK,MELI,NVTA,HUBS,EL,BBY,ATUS,LTC,SID,NMR,WDAY,QSR,FOLD,CLF,BIP,HL,PANW,W,ITUB,UA,UNH,CTL,ALGN,CG,CRWD,WELL,VLO,UXIN,FWONK,DOW,AMT,SYY,FTNT,TRGP,K,TSN,TTWO,RUN,EMR,XEC,DUK,BEN,WRK,INFY,FTI,LVS,MCO,PAA,APD,FSK,ZEN,YPF,JKS,FLR,MSI,STX,MIK,STZ,A,MTCH,OSTK,NYT,USB,MCHP,WEN,PRU,EQIX,CBRE,NTNX,PAAS,MRVL,TD,LRCX,JWN,ADP,MET,SYF,MOMO,SFIX,MYL,KR,ECL,CAH,PS,PCG,MFG,BHC,WWE,LB,GGB,ANSS,WUBA,ADM,VALE,GEO,HLF,KMB,FVE,HDB,ON,INTU,LOW,MNST,CFG,XLNX,TJX,PE,FLEX,NOC,D,FCX,BF.B,DLR,PPL,SYK,MAT,EOG,AAP,SO,CHWY,BHP,SNPS,HUYA,DHR,BMA,VIPS,KMI,HCM,GLUU,VER,PBI,SKX,GIS,ZS,BOX,FEYE,EXEL,BG,KKR,ZTO,IBN,SUPV,VIV,EW,YUM,CC,WIT,SBSW,TIF,TCOM,MPLX,GRUB,NBIX,TIGR,IQ,TPR,DLTR,SCHW,LNG,ADI,PXD,CNX,ANET,BAX,RMD,VEEV,HRL,BJ,VGR,KAR,HSIC,QRTEA,DHI,ENIA,NKTR,PAM,CNC,NAVI,SHW,AOS,GDS,CGNX,IPG,BITA,ROK,HOLX,PHM,ATR,ESI,STAY,ITW,BAP,HTHT,TFX,MXIM,HBAN,COMM,MORN,HIG,CXO,JEF,ISBC,SSNC,MLCO,DRE,LKQ,TSU,FTV,TW,KDP,QLYS,LEN,NWSA,MMC,MAS,YUMC,KT,CSX,GO,OTIS,PWR,FITB,NTRS,VRSK,CBOE,SLM,INVH,FDS,CIEN,ORLY,GPK,WING,PGR,PFPT,ZION,PCAR,WAB,NSC,QTWO,PEG,NUVA,HUN,NYCB,JBHT,KNX,PBH,PRI,NOAH,SBS,BAH,FIS,UGP,DFS,LVGO,NKLA,NVAX,TDOC,AG,WMB,SEB,FAST,BDC,BK,FLT,ARCC,NVCR,BDX,X,SFM,Z,IBKR,CF,AVLR,CI,LYV,ATHM,LTHM,CY,HRB,SPLK,CTAS,DD,DG,MUFG,NTAP,ED,HST,ISRG,FE,UNM,NTCO,UNP,GD,HUM,BIO,GGAL,SKT,HD,AXTA,WTM,NTES,ODP,HWM,IP,BKR,IT,CABO,IDXX,ROST,BLL,MBT,UNIT,UCTT,CVNA,DRI,REAL,MDLZ,MDB,LX,ARMK,MS,SQM,SRE,ETFC,DVA,PD,EPAM,PH,CERN,BRX,FFIV,OLN,STT,ZBH,OMC,JNPR,IAG,VICI,RF,RH,TEAM,KEY,SE,SU,SWK,SWN,ICE,MKL,BVN,TECK,TV,CROX,BWA,CTSH,SWKS,KIM,VG,TMUS,CTVA,WB,WY,ALLT,MOS,ELAN,IGT,FSLY,XEL,CARR,MPW,CRSP,YY,IGMS,ULTA,CTXS,XGN,ZBRA,IIPR,ETRN,PLAN,ANTM,TAK,MTD,MTG,TAL,VFC,EDU,CAG,EFX,BMRN,NPTN,CCI,SMAR,RAD,NLOK,TER,CDE,CWEN,GLW,TFC,VIACA,CHGG,BILI,SOGO,EIX,INCY,TGT,IRM,GNL,LSCC,WYNN,GNW,ZTS,BIPC,SMFG,VMW,RES,CHKP,CIG,PLNT,PAYX,AEP,AFL,RLGY,INFN,PDD,SSSS,KTOS,TMO,CLVS,TROW,CLR,CLX,CMA,CME,CMG,EQR,EQT,PDCE,VST,LDOS,CNP,CSGP,ASML,VTR,COF,TPX,COG,BEPC,DISH,ALL,RNG,TRV,AME,AMH,TSG,CHTR,EVR,TTD,PLD,GNTX,EXC,MRNA,JKHY,APH,CDNS,VRSN,RRC,PNC,PBCT,KEYS,SEDG,LOMA,PPG,TXN,GDDY,RTX,NLY,DISCA,DISCK,FISV,HBI,FREQ,PSA,HCA,AVB,NOW,SCCO,HEI,SIRI,HES,COLD,NRG,HFC,GWPH,SGMO,LMND,AYX,AZO,HGV,NICE,WEX,NUE,DAN,STLD,AMTD,NVR,DNKN,EQNR,NWL,FHN,URBN,PSTG,BNTX,SBH
\ No newline at end of file