Warning, /network/kdeconnect-android/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandControlsProviderService.kt is written in an unsupported language. File is not indexed.
0001 /*
0002 * SPDX-FileCopyrightText: 2021 Maxim Leshchenko <cnmaks90@gmail.com>
0003 *
0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006
0007 package org.kde.kdeconnect.Plugins.RunCommandPlugin
0008
0009 import android.app.PendingIntent
0010 import android.content.Intent
0011 import android.content.SharedPreferences
0012 import android.graphics.drawable.Icon
0013 import android.os.Build
0014 import android.service.controls.Control
0015 import android.service.controls.ControlsProviderService
0016 import android.service.controls.DeviceTypes
0017 import android.service.controls.actions.CommandAction
0018 import android.service.controls.actions.ControlAction
0019 import android.service.controls.templates.StatelessTemplate
0020 import android.util.Log
0021 import androidx.annotation.RequiresApi
0022 import androidx.preference.PreferenceManager
0023 import io.reactivex.Flowable
0024 import io.reactivex.processors.ReplayProcessor
0025 import org.json.JSONArray
0026 import org.json.JSONException
0027 import org.json.JSONObject
0028 import org.kde.kdeconnect.Device
0029 import org.kde.kdeconnect.KdeConnect
0030 import org.kde.kdeconnect.UserInterface.MainActivity
0031 import org.kde.kdeconnect_tp.R
0032 import org.reactivestreams.FlowAdapters
0033 import java.util.concurrent.Flow
0034 import java.util.function.Consumer
0035
0036 private class CommandEntryWithDevice(o: JSONObject, val device: Device) : CommandEntry(o)
0037
0038 @RequiresApi(Build.VERSION_CODES.R)
0039 class RunCommandControlsProviderService : ControlsProviderService() {
0040 private lateinit var updatePublisher: ReplayProcessor<Control>
0041 private lateinit var sharedPreferences: SharedPreferences
0042
0043 override fun createPublisherForAllAvailable(): Flow.Publisher<Control> {
0044 return FlowAdapters.toFlowPublisher(Flowable.fromIterable(getAllCommandsList().map { commandEntry ->
0045 Control.StatelessBuilder(commandEntry.device.deviceId + ":" + commandEntry.key, getIntent(commandEntry.device))
0046 .setTitle(commandEntry.name)
0047 .setSubtitle(commandEntry.command)
0048 .setStructure(commandEntry.device.name)
0049 .setCustomIcon(Icon.createWithResource(this, R.drawable.run_command_plugin_icon_24dp))
0050 .build()
0051 }))
0052 }
0053
0054 override fun createPublisherFor(controlIds: MutableList<String>): Flow.Publisher<Control> {
0055 updatePublisher = ReplayProcessor.create()
0056
0057 for (controlId in controlIds) {
0058 val commandEntry = getCommandByControlId(controlId)
0059 if (commandEntry != null && commandEntry.device.isReachable) {
0060 updatePublisher.onNext(createStatefulBuilder(commandEntry, controlId)
0061 .setStatus(Control.STATUS_OK)
0062 .setStatusText(getString(R.string.tap_to_execute))
0063 .build())
0064 } else if (commandEntry != null && commandEntry.device.isPaired && !commandEntry.device.isReachable) {
0065 updatePublisher.onNext(createStatefulBuilder(commandEntry, controlId)
0066 .setStatus(Control.STATUS_DISABLED)
0067 .build())
0068 } else {
0069 updatePublisher.onNext(Control.StatefulBuilder(controlId, getIntent(commandEntry?.device))
0070 .setStatus(Control.STATUS_NOT_FOUND)
0071 .build())
0072 }
0073 }
0074
0075 return FlowAdapters.toFlowPublisher(updatePublisher)
0076 }
0077
0078 override fun performControlAction(controlId: String, action: ControlAction, consumer: Consumer<Int>) {
0079 if (!this::updatePublisher.isInitialized) {
0080 updatePublisher = ReplayProcessor.create()
0081 }
0082
0083 if (action is CommandAction) {
0084 val commandEntry = getCommandByControlId(controlId)
0085 if (commandEntry != null) {
0086 val deviceId = controlId.split(":")[0]
0087 val plugin = KdeConnect.getInstance().getDevicePlugin(deviceId ,RunCommandPlugin::class.java)
0088 if (plugin != null) {
0089 plugin.runCommand(commandEntry.key)
0090 consumer.accept(ControlAction.RESPONSE_OK)
0091 } else {
0092 consumer.accept(ControlAction.RESPONSE_FAIL)
0093 }
0094
0095 updatePublisher.onNext(createStatefulBuilder(commandEntry, controlId)
0096 .setStatus(Control.STATUS_OK)
0097 .setStatusText(getString(R.string.tap_to_execute))
0098 .build())
0099 }
0100 }
0101 }
0102
0103 private fun getSavedCommandsList(device: Device): List<CommandEntryWithDevice> {
0104 if (!this::sharedPreferences.isInitialized) {
0105 sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
0106 }
0107
0108 val commandList = mutableListOf<CommandEntryWithDevice>()
0109
0110 return try {
0111 val jsonArray = JSONArray(sharedPreferences.getString(RunCommandPlugin.KEY_COMMANDS_PREFERENCE + device.deviceId, "[]"))
0112
0113 for (index in 0 until jsonArray.length()) {
0114 val jsonObject = jsonArray.getJSONObject(index)
0115 commandList.add(CommandEntryWithDevice(jsonObject, device))
0116 }
0117
0118 commandList
0119 } catch (error: JSONException) {
0120 Log.e("RunCommand", "Error parsing JSON", error)
0121 listOf()
0122 }
0123 }
0124
0125 private fun getAllCommandsList(): List<CommandEntryWithDevice> {
0126 val commandList = mutableListOf<CommandEntryWithDevice>()
0127
0128 for (device in KdeConnect.getInstance().devices.values) {
0129 if (!device.isReachable) {
0130 commandList.addAll(getSavedCommandsList(device))
0131 continue
0132 } else if (!device.isPaired) {
0133 continue
0134 }
0135
0136 val plugin = device.getPlugin(RunCommandPlugin::class.java)
0137 if (plugin != null) {
0138 for (jsonObject in plugin.commandList) {
0139 try {
0140 commandList.add(CommandEntryWithDevice(jsonObject, device))
0141 } catch (error: JSONException) {
0142 Log.e("RunCommand", "Error parsing JSON", error)
0143 }
0144 }
0145 }
0146 }
0147
0148 return commandList
0149 }
0150
0151 private fun getCommandByControlId(controlId: String): CommandEntryWithDevice? {
0152 val controlIdParts = controlId.split(":")
0153
0154 val device = KdeConnect.getInstance().getDevice(controlIdParts[0])
0155
0156 if (device == null || !device.isPaired) return null
0157
0158 val commandList = if (device.isReachable) {
0159 device.getPlugin(RunCommandPlugin::class.java)?.commandList?.map { jsonObject ->
0160 CommandEntryWithDevice(jsonObject, device)
0161 }
0162 } else {
0163 getSavedCommandsList(device)
0164 }
0165
0166 return commandList?.find { command ->
0167 try {
0168 command.key == controlIdParts[1]
0169 } catch (error: JSONException) {
0170 Log.e("RunCommand", "Error parsing JSON", error)
0171 false
0172 }
0173 }
0174 }
0175
0176 private fun createStatefulBuilder(commandEntry: CommandEntryWithDevice, controlId: String): Control.StatefulBuilder {
0177 if (!this::sharedPreferences.isInitialized) {
0178 sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
0179 }
0180 val useNameForTitle = sharedPreferences.getBoolean(getString(R.string.set_runcommand_name_as_title), true)
0181
0182 return Control.StatefulBuilder(controlId, getIntent(commandEntry.device))
0183 .setTitle(if (useNameForTitle) commandEntry.name else commandEntry.command)
0184 .setSubtitle(if (useNameForTitle) commandEntry.command else commandEntry.name)
0185 .setStructure(commandEntry.device.name)
0186 .setControlTemplate(StatelessTemplate(commandEntry.key))
0187 .setDeviceType(DeviceTypes.TYPE_ROUTINE)
0188 .setCustomIcon(Icon.createWithResource(this, R.drawable.run_command_plugin_icon_24dp))
0189 }
0190
0191 private fun getIntent(device: Device?): PendingIntent {
0192 val target = if (device?.isReachable == true) RunCommandActivity::class else MainActivity::class
0193
0194 val intent = Intent(Intent.ACTION_MAIN)
0195 .setClass(this, target.java)
0196 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
0197 .putExtra(MainActivity.EXTRA_DEVICE_ID, device?.deviceId)
0198
0199 return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
0200 }
0201 }