-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #112 from NatKarmios/public-receiver
Add a public receiver for Tasker, Automate, etc.
- Loading branch information
Showing
17 changed files
with
522 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
app/src/main/java/com/orgzly/android/external/ExternalAccessReceiver.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.orgzly.android.external | ||
|
||
import android.content.BroadcastReceiver | ||
import android.content.Context | ||
import android.content.Intent | ||
import com.google.gson.GsonBuilder | ||
import com.orgzly.android.external.actionhandlers.* | ||
import com.orgzly.android.external.types.Response | ||
|
||
class ExternalAccessReceiver : BroadcastReceiver() { | ||
val actionHandlers = listOf( | ||
GetOrgInfo(), | ||
RunSearch(), | ||
EditNotes(), | ||
EditSavedSearches(), | ||
ManageWidgets() | ||
) | ||
|
||
override fun onReceive(context: Context?, intent: Intent?) { | ||
val response = actionHandlers.asSequence() | ||
.mapNotNull { it.handle(intent!!, context!!) } | ||
.firstOrNull() | ||
?: Response(false, "Invalid action") | ||
val gson = GsonBuilder().serializeNulls().create() | ||
resultData = gson.toJson(response) | ||
} | ||
} |
53 changes: 53 additions & 0 deletions
53
app/src/main/java/com/orgzly/android/external/actionhandlers/EditNotes.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package com.orgzly.android.external.actionhandlers | ||
|
||
import android.content.Intent | ||
import com.orgzly.android.external.types.ExternalHandlerFailure | ||
|
||
class EditNotes : ExternalAccessActionHandler() { | ||
override val actions = listOf( | ||
action(::addNote, "ADD_NOTE"), | ||
action(::editNote, "EDIT_NOTE"), | ||
action(::refileNote, "REFILE_NOTE", "REFILE_NOTES"), | ||
action(::moveNote, "MOVE_NOTE", "MOVE_NOTES"), | ||
action(::deleteNote, "DELETE_NOTE", "DELETE_NOTES") | ||
) | ||
|
||
private fun addNote(intent: Intent): String { | ||
val place = intent.getNotePlace() | ||
val newNote = intent.getNotePayload() | ||
val note = dataRepository.createNote(newNote, place) | ||
return "${note.id}" | ||
} | ||
|
||
private fun editNote(intent: Intent) { | ||
val noteView = intent.getNote() | ||
val newNote = intent.getNotePayload(title=noteView.note.title) | ||
dataRepository.updateNote(noteView.note.id, newNote) | ||
} | ||
|
||
private fun refileNote(intent: Intent) { | ||
val notes = intent.getNoteIds() | ||
val place = intent.getNotePlace() | ||
dataRepository.refileNotes(notes, place) | ||
} | ||
|
||
private fun moveNote(intent: Intent) { | ||
val notes = intent.getNoteIds() | ||
with(dataRepository) { when (intent.getStringExtra("DIRECTION")) { | ||
"UP" -> moveNote(intent.getBook().id, notes, -1) | ||
"DOWN" -> moveNote(intent.getBook().id, notes, 1) | ||
"LEFT" -> promoteNotes(notes) | ||
"RIGHT" -> demoteNotes(notes) | ||
else -> throw ExternalHandlerFailure("invalid direction") | ||
} } | ||
} | ||
|
||
private fun deleteNote(intent: Intent) { | ||
intent.getNoteIds().groupBy { | ||
dataRepository.getNoteView(it)?.bookName | ||
?: throw ExternalHandlerFailure("invalid note id $it") | ||
}.forEach { (bookName, notes) -> | ||
dataRepository.deleteNotes(dataRepository.getBook(bookName)!!.id, notes.toSet()) | ||
} | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
app/src/main/java/com/orgzly/android/external/actionhandlers/EditSavedSearches.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package com.orgzly.android.external.actionhandlers | ||
|
||
import android.content.Intent | ||
import com.orgzly.android.db.entity.SavedSearch | ||
import com.orgzly.android.external.types.ExternalHandlerFailure | ||
|
||
class EditSavedSearches : ExternalAccessActionHandler() { | ||
override val actions = listOf( | ||
action(::addSavedSearch, "ADD_SAVED_SEARCH"), | ||
action(::editSavedSearch, "EDIT_SAVED_SEARCH"), | ||
action(::moveSavedSearch, "MOVE_SAVED_SEARCH"), | ||
action(::deleteSavedSearch, "DELETE_SAVED_SEARCH"), | ||
) | ||
|
||
private fun addSavedSearch(intent: Intent): String { | ||
val savedSearch = intent.getNewSavedSearch() | ||
val id = dataRepository.createSavedSearch(savedSearch) | ||
return "$id" | ||
} | ||
|
||
private fun editSavedSearch(intent: Intent) { | ||
val savedSearch = intent.getSavedSearch() | ||
val newSavedSearch = intent.getNewSavedSearch(allowBlank = true) | ||
dataRepository.updateSavedSearch(SavedSearch( | ||
savedSearch.id, | ||
newSavedSearch.name.ifBlank { savedSearch.name }, | ||
newSavedSearch.query.ifBlank { savedSearch.query }, | ||
savedSearch.position | ||
)) | ||
} | ||
|
||
private fun moveSavedSearch(intent: Intent) { | ||
val savedSearch = intent.getSavedSearch() | ||
when (intent.getStringExtra("DIRECTION")) { | ||
"UP" -> dataRepository.moveSavedSearchUp(savedSearch.id) | ||
"DOWN" -> dataRepository.moveSavedSearchDown(savedSearch.id) | ||
else -> throw ExternalHandlerFailure("invalid direction") | ||
} | ||
} | ||
|
||
private fun deleteSavedSearch(intent: Intent) { | ||
val savedSearch = intent.getSavedSearch() | ||
dataRepository.deleteSavedSearches(setOf(savedSearch.id)) | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
app/src/main/java/com/orgzly/android/external/actionhandlers/ExternalAccessActionHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package com.orgzly.android.external.actionhandlers | ||
|
||
import android.content.Context | ||
import android.content.Intent | ||
import com.orgzly.android.App | ||
import com.orgzly.android.data.DataRepository | ||
import com.orgzly.android.external.types.Response | ||
import javax.inject.Inject | ||
|
||
abstract class ExternalAccessActionHandler : ExternalIntentParser { | ||
@Inject | ||
override lateinit var dataRepository: DataRepository | ||
|
||
init { | ||
@Suppress("LeakingThis") | ||
App.appComponent.inject(this) | ||
} | ||
|
||
abstract val actions: List<List<Pair<String, (Intent, Context) -> Any>>> | ||
private val fullNameActions by lazy { | ||
actions.flatten().toMap().mapKeys { (key, _) -> "com.orgzly.android.$key" } | ||
} | ||
|
||
fun action(f: (Intent, Context) -> Any, vararg names: String) = names.map { it to f } | ||
|
||
@JvmName("intentAction") | ||
fun action(f: (Intent) -> Any, vararg names: String) = | ||
action({ i, _ -> f(i) }, *names) | ||
|
||
@JvmName("contextAction") | ||
fun action(f: (Context) -> Any, vararg names: String) = | ||
action({ _, c -> f(c) }, *names) | ||
|
||
fun action(f: () -> Any, vararg names: String) = | ||
action({ _, _ -> f() }, *names) | ||
|
||
|
||
fun handle(intent: Intent, context: Context) = try { | ||
fullNameActions[intent.action!!] | ||
?.let { it(intent, context) } | ||
?.let { Response(true, if (it is Unit) null else it) } | ||
} catch (e: Exception) { | ||
Response(false, e.message) | ||
} | ||
} |
135 changes: 135 additions & 0 deletions
135
app/src/main/java/com/orgzly/android/external/actionhandlers/ExternalIntentParser.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package com.orgzly.android.external.actionhandlers | ||
|
||
import android.content.Intent | ||
import com.google.gson.JsonElement | ||
import com.google.gson.JsonObject | ||
import com.google.gson.JsonParser | ||
import com.orgzly.android.data.DataRepository | ||
import com.orgzly.android.db.entity.NoteView | ||
import com.orgzly.android.db.entity.SavedSearch | ||
import com.orgzly.android.external.types.ExternalHandlerFailure | ||
import com.orgzly.android.query.user.InternalQueryParser | ||
import com.orgzly.android.ui.NotePlace | ||
import com.orgzly.android.ui.Place | ||
import com.orgzly.android.ui.note.NotePayload | ||
import com.orgzly.org.OrgProperties | ||
|
||
interface ExternalIntentParser { | ||
val dataRepository: DataRepository | ||
|
||
fun Intent.getNotePayload(title: String? = null): NotePayload { | ||
val rawJson = getStringExtra("NOTE_PAYLOAD") | ||
val json = try { | ||
JsonParser.parseString(rawJson) | ||
.let { if (it.isJsonObject) it.asJsonObject else null }!! | ||
} catch (e: Exception) { | ||
throw ExternalHandlerFailure("failed to parse json: ${e.message}\n$rawJson") | ||
} | ||
return NotePayload( | ||
json.getString("title") ?: title | ||
?: throw ExternalHandlerFailure("no title supplied!\n$rawJson"), | ||
json.getString("content"), | ||
json.getString("state"), | ||
json.getString("priority"), | ||
json.getString("scheduled"), | ||
json.getString("deadline"), | ||
json.getString("closed"), | ||
(json.getString("tags") ?: "") | ||
.split(" +".toRegex()) | ||
.filter { it.isNotEmpty() }, | ||
OrgProperties().apply { | ||
json["properties"]?.asMap?.forEach { (k, v) -> this[k] = v } | ||
} | ||
) | ||
} | ||
|
||
private fun getNoteByQuery(rawQuery: String?): NoteView { | ||
if (rawQuery == null) | ||
throw ExternalHandlerFailure("couldn't find note") | ||
val query = InternalQueryParser().parse(rawQuery) | ||
val notes = dataRepository.selectNotesFromQuery(query) | ||
if (notes.isEmpty()) | ||
throw ExternalHandlerFailure("couldn't find note") | ||
if (notes.size > 1) | ||
throw ExternalHandlerFailure("query \"$rawQuery\" gave multiple results") | ||
return notes[0] | ||
} | ||
|
||
fun Intent.getNote(prefix: String = "") = | ||
dataRepository.getNoteView(getLongExtra("${prefix}NOTE_ID", -1)) | ||
?: dataRepository.getNoteAtPath(getStringExtra("${prefix}NOTE_PATH") ?: "") | ||
?: getNoteByQuery(getStringExtra("${prefix}NOTE_QUERY")) | ||
|
||
fun Intent.getNoteAndProps(prefix: String = "") = getNote(prefix).let { | ||
it to dataRepository.getNoteProperties(it.note.id) | ||
} | ||
|
||
fun Intent.getBook(prefix: String = "") = | ||
dataRepository.getBook(getLongExtra("${prefix}BOOK_ID", -1)) | ||
?: dataRepository.getBook(getStringExtra("${prefix}BOOK_NAME") ?: "") | ||
?: throw ExternalHandlerFailure("couldn't find book") | ||
|
||
fun Intent.getNotePlace() = try { | ||
getNote(prefix="PARENT_").let { noteView -> | ||
val place = try { | ||
Place.valueOf(getStringExtra("PLACEMENT") ?: "") | ||
} catch (e: IllegalArgumentException) { Place.UNDER } | ||
dataRepository.getBook(noteView.bookName)?.let { book -> | ||
NotePlace(book.id, noteView.note.id, place) | ||
} | ||
} | ||
} catch (e: ExternalHandlerFailure) { null } ?: try { | ||
NotePlace(getBook(prefix="PARENT_").id) | ||
} catch (e: ExternalHandlerFailure) { | ||
throw ExternalHandlerFailure("couldn't find parent note/book") | ||
} | ||
|
||
fun Intent.getNoteIds(allowSingle: Boolean = true, allowEmpty: Boolean = false): Set<Long> { | ||
val id = if (allowSingle) getLongExtra("NOTE_ID", -1) else null | ||
val ids = getLongArrayExtra("NOTE_IDS")?.toTypedArray() ?: emptyArray() | ||
val path = | ||
if (allowSingle) | ||
getStringExtra("NOTE_PATH") | ||
?.let { dataRepository.getNoteAtPath(it)?.note?.id } | ||
else null | ||
val paths = (getStringArrayExtra("NOTE_PATHS") ?: emptyArray()) | ||
.mapNotNull { dataRepository.getNoteAtPath(it)?.note?.id } | ||
.toTypedArray() | ||
return listOfNotNull(id, *ids, path, *paths).filter { it >= 0 }.toSet().also { | ||
if (it.isEmpty() && !allowEmpty) | ||
throw ExternalHandlerFailure("no notes specified") | ||
} | ||
} | ||
|
||
fun Intent.getSavedSearch() = | ||
dataRepository.getSavedSearch(getLongExtra("SAVED_SEARCH_ID", -1)) | ||
?: dataRepository.getSavedSearches() | ||
.find { it.name == getStringExtra("SAVED_SEARCH_NAME") } | ||
?: throw ExternalHandlerFailure("couldn't find saved search") | ||
|
||
fun Intent.getNewSavedSearch(allowBlank: Boolean = false): SavedSearch { | ||
val name = getStringExtra("SAVED_SEARCH_NEW_NAME") | ||
val query = getStringExtra("SAVED_SEARCH_NEW_QUERY") | ||
if (!allowBlank && (name.isNullOrBlank() || query.isNullOrBlank())) | ||
throw ExternalHandlerFailure("invalid parameters for new saved search") | ||
return SavedSearch(0, name ?: "", query ?: "", 0) | ||
} | ||
|
||
private fun JsonObject.getString(name: String) = this[name]?.let { | ||
if (it.isJsonPrimitive && it.asJsonPrimitive.isString) | ||
it.asJsonPrimitive.asString | ||
else null | ||
} | ||
|
||
private val JsonElement.asMap: Map<String, String>? | ||
get() = if (this.isJsonObject) { | ||
this.asJsonObject | ||
.entrySet() | ||
.map { | ||
if (it.value.isJsonPrimitive) | ||
it.key to it.value.asJsonPrimitive.asString | ||
else return null | ||
} | ||
.toMap() | ||
} else null | ||
} |
21 changes: 21 additions & 0 deletions
21
app/src/main/java/com/orgzly/android/external/actionhandlers/GetOrgInfo.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.orgzly.android.external.actionhandlers | ||
|
||
import android.content.Intent | ||
import com.orgzly.android.external.types.* | ||
|
||
class GetOrgInfo : ExternalAccessActionHandler() { | ||
override val actions = listOf( | ||
action(::getBooks, "GET_BOOKS"), | ||
action(::getSavedSearches, "GET_SAVED_SEARCHES"), | ||
action(::getNote, "GET_NOTE") | ||
) | ||
|
||
private fun getBooks() = | ||
dataRepository.getBooks().map(Book::from).toTypedArray() | ||
|
||
private fun getSavedSearches() = | ||
dataRepository.getSavedSearches().map(SavedSearch::from).toTypedArray() | ||
|
||
private fun getNote(intent: Intent) = | ||
Note.from(intent.getNoteAndProps()) | ||
} |
Oops, something went wrong.