Warning, /network/kdeconnect-android/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetProvider.kt is written in an unsupported language. File is not indexed.
0001 /* 0002 * SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@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.appwidget.AppWidgetManager 0011 import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID 0012 import android.appwidget.AppWidgetProvider 0013 import android.content.ComponentName 0014 import android.content.Context 0015 import android.content.Intent 0016 import android.net.Uri 0017 import android.util.Log 0018 import android.widget.RemoteViews 0019 import org.kde.kdeconnect.Device 0020 import org.kde.kdeconnect.KdeConnect 0021 import org.kde.kdeconnect_tp.BuildConfig 0022 import org.kde.kdeconnect_tp.R 0023 0024 const val RUN_COMMAND_ACTION = "RUN_COMMAND_ACTION" 0025 const val TARGET_COMMAND = "TARGET_COMMAND" 0026 const val TARGET_DEVICE = "TARGET_DEVICE" 0027 0028 class RunCommandWidgetProvider : AppWidgetProvider() { 0029 0030 override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { 0031 for (appWidgetId in appWidgetIds) { 0032 updateAppWidget(context, appWidgetManager, appWidgetId) 0033 } 0034 } 0035 0036 override fun onDeleted(context: Context, appWidgetIds: IntArray) { 0037 for (appWidgetId in appWidgetIds) { 0038 deleteWidgetDeviceIdPref(context, appWidgetId) 0039 } 0040 } 0041 0042 override fun onEnabled(context: Context) { 0043 super.onEnabled(context) 0044 KdeConnect.getInstance().addDeviceListChangedCallback("RunCommandWidget") { 0045 forceRefreshWidgets(context) 0046 } 0047 } 0048 0049 override fun onDisabled(context: Context) { 0050 KdeConnect.getInstance().removeDeviceListChangedCallback("RunCommandWidget") 0051 super.onDisabled(context) 0052 } 0053 0054 override fun onReceive(context: Context, intent: Intent) { 0055 Log.d("WidgetProvider", "onReceive " + intent.action) 0056 0057 if (intent.action == RUN_COMMAND_ACTION) { 0058 val targetCommand = intent.getStringExtra(TARGET_COMMAND) 0059 val targetDevice = intent.getStringExtra(TARGET_DEVICE) 0060 val plugin = KdeConnect.getInstance().getDevicePlugin(targetDevice, RunCommandPlugin::class.java) 0061 if (plugin != null) { 0062 try { 0063 plugin.runCommand(targetCommand) 0064 } catch (ex: Exception) { 0065 Log.e("RunCommandWidget", "Error running command", ex) 0066 } 0067 } else { 0068 Log.w("RunCommandWidget", "Device not available or runcommand plugin disabled"); 0069 } 0070 } else { 0071 super.onReceive(context, intent); 0072 } 0073 } 0074 } 0075 0076 fun getAllWidgetIds(context : Context) : IntArray { 0077 return AppWidgetManager.getInstance(context).getAppWidgetIds( 0078 ComponentName(context, RunCommandWidgetProvider::class.java) 0079 ) 0080 } 0081 0082 fun forceRefreshWidgets(context : Context) { 0083 val intent = Intent(context, RunCommandWidgetProvider::class.java) 0084 intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE 0085 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, getAllWidgetIds(context)) 0086 context.sendBroadcast(intent) 0087 } 0088 0089 /** 0090 * Recreate the [RemoteViews] layout of a given widget. 0091 * 0092 * This function is called when a new widget is created, or when the list of devices changes, or if 0093 * a device enables/disables its [RunCommandPlugin]. Hosting apps that contain our widgets will do 0094 * anything they can to avoid extra renders. 0095 * 0096 * 1. We use [appWidgetId] as a request code in [assignTitleIntent] to force hosting apps to track a 0097 * separate intent for each widget. 0098 * 2. We call [AppWidgetManager.notifyAppWidgetViewDataChanged] at the end of this function, which 0099 * lets the list adapter know that it might be referring to the wrong device id. 0100 * 0101 * See also [RunCommandWidgetDataProvider.onDataSetChanged]. 0102 */ 0103 internal fun updateAppWidget( 0104 context: Context, 0105 appWidgetManager: AppWidgetManager, 0106 appWidgetId: Int 0107 ) { 0108 Log.d("WidgetProvider", "updateAppWidget: $appWidgetId") 0109 0110 // Determine which device provided these commands 0111 val deviceId = loadWidgetDeviceIdPref(context, appWidgetId) 0112 val device: Device? = if (deviceId != null) KdeConnect.getInstance().getDevice(deviceId) else null 0113 0114 val views = RemoteViews(BuildConfig.APPLICATION_ID, R.layout.widget_remotecommandplugin) 0115 assignTitleIntent(context, appWidgetId, views) 0116 0117 Log.d("WidgetProvider", "updateAppWidget device: " + if (device == null) "null" else device.name) 0118 0119 // Android should automatically toggle between the command list and the error text 0120 views.setEmptyView(R.id.widget_command_list, R.id.widget_error_text) 0121 0122 // TODO: Use string resources 0123 0124 if (device == null) { 0125 // There are two reasons we reach this condition: 0126 // 1. there is no preference string for this widget id 0127 // 2. the string id does not match any devices in KdeConnect.getInstance() 0128 // In both cases, we want the user to assign a device to this widget 0129 views.setTextViewText(R.id.widget_title_text, context.getString(R.string.kde_connect)) 0130 views.setTextViewText(R.id.widget_error_text, "Whose commands should we show? Click the title to set a device.") 0131 } else { 0132 views.setTextViewText(R.id.widget_title_text, device.name) 0133 val plugin = device.getPlugin(RunCommandPlugin::class.java) 0134 if (device.isReachable) { 0135 val message: String = if (plugin == null) { 0136 "Device doesn't allow us to run commands." 0137 } else { 0138 "Device has no commands available." 0139 } 0140 views.setTextViewText(R.id.widget_error_text, message) 0141 assignListAdapter(context, appWidgetId, views) 0142 assignListIntent(context, appWidgetId, views) 0143 } else { 0144 views.setTextViewText(R.id.widget_error_text, context.getString(R.string.runcommand_notreachable)) 0145 } 0146 } 0147 0148 appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_command_list) 0149 appWidgetManager.updateAppWidget(appWidgetId, views) 0150 } 0151 0152 /** 0153 * Create an Intent to launch the config activity whenever the title is clicked. 0154 * 0155 * See [RunCommandWidgetConfigActivity]. 0156 */ 0157 private fun assignTitleIntent(context: Context, appWidgetId: Int, views: RemoteViews) { 0158 val setDeviceIntent = Intent(context, RunCommandWidgetConfigActivity::class.java) 0159 setDeviceIntent.putExtra(EXTRA_APPWIDGET_ID, appWidgetId) 0160 // We pass appWidgetId as requestCode even if it's not used to force the creation a new PendingIntent 0161 // instead of reusing an existing one, which is what happens if only the "extras" field differs. 0162 // Docs: https://developer.android.com/reference/android/app/PendingIntent.html 0163 val setDevicePendingIntent = PendingIntent.getActivity(context, appWidgetId, setDeviceIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) 0164 views.setOnClickPendingIntent(R.id.widget_title_wrapper, setDevicePendingIntent) 0165 } 0166 0167 /** 0168 * Configure remote adapter 0169 * 0170 * This function can only be called once in the lifetime of the widget. Subsequent calls do nothing. 0171 * Use [RunCommandWidgetConfigActivity] and the config function [saveWidgetDeviceIdPref] to change 0172 * the adapter's behavior. 0173 */ 0174 private fun assignListAdapter(context: Context, appWidgetId: Int, views: RemoteViews) { 0175 val dataProviderIntent = Intent(context, CommandsRemoteViewsService::class.java) 0176 dataProviderIntent.putExtra(EXTRA_APPWIDGET_ID, appWidgetId) 0177 dataProviderIntent.data = Uri.parse(dataProviderIntent.toUri(Intent.URI_INTENT_SCHEME)) 0178 views.setRemoteAdapter(R.id.widget_command_list, dataProviderIntent) 0179 } 0180 0181 /** 0182 * This pending intent allows the remote adapter to call fillInIntent so list items can do things. 0183 * 0184 * See [RemoteViews.setOnClickFillInIntent]. 0185 */ 0186 private fun assignListIntent(context: Context, appWidgetId: Int, views: RemoteViews) { 0187 val runCommandTemplateIntent = Intent(context, RunCommandWidgetProvider::class.java) 0188 runCommandTemplateIntent.action = RUN_COMMAND_ACTION 0189 runCommandTemplateIntent.putExtra(EXTRA_APPWIDGET_ID, appWidgetId) 0190 val runCommandTemplatePendingIntent = PendingIntent.getBroadcast(context, appWidgetId, runCommandTemplateIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE) 0191 views.setPendingIntentTemplate(R.id.widget_command_list, runCommandTemplatePendingIntent) 0192 }