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 }